MtpDocumentsProviderTest.java revision 81d48536aef702d301cdc3a339008de767b51f99
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.provider.DocumentsContract.Root;
24import android.provider.DocumentsContract;
25import android.test.AndroidTestCase;
26import android.test.suitebuilder.annotation.SmallTest;
27
28import java.io.FileNotFoundException;
29import java.io.IOException;
30import java.util.concurrent.TimeoutException;
31
32import static com.android.mtp.MtpDatabase.strings;
33
34@SmallTest
35public class MtpDocumentsProviderTest extends AndroidTestCase {
36    private final static Uri ROOTS_URI =
37            DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
38    private TestContentResolver mResolver;
39    private MtpDocumentsProvider mProvider;
40    private TestMtpManager mMtpManager;
41    private final TestResources mResources = new TestResources();
42    private MtpDatabase mDatabase;
43
44    @Override
45    public void setUp() throws IOException {
46        mResolver = new TestContentResolver();
47        mMtpManager = new TestMtpManager(getContext());
48    }
49
50    @Override
51    public void tearDown() {
52        mProvider.shutdown();
53        MtpDatabase.deleteDatabase(getContext());
54    }
55
56    public void testOpenAndCloseDevice() throws Exception {
57        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
58        mMtpManager.addValidDevice(new MtpDeviceRecord(
59                0,
60                "Device",
61                false /* unopened */,
62                new MtpRoot[] {
63                    new MtpRoot(
64                            0 /* deviceId */,
65                            1 /* storageId */,
66                            "Device A" /* device model name */,
67                            "Storage A" /* volume description */,
68                            1024 /* free space */,
69                            2048 /* total space */,
70                            "" /* no volume identifier */)
71                }));
72
73        mProvider.openDevice(0);
74        mResolver.waitForNotification(ROOTS_URI, 1);
75
76        mProvider.closeDevice(0);
77        mResolver.waitForNotification(ROOTS_URI, 2);
78    }
79
80    public void testOpenAndCloseErrorDevice() throws Exception {
81        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
82        try {
83            mProvider.openDevice(1);
84            fail();
85        } catch (Throwable error) {
86            assertTrue(error instanceof IOException);
87        }
88
89        try {
90            mProvider.closeDevice(1);
91            fail();
92        } catch (Throwable error) {
93            assertTrue(error instanceof IOException);
94        }
95
96        // Check if the following notification is the first one or not.
97        mMtpManager.addValidDevice(new MtpDeviceRecord(
98                0,
99                "Device",
100                false /* unopened */,
101                new MtpRoot[] {
102                    new MtpRoot(
103                            0 /* deviceId */,
104                            1 /* storageId */,
105                            "Device A" /* device model name */,
106                            "Storage A" /* volume description */,
107                            1024 /* free space */,
108                            2048 /* total space */,
109                            "" /* no volume identifier */)
110                }));
111        mProvider.openDevice(0);
112        mResolver.waitForNotification(ROOTS_URI, 1);
113    }
114
115    public void testQueryRoots() throws Exception {
116        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
117        mMtpManager.addValidDevice(new MtpDeviceRecord(
118                0,
119                "Device",
120                false /* unopened */,
121                new MtpRoot[] {
122                        new MtpRoot(
123                                0 /* deviceId */,
124                                1 /* storageId */,
125                                "Device A" /* device model name */,
126                                "Storage A" /* volume description */,
127                                1024 /* free space */,
128                                2048 /* total space */,
129                                "" /* no volume identifier */)
130                }));
131        mMtpManager.addValidDevice(new MtpDeviceRecord(
132                1,
133                "Device",
134                false /* unopened */,
135                new MtpRoot[] {
136                    new MtpRoot(
137                            1 /* deviceId */,
138                            1 /* storageId */,
139                            "Device B" /* device model name */,
140                            "Storage B" /* volume description */,
141                            2048 /* free space */,
142                            4096 /* total space */,
143                            "Identifier B" /* no volume identifier */)
144                }));
145
146        {
147            mProvider.openDevice(0);
148            mResolver.waitForNotification(ROOTS_URI, 1);
149            final Cursor cursor = mProvider.queryRoots(null);
150            assertEquals(2, cursor.getCount());
151            cursor.moveToNext();
152            assertEquals("3", cursor.getString(0));
153            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
154            assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
155            assertEquals("Device A Storage A", cursor.getString(3));
156            assertEquals("3", cursor.getString(4));
157            assertEquals(1024, cursor.getInt(5));
158        }
159
160        {
161            mProvider.openDevice(1);
162            mResolver.waitForNotification(ROOTS_URI, 2);
163            final Cursor cursor = mProvider.queryRoots(null);
164            assertEquals(2, cursor.getCount());
165            cursor.moveToNext();
166            cursor.moveToNext();
167            assertEquals("4", cursor.getString(0));
168            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
169            assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
170            assertEquals("Device B Storage B", cursor.getString(3));
171            assertEquals("4", cursor.getString(4));
172            assertEquals(2048, cursor.getInt(5));
173        }
174    }
175
176    public void testQueryRoots_error() throws Exception {
177        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
178        mMtpManager.addValidDevice(
179                new MtpDeviceRecord(0, "Device A", false /* unopened */, new MtpRoot[0]));
180        mMtpManager.addValidDevice(new MtpDeviceRecord(
181                1,
182                "Device",
183                false /* unopened */,
184                new MtpRoot[] {
185                    new MtpRoot(
186                            1 /* deviceId */,
187                            1 /* storageId */,
188                            "Device B" /* device model name */,
189                            "Storage B" /* volume description */,
190                            2048 /* free space */,
191                            4096 /* total space */,
192                            "Identifier B" /* no volume identifier */)
193                }));
194        {
195            mProvider.openDevice(0);
196            mProvider.openDevice(1);
197            mResolver.waitForNotification(ROOTS_URI, 1);
198
199            final Cursor cursor = mProvider.queryRoots(null);
200            assertEquals(2, cursor.getCount());
201
202            cursor.moveToNext();
203            assertEquals("1", cursor.getString(0));
204            assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
205            assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
206            assertEquals("Device A", cursor.getString(3));
207            assertEquals("1", cursor.getString(4));
208            assertEquals(0, cursor.getInt(5));
209
210            cursor.moveToNext();
211            assertEquals("3", 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 B Storage B", cursor.getString(3));
215            assertEquals("3", cursor.getString(4));
216            assertEquals(2048, cursor.getInt(5));
217        }
218    }
219
220    public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
221        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
222        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
223        setupDocuments(
224                0,
225                0,
226                MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
227                "1",
228                new MtpObjectInfo[] {
229                        new MtpObjectInfo.Builder()
230                                .setObjectHandle(100)
231                                .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
232                                .setName("image.jpg")
233                                .setDateModified(1422716400000L)
234                                .setCompressedSize(1024 * 1024 * 5)
235                                .setThumbCompressedSize(50 * 1024)
236                                .build()
237                });
238
239        final Cursor cursor = mProvider.queryDocument("3", null);
240        assertEquals(1, cursor.getCount());
241
242        cursor.moveToNext();
243
244        assertEquals("3", cursor.getString(0));
245        assertEquals("image/jpeg", cursor.getString(1));
246        assertEquals("image.jpg", cursor.getString(2));
247        assertEquals(1422716400000L, cursor.getLong(3));
248        assertEquals(
249                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
250                DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
251                DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL,
252                cursor.getInt(4));
253        assertEquals(1024 * 1024 * 5, cursor.getInt(5));
254    }
255
256    public void testQueryDocument_directory()
257            throws IOException, InterruptedException, TimeoutException {
258        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
259        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
260        setupDocuments(
261                0,
262                0,
263                MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
264                "1",
265                new MtpObjectInfo[] {
266                        new MtpObjectInfo.Builder()
267                                .setObjectHandle(2)
268                                .setStorageId(1)
269                                .setFormat(MtpConstants.FORMAT_ASSOCIATION)
270                                .setName("directory")
271                                .setDateModified(1422716400000L)
272                                .build()
273                });
274
275        final Cursor cursor = mProvider.queryDocument("3", null);
276        assertEquals(1, cursor.getCount());
277
278        cursor.moveToNext();
279        assertEquals("3", cursor.getString(0));
280        assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
281        assertEquals("directory", cursor.getString(2));
282        assertEquals(1422716400000L, cursor.getLong(3));
283        assertEquals(
284                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
285                DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
286                DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE,
287                cursor.getInt(4));
288        assertEquals(0, cursor.getInt(5));
289    }
290
291    public void testQueryDocument_forRoot()
292            throws IOException, InterruptedException, TimeoutException {
293        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
294        setupRoots(0, new MtpRoot[] {
295                new MtpRoot(
296                        0 /* deviceId */,
297                        1 /* storageId */,
298                        "Device A" /* device model name */,
299                        "Storage A" /* volume description */,
300                        1024 /* free space */,
301                        4096 /* total space */,
302                        "" /* no volume identifier */)
303        });
304        final Cursor cursor = mProvider.queryDocument("2", null);
305        assertEquals(1, cursor.getCount());
306
307        cursor.moveToNext();
308        assertEquals("2", cursor.getString(0));
309        assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
310        assertEquals("Device A Storage A", cursor.getString(2));
311        assertTrue(cursor.isNull(3));
312        assertEquals(0, cursor.getInt(4));
313        assertEquals(3072, cursor.getInt(5));
314    }
315
316    public void testQueryChildDocuments() throws Exception {
317        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
318        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
319        setupDocuments(
320                0,
321                0,
322                MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
323                "1",
324                new MtpObjectInfo[] {
325                        new MtpObjectInfo.Builder()
326                                .setObjectHandle(100)
327                                .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
328                                .setName("image.jpg")
329                                .setCompressedSize(1024 * 1024 * 5)
330                                .setThumbCompressedSize(5 * 1024)
331                                .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
332                                .build()
333                });
334
335        final Cursor cursor = mProvider.queryChildDocuments("1", null, null);
336        assertEquals(1, cursor.getCount());
337
338        assertTrue(cursor.moveToNext());
339        assertEquals("3", cursor.getString(0));
340        assertEquals("image/jpeg", cursor.getString(1));
341        assertEquals("image.jpg", cursor.getString(2));
342        assertEquals(0, cursor.getLong(3));
343        assertEquals(DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
344        assertEquals(1024 * 1024 * 5, cursor.getInt(5));
345
346        cursor.close();
347    }
348
349    public void testQueryChildDocuments_cursorError() throws Exception {
350        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
351        try {
352            mProvider.queryChildDocuments("1", null, null);
353            fail();
354        } catch (Throwable error) {
355            assertTrue(error instanceof FileNotFoundException);
356        }
357    }
358
359    public void testQueryChildDocuments_documentError() throws Exception {
360        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
361        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
362        mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
363        try {
364            mProvider.queryChildDocuments("1", null, null);
365            fail();
366        } catch (Throwable error) {
367            assertTrue(error instanceof FileNotFoundException);
368        }
369    }
370
371    public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
372        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
373        setupRoots(0, new MtpRoot[] {
374                new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
375        });
376        setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
377                new MtpObjectInfo.Builder()
378                    .setName("test.txt")
379                    .setObjectHandle(1)
380                    .setParent(-1)
381                    .build()
382        });
383
384        mProvider.deleteDocument("3");
385        assertEquals(1, mResolver.getChangeCount(
386                DocumentsContract.buildChildDocumentsUri(
387                        MtpDocumentsProvider.AUTHORITY, "1")));
388    }
389
390    public void testDeleteDocument_error()
391            throws IOException, InterruptedException, TimeoutException {
392        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
393        setupRoots(0, new MtpRoot[] {
394                new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
395        });
396        setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
397                new MtpObjectInfo.Builder()
398                    .setName("test.txt")
399                    .setObjectHandle(1)
400                    .setParent(-1)
401                    .build()
402        });
403        try {
404            mProvider.deleteDocument("4");
405            fail();
406        } catch (Throwable e) {
407            assertTrue(e instanceof IOException);
408        }
409        assertEquals(0, mResolver.getChangeCount(
410                DocumentsContract.buildChildDocumentsUri(
411                        MtpDocumentsProvider.AUTHORITY, "1")));
412    }
413
414    private void setupProvider(int flag) {
415        mDatabase = new MtpDatabase(getContext(), flag);
416        mProvider = new MtpDocumentsProvider();
417        mProvider.onCreateForTesting(mResources, mMtpManager, mResolver, mDatabase);
418    }
419
420    private String[] getStrings(Cursor cursor) {
421        try {
422            final String[] results = new String[cursor.getCount()];
423            for (int i = 0; cursor.moveToNext(); i++) {
424                results[i] = cursor.getString(0);
425            }
426            return results;
427        } finally {
428            cursor.close();
429        }
430    }
431
432    private String[] setupRoots(int deviceId, MtpRoot[] roots)
433            throws InterruptedException, TimeoutException, IOException {
434        final int changeCount = mResolver.getChangeCount(ROOTS_URI);
435        mMtpManager.addValidDevice(
436                new MtpDeviceRecord(deviceId, "Device", false /* unopened */, roots));
437        mProvider.openDevice(deviceId);
438        mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
439        return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
440    }
441
442    private String[] setupDocuments(
443            int deviceId,
444            int storageId,
445            int parentHandle,
446            String parentDocumentId,
447            MtpObjectInfo[] objects) throws FileNotFoundException {
448        final int[] handles = new int[objects.length];
449        int i = 0;
450        for (final MtpObjectInfo info : objects) {
451            handles[i] = info.getObjectHandle();
452            mMtpManager.setObjectInfo(deviceId, info);
453        }
454        mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
455        return getStrings(mProvider.queryChildDocuments(
456                parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
457    }
458}
459