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