| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.documentsui; |
| |
| import android.content.ContentProviderClient; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.ContextWrapper; |
| import android.content.Intent; |
| import android.content.pm.ProviderInfo; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Parcelable; |
| import android.os.RemoteException; |
| import android.provider.DocumentsContract; |
| import android.test.MoreAsserts; |
| import android.test.ServiceTestCase; |
| import android.test.mock.MockContentResolver; |
| import android.util.Log; |
| |
| import com.android.documentsui.model.DocumentInfo; |
| import com.android.documentsui.model.DocumentStack; |
| import com.android.documentsui.model.RootInfo; |
| import com.google.common.collect.Lists; |
| |
| import libcore.io.IoUtils; |
| import libcore.io.Streams; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| public class CopyTest extends ServiceTestCase<CopyService> { |
| |
| /** |
| * A test resolver that enables this test suite to listen for notifications that mark when copy |
| * operations are done. |
| */ |
| class TestContentResolver extends MockContentResolver { |
| private CountDownLatch mReadySignal; |
| private CountDownLatch mNotificationSignal; |
| |
| public TestContentResolver() { |
| mReadySignal = new CountDownLatch(1); |
| } |
| |
| /** |
| * Wait for the given number of files to be copied to destination. Times out after 1 sec. |
| */ |
| public void waitForChanges(int count) throws Exception { |
| // Wait for no more than 1 second by default. |
| waitForChanges(count, 1000); |
| } |
| |
| /** |
| * Wait for files to be copied to destination. |
| * |
| * @param count Number of files to wait for. |
| * @param timeOut Timeout in ms. TimeoutException will be thrown if this function times out. |
| */ |
| public void waitForChanges(int count, int timeOut) throws Exception { |
| mNotificationSignal = new CountDownLatch(count); |
| // Signal that the test is now waiting for files. |
| mReadySignal.countDown(); |
| if (!mNotificationSignal.await(timeOut, TimeUnit.MILLISECONDS)) { |
| throw new TimeoutException("Timed out waiting for files to be copied."); |
| } |
| } |
| |
| @Override |
| public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { |
| // Wait until the test is ready to receive file notifications. |
| try { |
| mReadySignal.await(); |
| } catch (InterruptedException e) { |
| Log.d(TAG, "Interrupted while waiting for file copy readiness"); |
| Thread.currentThread().interrupt(); |
| } |
| if (DocumentsContract.isDocumentUri(mContext, uri)) { |
| Log.d(TAG, "Notification: " + uri); |
| // Watch for document URI change notifications - this signifies the end of a copy. |
| mNotificationSignal.countDown(); |
| } |
| } |
| }; |
| |
| public CopyTest() { |
| super(CopyService.class); |
| } |
| |
| private static String AUTHORITY = "com.android.documentsui.stubprovider"; |
| private static String DST = "sd1"; |
| private static String SRC = "sd0"; |
| private static String TAG = "CopyTest"; |
| private List<RootInfo> mRoots; |
| private Context mContext; |
| private TestContentResolver mResolver; |
| private ContentProviderClient mClient; |
| private StubProvider mStorage; |
| private Context mSystemContext; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| |
| setupTestContext(); |
| mClient = mResolver.acquireContentProviderClient(AUTHORITY); |
| |
| // Reset the stub provider's storage. |
| mStorage.clearCacheAndBuildRoots(); |
| |
| mRoots = Lists.newArrayList(); |
| Uri queryUri = DocumentsContract.buildRootsUri(AUTHORITY); |
| Cursor cursor = null; |
| try { |
| cursor = mClient.query(queryUri, null, null, null, null); |
| while (cursor.moveToNext()) { |
| mRoots.add(RootInfo.fromRootsCursor(AUTHORITY, cursor)); |
| } |
| } finally { |
| IoUtils.closeQuietly(cursor); |
| } |
| |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| mClient.release(); |
| super.tearDown(); |
| } |
| |
| /** |
| * Test copying a single file. |
| */ |
| public void testCopyFile() throws Exception { |
| String srcPath = "/test0.txt"; |
| Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", |
| "The five boxing wizards jump quickly".getBytes()); |
| |
| assertDstFileCountEquals(0); |
| |
| copyToDestination(Lists.newArrayList(testFile)); |
| |
| // 2 operations: file creation, then writing data. |
| mResolver.waitForChanges(2); |
| |
| // Verify that one file was copied; check file contents. |
| assertDstFileCountEquals(1); |
| assertCopied(srcPath); |
| } |
| |
| /** |
| * Test copying multiple files. |
| */ |
| public void testCopyMultipleFiles() throws Exception { |
| String testContent[] = { |
| "The five boxing wizards jump quickly", |
| "The quick brown fox jumps over the lazy dog", |
| "Jackdaws love my big sphinx of quartz" |
| }; |
| String srcPaths[] = { |
| "/test0.txt", |
| "/test1.txt", |
| "/test2.txt" |
| }; |
| List<Uri> testFiles = Lists.newArrayList( |
| mStorage.createFile(SRC, srcPaths[0], "text/plain", testContent[0].getBytes()), |
| mStorage.createFile(SRC, srcPaths[1], "text/plain", testContent[1].getBytes()), |
| mStorage.createFile(SRC, srcPaths[2], "text/plain", testContent[2].getBytes())); |
| |
| assertDstFileCountEquals(0); |
| |
| // Copy all the test files. |
| copyToDestination(testFiles); |
| |
| // 3 file creations, 3 file writes. |
| mResolver.waitForChanges(6); |
| |
| assertDstFileCountEquals(3); |
| for (String path : srcPaths) { |
| assertCopied(path); |
| } |
| } |
| |
| public void testCopyEmptyDir() throws Exception { |
| String srcPath = "/emptyDir"; |
| Uri testDir = mStorage.createFile(SRC, srcPath, DocumentsContract.Document.MIME_TYPE_DIR, |
| null); |
| |
| assertDstFileCountEquals(0); |
| |
| copyToDestination(Lists.newArrayList(testDir)); |
| |
| // Just 1 operation: Directory creation. |
| mResolver.waitForChanges(1); |
| |
| assertDstFileCountEquals(1); |
| |
| File dst = mStorage.getFile(DST, srcPath); |
| assertTrue(dst.isDirectory()); |
| } |
| |
| public void testReadErrors() throws Exception { |
| String srcPath = "/test0.txt"; |
| Uri testFile = mStorage.createFile(SRC, srcPath, "text/plain", |
| "The five boxing wizards jump quickly".getBytes()); |
| |
| assertDstFileCountEquals(0); |
| |
| mStorage.simulateReadErrors(true); |
| |
| copyToDestination(Lists.newArrayList(testFile)); |
| |
| // 3 operations: file creation, writing, then deletion (due to failed copy). |
| mResolver.waitForChanges(3); |
| |
| assertDstFileCountEquals(0); |
| } |
| |
| /** |
| * Copies the given files to a pre-determined destination. |
| * |
| * @throws FileNotFoundException |
| */ |
| private void copyToDestination(List<Uri> srcs) throws FileNotFoundException { |
| final ArrayList<DocumentInfo> srcDocs = Lists.newArrayList(); |
| for (Uri src : srcs) { |
| srcDocs.add(DocumentInfo.fromUri(mResolver, src)); |
| } |
| |
| final Uri dst = DocumentsContract.buildDocumentUri(AUTHORITY, mRoots.get(1).documentId); |
| DocumentStack stack = new DocumentStack(); |
| stack.push(DocumentInfo.fromUri(mResolver, dst)); |
| final Intent copyIntent = new Intent(mContext, CopyService.class); |
| copyIntent.putParcelableArrayListExtra(CopyService.EXTRA_SRC_LIST, srcDocs); |
| copyIntent.putExtra(CopyService.EXTRA_STACK, (Parcelable) stack); |
| |
| startService(copyIntent); |
| } |
| |
| /** |
| * Returns a count of the files in the given directory. |
| */ |
| private void assertDstFileCountEquals(int expected) throws RemoteException { |
| final Uri queryUri = DocumentsContract.buildChildDocumentsUri(AUTHORITY, |
| mRoots.get(1).documentId); |
| Cursor c = null; |
| int count = 0; |
| try { |
| c = mClient.query(queryUri, null, null, null, null); |
| count = c.getCount(); |
| } finally { |
| IoUtils.closeQuietly(c); |
| } |
| assertEquals("Incorrect file count after copy", expected, count); |
| } |
| |
| private void assertCopied(String path) throws Exception { |
| File srcFile = mStorage.getFile(SRC, path); |
| File dstFile = mStorage.getFile(DST, path); |
| assertNotNull(dstFile); |
| |
| FileInputStream src = null; |
| FileInputStream dst = null; |
| try { |
| src = new FileInputStream(srcFile); |
| dst = new FileInputStream(dstFile); |
| byte[] srcbuf = Streams.readFully(src); |
| byte[] dstbuf = Streams.readFully(dst); |
| |
| MoreAsserts.assertEquals(srcbuf, dstbuf); |
| } finally { |
| IoUtils.closeQuietly(src); |
| IoUtils.closeQuietly(dst); |
| } |
| } |
| |
| /** |
| * Sets up a ContextWrapper that substitutes a stub NotificationManager. This allows the test to |
| * listen for notification events, to gauge copy progress. |
| * |
| * @throws FileNotFoundException |
| */ |
| private void setupTestContext() throws FileNotFoundException { |
| mSystemContext = getSystemContext(); |
| |
| // Set up the context with the test content resolver. |
| mResolver = new TestContentResolver(); |
| mContext = new ContextWrapper(mSystemContext) { |
| @Override |
| public ContentResolver getContentResolver() { |
| return mResolver; |
| } |
| }; |
| setContext(mContext); |
| |
| // Create a local stub provider and add it to the content resolver. |
| ProviderInfo info = new ProviderInfo(); |
| info.authority = AUTHORITY; |
| info.exported = true; |
| info.grantUriPermissions = true; |
| info.readPermission = android.Manifest.permission.MANAGE_DOCUMENTS; |
| info.writePermission = android.Manifest.permission.MANAGE_DOCUMENTS; |
| |
| mStorage = new StubProvider(); |
| mStorage.attachInfo(mContext, info); |
| mResolver.addProvider(AUTHORITY, mStorage); |
| } |
| } |