1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.documentsui; 18 19 import android.content.ContentProviderClient; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.ContextWrapper; 23 import android.content.Intent; 24 import android.content.pm.ProviderInfo; 25 import android.database.ContentObserver; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.Parcelable; 29 import android.os.RemoteException; 30 import android.provider.DocumentsContract; 31 import android.test.MoreAsserts; 32 import android.test.ServiceTestCase; 33 import android.test.mock.MockContentResolver; 34 import android.util.Log; 35 36 import com.android.documentsui.model.DocumentInfo; 37 import com.android.documentsui.model.DocumentStack; 38 import com.android.documentsui.model.RootInfo; 39 import com.google.common.collect.Lists; 40 41 import libcore.io.IoUtils; 42 import libcore.io.Streams; 43 44 import java.io.File; 45 import java.io.FileInputStream; 46 import java.io.FileNotFoundException; 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.concurrent.CountDownLatch; 50 import java.util.concurrent.TimeUnit; 51 import java.util.concurrent.TimeoutException; 52 53 public class CopyTest extends ServiceTestCase<CopyService> { 54 55 /** 56 * A test resolver that enables this test suite to listen for notifications that mark when copy 57 * operations are done. 58 */ 59 class TestContentResolver extends MockContentResolver { 60 private CountDownLatch mReadySignal; 61 private CountDownLatch mNotificationSignal; 62 TestContentResolver()63 public TestContentResolver() { 64 mReadySignal = new CountDownLatch(1); 65 } 66 67 /** 68 * Wait for the given number of files to be copied to destination. Times out after 1 sec. 69 */ waitForChanges(int count)70 public void waitForChanges(int count) throws Exception { 71 // Wait for no more than 1 second by default. 72 waitForChanges(count, 1000); 73 } 74 75 /** 76 * Wait for files to be copied to destination. 77 * 78 * @param count Number of files to wait for. 79 * @param timeOut Timeout in ms. TimeoutException will be thrown if this function times out. 80 */ waitForChanges(int count, int timeOut)81 public void waitForChanges(int count, int timeOut) throws Exception { 82 mNotificationSignal = new CountDownLatch(count); 83 // Signal that the test is now waiting for files. 84 mReadySignal.countDown(); 85 if (!mNotificationSignal.await(timeOut, TimeUnit.MILLISECONDS)) { 86 throw new TimeoutException("Timed out waiting for files to be copied."); 87 } 88 } 89 90 @Override notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork)91 public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { 92 // Wait until the test is ready to receive file notifications. 93 try { 94 mReadySignal.await(); 95 } catch (InterruptedException e) { 96 Log.d(TAG, "Interrupted while waiting for file copy readiness"); 97 Thread.currentThread().interrupt(); 98 } 99 if (DocumentsContract.isDocumentUri(mContext, uri)) { 100 Log.d(TAG, "Notification: " + uri); 101 // Watch for document URI change notifications - this signifies the end of a copy. 102 mNotificationSignal.countDown(); 103 } 104 } 105 }; 106 CopyTest()107 public CopyTest() { 108 super(CopyService.class); 109 } 110 111 private static String AUTHORITY = "com.android.documentsui.stubprovider"; 112 private static String DST = "sd1"; 113 private static String SRC = "sd0"; 114 private static String TAG = "CopyTest"; 115 private List<RootInfo> mRoots; 116 private Context mContext; 117 private TestContentResolver mResolver; 118 private ContentProviderClient mClient; 119 private StubProvider mStorage; 120 private Context mSystemContext; 121 122 @Override setUp()123 protected void setUp() throws Exception { 124 super.setUp(); 125 126 setupTestContext(); 127 mClient = mResolver.acquireContentProviderClient(AUTHORITY); 128 129 // Reset the stub provider's storage. 130 mStorage.clearCacheAndBuildRoots(); 131 132 mRoots = Lists.newArrayList(); 133 Uri queryUri = DocumentsContract.buildRootsUri(AUTHORITY); 134 Cursor cursor = null; 135 try { 136 cursor = mClient.query(queryUri, null, null, null, null); 137 while (cursor.moveToNext()) { 138 mRoots.add(RootInfo.fromRootsCursor(AUTHORITY, cursor)); 139 } 140 } finally { 141 IoUtils.closeQuietly(cursor); 142 } 143 144 } 145 146 @Override tearDown()147 protected void tearDown() throws Exception { 148 mClient.release(); 149 super.tearDown(); 150 } 151 152 /** 153 * Test copying a single file. 154 */ testCopyFile()155 public void testCopyFile() throws Exception { 156 String srcPath = "/test0.txt"; 157 Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", 158 "The five boxing wizards jump quickly".getBytes()); 159 160 assertDstFileCountEquals(0); 161 162 copyToDestination(Lists.newArrayList(testFile)); 163 164 // 2 operations: file creation, then writing data. 165 mResolver.waitForChanges(2); 166 167 // Verify that one file was copied; check file contents. 168 assertDstFileCountEquals(1); 169 assertCopied(srcPath); 170 } 171 172 /** 173 * Test copying multiple files. 174 */ testCopyMultipleFiles()175 public void testCopyMultipleFiles() throws Exception { 176 String testContent[] = { 177 "The five boxing wizards jump quickly", 178 "The quick brown fox jumps over the lazy dog", 179 "Jackdaws love my big sphinx of quartz" 180 }; 181 String srcPaths[] = { 182 "/test0.txt", 183 "/test1.txt", 184 "/test2.txt" 185 }; 186 List<Uri> testFiles = Lists.newArrayList( 187 mStorage.createFile(SRC, srcPaths[0], "text/plain", testContent[0].getBytes()), 188 mStorage.createFile(SRC, srcPaths[1], "text/plain", testContent[1].getBytes()), 189 mStorage.createFile(SRC, srcPaths[2], "text/plain", testContent[2].getBytes())); 190 191 assertDstFileCountEquals(0); 192 193 // Copy all the test files. 194 copyToDestination(testFiles); 195 196 // 3 file creations, 3 file writes. 197 mResolver.waitForChanges(6); 198 199 assertDstFileCountEquals(3); 200 for (String path : srcPaths) { 201 assertCopied(path); 202 } 203 } 204 testCopyEmptyDir()205 public void testCopyEmptyDir() throws Exception { 206 String srcPath = "/emptyDir"; 207 Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR, 208 null); 209 210 assertDstFileCountEquals(0); 211 212 copyToDestination(Lists.newArrayList(testDir)); 213 214 // Just 1 operation: Directory creation. 215 mResolver.waitForChanges(1); 216 217 assertDstFileCountEquals(1); 218 219 File dst = mStorage.getFile(DST, srcPath); 220 assertTrue(dst.isDirectory()); 221 } 222 testReadErrors()223 public void testReadErrors() throws Exception { 224 String srcPath = "/test0.txt"; 225 Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", 226 "The five boxing wizards jump quickly".getBytes()); 227 228 assertDstFileCountEquals(0); 229 230 mStorage.simulateReadErrors(true); 231 232 copyToDestination(Lists.newArrayList(testFile)); 233 234 // 3 operations: file creation, writing, then deletion (due to failed copy). 235 mResolver.waitForChanges(3); 236 237 assertDstFileCountEquals(0); 238 } 239 240 /** 241 * Copies the given files to a pre-determined destination. 242 * 243 * @throws FileNotFoundException 244 */ copyToDestination(List<Uri> srcs)245 private void copyToDestination(List<Uri> srcs) throws FileNotFoundException { 246 final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList(); 247 for (Uri src : srcs) { 248 srcDocs.add(DocumentInfo.fromUri(mResolver, src)); 249 } 250 251 final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(1).documentId); 252 DocumentStack stack = new DocumentStack(); 253 stack.push(DocumentInfo.fromUri(mResolver, dst)); 254 final Intent copyIntent = new Intent(mContext, CopyService.class); 255 copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs); 256 copyIntent.putExtra(CopyService.EXTRA_STACK, (Parcelable) stack); 257 258 startService(copyIntent); 259 } 260 261 /** 262 * Returns a count of the files in the given directory. 263 */ assertDstFileCountEquals(int expected)264 private void assertDstFileCountEquals(int expected) throws RemoteException { 265 final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY, 266 mRoots.get(1).documentId); 267 Cursor c = null; 268 int count = 0; 269 try { 270 c = mClient.query(queryUri, null, null, null, null); 271 count = c.getCount(); 272 } finally { 273 IoUtils.closeQuietly(c); 274 } 275 assertEquals("Incorrect file count after copy", expected, count); 276 } 277 assertCopied(String path)278 private void assertCopied(String path) throws Exception { 279 File srcFile = mStorage.getFile(SRC, path); 280 File dstFile = mStorage.getFile(DST, path); 281 assertNotNull(dstFile); 282 283 FileInputStream src = null; 284 FileInputStream dst = null; 285 try { 286 src = new FileInputStream(srcFile); 287 dst = new FileInputStream(dstFile); 288 byte[] srcbuf = Streams.readFully(src); 289 byte[] dstbuf = Streams.readFully(dst); 290 291 MoreAsserts.assertEquals(srcbuf, dstbuf); 292 } finally { 293 IoUtils.closeQuietly(src); 294 IoUtils.closeQuietly(dst); 295 } 296 } 297 298 /** 299 * Sets up a ContextWrapper that substitutes a stub NotificationManager. This allows the test to 300 * listen for notification events, to gauge copy progress. 301 * 302 * @throws FileNotFoundException 303 */ setupTestContext()304 private void setupTestContext() throws FileNotFoundException { 305 mSystemContext = getSystemContext(); 306 307 // Set up the context with the test content resolver. 308 mResolver = new TestContentResolver(); 309 mContext = new ContextWrapper(mSystemContext) { 310 @Override 311 public ContentResolver getContentResolver() { 312 return mResolver; 313 } 314 }; 315 setContext(mContext); 316 317 // Create a local stub provider and add it to the content resolver. 318 ProviderInfo info = new ProviderInfo(); 319 info.authority = AUTHORITY; 320 info.exported = true; 321 info.grantUriPermissions = true; 322 info.readPermission = android.Manifest.permission.MANAGE_DOCUMENTS; 323 info.writePermission = android.Manifest.permission.MANAGE_DOCUMENTS; 324 325 mStorage = new StubProvider(); 326 mStorage.attachInfo(mContext, info); 327 mResolver.addProvider(AUTHORITY, mStorage); 328 } 329 } 330