• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ParcelFileDescriptor.AutoCloseOutputStream;
25 import android.os.storage.StorageManager;
26 import android.provider.DocumentsContract.Document;
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.concurrent.TimeoutException;
38 
39 import static com.android.mtp.MtpDatabase.strings;
40 import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED;
41 
42 @MediumTest
43 public class MtpDocumentsProviderTest extends AndroidTestCase {
44     private final static Uri ROOTS_URI =
45             DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
46     private TestContentResolver mResolver;
47     private MtpDocumentsProvider mProvider;
48     private TestMtpManager mMtpManager;
49     private final TestResources mResources = new TestResources();
50     private MtpDatabase mDatabase;
51 
52     @Override
setUp()53     public void setUp() throws IOException {
54         mResolver = new TestContentResolver();
55         mMtpManager = new TestMtpManager(getContext());
56     }
57 
58     @Override
tearDown()59     public void tearDown() {
60         mProvider.shutdown();
61         MtpDatabase.deleteDatabase(getContext());
62     }
63 
testOpenAndCloseDevice()64     public void testOpenAndCloseDevice() throws Exception {
65         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
66         mMtpManager.addValidDevice(new MtpDeviceRecord(
67                 0,
68                 "Device A",
69                 null /* deviceKey */,
70                 false /* unopened */,
71                 new MtpRoot[] {
72                     new MtpRoot(
73                             0 /* deviceId */,
74                             1 /* storageId */,
75                             "Storage A" /* volume description */,
76                             1024 /* free space */,
77                             2048 /* total space */,
78                             "" /* no volume identifier */)
79                 },
80                 OPERATIONS_SUPPORTED,
81                 null));
82 
83         mProvider.resumeRootScanner();
84         mResolver.waitForNotification(ROOTS_URI, 1);
85 
86         mProvider.openDevice(0);
87         mResolver.waitForNotification(ROOTS_URI, 2);
88 
89         mProvider.closeDevice(0);
90         mResolver.waitForNotification(ROOTS_URI, 3);
91     }
92 
testOpenAndCloseErrorDevice()93     public void testOpenAndCloseErrorDevice() throws Exception {
94         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
95         try {
96             mProvider.openDevice(1);
97             fail();
98         } catch (Throwable error) {
99             assertTrue(error instanceof IOException);
100         }
101         assertEquals(0, mProvider.getOpenedDeviceRecordsCache().length);
102 
103         // Check if the following notification is the first one or not.
104         mMtpManager.addValidDevice(new MtpDeviceRecord(
105                 0,
106                 "Device A",
107                 null /* deviceKey */,
108                 false /* unopened */,
109                 new MtpRoot[] {
110                     new MtpRoot(
111                             0 /* deviceId */,
112                             1 /* storageId */,
113                             "Storage A" /* volume description */,
114                             1024 /* free space */,
115                             2048 /* total space */,
116                             "" /* no volume identifier */)
117                 },
118                 OPERATIONS_SUPPORTED,
119                 null));
120         mProvider.resumeRootScanner();
121         mResolver.waitForNotification(ROOTS_URI, 1);
122         mProvider.openDevice(0);
123         mResolver.waitForNotification(ROOTS_URI, 2);
124     }
125 
testOpenDeviceOnDemand()126     public void testOpenDeviceOnDemand() throws Exception {
127         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
128         mMtpManager.addValidDevice(new MtpDeviceRecord(
129                 0,
130                 "Device A",
131                 null /* deviceKey */,
132                 false /* unopened */,
133                 new MtpRoot[] {
134                     new MtpRoot(
135                             0 /* deviceId */,
136                             1 /* storageId */,
137                             "Storage A" /* volume description */,
138                             1024 /* free space */,
139                             2048 /* total space */,
140                             "" /* no volume identifier */)
141                 },
142                 OPERATIONS_SUPPORTED,
143                 null));
144         mMtpManager.setObjectHandles(0, 1, -1, new int[0]);
145         mProvider.resumeRootScanner();
146         mResolver.waitForNotification(ROOTS_URI, 1);
147         final String[] columns = new String[] {
148                 DocumentsContract.Root.COLUMN_TITLE,
149                 DocumentsContract.Root.COLUMN_DOCUMENT_ID
150         };
151         try (final Cursor cursor = mProvider.queryRoots(columns)) {
152             assertEquals(1, cursor.getCount());
153             assertTrue(cursor.moveToNext());
154             assertEquals("Device A", cursor.getString(0));
155             assertEquals(1, cursor.getLong(1));
156         }
157         {
158             final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
159             assertEquals(0, openedDevice.length);
160         }
161         // Device is opened automatically when querying its children.
162         try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {}
163 
164         {
165             final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
166             assertEquals(1, openedDevice.length);
167             assertEquals(0, openedDevice[0].deviceId);
168         }
169     }
170 
testQueryRoots()171     public void testQueryRoots() throws Exception {
172         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
173         mMtpManager.addValidDevice(new MtpDeviceRecord(
174                 0,
175                 "Device A",
176                 "Device key A",
177                 false /* unopened */,
178                 new MtpRoot[] {
179                         new MtpRoot(
180                                 0 /* deviceId */,
181                                 1 /* storageId */,
182                                 "Storage A" /* volume description */,
183                                 1024 /* free space */,
184                                 2048 /* total space */,
185                                 "" /* no volume identifier */)
186                 },
187                 OPERATIONS_SUPPORTED,
188                 null));
189         mMtpManager.addValidDevice(new MtpDeviceRecord(
190                 1,
191                 "Device B",
192                 "Device key B",
193                 false /* unopened */,
194                 new MtpRoot[] {
195                     new MtpRoot(
196                             1 /* deviceId */,
197                             1 /* storageId */,
198                             "Storage B" /* volume description */,
199                             2048 /* free space */,
200                             4096 /* total space */,
201                             "Identifier B" /* no volume identifier */)
202                 },
203                 new int[0] /* No operations supported */,
204                 null));
205 
206         {
207             mProvider.openDevice(0);
208             mResolver.waitForNotification(ROOTS_URI, 1);
209             final Cursor cursor = mProvider.queryRoots(null);
210             assertEquals(2, cursor.getCount());
211             cursor.moveToNext();
212             assertEquals("1", cursor.getString(0));
213             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
214             assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
215             assertEquals("Device A Storage A", cursor.getString(3));
216             assertEquals("1", cursor.getString(4));
217             assertEquals(1024, cursor.getInt(5));
218         }
219 
220         {
221             mProvider.openDevice(1);
222             mResolver.waitForNotification(ROOTS_URI, 2);
223             final Cursor cursor = mProvider.queryRoots(null);
224             assertEquals(2, cursor.getCount());
225             cursor.moveToNext();
226             cursor.moveToNext();
227             assertEquals("2", cursor.getString(0));
228             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD, cursor.getInt(1));
229             assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
230             assertEquals("Device B Storage B", cursor.getString(3));
231             assertEquals("2", cursor.getString(4));
232             assertEquals(2048, cursor.getInt(5));
233         }
234     }
235 
testQueryRoots_error()236     public void testQueryRoots_error() throws Exception {
237         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
238         mMtpManager.addValidDevice(new MtpDeviceRecord(
239                 0,
240                 "Device A",
241                 "Device key A",
242                 false /* unopened */,
243                 new MtpRoot[0],
244                 OPERATIONS_SUPPORTED,
245                 null));
246         mMtpManager.addValidDevice(new MtpDeviceRecord(
247                 1,
248                 "Device B",
249                 "Device key B",
250                 false /* unopened */,
251                 new MtpRoot[] {
252                     new MtpRoot(
253                             1 /* deviceId */,
254                             1 /* storageId */,
255                             "Storage B" /* volume description */,
256                             2048 /* free space */,
257                             4096 /* total space */,
258                             "Identifier B" /* no volume identifier */)
259                 },
260                 OPERATIONS_SUPPORTED,
261                 null));
262         {
263             mProvider.openDevice(0);
264             mResolver.waitForNotification(ROOTS_URI, 1);
265 
266             mProvider.openDevice(1);
267             mResolver.waitForNotification(ROOTS_URI, 2);
268 
269             final Cursor cursor = mProvider.queryRoots(null);
270             assertEquals(2, cursor.getCount());
271 
272             cursor.moveToNext();
273             assertEquals("1", cursor.getString(0));
274             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
275             assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
276             assertEquals("Device A", cursor.getString(3));
277             assertEquals("1", cursor.getString(4));
278             assertEquals(0, cursor.getInt(5));
279 
280             cursor.moveToNext();
281             assertEquals("2", cursor.getString(0));
282             assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
283             assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
284             assertEquals("Device B Storage B", cursor.getString(3));
285             assertEquals("2", cursor.getString(4));
286             assertEquals(2048, cursor.getInt(5));
287         }
288     }
289 
testQueryDocument()290     public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
291         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
292         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
293         setupDocuments(
294                 0,
295                 0,
296                 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
297                 "1",
298                 new MtpObjectInfo[] {
299                         new MtpObjectInfo.Builder()
300                                 .setObjectHandle(100)
301                                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
302                                 .setName("image.jpg")
303                                 .setDateModified(1422716400000L)
304                                 .setCompressedSize(1024 * 1024 * 5)
305                                 .setThumbCompressedSize(50 * 1024)
306                                 .build()
307                 });
308 
309         final Cursor cursor = mProvider.queryDocument("3", null);
310         assertEquals(1, cursor.getCount());
311 
312         cursor.moveToNext();
313 
314         assertEquals("3", cursor.getString(0));
315         assertEquals("image/jpeg", cursor.getString(1));
316         assertEquals("image.jpg", cursor.getString(2));
317         assertEquals(1422716400000L, cursor.getLong(3));
318         assertEquals(
319                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
320                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
321                 DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL,
322                 cursor.getInt(4));
323         assertEquals(1024 * 1024 * 5, cursor.getInt(5));
324     }
325 
testQueryDocument_directory()326     public void testQueryDocument_directory()
327             throws IOException, InterruptedException, TimeoutException {
328         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
329         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
330         setupDocuments(
331                 0,
332                 0,
333                 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
334                 "1",
335                 new MtpObjectInfo[] {
336                         new MtpObjectInfo.Builder()
337                                 .setObjectHandle(2)
338                                 .setStorageId(1)
339                                 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
340                                 .setName("directory")
341                                 .setDateModified(1422716400000L)
342                                 .build()
343                 });
344 
345         final Cursor cursor = mProvider.queryDocument("3", null);
346         assertEquals(1, cursor.getCount());
347 
348         cursor.moveToNext();
349         assertEquals("3", cursor.getString(0));
350         assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
351         assertEquals("directory", cursor.getString(2));
352         assertEquals(1422716400000L, cursor.getLong(3));
353         assertEquals(
354                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
355                 DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE,
356                 cursor.getInt(4));
357         assertEquals(0, cursor.getInt(5));
358     }
359 
testQueryDocument_forRoot()360     public void testQueryDocument_forRoot()
361             throws IOException, InterruptedException, TimeoutException {
362         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
363         setupRoots(0, new MtpRoot[] {
364                 new MtpRoot(
365                         0 /* deviceId */,
366                         1 /* storageId */,
367                         "Storage A" /* volume description */,
368                         1024 /* free space */,
369                         4096 /* total space */,
370                         "" /* no volume identifier */)
371         });
372         final Cursor cursor = mProvider.queryDocument("2", null);
373         assertEquals(1, cursor.getCount());
374 
375         cursor.moveToNext();
376         assertEquals("2", cursor.getString(0));
377         assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
378         assertEquals("Storage A", cursor.getString(2));
379         assertTrue(cursor.isNull(3));
380         assertEquals(0, cursor.getInt(4));
381         assertEquals(3072, cursor.getInt(5));
382     }
383 
testQueryChildDocuments()384     public void testQueryChildDocuments() throws Exception {
385         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
386         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
387         setupDocuments(
388                 0,
389                 0,
390                 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
391                 "1",
392                 new MtpObjectInfo[] {
393                         new MtpObjectInfo.Builder()
394                                 .setObjectHandle(100)
395                                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
396                                 .setName("image.jpg")
397                                 .setCompressedSize(1024 * 1024 * 5)
398                                 .setThumbCompressedSize(5 * 1024)
399                                 .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
400                                 .build()
401                 });
402 
403         final Cursor cursor = mProvider.queryChildDocuments("1", null, null);
404         assertEquals(1, cursor.getCount());
405 
406         assertTrue(cursor.moveToNext());
407         assertEquals("3", cursor.getString(0));
408         assertEquals("image/jpeg", cursor.getString(1));
409         assertEquals("image.jpg", cursor.getString(2));
410         assertEquals(0, cursor.getLong(3));
411         assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
412         assertEquals(1024 * 1024 * 5, cursor.getInt(5));
413 
414         cursor.close();
415     }
416 
testQueryChildDocuments_cursorError()417     public void testQueryChildDocuments_cursorError() throws Exception {
418         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
419         try {
420             mProvider.queryChildDocuments("1", null, null);
421             fail();
422         } catch (FileNotFoundException error) {}
423     }
424 
testQueryChildDocuments_documentError()425     public void testQueryChildDocuments_documentError() throws Exception {
426         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
427         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
428         mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
429         try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
430             assertEquals(0, cursor.getCount());
431             assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
432         }
433     }
434 
testDeleteDocument()435     public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
436         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
437         setupRoots(0, new MtpRoot[] {
438                 new MtpRoot(0, 0, "Storage", 0, 0, "")
439         });
440         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
441                 new MtpObjectInfo.Builder()
442                     .setName("test.txt")
443                     .setObjectHandle(1)
444                     .setParent(-1)
445                     .build()
446         });
447 
448         mProvider.deleteDocument("3");
449         assertEquals(1, mResolver.getChangeCount(
450                 DocumentsContract.buildChildDocumentsUri(
451                         MtpDocumentsProvider.AUTHORITY, "1")));
452     }
453 
testDeleteDocument_error()454     public void testDeleteDocument_error()
455             throws IOException, InterruptedException, TimeoutException {
456         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
457         setupRoots(0, new MtpRoot[] {
458                 new MtpRoot(0, 0, "Storage", 0, 0, "")
459         });
460         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
461                 new MtpObjectInfo.Builder()
462                     .setName("test.txt")
463                     .setObjectHandle(1)
464                     .setParent(-1)
465                     .build()
466         });
467         try {
468             mProvider.deleteDocument("4");
469             fail();
470         } catch (Throwable e) {
471             assertTrue(e instanceof IOException);
472         }
473         assertEquals(0, mResolver.getChangeCount(
474                 DocumentsContract.buildChildDocumentsUri(
475                         MtpDocumentsProvider.AUTHORITY, "1")));
476     }
477 
testOpenDocument()478     public void testOpenDocument() throws Exception {
479         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
480         setupRoots(0, new MtpRoot[] {
481                 new MtpRoot(0, 0, "Storage", 0, 0, "")
482         });
483         final byte[] bytes = "Hello world".getBytes();
484         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
485                 new MtpObjectInfo.Builder()
486                         .setName("test.txt")
487                         .setObjectHandle(1)
488                         .setCompressedSize(bytes.length)
489                         .setParent(-1)
490                         .build()
491         });
492         mMtpManager.setImportFileBytes(0, 1, bytes);
493         try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
494             final byte[] readBytes = new byte[5];
495             assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET));
496             assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
497             assertTrue(Arrays.equals("world".getBytes(), readBytes));
498 
499             assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET));
500             assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
501             assertTrue(Arrays.equals("Hello".getBytes(), readBytes));
502         }
503     }
504 
testOpenDocument_shortBytes()505     public void testOpenDocument_shortBytes() throws Exception {
506         mMtpManager = new TestMtpManager(getContext()) {
507             @Override
508             MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
509                 if (objectHandle == 1) {
510                     return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle))
511                             .setObjectHandle(1).setCompressedSize(1024 * 1024).build();
512                 }
513 
514                 return super.getObjectInfo(deviceId, objectHandle);
515             }
516         };
517         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
518         setupRoots(0, new MtpRoot[] {
519                 new MtpRoot(0, 0, "Storage", 0, 0, "")
520         });
521         final byte[] bytes = "Hello world".getBytes();
522         setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
523                 new MtpObjectInfo.Builder()
524                         .setName("test.txt")
525                         .setObjectHandle(1)
526                         .setCompressedSize(bytes.length)
527                         .setParent(-1)
528                         .build()
529         });
530         mMtpManager.setImportFileBytes(0, 1, bytes);
531         try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
532             final byte[] readBytes = new byte[1024 * 1024];
533             assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length));
534         }
535     }
536 
testOpenDocument_writing()537     public void testOpenDocument_writing() throws Exception {
538         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
539         setupRoots(0, new MtpRoot[] {
540                 new MtpRoot(0, 0, "Storage", 0, 0, "")
541         });
542         final String documentId = mProvider.createDocument("2", "text/plain", "test.txt");
543         {
544             final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null);
545             try (ParcelFileDescriptor.AutoCloseOutputStream stream =
546                     new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
547                 stream.write("Hello".getBytes());
548             }
549         }
550         {
551             final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null);
552             try (ParcelFileDescriptor.AutoCloseInputStream stream =
553                     new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
554                 final byte[] bytes = new byte[5];
555                 stream.read(bytes);
556                 assertTrue(Arrays.equals("Hello".getBytes(), bytes));
557             }
558         }
559     }
560 
testBusyDevice()561     public void testBusyDevice() throws Exception {
562         mMtpManager = new TestMtpManager(getContext()) {
563             @Override
564             MtpDeviceRecord openDevice(int deviceId) throws IOException {
565                 throw new BusyDeviceException();
566             }
567         };
568         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
569         mMtpManager.addValidDevice(new MtpDeviceRecord(
570                 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0],
571                 OPERATIONS_SUPPORTED, null));
572 
573         mProvider.resumeRootScanner();
574         mResolver.waitForNotification(ROOTS_URI, 1);
575 
576         try (final Cursor cursor = mProvider.queryRoots(null)) {
577             assertEquals(1, cursor.getCount());
578         }
579 
580         try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
581             assertEquals(0, cursor.getCount());
582             assertEquals(
583                     "error_busy_device",
584                     cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
585         }
586     }
587 
testLockedDevice()588     public void testLockedDevice() throws Exception {
589         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
590         mMtpManager.addValidDevice(new MtpDeviceRecord(
591                 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED,
592                 null));
593 
594         mProvider.resumeRootScanner();
595         mResolver.waitForNotification(ROOTS_URI, 1);
596 
597         try (final Cursor cursor = mProvider.queryRoots(null)) {
598             assertEquals(1, cursor.getCount());
599         }
600 
601         try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
602             assertEquals(0, cursor.getCount());
603             assertEquals(
604                     "error_locked_device",
605                     cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
606         }
607     }
608 
testMappingDisconnectedDocuments()609     public void testMappingDisconnectedDocuments() throws Exception {
610         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
611         mMtpManager.addValidDevice(new MtpDeviceRecord(
612                 0,
613                 "Device A",
614                 "device key",
615                 true /* opened */,
616                 new MtpRoot[] {
617                     new MtpRoot(
618                             0 /* deviceId */,
619                             1 /* storageId */,
620                             "Storage A" /* volume description */,
621                             1024 /* free space */,
622                             2048 /* total space */,
623                             "" /* no volume identifier */)
624                 },
625                 OPERATIONS_SUPPORTED,
626                 null));
627 
628         final String[] names = strings("Directory A", "Directory B", "Directory C");
629         final int objectHandleOffset = 100;
630         for (int i = 0; i < names.length; i++) {
631             final int parentHandle = i == 0 ?
632                     MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1;
633             final int objectHandle = i + objectHandleOffset;
634             mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle });
635             mMtpManager.setObjectInfo(
636                     0,
637                     new MtpObjectInfo.Builder()
638                             .setName(names[i])
639                             .setObjectHandle(objectHandle)
640                             .setFormat(MtpConstants.FORMAT_ASSOCIATION)
641                             .setStorageId(1)
642                             .build());
643         }
644 
645         mProvider.resumeRootScanner();
646         mResolver.waitForNotification(ROOTS_URI, 1);
647 
648         final int documentIdOffset = 2;
649         for (int i = 0; i < names.length; i++) {
650             try (final Cursor cursor = mProvider.queryChildDocuments(
651                     String.valueOf(documentIdOffset + i),
652                     strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME),
653                     null)) {
654                 assertEquals(1, cursor.getCount());
655                 cursor.moveToNext();
656                 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
657                 assertEquals(names[i], cursor.getString(1));
658             }
659         }
660 
661         mProvider.closeDevice(0);
662         mResolver.waitForNotification(ROOTS_URI, 2);
663 
664         mProvider.openDevice(0);
665         mResolver.waitForNotification(ROOTS_URI, 3);
666 
667         for (int i = 0; i < names.length; i++) {
668             mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri(
669                     MtpDocumentsProvider.AUTHORITY,
670                     String.valueOf(documentIdOffset + i)), 1);
671             try (final Cursor cursor = mProvider.queryChildDocuments(
672                     String.valueOf(documentIdOffset + i),
673                     strings(Document.COLUMN_DOCUMENT_ID),
674                     null)) {
675                 assertEquals(1, cursor.getCount());
676                 cursor.moveToNext();
677                 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
678             }
679         }
680     }
681 
testCreateDocument_noWritingSupport()682     public void testCreateDocument_noWritingSupport() throws Exception {
683         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
684         mMtpManager.addValidDevice(new MtpDeviceRecord(
685                 0, "Device A", null /* deviceKey */, false /* unopened */,
686                 new MtpRoot[] {
687                         new MtpRoot(
688                                 0 /* deviceId */,
689                                 1 /* storageId */,
690                                 "Storage A" /* volume description */,
691                                 1024 /* free space */,
692                                 2048 /* total space */,
693                                 "" /* no volume identifier */)
694                 },
695                 new int[0] /* no operations supported */, null));
696         mProvider.resumeRootScanner();
697         mResolver.waitForNotification(ROOTS_URI, 1);
698         try {
699             mProvider.createDocument("1", "text/palin", "note.txt");
700             fail();
701         } catch (UnsupportedOperationException exception) {}
702     }
703 
testOpenDocument_noWritingSupport()704     public void testOpenDocument_noWritingSupport() throws Exception {
705         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
706         mMtpManager.addValidDevice(new MtpDeviceRecord(
707                 0, "Device A", null /* deviceKey */, false /* unopened */,
708                 new MtpRoot[] {
709                         new MtpRoot(
710                                 0 /* deviceId */,
711                                 1 /* storageId */,
712                                 "Storage A" /* volume description */,
713                                 1024 /* free space */,
714                                 2048 /* total space */,
715                                 "" /* no volume identifier */)
716                 },
717                 new int[0] /* no operations supported */, null));
718         mMtpManager.setObjectHandles(
719                 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 });
720         mMtpManager.setObjectInfo(
721                 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build());
722         mProvider.resumeRootScanner();
723         mResolver.waitForNotification(ROOTS_URI, 1);
724         try (final Cursor cursor = mProvider.queryChildDocuments(
725                 "1", strings(Document.COLUMN_DOCUMENT_ID), null)) {
726             assertEquals(1, cursor.getCount());
727             cursor.moveToNext();
728             assertEquals("3", cursor.getString(0));
729         }
730         try {
731             mProvider.openDocument("3", "w", null);
732             fail();
733         } catch (UnsupportedOperationException exception) {}
734     }
735 
testObjectSizeLong()736     public void testObjectSizeLong() throws Exception {
737         setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
738         setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
739         mMtpManager.setObjectSizeLong(0, 100, MtpConstants.FORMAT_EXIF_JPEG, 0x400000000L);
740         setupDocuments(
741                 0,
742                 0,
743                 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
744                 "1",
745                 new MtpObjectInfo[] {
746                         new MtpObjectInfo.Builder()
747                                 .setObjectHandle(100)
748                                 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
749                                 .setName("image.jpg")
750                                 .setCompressedSize(0xffffffffl)
751                                 .build()
752                 });
753 
754         final Cursor cursor = mProvider.queryDocument("3", new String[] {
755                 DocumentsContract.Document.COLUMN_SIZE
756         });
757         assertEquals(1, cursor.getCount());
758 
759         cursor.moveToNext();
760         assertEquals(0x400000000L, cursor.getLong(0));
761     }
762 
setupProvider(int flag)763     private void setupProvider(int flag) {
764         mDatabase = new MtpDatabase(getContext(), flag);
765         mProvider = new MtpDocumentsProvider();
766         final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
767         assertTrue(mProvider.onCreateForTesting(
768                 getContext(),
769                 mResources,
770                 mMtpManager,
771                 mResolver,
772                 mDatabase,
773                 storageManager,
774                 new TestServiceIntentSender()));
775     }
776 
getStrings(Cursor cursor)777     private String[] getStrings(Cursor cursor) {
778         try {
779             final String[] results = new String[cursor.getCount()];
780             for (int i = 0; cursor.moveToNext(); i++) {
781                 results[i] = cursor.getString(0);
782             }
783             return results;
784         } finally {
785             cursor.close();
786         }
787     }
788 
setupRoots(int deviceId, MtpRoot[] roots)789     private String[] setupRoots(int deviceId, MtpRoot[] roots)
790             throws InterruptedException, TimeoutException, IOException {
791         final int changeCount = mResolver.getChangeCount(ROOTS_URI);
792         mMtpManager.addValidDevice(
793                 new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */,
794                 roots, OPERATIONS_SUPPORTED, null));
795         mProvider.openDevice(deviceId);
796         mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
797         return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
798     }
799 
setupDocuments( int deviceId, int storageId, int parentHandle, String parentDocumentId, MtpObjectInfo[] objects)800     private String[] setupDocuments(
801             int deviceId,
802             int storageId,
803             int parentHandle,
804             String parentDocumentId,
805             MtpObjectInfo[] objects) throws FileNotFoundException {
806         final int[] handles = new int[objects.length];
807         int i = 0;
808         for (final MtpObjectInfo info : objects) {
809             handles[i] = info.getObjectHandle();
810             mMtpManager.setObjectInfo(deviceId, info);
811         }
812         mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
813         return getStrings(mProvider.queryChildDocuments(
814                 parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
815     }
816 }
817