• 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.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
setUp()55     public void setUp() throws IOException {
56         mResolver = new TestContentResolver();
57         mMtpManager = new TestMtpManager(getContext());
58     }
59 
60     @Override
tearDown()61     public void tearDown() {
62         mProvider.shutdown();
63         MtpDatabase.deleteDatabase(getContext());
64     }
65 
testOpenAndCloseDevice()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 
testOpenAndCloseErrorDevice()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 
testOpenDeviceOnDemand()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 
testQueryRoots()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 
testQueryRoots_error()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 
testQueryDocument()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 
testQueryDocument_directory()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 
testQueryDocument_forStorage()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 
testQueryDocument_forDeviceWithSingleStorage()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 
testQueryDocument_forDeviceWithTwoStorages()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 
testQueryChildDocuments()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 
testQueryChildDocuments_cursorError()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 
testQueryChildDocuments_documentError()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 
testDeleteDocument()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 
testDeleteDocument_error()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 
testOpenDocument()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 
testOpenDocument_shortBytes()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 
testOpenDocument_writing()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 
testBusyDevice()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 
testLockedDevice()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 
testMappingDisconnectedDocuments()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 
testCreateDocument()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 
testCreateDocument_noWritingSupport()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 
testOpenDocument_noWritingSupport()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 
testObjectSizeLong()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 
testFindDocumentPath_singleStorage_toRoot()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 
testFindDocumentPath_singleStorage_toDoc()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 
testFindDocumentPath_multiStorage_toRoot()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 
testFindDocumentPath_multiStorage_toDoc()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 
testIsChildDocument()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 
setupProvider(int flag)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 
getStrings(Cursor cursor)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 
setupRoots(int deviceId, MtpRoot[] roots)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 
setupDocuments( int deviceId, int storageId, int parentHandle, String parentDocumentId, MtpObjectInfo[] objects)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 
createChildDocument(int newHandle)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 
toObjectInfo()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 
setupHierarchyDocuments(String documentId)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