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.media.MediaFile;
21 import android.media.MediaFile.MediaFileType;
22 import android.mtp.MtpConstants;
23 import android.mtp.MtpObjectInfo;
24 import android.net.Uri;
25 import android.provider.DocumentsContract;
26 import android.provider.DocumentsContract.Document;
27 import android.provider.DocumentsContract.Root;
28 import android.test.AndroidTestCase;
29 import android.test.suitebuilder.annotation.SmallTest;
30 
31 import java.io.FileNotFoundException;
32 import java.util.Arrays;
33 
34 import static android.provider.DocumentsContract.Document.*;
35 import static com.android.mtp.MtpDatabase.strings;
36 import static com.android.mtp.MtpDatabaseConstants.*;
37 import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED;
38 
39 @SmallTest
40 public class MtpDatabaseTest extends AndroidTestCase {
41     private static final String[] COLUMN_NAMES = new String[] {
42         DocumentsContract.Document.COLUMN_DOCUMENT_ID,
43         MtpDatabaseConstants.COLUMN_DEVICE_ID,
44         MtpDatabaseConstants.COLUMN_STORAGE_ID,
45         MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
46         DocumentsContract.Document.COLUMN_MIME_TYPE,
47         DocumentsContract.Document.COLUMN_DISPLAY_NAME,
48         DocumentsContract.Document.COLUMN_SUMMARY,
49         DocumentsContract.Document.COLUMN_LAST_MODIFIED,
50         DocumentsContract.Document.COLUMN_ICON,
51         DocumentsContract.Document.COLUMN_FLAGS,
52         DocumentsContract.Document.COLUMN_SIZE,
53         MtpDatabaseConstants.COLUMN_DOCUMENT_TYPE
54     };
55 
56     private final TestResources resources = new TestResources();
57     MtpDatabase mDatabase;
58 
59     @Override
setUp()60     public void setUp() {
61         mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
62     }
63 
64     @Override
tearDown()65     public void tearDown() {
66         mDatabase.close();
67         mDatabase = null;
68     }
69 
getInt(Cursor cursor, String columnName)70     private static int getInt(Cursor cursor, String columnName) {
71         return cursor.getInt(cursor.getColumnIndex(columnName));
72     }
73 
isNull(Cursor cursor, String columnName)74     private static boolean isNull(Cursor cursor, String columnName) {
75         return cursor.isNull(cursor.getColumnIndex(columnName));
76     }
77 
getString(Cursor cursor, String columnName)78     private static String getString(Cursor cursor, String columnName) {
79         return cursor.getString(cursor.getColumnIndex(columnName));
80     }
81 
testPutSingleStorageDocuments()82     public void testPutSingleStorageDocuments() throws Exception {
83         addTestDevice();
84 
85         mDatabase.getMapper().startAddingDocuments("1");
86         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
87                 new MtpRoot(0, 1, "Storage", 1000, 2000, "")
88         });
89         mDatabase.getMapper().stopAddingDocuments("1");
90 
91         {
92             final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES);
93             assertEquals(1, cursor.getCount());
94 
95             cursor.moveToNext();
96             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
97             assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
98             assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
99             assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
100             assertEquals(
101                     DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
102             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
103             assertTrue(isNull(cursor, COLUMN_SUMMARY));
104             assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
105             assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
106             assertEquals(Document.FLAG_DIR_SUPPORTS_CREATE, getInt(cursor, COLUMN_FLAGS));
107             assertEquals(1000, getInt(cursor, COLUMN_SIZE));
108             assertEquals(
109                     MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE,
110                     getInt(cursor, COLUMN_DOCUMENT_TYPE));
111 
112             cursor.close();
113         }
114 
115         {
116             final Cursor cursor = mDatabase.queryRoots(resources, new String [] {
117                     Root.COLUMN_ROOT_ID,
118                     Root.COLUMN_FLAGS,
119                     Root.COLUMN_ICON,
120                     Root.COLUMN_TITLE,
121                     Root.COLUMN_SUMMARY,
122                     Root.COLUMN_DOCUMENT_ID,
123                     Root.COLUMN_AVAILABLE_BYTES,
124                     Root.COLUMN_CAPACITY_BYTES
125             });
126             assertEquals(1, cursor.getCount());
127 
128             cursor.moveToNext();
129             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
130             assertEquals(
131                     Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY,
132                     getInt(cursor, Root.COLUMN_FLAGS));
133             assertEquals(R.drawable.ic_root_mtp, getInt(cursor, Root.COLUMN_ICON));
134             assertEquals("Device Storage", getString(cursor, Root.COLUMN_TITLE));
135             assertTrue(isNull(cursor, Root.COLUMN_SUMMARY));
136             assertEquals(1, getInt(cursor, Root.COLUMN_DOCUMENT_ID));
137             assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
138             assertEquals(2000, getInt(cursor, Root.COLUMN_CAPACITY_BYTES));
139 
140             cursor.close();
141         }
142     }
143 
testPutStorageDocuments()144     public void testPutStorageDocuments() throws Exception {
145         addTestDevice();
146 
147         mDatabase.getMapper().startAddingDocuments("1");
148         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
149                 new MtpRoot(0, 1, "Storage", 1000, 2000, ""),
150                 new MtpRoot(0, 2, "Storage", 2000, 4000, ""),
151                 new MtpRoot(0, 3, "/@#%&<>Storage", 3000, 6000,"")
152         });
153 
154         {
155             final Cursor cursor = mDatabase.queryRootDocuments(COLUMN_NAMES);
156             assertEquals(3, cursor.getCount());
157 
158             cursor.moveToNext();
159             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
160             assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
161             assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
162             assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
163             assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
164             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
165             assertTrue(isNull(cursor, COLUMN_SUMMARY));
166             assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
167             assertEquals(R.drawable.ic_root_mtp, getInt(cursor, COLUMN_ICON));
168             assertEquals(Document.FLAG_DIR_SUPPORTS_CREATE, getInt(cursor, COLUMN_FLAGS));
169             assertEquals(1000, getInt(cursor, COLUMN_SIZE));
170             assertEquals(
171                     MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE));
172 
173             cursor.moveToNext();
174             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
175             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
176 
177             cursor.moveToNext();
178             assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
179             assertEquals("/@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME));
180 
181             cursor.close();
182         }
183     }
184 
createDocument(int objectHandle, String name, int format, int size)185     private MtpObjectInfo createDocument(int objectHandle, String name, int format, int size) {
186         final MtpObjectInfo.Builder builder = new MtpObjectInfo.Builder();
187         builder.setObjectHandle(objectHandle);
188         builder.setName(name);
189         builder.setFormat(format);
190         builder.setCompressedSize(size);
191         return builder.build();
192     }
193 
testPutChildDocuments()194     public void testPutChildDocuments() throws Exception {
195         addTestDevice();
196         addTestStorage("1");
197 
198         mDatabase.getMapper().startAddingDocuments("2");
199         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
200                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
201                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
202                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
203         }, new long[] { 1024L, 2L * 1024L * 1024L, 3L * 1024L * 1024L});
204 
205         final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "2");
206         assertEquals(3, cursor.getCount());
207 
208         cursor.moveToNext();
209         assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
210         assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
211         assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
212         assertEquals(100, getInt(cursor, COLUMN_OBJECT_HANDLE));
213         assertEquals("text/plain", getString(cursor, COLUMN_MIME_TYPE));
214         assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
215         assertTrue(isNull(cursor, COLUMN_SUMMARY));
216         assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
217         assertTrue(isNull(cursor, COLUMN_ICON));
218         assertEquals(
219                 COLUMN_FLAGS,
220                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
221                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
222                 cursor.getInt(9));
223         assertEquals(1024, getInt(cursor, COLUMN_SIZE));
224         assertEquals(
225                 MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
226 
227         cursor.moveToNext();
228         assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
229         assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
230         assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
231         assertEquals(101, getInt(cursor, COLUMN_OBJECT_HANDLE));
232         assertEquals("image/jpeg", getString(cursor, COLUMN_MIME_TYPE));
233         assertEquals("image.jpg", getString(cursor, COLUMN_DISPLAY_NAME));
234         assertTrue(isNull(cursor, COLUMN_SUMMARY));
235         assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
236         assertTrue(isNull(cursor, COLUMN_ICON));
237         assertEquals(
238                 COLUMN_FLAGS,
239                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
240                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
241                 DocumentsContract.Document.FLAG_SUPPORTS_METADATA,
242                 cursor.getInt(9));
243         assertEquals(2 * 1024 * 1024, getInt(cursor, COLUMN_SIZE));
244         assertEquals(
245                 MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
246 
247         cursor.moveToNext();
248         assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
249         assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
250         assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
251         assertEquals(102, getInt(cursor, COLUMN_OBJECT_HANDLE));
252         assertEquals("audio/mpeg", getString(cursor, COLUMN_MIME_TYPE));
253         assertEquals("music.mp3", getString(cursor, COLUMN_DISPLAY_NAME));
254         assertTrue(isNull(cursor, COLUMN_SUMMARY));
255         assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
256         assertTrue(isNull(cursor, COLUMN_ICON));
257         assertEquals(
258                 COLUMN_FLAGS,
259                 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
260                 DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
261                 cursor.getInt(9));
262         assertEquals(3 * 1024 * 1024, getInt(cursor, COLUMN_SIZE));
263         assertEquals(
264                 MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
265 
266         cursor.close();
267     }
268 
testPutChildDocuments_operationsSupported()269     public void testPutChildDocuments_operationsSupported() throws Exception {
270         addTestDevice();
271         addTestStorage("1");
272 
273         // Put a document with empty supported operations.
274         mDatabase.getMapper().startAddingDocuments("2");
275         mDatabase.getMapper().putChildDocuments(0, "2", new int[0], new MtpObjectInfo[] {
276                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024)
277         }, new long[] { 1024L });
278         mDatabase.getMapper().stopAddingDocuments("2");
279 
280         try (final Cursor cursor =
281                 mDatabase.queryChildDocuments(strings(Document.COLUMN_FLAGS), "2")) {
282             assertEquals(1, cursor.getCount());
283             cursor.moveToNext();
284             assertEquals(0, cursor.getInt(0));
285         }
286 
287         // Put a document with writable operations.
288         mDatabase.getMapper().startAddingDocuments("2");
289         mDatabase.getMapper().putChildDocuments(0, "2", new int[] {
290                 MtpConstants.OPERATION_SEND_OBJECT,
291                 MtpConstants.OPERATION_SEND_OBJECT_INFO,
292         }, new MtpObjectInfo[] {
293                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024)
294         }, new long[] { 1024L });
295         mDatabase.getMapper().stopAddingDocuments("2");
296 
297         try (final Cursor cursor =
298                 mDatabase.queryChildDocuments(strings(Document.COLUMN_FLAGS), "2")) {
299             assertEquals(1, cursor.getCount());
300             cursor.moveToNext();
301             assertEquals(Document.FLAG_SUPPORTS_WRITE, cursor.getInt(0));
302         }
303 
304         // Put a document with deletable operations.
305         mDatabase.getMapper().startAddingDocuments("2");
306         mDatabase.getMapper().putChildDocuments(0, "2", new int[] {
307                 MtpConstants.OPERATION_DELETE_OBJECT
308         }, new MtpObjectInfo[] {
309                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024)
310         }, new long[] { 1024L });
311         mDatabase.getMapper().stopAddingDocuments("2");
312 
313         try (final Cursor cursor =
314                 mDatabase.queryChildDocuments(strings(Document.COLUMN_FLAGS), "2")) {
315             assertEquals(1, cursor.getCount());
316             cursor.moveToNext();
317             assertEquals(Document.FLAG_SUPPORTS_DELETE, cursor.getInt(0));
318         }
319     }
320 
testRestoreIdForRootDocuments()321     public void testRestoreIdForRootDocuments() throws Exception {
322         final String[] columns = new String[] {
323                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
324                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
325                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
326         };
327 
328         // Add device and two storages.
329         addTestDevice();
330         mDatabase.getMapper().startAddingDocuments("1");
331         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
332                 new MtpRoot(0, 100, "Storage A", 1000, 0, ""),
333                 new MtpRoot(0, 101, "Storage B", 1001, 0, "")
334         });
335 
336         {
337             final Cursor cursor = mDatabase.queryRootDocuments(columns);
338             assertEquals(2, cursor.getCount());
339             cursor.moveToNext();
340             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
341             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
342             assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
343             cursor.moveToNext();
344             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
345             assertEquals(101, getInt(cursor, COLUMN_STORAGE_ID));
346             assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
347             cursor.close();
348         }
349 
350         // Clear mapping and add a device.
351         mDatabase.getMapper().clearMapping();
352         addTestDevice();
353 
354         {
355             final Cursor cursor = mDatabase.queryRootDocuments(columns);
356             assertEquals(0, cursor.getCount());
357             cursor.close();
358         }
359 
360         // Add two storages, but one's name is different from previous one.
361         mDatabase.getMapper().startAddingDocuments("1");
362         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
363                 new MtpRoot(0, 200, "Storage A", 2000, 0, ""),
364                 new MtpRoot(0, 202, "Storage C", 2002, 0, "")
365         });
366         mDatabase.getMapper().stopAddingDocuments("1");
367 
368         {
369             // After compeleting mapping, Storage A can be obtained with new storage ID.
370             final Cursor cursor = mDatabase.queryRootDocuments(columns);
371             assertEquals(2, cursor.getCount());
372             cursor.moveToNext();
373             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
374             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
375             assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
376             cursor.moveToNext();
377             assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
378             assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
379             assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
380             cursor.close();
381         }
382     }
383 
testRestoreIdForChildDocuments()384     public void testRestoreIdForChildDocuments() throws Exception {
385         final String[] columns = new String[] {
386                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
387                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
388                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
389         };
390 
391         addTestDevice();
392         addTestStorage("1");
393 
394         mDatabase.getMapper().startAddingDocuments("2");
395         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
396                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
397                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
398                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
399         }, new long[] { 1024L, 2L * 1024L * 1024L, 3L * 1024L * 1024L});
400         mDatabase.getMapper().clearMapping();
401 
402         addTestDevice();
403         addTestStorage("1");
404 
405         {
406             // Don't return objects that lost MTP object handles.
407             final Cursor cursor = mDatabase.queryChildDocuments(columns, "2");
408             assertEquals(0, cursor.getCount());
409             cursor.close();
410         }
411 
412         mDatabase.getMapper().startAddingDocuments("2");
413         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
414                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
415                 createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
416         }, new long[] { 1024L, 1024L });
417         mDatabase.getMapper().stopAddingDocuments("2");
418 
419         {
420             final Cursor cursor = mDatabase.queryChildDocuments(columns, "2");
421             assertEquals(2, cursor.getCount());
422 
423             cursor.moveToNext();
424             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
425             assertEquals(200, getInt(cursor, COLUMN_OBJECT_HANDLE));
426             assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
427 
428             cursor.moveToNext();
429             assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
430             assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
431             assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
432 
433             cursor.close();
434         }
435     }
436 
testRestoreIdForDifferentDevices()437     public void testRestoreIdForDifferentDevices() throws Exception {
438         final String[] columns = new String[] {
439                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
440                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
441                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
442         };
443         final String[] rootColumns = new String[] {
444                 Root.COLUMN_ROOT_ID,
445                 Root.COLUMN_AVAILABLE_BYTES
446         };
447         mDatabase.getMapper().startAddingDocuments(null);
448         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
449                 0, "Device A", "Device key A", true, new MtpRoot[0], null, null));
450         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
451                 1, "Device B", "Device key B", true, new MtpRoot[0], null, null));
452         mDatabase.getMapper().stopAddingDocuments(null);
453 
454         mDatabase.getMapper().startAddingDocuments("1");
455         mDatabase.getMapper().startAddingDocuments("2");
456         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
457                 new MtpRoot(0, 100, "Storage", 0, 0, "")
458         });
459         mDatabase.getMapper().putStorageDocuments("2", OPERATIONS_SUPPORTED, new MtpRoot[] {
460                 new MtpRoot(1, 100, "Storage", 0, 0, "")
461         });
462 
463         {
464             final Cursor cursor = mDatabase.queryRootDocuments(columns);
465             assertEquals(2, cursor.getCount());
466             cursor.moveToNext();
467             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
468             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
469             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
470             cursor.moveToNext();
471             assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
472             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
473             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
474             cursor.close();
475         }
476 
477         {
478             final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
479             assertEquals(2, cursor.getCount());
480             cursor.moveToNext();
481             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
482             assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
483             cursor.moveToNext();
484             assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
485             assertEquals(0, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
486             cursor.close();
487         }
488 
489         mDatabase.getMapper().clearMapping();
490 
491         mDatabase.getMapper().startAddingDocuments(null);
492         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
493                 0, "Device A", "Device key A", true, new MtpRoot[0], null, null));
494         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
495                 1, "Device B", "Device key B", true, new MtpRoot[0], null, null));
496         mDatabase.getMapper().stopAddingDocuments(null);
497 
498         mDatabase.getMapper().startAddingDocuments("1");
499         mDatabase.getMapper().startAddingDocuments("2");
500         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
501                 new MtpRoot(0, 200, "Storage", 2000, 0, "")
502         });
503         mDatabase.getMapper().putStorageDocuments("2", OPERATIONS_SUPPORTED, new MtpRoot[] {
504                 new MtpRoot(1, 300, "Storage", 3000, 0, "")
505         });
506         mDatabase.getMapper().stopAddingDocuments("1");
507         mDatabase.getMapper().stopAddingDocuments("2");
508 
509         {
510             final Cursor cursor = mDatabase.queryRootDocuments(columns);
511             assertEquals(2, cursor.getCount());
512             cursor.moveToNext();
513             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
514             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
515             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
516             cursor.moveToNext();
517             assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
518             assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
519             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
520             cursor.close();
521         }
522 
523         {
524             final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
525             assertEquals(2, cursor.getCount());
526             cursor.moveToNext();
527             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
528             assertEquals(2000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
529             cursor.moveToNext();
530             assertEquals(2, getInt(cursor, Root.COLUMN_ROOT_ID));
531             assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
532             cursor.close();
533         }
534     }
535 
testRestoreIdForDifferentParents()536     public void testRestoreIdForDifferentParents() throws Exception {
537         final String[] columns = new String[] {
538                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
539                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
540         };
541 
542         // Add device, storage, and two directories.
543         addTestDevice();
544         addTestStorage("1");
545         mDatabase.getMapper().startAddingDocuments("2");
546         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
547                 createDocument(50, "A", MtpConstants.FORMAT_ASSOCIATION, 0),
548                 createDocument(51, "B", MtpConstants.FORMAT_ASSOCIATION, 0),
549         }, new long[] { 0L, 0L });
550         mDatabase.getMapper().stopAddingDocuments("2");
551 
552         // Put note.txt in each directory.
553         mDatabase.getMapper().startAddingDocuments("3");
554         mDatabase.getMapper().startAddingDocuments("4");
555         mDatabase.getMapper().putChildDocuments(0, "3", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
556                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
557         }, new long[] { 1024L });
558         mDatabase.getMapper().putChildDocuments(0, "4", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
559                 createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
560         }, new long[] { 1024L });
561 
562         // Clear mapping.
563         mDatabase.getMapper().clearMapping();
564 
565         // Add device, storage, and two directories again.
566         addTestDevice();
567         addTestStorage("1");
568         mDatabase.getMapper().startAddingDocuments("2");
569         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
570                 createDocument(50, "A", MtpConstants.FORMAT_ASSOCIATION, 0),
571                 createDocument(51, "B", MtpConstants.FORMAT_ASSOCIATION, 0),
572         }, new long[] { 0L, 0L });
573         mDatabase.getMapper().stopAddingDocuments("2");
574 
575         // Add note.txt in each directory again.
576         mDatabase.getMapper().startAddingDocuments("3");
577         mDatabase.getMapper().startAddingDocuments("4");
578         mDatabase.getMapper().putChildDocuments(0, "3", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
579                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
580         }, new long[] { 1024L });
581         mDatabase.getMapper().putChildDocuments(0, "4", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
582                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
583         }, new long[] { 1024L });
584         mDatabase.getMapper().stopAddingDocuments("3");
585         mDatabase.getMapper().stopAddingDocuments("4");
586 
587         // Check if the two note.txt are mapped correctly.
588         {
589             final Cursor cursor = mDatabase.queryChildDocuments(columns, "3");
590             assertEquals(1, cursor.getCount());
591             cursor.moveToNext();
592             assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
593             assertEquals(200, getInt(cursor, COLUMN_OBJECT_HANDLE));
594             cursor.close();
595         }
596         {
597             final Cursor cursor = mDatabase.queryChildDocuments(columns, "4");
598             assertEquals(1, cursor.getCount());
599             cursor.moveToNext();
600             assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
601             assertEquals(201, getInt(cursor, COLUMN_OBJECT_HANDLE));
602             cursor.close();
603         }
604     }
605 
testClearMtpIdentifierBeforeResolveRootDocuments()606     public void testClearMtpIdentifierBeforeResolveRootDocuments() throws Exception {
607         final String[] columns = new String[] {
608                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
609                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
610                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
611         };
612         final String[] rootColumns = new String[] {
613                 Root.COLUMN_ROOT_ID,
614                 Root.COLUMN_AVAILABLE_BYTES
615         };
616 
617         addTestDevice();
618 
619         mDatabase.getMapper().startAddingDocuments("1");
620         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
621                 new MtpRoot(0, 100, "Storage", 0, 0, ""),
622         });
623         mDatabase.getMapper().clearMapping();
624 
625         addTestDevice();
626 
627         try (final Cursor cursor = mDatabase.queryRoots(resources, rootColumns)) {
628             assertEquals(1, cursor.getCount());
629             cursor.moveToNext();
630             assertEquals("1", getString(cursor, Root.COLUMN_ROOT_ID));
631         }
632 
633         mDatabase.getMapper().startAddingDocuments("1");
634         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
635                 new MtpRoot(0, 200, "Storage", 2000, 0, ""),
636         });
637         mDatabase.getMapper().clearMapping();
638 
639         addTestDevice();
640 
641         mDatabase.getMapper().startAddingDocuments("1");
642         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
643                 new MtpRoot(0, 300, "Storage", 3000, 0, ""),
644         });
645         mDatabase.getMapper().stopAddingDocuments("1");
646 
647         {
648             final Cursor cursor = mDatabase.queryRootDocuments(columns);
649             assertEquals(1, cursor.getCount());
650             cursor.moveToNext();
651             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
652             assertEquals(300, getInt(cursor, COLUMN_STORAGE_ID));
653             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
654             cursor.close();
655         }
656         {
657             final Cursor cursor = mDatabase.queryRoots(resources, rootColumns);
658             assertEquals(1, cursor.getCount());
659             cursor.moveToNext();
660             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
661             assertEquals(3000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
662             cursor.close();
663         }
664     }
665 
testPutSameNameRootsAfterClearing()666     public void testPutSameNameRootsAfterClearing() throws Exception {
667         final String[] columns = new String[] {
668                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
669                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
670                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
671         };
672 
673         // Add a device and a storage.
674         addTestDevice();
675         addTestStorage("1");
676 
677         // Disconnect devices.
678         mDatabase.getMapper().clearMapping();
679 
680         // Add a device and two storages that has same name.
681         addTestDevice();
682         mDatabase.getMapper().startAddingDocuments("1");
683         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
684                 new MtpRoot(0, 200, "Storage", 2000, 0, ""),
685                 new MtpRoot(0, 201, "Storage", 2001, 0, ""),
686         });
687         mDatabase.getMapper().stopAddingDocuments("1");
688 
689         {
690             final Cursor cursor = mDatabase.queryRootDocuments(columns);
691             assertEquals(2, cursor.getCount());
692 
693             // First storage reuse document ID of previous storage.
694             cursor.moveToNext();
695             // One reuses exisitng document ID 1.
696             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
697             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
698             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
699 
700             // Second one has new document ID.
701             cursor.moveToNext();
702             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
703             assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
704             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
705 
706             cursor.close();
707         }
708     }
709 
testReplaceExistingRoots()710     public void testReplaceExistingRoots() throws Exception {
711         addTestDevice();
712 
713         // The client code should be able to replace existing rows with new information.
714         // Add one.
715         mDatabase.getMapper().startAddingDocuments("1");
716         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
717                 new MtpRoot(0, 100, "Storage A", 0, 0, ""),
718         });
719         mDatabase.getMapper().stopAddingDocuments("1");
720         // Replace it.
721         mDatabase.getMapper().startAddingDocuments("1");
722         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
723                 new MtpRoot(0, 100, "Storage B", 1000, 1000, ""),
724         });
725         mDatabase.getMapper().stopAddingDocuments("1");
726         {
727             final String[] columns = new String[] {
728                     DocumentsContract.Document.COLUMN_DOCUMENT_ID,
729                     MtpDatabaseConstants.COLUMN_STORAGE_ID,
730                     DocumentsContract.Document.COLUMN_DISPLAY_NAME
731             };
732             final Cursor cursor = mDatabase.queryRootDocuments(columns);
733             assertEquals(1, cursor.getCount());
734             cursor.moveToNext();
735             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
736             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
737             assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
738             cursor.close();
739         }
740         {
741             final String[] columns = new String[] {
742                     Root.COLUMN_ROOT_ID,
743                     Root.COLUMN_TITLE,
744                     Root.COLUMN_AVAILABLE_BYTES
745             };
746             final Cursor cursor = mDatabase.queryRoots(resources, columns);
747             assertEquals(1, cursor.getCount());
748             cursor.moveToNext();
749             assertEquals(1, getInt(cursor, Root.COLUMN_ROOT_ID));
750             assertEquals("Device Storage B", getString(cursor, Root.COLUMN_TITLE));
751             assertEquals(1000, getInt(cursor, Root.COLUMN_AVAILABLE_BYTES));
752             cursor.close();
753         }
754     }
755 
testFailToReplaceExisitingUnmappedRoots()756     public void testFailToReplaceExisitingUnmappedRoots() throws Exception {
757         // The client code should not be able to replace rows before resolving 'unmapped' rows.
758         // Add one.
759         addTestDevice();
760         mDatabase.getMapper().startAddingDocuments("1");
761         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
762                 new MtpRoot(0, 100, "Storage A", 0, 0, ""),
763         });
764         mDatabase.getMapper().clearMapping();
765 
766         addTestDevice();
767         try (final Cursor oldCursor =
768                 mDatabase.queryRoots(resources, strings(Root.COLUMN_ROOT_ID))) {
769             assertEquals(1, oldCursor.getCount());
770             oldCursor.moveToNext();
771             assertEquals("1", getString(oldCursor, Root.COLUMN_ROOT_ID));
772 
773             // Add one.
774             mDatabase.getMapper().startAddingDocuments("1");
775             mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
776                     new MtpRoot(0, 101, "Storage B", 1000, 1000, ""),
777             });
778             // Add one more before resolving unmapped documents.
779             mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
780                     new MtpRoot(0, 102, "Storage B", 1000, 1000, ""),
781             });
782             mDatabase.getMapper().stopAddingDocuments("1");
783 
784             // Because the roots shares the same name, the roots should have new IDs.
785             try (final Cursor newCursor = mDatabase.queryChildDocuments(
786                     strings(Document.COLUMN_DOCUMENT_ID), "1")) {
787                 assertEquals(2, newCursor.getCount());
788                 newCursor.moveToNext();
789                 assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
790                 newCursor.moveToNext();
791                 assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
792             }
793         }
794     }
795 
testQueryDocuments()796     public void testQueryDocuments() throws Exception {
797         addTestDevice();
798         addTestStorage("1");
799 
800         final Cursor cursor = mDatabase.queryDocument("2", strings(Document.COLUMN_DISPLAY_NAME));
801         assertEquals(1, cursor.getCount());
802         cursor.moveToNext();
803         assertEquals("Storage", getString(cursor, Document.COLUMN_DISPLAY_NAME));
804         cursor.close();
805     }
806 
testQueryRoots()807     public void testQueryRoots() throws Exception {
808         // Add device document.
809         addTestDevice();
810 
811         // It the device does not have storages, it shows a device root.
812         {
813             final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
814             assertEquals(1, cursor.getCount());
815             cursor.moveToNext();
816             assertEquals("Device", cursor.getString(0));
817             cursor.close();
818         }
819 
820         mDatabase.getMapper().startAddingDocuments("1");
821         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
822                 new MtpRoot(0, 100, "Storage A", 0, 0, "")
823         });
824         mDatabase.getMapper().stopAddingDocuments("1");
825 
826         // It the device has single storage, it shows a storage root.
827         {
828             final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
829             assertEquals(1, cursor.getCount());
830             cursor.moveToNext();
831             assertEquals("Device Storage A", cursor.getString(0));
832             cursor.close();
833         }
834 
835         mDatabase.getMapper().startAddingDocuments("1");
836         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
837                 new MtpRoot(0, 100, "Storage A", 0, 0, ""),
838                 new MtpRoot(0, 101, "Storage B", 0, 0, "")
839         });
840         mDatabase.getMapper().stopAddingDocuments("1");
841 
842         // It the device has multiple storages, it shows a device root.
843         {
844             final Cursor cursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_TITLE));
845             assertEquals(1, cursor.getCount());
846             cursor.moveToNext();
847             assertEquals("Device", cursor.getString(0));
848             cursor.close();
849         }
850     }
851 
testGetParentId()852     public void testGetParentId() throws FileNotFoundException {
853         addTestDevice();
854 
855         mDatabase.getMapper().startAddingDocuments("1");
856         mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] {
857                 new MtpRoot(0, 100, "Storage A", 0, 0, ""),
858         });
859         mDatabase.getMapper().stopAddingDocuments("1");
860 
861         mDatabase.getMapper().startAddingDocuments("2");
862         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
863                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
864         }, new long[] { 1024L });
865         mDatabase.getMapper().stopAddingDocuments("2");
866 
867         assertEquals("2", mDatabase.getParentIdentifier("3").mDocumentId);
868     }
869 
testDeleteDocument()870     public void testDeleteDocument() throws Exception {
871         addTestDevice();
872         addTestStorage("1");
873 
874         mDatabase.getMapper().startAddingDocuments("2");
875         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
876                 createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
877         }, new long[] { 1024L });
878         mDatabase.getMapper().stopAddingDocuments("2");
879 
880         mDatabase.getMapper().startAddingDocuments("3");
881         mDatabase.getMapper().putChildDocuments(0, "3", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
882                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
883         }, new long[] { 1024L });
884         mDatabase.getMapper().stopAddingDocuments("3");
885 
886         mDatabase.deleteDocument("3");
887 
888         {
889             // Do not query deleted documents.
890             final Cursor cursor =
891                     mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
892             assertEquals(0, cursor.getCount());
893             cursor.close();
894         }
895 
896         {
897             // Child document should be deleted also.
898             final Cursor cursor =
899                     mDatabase.queryDocument("4", strings(Document.COLUMN_DOCUMENT_ID));
900             assertEquals(0, cursor.getCount());
901             cursor.close();
902         }
903     }
904 
testPutNewDocument()905     public void testPutNewDocument() throws Exception {
906         addTestDevice();
907         addTestStorage("1");
908 
909         assertEquals(
910                 "3",
911                 mDatabase.putNewDocument(
912                         0, "2", OPERATIONS_SUPPORTED,
913                         createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
914                         1024L));
915 
916         {
917             final Cursor cursor =
918                     mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
919             assertEquals(1, cursor.getCount());
920             cursor.moveToNext();
921             assertEquals("3", cursor.getString(0));
922             cursor.close();
923         }
924 
925         // The new document should not be mapped with existing invalidated document.
926         mDatabase.getMapper().clearMapping();
927         addTestDevice();
928         addTestStorage("1");
929 
930         mDatabase.getMapper().startAddingDocuments("2");
931         mDatabase.putNewDocument(
932                 0, "2", OPERATIONS_SUPPORTED,
933                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
934                 1024L);
935         mDatabase.getMapper().stopAddingDocuments("2");
936 
937         {
938             final Cursor cursor =
939                     mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
940             assertEquals(1, cursor.getCount());
941             cursor.moveToNext();
942             assertEquals("4", cursor.getString(0));
943             cursor.close();
944         }
945     }
946 
testGetDocumentIdForDevice()947     public void testGetDocumentIdForDevice() throws Exception {
948         addTestDevice();
949         assertEquals("1", mDatabase.getDocumentIdForDevice(0));
950     }
951 
testGetClosedDevice()952     public void testGetClosedDevice() throws Exception {
953         mDatabase.getMapper().startAddingDocuments(null);
954         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
955                 0, "Device", null /* deviceKey */, /* opened is */ false, new MtpRoot[0], null,
956                 null));
957         mDatabase.getMapper().stopAddingDocuments(null);
958 
959         final String[] columns = new String [] {
960                 DocumentsContract.Root.COLUMN_ROOT_ID,
961                 DocumentsContract.Root.COLUMN_TITLE,
962                 DocumentsContract.Root.COLUMN_AVAILABLE_BYTES
963         };
964         try (final Cursor cursor = mDatabase.queryRoots(resources, columns)) {
965             assertEquals(1, cursor.getCount());
966             assertTrue(cursor.moveToNext());
967             assertEquals(1, cursor.getLong(0));
968             assertEquals("Device", cursor.getString(1));
969             assertTrue(cursor.isNull(2));
970         }
971     }
972 
testMappingWithoutKey()973     public void testMappingWithoutKey() throws FileNotFoundException {
974         mDatabase.getMapper().startAddingDocuments(null);
975         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
976                 0, "Device", null /* device key */, /* opened is */ true, new MtpRoot[0], null,
977                 null));
978         mDatabase.getMapper().stopAddingDocuments(null);
979 
980         mDatabase.getMapper().startAddingDocuments(null);
981         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
982                 0, "Device", null /* device key */, /* opened is */ true, new MtpRoot[0], null,
983                 null));
984         mDatabase.getMapper().stopAddingDocuments(null);
985 
986         try (final Cursor cursor =
987                 mDatabase.queryRoots(resources, strings(DocumentsContract.Root.COLUMN_ROOT_ID))) {
988             assertEquals(1, cursor.getCount());
989             assertTrue(cursor.moveToNext());
990             assertEquals(1, cursor.getLong(0));
991         }
992     }
993 
testMappingFailsWithoutKey()994     public void testMappingFailsWithoutKey() throws FileNotFoundException {
995         mDatabase.getMapper().startAddingDocuments(null);
996         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
997                 0, "Device", null /* device key */, /* opened is */ true, new MtpRoot[0], null,
998                 null));
999         mDatabase.getMapper().stopAddingDocuments(null);
1000 
1001         // MTP identifier is cleared here. Mapping no longer works without device key.
1002         mDatabase.getMapper().startAddingDocuments(null);
1003         mDatabase.getMapper().stopAddingDocuments(null);
1004 
1005         mDatabase.getMapper().startAddingDocuments(null);
1006         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
1007                 0, "Device", null /* device key */, /* opened is */ true, new MtpRoot[0], null,
1008                 null));
1009         mDatabase.getMapper().stopAddingDocuments(null);
1010 
1011         try (final Cursor cursor =
1012                 mDatabase.queryRoots(resources, strings(DocumentsContract.Root.COLUMN_ROOT_ID))) {
1013             assertEquals(1, cursor.getCount());
1014             assertTrue(cursor.moveToNext());
1015             assertEquals(2, cursor.getLong(0));
1016         }
1017     }
1018 
testUpdateDocumentWithoutChange()1019     public void testUpdateDocumentWithoutChange() throws FileNotFoundException {
1020         mDatabase.getMapper().startAddingDocuments(null);
1021         assertTrue(mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
1022                 0, "Device", "device_key", /* opened is */ true, new MtpRoot[0], null,
1023                 null)));
1024         assertFalse(mDatabase.getMapper().stopAddingDocuments(null));
1025 
1026         mDatabase.getMapper().startAddingDocuments(null);
1027         assertFalse(mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
1028                 0, "Device", "device_key", /* opened is */ true, new MtpRoot[0], null,
1029                 null)));
1030         assertFalse(mDatabase.getMapper().stopAddingDocuments(null));
1031     }
1032 
testSetBootCount()1033     public void testSetBootCount() {
1034         assertEquals(0, mDatabase.getLastBootCount());
1035         mDatabase.setLastBootCount(10);
1036         assertEquals(10, mDatabase.getLastBootCount());
1037         try {
1038             mDatabase.setLastBootCount(-1);
1039             fail();
1040         } catch (IllegalArgumentException e) {}
1041     }
1042 
testCleanDatabase()1043     public void testCleanDatabase() throws FileNotFoundException {
1044         // Add tree.
1045         addTestDevice();
1046         addTestStorage("1");
1047         mDatabase.getMapper().startAddingDocuments("2");
1048         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
1049                 createDocument(100, "apple.txt", MtpConstants.FORMAT_TEXT, 1024),
1050                 createDocument(101, "orange.txt", MtpConstants.FORMAT_TEXT, 1024),
1051         }, new long[] { 1024L, 1024L });
1052         mDatabase.getMapper().stopAddingDocuments("2");
1053 
1054         // Disconnect the device.
1055         mDatabase.getMapper().startAddingDocuments(null);
1056         mDatabase.getMapper().stopAddingDocuments(null);
1057 
1058         // Clean database.
1059         mDatabase.cleanDatabase(new Uri[] {
1060                 DocumentsContract.buildDocumentUri(MtpDocumentsProvider.AUTHORITY, "3")
1061         });
1062 
1063         // Add tree again.
1064         addTestDevice();
1065         addTestStorage("1");
1066         mDatabase.getMapper().startAddingDocuments("2");
1067         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
1068                 createDocument(100, "apple.txt", MtpConstants.FORMAT_TEXT, 1024),
1069                 createDocument(101, "orange.txt", MtpConstants.FORMAT_TEXT, 1024),
1070         }, new long[] { 1024L, 1024L });
1071         mDatabase.getMapper().stopAddingDocuments("2");
1072 
1073         try (final Cursor cursor = mDatabase.queryChildDocuments(
1074                 strings(COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME), "2")) {
1075             assertEquals(2, cursor.getCount());
1076 
1077             // Persistent uri uses the same ID.
1078             cursor.moveToNext();
1079             assertEquals("3", cursor.getString(0));
1080             assertEquals("apple.txt", cursor.getString(1));
1081 
1082             // Others does not.
1083             cursor.moveToNext();
1084             assertEquals("5", cursor.getString(0));
1085             assertEquals("orange.txt", cursor.getString(1));
1086         }
1087     }
1088 
testFormatCodeForMpeg()1089     public void testFormatCodeForMpeg() throws FileNotFoundException {
1090         addTestDevice();
1091         addTestStorage("1");
1092         mDatabase.getMapper().startAddingDocuments("2");
1093         mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] {
1094             createDocument(100, "audio.m4a", MtpConstants.FORMAT_MPEG, 1000),
1095             createDocument(101, "video.m4v", MtpConstants.FORMAT_MPEG, 1000),
1096             createDocument(102, "unknown.mp4", MtpConstants.FORMAT_MPEG, 1000),
1097             createDocument(103, "inconsistent.txt", MtpConstants.FORMAT_MPEG, 1000),
1098             createDocument(104, "noext", MtpConstants.FORMAT_UNDEFINED, 1000),
1099         }, new long[] { 1000L, 1000L, 1000L, 1000L, 1000L });
1100         mDatabase.getMapper().stopAddingDocuments("2");
1101         try (final Cursor cursor = mDatabase.queryChildDocuments(
1102                 strings(COLUMN_DISPLAY_NAME,  COLUMN_MIME_TYPE),
1103                 "2")) {
1104             assertEquals(5, cursor.getCount());
1105             cursor.moveToNext();
1106             assertEquals("audio.m4a", cursor.getString(0));
1107             assertEquals("audio/mp4", cursor.getString(1));
1108             cursor.moveToNext();
1109             assertEquals("video.m4v", cursor.getString(0));
1110             assertEquals("video/mp4", cursor.getString(1));
1111             cursor.moveToNext();
1112             // Assume that the file is video as we don't have any hints to find out if the file is
1113             // video or audio.
1114             assertEquals("unknown.mp4", cursor.getString(0));
1115             assertEquals("video/mp4", cursor.getString(1));
1116             // Don't return mime type that is inconsistent with format code.
1117             cursor.moveToNext();
1118             assertEquals("inconsistent.txt", cursor.getString(0));
1119             assertEquals("video/mpeg", cursor.getString(1));
1120             cursor.moveToNext();
1121             assertEquals("noext", cursor.getString(0));
1122             assertEquals("application/octet-stream", cursor.getString(1));
1123         }
1124     }
1125 
addTestDevice()1126     private void addTestDevice() throws FileNotFoundException {
1127         TestUtil.addTestDevice(mDatabase);
1128     }
1129 
addTestStorage(String parentId)1130     private void addTestStorage(String parentId) throws FileNotFoundException {
1131         TestUtil.addTestStorage(mDatabase, parentId);
1132     }
1133 }
1134