Home | History | Annotate | Download | only in documentsui
      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 
     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          */
     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          */
     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
     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 
    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
    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
    147     protected void tearDown() throws Exception {
    148         mClient.release();
    149         super.tearDown();
    150     }
    151 
    152     /**
    153      * Test copying a single file.
    154      */
    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      */
    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 
    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 
    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      */
    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      */
    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 
    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      */
    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