Home | History | Annotate | Download | only in mtp
      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.mtp;
     18 
     19 import android.database.Cursor;
     20 import android.mtp.MtpConstants;
     21 import android.mtp.MtpObjectInfo;
     22 import android.net.Uri;
     23 import android.os.ParcelFileDescriptor;
     24 import android.os.storage.StorageManager;
     25 import android.provider.DocumentsContract.Document;
     26 import android.provider.DocumentsContract.Path;
     27 import android.provider.DocumentsContract.Root;
     28 import android.system.Os;
     29 import android.system.OsConstants;
     30 import android.provider.DocumentsContract;
     31 import android.test.AndroidTestCase;
     32 import android.test.suitebuilder.annotation.MediumTest;
     33 
     34 import java.io.FileNotFoundException;
     35 import java.io.IOException;
     36 import java.util.Arrays;
     37 import java.util.LinkedList;
     38 import java.util.Queue;
     39 import java.util.concurrent.TimeoutException;
     40 
     41 import static com.android.mtp.MtpDatabase.strings;
     42 import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED;
     43 
     44 @MediumTest
     45 public class MtpDocumentsProviderTest extends AndroidTestCase {
     46     private final static Uri ROOTS_URI =
     47             DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
     48     private TestContentResolver mResolver;
     49     private MtpDocumentsProvider mProvider;
     50     private TestMtpManager mMtpManager;
     51     private final TestResources mResources = new TestResources();
     52     private MtpDatabase mDatabase;
     53 
     54     @Override
     55     public void setUp() throws IOException {
     56         mResolver = new TestContentResolver();
     57         mMtpManager = new TestMtpManager(getContext());
     58     }
     59 
     60     @Override
     61     public void tearDown() {
     62         mProvider.shutdown();
     63         MtpDatabase.deleteDatabase(getContext());
     64     }
     65 
     66     public void testOpenAndCloseDevice() throws Exception {
     67         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
     68         mMtpManager.addValidDevice(new MtpDeviceRecord(
     69                 0,
     70                 "Device A",
     71                 null /* deviceKey */,
     72                 false /* unopened */,
     73                 new MtpRoot[] {
     74                     new MtpRoot(
     75                             0 /* deviceId */,
     76                             1 /* storageId */,
     77                             "Storage A" /* volume description */,
     78                             1024 /* free space */,
     79                             2048 /* total space */,
     80                             "" /* no volume identifier */)
     81                 },
     82                 OPERATIONS_SUPPORTED,
     83                 null));
     84 
     85         mProvider.resumeRootScanner();
     86         mResolver.waitForNotification(ROOTS_URI, 1);
     87 
     88         mProvider.openDevice(0);
     89         mResolver.waitForNotification(ROOTS_URI, 2);
     90 
     91         mProvider.closeDevice(0);
     92         mResolver.waitForNotification(ROOTS_URI, 3);
     93     }
     94 
     95     public void testOpenAndCloseErrorDevice() throws Exception {
     96         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
     97         try {
     98             mProvider.openDevice(1);
     99             fail();
    100         } catch (Throwable error) {
    101             assertTrue(error instanceof IOException);
    102         }
    103         assertEquals(0, mProvider.getOpenedDeviceRecordsCache().length);
    104 
    105         // Check if the following notification is the first one or not.
    106         mMtpManager.addValidDevice(new MtpDeviceRecord(
    107                 0,
    108                 "Device A",
    109                 null /* deviceKey */,
    110                 false /* unopened */,
    111                 new MtpRoot[] {
    112                     new MtpRoot(
    113                             0 /* deviceId */,
    114                             1 /* storageId */,
    115                             "Storage A" /* volume description */,
    116                             1024 /* free space */,
    117                             2048 /* total space */,
    118                             "" /* no volume identifier */)
    119                 },
    120                 OPERATIONS_SUPPORTED,
    121                 null));
    122         mProvider.resumeRootScanner();
    123         mResolver.waitForNotification(ROOTS_URI, 1);
    124         mProvider.openDevice(0);
    125         mResolver.waitForNotification(ROOTS_URI, 2);
    126     }
    127 
    128     public void testOpenDeviceOnDemand() throws Exception {
    129         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    130         mMtpManager.addValidDevice(new MtpDeviceRecord(
    131                 0,
    132                 "Device A",
    133                 null /* deviceKey */,
    134                 false /* unopened */,
    135                 new MtpRoot[] {
    136                     new MtpRoot(
    137                             0 /* deviceId */,
    138                             1 /* storageId */,
    139                             "Storage A" /* volume description */,
    140                             1024 /* free space */,
    141                             2048 /* total space */,
    142                             "" /* no volume identifier */)
    143                 },
    144                 OPERATIONS_SUPPORTED,
    145                 null));
    146         mMtpManager.setObjectHandles(0, 1, -1, new int[0]);
    147         mProvider.resumeRootScanner();
    148         mResolver.waitForNotification(ROOTS_URI, 1);
    149         final String[] columns = new String[] {
    150                 DocumentsContract.Root.COLUMN_TITLE,
    151                 DocumentsContract.Root.COLUMN_DOCUMENT_ID
    152         };
    153         try (final Cursor cursor = mProvider.queryRoots(columns)) {
    154             assertEquals(1, cursor.getCount());
    155             assertTrue(cursor.moveToNext());
    156             assertEquals("Device A", cursor.getString(0));
    157             assertEquals(1, cursor.getLong(1));
    158         }
    159         {
    160             final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
    161             assertEquals(0, openedDevice.length);
    162         }
    163         // Device is opened automatically when querying its children.
    164         try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {}
    165 
    166         {
    167             final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
    168             assertEquals(1, openedDevice.length);
    169             assertEquals(0, openedDevice[0].deviceId);
    170         }
    171     }
    172 
    173     public void testQueryRoots() throws Exception {
    174         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    175         mMtpManager.addValidDevice(new MtpDeviceRecord(
    176                 0,
    177                 "Device A",
    178                 "Device key A",
    179                 false /* unopened */,
    180                 new MtpRoot[] {
    181                         new MtpRoot(
    182                                 0 /* deviceId */,
    183                                 1 /* storageId */,
    184                                 "Storage A" /* volume description */,
    185                                 1024 /* free space */,
    186                                 2048 /* total space */,
    187                                 "" /* no volume identifier */)
    188                 },
    189                 OPERATIONS_SUPPORTED,
    190                 null));
    191         mMtpManager.addValidDevice(new MtpDeviceRecord(
    192                 1,
    193                 "Device B",
    194                 "Device key B",
    195                 false /* unopened */,
    196                 new MtpRoot[] {
    197                     new MtpRoot(
    198                             1 /* deviceId */,
    199                             1 /* storageId */,
    200                             "Storage B" /* volume description */,
    201                             2048 /* free space */,
    202                             4096 /* total space */,
    203                             "Identifier B" /* no volume identifier */)
    204                 },
    205                 new int[0] /* No operations supported */,
    206                 null));
    207 
    208         {
    209             mProvider.openDevice(0);
    210             mResolver.waitForNotification(ROOTS_URI, 1);
    211             final Cursor cursor = mProvider.queryRoots(null);
    212             assertEquals(2, cursor.getCount());
    213             cursor.moveToNext();
    214             assertEquals("1", cursor.getString(0));
    215             assertEquals(
    216                     Root.FLAG_SUPPORTS_IS_CHILD |
    217                     Root.FLAG_SUPPORTS_CREATE |
    218                     Root.FLAG_LOCAL_ONLY,
    219                     cursor.getInt(1));
    220             assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
    221             assertEquals("Device A Storage A", cursor.getString(3));
    222             assertEquals("1", cursor.getString(4));
    223             assertEquals(1024, cursor.getInt(5));
    224         }
    225 
    226         {
    227             mProvider.openDevice(1);
    228             mResolver.waitForNotification(ROOTS_URI, 2);
    229             final Cursor cursor = mProvider.queryRoots(null);
    230             assertEquals(2, cursor.getCount());
    231             cursor.moveToNext();
    232             cursor.moveToNext();
    233             assertEquals("2", cursor.getString(0));
    234             assertEquals(
    235                     Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_LOCAL_ONLY, cursor.getInt(1));
    236             assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
    237             assertEquals("Device B Storage B", cursor.getString(3));
    238             assertEquals("2", cursor.getString(4));
    239             assertEquals(2048, cursor.getInt(5));
    240         }
    241     }
    242 
    243     public void testQueryRoots_error() throws Exception {
    244         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    245         mMtpManager.addValidDevice(new MtpDeviceRecord(
    246                 0,
    247                 "Device A",
    248                 "Device key A",
    249                 false /* unopened */,
    250                 new MtpRoot[0],
    251                 OPERATIONS_SUPPORTED,
    252                 null));
    253         mMtpManager.addValidDevice(new MtpDeviceRecord(
    254                 1,
    255                 "Device B",
    256                 "Device key B",
    257                 false /* unopened */,
    258                 new MtpRoot[] {
    259                     new MtpRoot(
    260                             1 /* deviceId */,
    261                             1 /* storageId */,
    262                             "Storage B" /* volume description */,
    263                             2048 /* free space */,
    264                             4096 /* total space */,
    265                             "Identifier B" /* no volume identifier */)
    266                 },
    267                 OPERATIONS_SUPPORTED,
    268                 null));
    269         {
    270             mProvider.openDevice(0);
    271             mResolver.waitForNotification(ROOTS_URI, 1);
    272 
    273             mProvider.openDevice(1);
    274             mResolver.waitForNotification(ROOTS_URI, 2);
    275 
    276             final Cursor cursor = mProvider.queryRoots(null);
    277             assertEquals(2, cursor.getCount());
    278 
    279             cursor.moveToNext();
    280             assertEquals("1", cursor.getString(0));
    281             assertEquals(
    282                     Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY,
    283                     cursor.getInt(1));
    284             assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
    285             assertEquals("Device A", cursor.getString(3));
    286             assertEquals("1", cursor.getString(4));
    287             assertEquals(0, cursor.getInt(5));
    288 
    289             cursor.moveToNext();
    290             assertEquals("2", cursor.getString(0));
    291             assertEquals(
    292                     Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY,
    293                     cursor.getInt(1));
    294             assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
    295             assertEquals("Device B Storage B", cursor.getString(3));
    296             assertEquals("2", cursor.getString(4));
    297             assertEquals(2048, cursor.getInt(5));
    298         }
    299     }
    300 
    301     public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
    302         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    303         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
    304         setupDocuments(
    305                 0,
    306                 0,
    307                 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
    308                 "1",
    309                 new MtpObjectInfo[] {
    310                         new MtpObjectInfo.Builder()
    311                                 .setObjectHandle(100)
    312                                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
    313                                 .setName("image.jpg")
    314                                 .setDateModified(1422716400000L)
    315                                 .setCompressedSize(1024 * 1024 * 5)
    316                                 .setThumbCompressedSize(50 * 1024)
    317                                 .build()
    318                 });
    319 
    320         final Cursor cursor = mProvider.queryDocument("3", null);
    321         assertEquals(1, cursor.getCount());
    322 
    323         cursor.moveToNext();
    324 
    325         assertEquals("3", cursor.getString(0));
    326         assertEquals("image/jpeg", cursor.getString(1));
    327         assertEquals("image.jpg", cursor.getString(2));
    328         assertEquals(1422716400000L, cursor.getLong(3));
    329         assertEquals(
    330                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
    331                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
    332                 DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL,
    333                 cursor.getInt(4));
    334         assertEquals(1024 * 1024 * 5, cursor.getInt(5));
    335     }
    336 
    337     public void testQueryDocument_directory()
    338             throws IOException, InterruptedException, TimeoutException {
    339         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    340         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
    341         setupDocuments(
    342                 0,
    343                 0,
    344                 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
    345                 "1",
    346                 new MtpObjectInfo[] {
    347                         new MtpObjectInfo.Builder()
    348                                 .setObjectHandle(2)
    349                                 .setStorageId(1)
    350                                 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
    351                                 .setName("directory")
    352                                 .setDateModified(1422716400000L)
    353                                 .build()
    354                 });
    355 
    356         final Cursor cursor = mProvider.queryDocument("3", null);
    357         assertEquals(1, cursor.getCount());
    358 
    359         cursor.moveToNext();
    360         assertEquals("3", cursor.getString(0));
    361         assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
    362         assertEquals("directory", cursor.getString(2));
    363         assertEquals(1422716400000L, cursor.getLong(3));
    364         assertEquals(
    365                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
    366                 DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE,
    367                 cursor.getInt(4));
    368         assertEquals(0, cursor.getInt(5));
    369     }
    370 
    371     public void testQueryDocument_forStorage()
    372             throws IOException, InterruptedException, TimeoutException {
    373         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    374         setupRoots(0, new MtpRoot[] {
    375                 new MtpRoot(
    376                         0 /* deviceId */,
    377                         1 /* storageId */,
    378                         "Storage A" /* volume description */,
    379                         1024 /* free space */,
    380                         4096 /* total space */,
    381                         "" /* no volume identifier */)
    382         });
    383         final Cursor cursor = mProvider.queryDocument("2", null);
    384         assertEquals(1, cursor.getCount());
    385 
    386         cursor.moveToNext();
    387         assertEquals("2", cursor.getString(0));
    388         assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
    389         assertEquals("Storage A", cursor.getString(2));
    390         assertTrue(cursor.isNull(3));
    391         assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4));
    392         assertEquals(3072, cursor.getInt(5));
    393     }
    394 
    395     public void testQueryDocument_forDeviceWithSingleStorage()
    396             throws IOException, InterruptedException, TimeoutException {
    397         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    398         setupRoots(0, new MtpRoot[] {
    399                 new MtpRoot(
    400                         0 /* deviceId */,
    401                         1 /* storageId */,
    402                         "Storage A" /* volume description */,
    403                         1024 /* free space */,
    404                         4096 /* total space */,
    405                         "" /* no volume identifier */)
    406         });
    407         final Cursor cursor = mProvider.queryDocument("1", null);
    408         assertEquals(1, cursor.getCount());
    409 
    410         cursor.moveToNext();
    411         assertEquals("1", cursor.getString(0));
    412         assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
    413         assertEquals("Device Storage A", cursor.getString(2));
    414         assertTrue(cursor.isNull(3));
    415         assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4));
    416         assertTrue(cursor.isNull(5));
    417     }
    418 
    419     public void testQueryDocument_forDeviceWithTwoStorages()
    420             throws IOException, InterruptedException, TimeoutException {
    421         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    422         setupRoots(0, new MtpRoot[] {
    423                 new MtpRoot(
    424                         0 /* deviceId */,
    425                         1 /* storageId */,
    426                         "Storage A" /* volume description */,
    427                         1024 /* free space */,
    428                         4096 /* total space */,
    429                         "" /* no volume identifier */),
    430                 new MtpRoot(
    431                         0 /* deviceId */,
    432                         2 /* storageId */,
    433                         "Storage B" /* volume description */,
    434                         1024 /* free space */,
    435                         4096 /* total space */,
    436                         "" /* no volume identifier */)
    437         });
    438         final Cursor cursor = mProvider.queryDocument("1", null);
    439         assertEquals(1, cursor.getCount());
    440 
    441         cursor.moveToNext();
    442         assertEquals("1", cursor.getString(0));
    443         assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
    444         assertEquals("Device", cursor.getString(2));
    445         assertTrue(cursor.isNull(3));
    446         assertEquals(0, cursor.getInt(4));
    447         assertTrue(cursor.isNull(5));
    448     }
    449 
    450     public void testQueryChildDocuments() throws Exception {
    451         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    452         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
    453         setupDocuments(
    454                 0,
    455                 0,
    456                 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
    457                 "1",
    458                 new MtpObjectInfo[] {
    459                         new MtpObjectInfo.Builder()
    460                                 .setObjectHandle(100)
    461                                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
    462                                 .setName("image.jpg")
    463                                 .setCompressedSize(1024 * 1024 * 5)
    464                                 .setThumbCompressedSize(5 * 1024)
    465                                 .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
    466                                 .build()
    467                 });
    468 
    469         final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null);
    470         assertEquals(1, cursor.getCount());
    471 
    472         assertTrue(cursor.moveToNext());
    473         assertEquals("3", cursor.getString(0));
    474         assertEquals("image/jpeg", cursor.getString(1));
    475         assertEquals("image.jpg", cursor.getString(2));
    476         assertEquals(0, cursor.getLong(3));
    477         assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
    478         assertEquals(1024 * 1024 * 5, cursor.getInt(5));
    479 
    480         cursor.close();
    481     }
    482 
    483     public void testQueryChildDocuments_cursorError() throws Exception {
    484         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    485         try {
    486             mProvider.queryChildDocuments("1", null, (String) null);
    487             fail();
    488         } catch (FileNotFoundException error) {}
    489     }
    490 
    491     public void testQueryChildDocuments_documentError() throws Exception {
    492         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    493         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
    494         mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
    495         try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
    496             assertEquals(0, cursor.getCount());
    497             assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
    498         }
    499     }
    500 
    501     public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
    502         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    503         setupRoots(0, new MtpRoot[] {
    504                 new MtpRoot(0, 0, "Storage", 0, 0, "")
    505         });
    506         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
    507                 new MtpObjectInfo.Builder()
    508                     .setName("test.txt")
    509                     .setObjectHandle(1)
    510                     .setParent(-1)
    511                     .build()
    512         });
    513 
    514         mProvider.deleteDocument("3");
    515         assertEquals(1, mResolver.getChangeCount(
    516                 DocumentsContract.buildChildDocumentsUri(
    517                         MtpDocumentsProvider.AUTHORITY, "1")));
    518     }
    519 
    520     public void testDeleteDocument_error()
    521             throws IOException, InterruptedException, TimeoutException {
    522         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    523         setupRoots(0, new MtpRoot[] {
    524                 new MtpRoot(0, 0, "Storage", 0, 0, "")
    525         });
    526         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
    527                 new MtpObjectInfo.Builder()
    528                     .setName("test.txt")
    529                     .setObjectHandle(1)
    530                     .setParent(-1)
    531                     .build()
    532         });
    533         try {
    534             mProvider.deleteDocument("4");
    535             fail();
    536         } catch (Throwable e) {
    537             assertTrue(e instanceof IOException);
    538         }
    539         assertEquals(0, mResolver.getChangeCount(
    540                 DocumentsContract.buildChildDocumentsUri(
    541                         MtpDocumentsProvider.AUTHORITY, "1")));
    542     }
    543 
    544     public void testOpenDocument() throws Exception {
    545         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    546         setupRoots(0, new MtpRoot[] {
    547                 new MtpRoot(0, 0, "Storage", 0, 0, "")
    548         });
    549         final byte[] bytes = "Hello world".getBytes();
    550         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
    551                 new MtpObjectInfo.Builder()
    552                         .setName("test.txt")
    553                         .setObjectHandle(1)
    554                         .setCompressedSize(bytes.length)
    555                         .setParent(-1)
    556                         .build()
    557         });
    558         mMtpManager.setImportFileBytes(0, 1, bytes);
    559         try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
    560             final byte[] readBytes = new byte[5];
    561             assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET));
    562             assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
    563             assertTrue(Arrays.equals("world".getBytes(), readBytes));
    564 
    565             assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET));
    566             assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
    567             assertTrue(Arrays.equals("Hello".getBytes(), readBytes));
    568         }
    569     }
    570 
    571     public void testOpenDocument_shortBytes() throws Exception {
    572         mMtpManager = new TestMtpManager(getContext()) {
    573             @Override
    574             MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
    575                 if (objectHandle == 1) {
    576                     return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle))
    577                             .setObjectHandle(1).setCompressedSize(1024 * 1024).build();
    578                 }
    579 
    580                 return super.getObjectInfo(deviceId, objectHandle);
    581             }
    582         };
    583         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    584         setupRoots(0, new MtpRoot[] {
    585                 new MtpRoot(0, 0, "Storage", 0, 0, "")
    586         });
    587         final byte[] bytes = "Hello world".getBytes();
    588         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
    589                 new MtpObjectInfo.Builder()
    590                         .setName("test.txt")
    591                         .setObjectHandle(1)
    592                         .setCompressedSize(bytes.length)
    593                         .setParent(-1)
    594                         .build()
    595         });
    596         mMtpManager.setImportFileBytes(0, 1, bytes);
    597         try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
    598             final byte[] readBytes = new byte[1024 * 1024];
    599             assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length));
    600         }
    601     }
    602 
    603     public void testOpenDocument_writing() throws Exception {
    604         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    605         setupRoots(0, new MtpRoot[] {
    606                 new MtpRoot(0, 100, "Storage", 0, 0, "")
    607         });
    608         final String documentId = mProvider.createDocument("2", "text/plain", "test.txt");
    609         {
    610             final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null);
    611             try (ParcelFileDescriptor.AutoCloseOutputStream stream =
    612                     new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
    613                 stream.write("Hello".getBytes());
    614                 fd.getFileDescriptor().sync();
    615             }
    616         }
    617         {
    618             final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null);
    619             try (ParcelFileDescriptor.AutoCloseInputStream stream =
    620                     new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
    621                 final byte[] bytes = new byte[5];
    622                 stream.read(bytes);
    623                 assertTrue(Arrays.equals("Hello".getBytes(), bytes));
    624             }
    625         }
    626     }
    627 
    628     public void testBusyDevice() throws Exception {
    629         mMtpManager = new TestMtpManager(getContext()) {
    630             @Override
    631             synchronized MtpDeviceRecord openDevice(int deviceId)
    632                     throws IOException {
    633                 throw new BusyDeviceException();
    634             }
    635         };
    636         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    637         mMtpManager.addValidDevice(new MtpDeviceRecord(
    638                 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0],
    639                 OPERATIONS_SUPPORTED, null));
    640 
    641         mProvider.resumeRootScanner();
    642         mResolver.waitForNotification(ROOTS_URI, 1);
    643 
    644         try (final Cursor cursor = mProvider.queryRoots(null)) {
    645             assertEquals(1, cursor.getCount());
    646         }
    647 
    648         try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
    649             assertEquals(0, cursor.getCount());
    650             assertEquals(
    651                     "error_busy_device",
    652                     cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
    653         }
    654     }
    655 
    656     public void testLockedDevice() throws Exception {
    657         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    658         mMtpManager.addValidDevice(new MtpDeviceRecord(
    659                 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED,
    660                 null));
    661 
    662         mProvider.resumeRootScanner();
    663         mResolver.waitForNotification(ROOTS_URI, 1);
    664 
    665         try (final Cursor cursor = mProvider.queryRoots(null)) {
    666             assertEquals(1, cursor.getCount());
    667         }
    668 
    669         try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
    670             assertEquals(0, cursor.getCount());
    671             assertEquals(
    672                     "error_locked_device",
    673                     cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
    674         }
    675     }
    676 
    677     public void testMappingDisconnectedDocuments() throws Exception {
    678         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    679         mMtpManager.addValidDevice(new MtpDeviceRecord(
    680                 0,
    681                 "Device A",
    682                 "device key",
    683                 true /* opened */,
    684                 new MtpRoot[] {
    685                     new MtpRoot(
    686                             0 /* deviceId */,
    687                             1 /* storageId */,
    688                             "Storage A" /* volume description */,
    689                             1024 /* free space */,
    690                             2048 /* total space */,
    691                             "" /* no volume identifier */)
    692                 },
    693                 OPERATIONS_SUPPORTED,
    694                 null));
    695 
    696         final String[] names = strings("Directory A", "Directory B", "Directory C");
    697         final int objectHandleOffset = 100;
    698         for (int i = 0; i < names.length; i++) {
    699             final int parentHandle = i == 0 ?
    700                     MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1;
    701             final int objectHandle = i + objectHandleOffset;
    702             mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle });
    703             mMtpManager.setObjectInfo(
    704                     0,
    705                     new MtpObjectInfo.Builder()
    706                             .setName(names[i])
    707                             .setObjectHandle(objectHandle)
    708                             .setFormat(MtpConstants.FORMAT_ASSOCIATION)
    709                             .setStorageId(1)
    710                             .build());
    711         }
    712 
    713         mProvider.resumeRootScanner();
    714         mResolver.waitForNotification(ROOTS_URI, 1);
    715 
    716         final int documentIdOffset = 2;
    717         for (int i = 0; i < names.length; i++) {
    718             try (final Cursor cursor = mProvider.queryChildDocuments(
    719                     String.valueOf(documentIdOffset + i),
    720                     strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME),
    721                     (String) null)) {
    722                 assertEquals(1, cursor.getCount());
    723                 cursor.moveToNext();
    724                 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
    725                 assertEquals(names[i], cursor.getString(1));
    726             }
    727         }
    728 
    729         mProvider.closeDevice(0);
    730         mResolver.waitForNotification(ROOTS_URI, 2);
    731 
    732         mProvider.openDevice(0);
    733         mResolver.waitForNotification(ROOTS_URI, 3);
    734 
    735         for (int i = 0; i < names.length; i++) {
    736             mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri(
    737                     MtpDocumentsProvider.AUTHORITY,
    738                     String.valueOf(documentIdOffset + i)), 1);
    739             try (final Cursor cursor = mProvider.queryChildDocuments(
    740                     String.valueOf(documentIdOffset + i),
    741                     strings(Document.COLUMN_DOCUMENT_ID),
    742                     (String) null)) {
    743                 assertEquals(1, cursor.getCount());
    744                 cursor.moveToNext();
    745                 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
    746             }
    747         }
    748     }
    749 
    750     public void testCreateDocument() throws Exception {
    751         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    752         setupRoots(0, new MtpRoot[] {
    753                 new MtpRoot(0, 100, "Storage A", 100, 100, null)
    754         });
    755         final String documentId = mProvider.createDocument("1", "text/plain", "note.txt");
    756         final Uri deviceUri = DocumentsContract.buildChildDocumentsUri(
    757                 MtpDocumentsProvider.AUTHORITY, "1");
    758         final Uri storageUri = DocumentsContract.buildChildDocumentsUri(
    759                 MtpDocumentsProvider.AUTHORITY, "2");
    760         mResolver.waitForNotification(storageUri, 1);
    761         mResolver.waitForNotification(deviceUri, 1);
    762         try (final Cursor cursor = mProvider.queryDocument(documentId, null)) {
    763             assertTrue(cursor.moveToNext());
    764             assertEquals(
    765                     "note.txt",
    766                     cursor.getString(cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)));
    767             assertEquals(
    768                     "text/plain",
    769                     cursor.getString(cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)));
    770         }
    771     }
    772 
    773     public void testCreateDocument_noWritingSupport() throws Exception {
    774         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    775         mMtpManager.addValidDevice(new MtpDeviceRecord(
    776                 0, "Device A", null /* deviceKey */, false /* unopened */,
    777                 new MtpRoot[] {
    778                         new MtpRoot(
    779                                 0 /* deviceId */,
    780                                 1 /* storageId */,
    781                                 "Storage A" /* volume description */,
    782                                 1024 /* free space */,
    783                                 2048 /* total space */,
    784                                 "" /* no volume identifier */)
    785                 },
    786                 new int[0] /* no operations supported */, null));
    787         mProvider.resumeRootScanner();
    788         mResolver.waitForNotification(ROOTS_URI, 1);
    789         try {
    790             mProvider.createDocument("1", "text/palin", "note.txt");
    791             fail();
    792         } catch (UnsupportedOperationException exception) {}
    793     }
    794 
    795     public void testOpenDocument_noWritingSupport() throws Exception {
    796         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    797         mMtpManager.addValidDevice(new MtpDeviceRecord(
    798                 0, "Device A", null /* deviceKey */, false /* unopened */,
    799                 new MtpRoot[] {
    800                         new MtpRoot(
    801                                 0 /* deviceId */,
    802                                 1 /* storageId */,
    803                                 "Storage A" /* volume description */,
    804                                 1024 /* free space */,
    805                                 2048 /* total space */,
    806                                 "" /* no volume identifier */)
    807                 },
    808                 new int[0] /* no operations supported */, null));
    809         mMtpManager.setObjectHandles(
    810                 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 });
    811         mMtpManager.setObjectInfo(
    812                 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build());
    813         mProvider.resumeRootScanner();
    814         mResolver.waitForNotification(ROOTS_URI, 1);
    815         try (final Cursor cursor = mProvider.queryChildDocuments(
    816                 "1", strings(Document.COLUMN_DOCUMENT_ID), (String) null)) {
    817             assertEquals(1, cursor.getCount());
    818             cursor.moveToNext();
    819             assertEquals("3", cursor.getString(0));
    820         }
    821         try {
    822             mProvider.openDocument("3", "w", null);
    823             fail();
    824         } catch (UnsupportedOperationException exception) {}
    825     }
    826 
    827     public void testObjectSizeLong() throws Exception {
    828         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    829         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
    830         mMtpManager.setObjectSizeLong(0, 100, MtpConstants.FORMAT_EXIF_JPEG, 0x400000000L);
    831         setupDocuments(
    832                 0,
    833                 0,
    834                 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
    835                 "1",
    836                 new MtpObjectInfo[] {
    837                         new MtpObjectInfo.Builder()
    838                                 .setObjectHandle(100)
    839                                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
    840                                 .setName("image.jpg")
    841                                 .setCompressedSize(0xffffffffl)
    842                                 .build()
    843                 });
    844 
    845         final Cursor cursor = mProvider.queryDocument("3", new String[] {
    846                 DocumentsContract.Document.COLUMN_SIZE
    847         });
    848         assertEquals(1, cursor.getCount());
    849 
    850         cursor.moveToNext();
    851         assertEquals(0x400000000L, cursor.getLong(0));
    852     }
    853 
    854     public void testFindDocumentPath_singleStorage_toRoot() throws Exception {
    855         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    856         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
    857         setupHierarchyDocuments("1");
    858 
    859         final Path path = mProvider.findDocumentPath(null, "15");
    860         assertEquals("1", path.getRootId());
    861         assertEquals(4, path.getPath().size());
    862         assertEquals("1", path.getPath().get(0));
    863         assertEquals("3", path.getPath().get(1));
    864         assertEquals("6", path.getPath().get(2));
    865         assertEquals("15", path.getPath().get(3));
    866     }
    867 
    868     public void testFindDocumentPath_singleStorage_toDoc() throws Exception {
    869         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    870         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
    871         setupHierarchyDocuments("1");
    872 
    873         final Path path = mProvider.findDocumentPath("3", "18");
    874         assertNull(path.getRootId());
    875         assertEquals(3, path.getPath().size());
    876         assertEquals("3", path.getPath().get(0));
    877         assertEquals("7", path.getPath().get(1));
    878         assertEquals("18", path.getPath().get(2));
    879     }
    880 
    881     public void testFindDocumentPath_multiStorage_toRoot() throws Exception {
    882         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    883         setupRoots(0, new MtpRoot[] {
    884                 new MtpRoot(0, 0, "Storage A", 1000, 1000, ""),
    885                 new MtpRoot(0, 1, "Storage B", 1000, 1000, "") });
    886         setupHierarchyDocuments("2");
    887 
    888         final Path path = mProvider.findDocumentPath(null, "16");
    889         assertEquals("2", path.getRootId());
    890         assertEquals(4, path.getPath().size());
    891         assertEquals("2", path.getPath().get(0));
    892         assertEquals("4", path.getPath().get(1));
    893         assertEquals("7", path.getPath().get(2));
    894         assertEquals("16", path.getPath().get(3));
    895     }
    896 
    897     public void testFindDocumentPath_multiStorage_toDoc() throws Exception {
    898         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    899         setupRoots(0, new MtpRoot[] {
    900                 new MtpRoot(0, 0, "Storage A", 1000, 1000, ""),
    901                 new MtpRoot(0, 1, "Storage B", 1000, 1000, "") });
    902         setupHierarchyDocuments("2");
    903 
    904         final Path path = mProvider.findDocumentPath("4", "19");
    905         assertNull(path.getRootId());
    906         assertEquals(3, path.getPath().size());
    907         assertEquals("4", path.getPath().get(0));
    908         assertEquals("8", path.getPath().get(1));
    909         assertEquals("19", path.getPath().get(2));
    910     }
    911 
    912     public void testIsChildDocument() throws Exception {
    913         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
    914         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
    915         setupHierarchyDocuments("1");
    916         assertTrue(mProvider.isChildDocument("1", "1"));
    917         assertTrue(mProvider.isChildDocument("1", "14"));
    918         assertTrue(mProvider.isChildDocument("2", "14"));
    919         assertTrue(mProvider.isChildDocument("5", "14"));
    920         assertFalse(mProvider.isChildDocument("3", "14"));
    921         assertFalse(mProvider.isChildDocument("6", "14"));
    922     }
    923 
    924     private void setupProvider(int flag) {
    925         mDatabase = new MtpDatabase(getContext(), flag);
    926         mProvider = new MtpDocumentsProvider();
    927         final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
    928         assertTrue(mProvider.onCreateForTesting(
    929                 getContext(),
    930                 mResources,
    931                 mMtpManager,
    932                 mResolver,
    933                 mDatabase,
    934                 storageManager,
    935                 new TestServiceIntentSender()));
    936     }
    937 
    938     private String[] getStrings(Cursor cursor) {
    939         try {
    940             final String[] results = new String[cursor.getCount()];
    941             for (int i = 0; cursor.moveToNext(); i++) {
    942                 results[i] = cursor.getString(0);
    943             }
    944             return results;
    945         } finally {
    946             cursor.close();
    947         }
    948     }
    949 
    950     private String[] setupRoots(int deviceId, MtpRoot[] roots)
    951             throws InterruptedException, TimeoutException, IOException {
    952         final int changeCount = mResolver.getChangeCount(ROOTS_URI);
    953         mMtpManager.addValidDevice(
    954                 new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */,
    955                 roots, OPERATIONS_SUPPORTED, null));
    956         mProvider.openDevice(deviceId);
    957         mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
    958         return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
    959     }
    960 
    961     private String[] setupDocuments(
    962             int deviceId,
    963             int storageId,
    964             int parentHandle,
    965             String parentDocumentId,
    966             MtpObjectInfo[] objects) throws FileNotFoundException {
    967         final int[] handles = new int[objects.length];
    968         int i = 0;
    969         for (final MtpObjectInfo info : objects) {
    970             handles[i++] = info.getObjectHandle();
    971             mMtpManager.setObjectInfo(deviceId, info);
    972         }
    973         mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
    974         return getStrings(mProvider.queryChildDocuments(
    975                 parentDocumentId,
    976                 strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
    977                 (String) null));
    978     }
    979 
    980     static class HierarchyDocument {
    981         int depth;
    982         String documentId;
    983         int objectHandle;
    984         int parentHandle;
    985 
    986         HierarchyDocument createChildDocument(int newHandle) {
    987             final HierarchyDocument doc = new HierarchyDocument();
    988             doc.depth = depth - 1;
    989             doc.objectHandle = newHandle;
    990             doc.parentHandle = objectHandle;
    991             return doc;
    992         }
    993 
    994         MtpObjectInfo toObjectInfo() {
    995             return new MtpObjectInfo.Builder()
    996                     .setName("doc_" + documentId)
    997                     .setFormat(depth > 0 ?
    998                             MtpConstants.FORMAT_ASSOCIATION : MtpConstants.FORMAT_TEXT)
    999                     .setObjectHandle(objectHandle)
   1000                     .setParent(parentHandle)
   1001                     .build();
   1002         }
   1003     }
   1004 
   1005     private void setupHierarchyDocuments(String documentId) throws Exception {
   1006         final Queue<HierarchyDocument> ids = new LinkedList<>();
   1007         final HierarchyDocument firstDocument = new HierarchyDocument();
   1008         firstDocument.depth = 3;
   1009         firstDocument.documentId = documentId;
   1010         firstDocument.objectHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
   1011         ids.add(firstDocument);
   1012 
   1013         int objectHandle = 100;
   1014         while (!ids.isEmpty()) {
   1015             final HierarchyDocument document = ids.remove();
   1016             final HierarchyDocument[] children = new HierarchyDocument[] {
   1017                     document.createChildDocument(objectHandle++),
   1018                     document.createChildDocument(objectHandle++),
   1019                     document.createChildDocument(objectHandle++),
   1020             };
   1021             final String[] childDocIds = setupDocuments(
   1022                     0, 0, document.objectHandle, document.documentId, new MtpObjectInfo[] {
   1023                             children[0].toObjectInfo(),
   1024                             children[1].toObjectInfo(),
   1025                             children[2].toObjectInfo(),
   1026                     });
   1027             children[0].documentId = childDocIds[0];
   1028             children[1].documentId = childDocIds[1];
   1029             children[2].documentId = childDocIds[2];
   1030 
   1031             if (children[0].depth > 0) {
   1032                 ids.add(children[0]);
   1033                 ids.add(children[1]);
   1034                 ids.add(children[2]);
   1035             }
   1036         }
   1037     }
   1038 }
   1039