blob: 29783e4165c78de3e44cd6737eec739174713d58 [file] [log] [blame]
Daichi Hirono9a05d6b2015-07-09 15:36:42 +09001/*
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
Daichi Hirono09109562015-07-28 20:57:05 +090019import android.database.Cursor;
Tomasz Mikolajewskibb430fa2015-08-25 18:34:30 +090020import android.mtp.MtpConstants;
Tomasz Mikolajewskibb430fa2015-08-25 18:34:30 +090021import android.mtp.MtpObjectInfo;
Daichi Hironod5152422015-07-15 13:31:51 +090022import android.net.Uri;
Daichi Hironob36b1552016-01-25 14:26:14 +090023import android.os.ParcelFileDescriptor;
24import android.os.storage.StorageManager;
Daichi Hirono4e94b8d2016-02-21 22:42:41 +090025import android.provider.DocumentsContract.Document;
Daichi Hironob9ffa2a2016-11-01 18:41:43 +090026import android.provider.DocumentsContract.Path;
Daichi Hirono09109562015-07-28 20:57:05 +090027import android.provider.DocumentsContract.Root;
Daichi Hironob36b1552016-01-25 14:26:14 +090028import android.system.Os;
29import android.system.OsConstants;
Tomasz Mikolajewskibb430fa2015-08-25 18:34:30 +090030import android.provider.DocumentsContract;
Daichi Hirono9a05d6b2015-07-09 15:36:42 +090031import android.test.AndroidTestCase;
Daichi Hironob36b1552016-01-25 14:26:14 +090032import android.test.suitebuilder.annotation.MediumTest;
Daichi Hirono9a05d6b2015-07-09 15:36:42 +090033
Daichi Hirono5fecc6c2015-08-04 17:45:51 +090034import java.io.FileNotFoundException;
Daichi Hironod5152422015-07-15 13:31:51 +090035import java.io.IOException;
Daichi Hironob36b1552016-01-25 14:26:14 +090036import java.util.Arrays;
Daichi Hironob9ffa2a2016-11-01 18:41:43 +090037import java.util.LinkedList;
Daichi Hironob9ffa2a2016-11-01 18:41:43 +090038import java.util.Queue;
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +090039import java.util.concurrent.TimeoutException;
40
Daichi Hirono259ce802015-11-20 17:51:53 +090041import static com.android.mtp.MtpDatabase.strings;
Daichi Hirono0f325372016-02-21 15:50:30 +090042import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED;
Daichi Hironod5152422015-07-15 13:31:51 +090043
Daichi Hironob36b1552016-01-25 14:26:14 +090044@MediumTest
Daichi Hirono9a05d6b2015-07-09 15:36:42 +090045public class MtpDocumentsProviderTest extends AndroidTestCase {
Daichi Hirono8b9024f2015-08-12 12:59:09 +090046 private final static Uri ROOTS_URI =
47 DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
Daichi Hirono6baa16e2015-08-12 13:51:59 +090048 private TestContentResolver mResolver;
Daichi Hironobeac2182015-07-29 16:28:57 +090049 private MtpDocumentsProvider mProvider;
Daichi Hirono6ee4a1c2015-07-31 10:30:05 +090050 private TestMtpManager mMtpManager;
Daichi Hironob999b0c2015-10-27 16:29:18 +090051 private final TestResources mResources = new TestResources();
Daichi Hironodc473442015-11-13 15:42:28 +090052 private MtpDatabase mDatabase;
Daichi Hironobeac2182015-07-29 16:28:57 +090053
54 @Override
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +090055 public void setUp() throws IOException {
Daichi Hirono6baa16e2015-08-12 13:51:59 +090056 mResolver = new TestContentResolver();
Daichi Hirono6ee4a1c2015-07-31 10:30:05 +090057 mMtpManager = new TestMtpManager(getContext());
Daichi Hironodc473442015-11-13 15:42:28 +090058 }
59
60 @Override
61 public void tearDown() {
Daichi Hironoe1d57712015-11-17 10:55:45 +090062 mProvider.shutdown();
Daichi Hironoe0282dd2015-11-26 15:20:08 +090063 MtpDatabase.deleteDatabase(getContext());
Daichi Hironobeac2182015-07-29 16:28:57 +090064 }
65
Daichi Hironod5152422015-07-15 13:31:51 +090066 public void testOpenAndCloseDevice() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +090067 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono20754c52015-12-15 18:52:26 +090068 mMtpManager.addValidDevice(new MtpDeviceRecord(
69 0,
Daichi Hironof83ccbd2016-02-04 16:58:55 +090070 "Device A",
Daichi Hironoebd24052016-02-06 21:05:57 +090071 null /* deviceKey */,
Daichi Hirono20754c52015-12-15 18:52:26 +090072 false /* unopened */,
73 new MtpRoot[] {
74 new MtpRoot(
75 0 /* deviceId */,
76 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +090077 "Storage A" /* volume description */,
78 1024 /* free space */,
79 2048 /* total space */,
80 "" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +090081 },
Daichi Hirono0f325372016-02-21 15:50:30 +090082 OPERATIONS_SUPPORTED,
Daichi Hirono148954a2016-01-21 19:55:45 +090083 null));
Daichi Hironod5152422015-07-15 13:31:51 +090084
Daichi Hironofda74742016-02-01 13:00:31 +090085 mProvider.resumeRootScanner();
Daichi Hirono8b9024f2015-08-12 12:59:09 +090086 mResolver.waitForNotification(ROOTS_URI, 1);
Daichi Hirono09109562015-07-28 20:57:05 +090087
Daichi Hironofda74742016-02-01 13:00:31 +090088 mProvider.openDevice(0);
Daichi Hirono8b9024f2015-08-12 12:59:09 +090089 mResolver.waitForNotification(ROOTS_URI, 2);
Daichi Hironofda74742016-02-01 13:00:31 +090090
91 mProvider.closeDevice(0);
92 mResolver.waitForNotification(ROOTS_URI, 3);
Daichi Hironod5152422015-07-15 13:31:51 +090093 }
94
Daichi Hirono8b9024f2015-08-12 12:59:09 +090095 public void testOpenAndCloseErrorDevice() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +090096 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono8b9024f2015-08-12 12:59:09 +090097 try {
98 mProvider.openDevice(1);
99 fail();
100 } catch (Throwable error) {
101 assertTrue(error instanceof IOException);
102 }
Daichi Hirono0f325372016-02-21 15:50:30 +0900103 assertEquals(0, mProvider.getOpenedDeviceRecordsCache().length);
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900104
105 // Check if the following notification is the first one or not.
Daichi Hirono20754c52015-12-15 18:52:26 +0900106 mMtpManager.addValidDevice(new MtpDeviceRecord(
107 0,
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900108 "Device A",
Daichi Hironoebd24052016-02-06 21:05:57 +0900109 null /* deviceKey */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900110 false /* unopened */,
111 new MtpRoot[] {
112 new MtpRoot(
113 0 /* deviceId */,
114 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900115 "Storage A" /* volume description */,
116 1024 /* free space */,
117 2048 /* total space */,
118 "" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900119 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900120 OPERATIONS_SUPPORTED,
Daichi Hirono148954a2016-01-21 19:55:45 +0900121 null));
Daichi Hironofda74742016-02-01 13:00:31 +0900122 mProvider.resumeRootScanner();
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900123 mResolver.waitForNotification(ROOTS_URI, 1);
Daichi Hironofda74742016-02-01 13:00:31 +0900124 mProvider.openDevice(0);
125 mResolver.waitForNotification(ROOTS_URI, 2);
126 }
127
128 public void testOpenDeviceOnDemand() throws Exception {
129 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
130 mMtpManager.addValidDevice(new MtpDeviceRecord(
131 0,
132 "Device A",
Daichi Hironoebd24052016-02-06 21:05:57 +0900133 null /* deviceKey */,
Daichi Hironofda74742016-02-01 13:00:31 +0900134 false /* unopened */,
135 new MtpRoot[] {
136 new MtpRoot(
137 0 /* deviceId */,
138 1 /* storageId */,
Daichi Hironofda74742016-02-01 13:00:31 +0900139 "Storage A" /* volume description */,
140 1024 /* free space */,
141 2048 /* total space */,
142 "" /* no volume identifier */)
143 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900144 OPERATIONS_SUPPORTED,
Daichi Hironofda74742016-02-01 13:00:31 +0900145 null));
146 mMtpManager.setObjectHandles(0, 1, -1, new int[0]);
147 mProvider.resumeRootScanner();
148 mResolver.waitForNotification(ROOTS_URI, 1);
149 final String[] columns = new String[] {
150 DocumentsContract.Root.COLUMN_TITLE,
151 DocumentsContract.Root.COLUMN_DOCUMENT_ID
152 };
153 try (final Cursor cursor = mProvider.queryRoots(columns)) {
154 assertEquals(1, cursor.getCount());
155 assertTrue(cursor.moveToNext());
156 assertEquals("Device A", cursor.getString(0));
157 assertEquals(1, cursor.getLong(1));
158 }
159 {
Daichi Hirono0f325372016-02-21 15:50:30 +0900160 final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
Daichi Hironofda74742016-02-01 13:00:31 +0900161 assertEquals(0, openedDevice.length);
162 }
163 // Device is opened automatically when querying its children.
Steve McKay29c3f682016-12-16 14:52:59 -0800164 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {}
Daichi Hironofda74742016-02-01 13:00:31 +0900165
166 {
Daichi Hirono0f325372016-02-21 15:50:30 +0900167 final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
Daichi Hironofda74742016-02-01 13:00:31 +0900168 assertEquals(1, openedDevice.length);
Daichi Hirono0f325372016-02-21 15:50:30 +0900169 assertEquals(0, openedDevice[0].deviceId);
Daichi Hironofda74742016-02-01 13:00:31 +0900170 }
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900171 }
172
Daichi Hirono09109562015-07-28 20:57:05 +0900173 public void testQueryRoots() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900174 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono20754c52015-12-15 18:52:26 +0900175 mMtpManager.addValidDevice(new MtpDeviceRecord(
176 0,
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900177 "Device A",
Daichi Hironoebd24052016-02-06 21:05:57 +0900178 "Device key A",
Daichi Hirono20754c52015-12-15 18:52:26 +0900179 false /* unopened */,
180 new MtpRoot[] {
181 new MtpRoot(
182 0 /* deviceId */,
183 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900184 "Storage A" /* volume description */,
185 1024 /* free space */,
186 2048 /* total space */,
187 "" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900188 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900189 OPERATIONS_SUPPORTED,
Daichi Hirono148954a2016-01-21 19:55:45 +0900190 null));
Daichi Hirono20754c52015-12-15 18:52:26 +0900191 mMtpManager.addValidDevice(new MtpDeviceRecord(
192 1,
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900193 "Device B",
Daichi Hironoebd24052016-02-06 21:05:57 +0900194 "Device key B",
Daichi Hirono20754c52015-12-15 18:52:26 +0900195 false /* unopened */,
196 new MtpRoot[] {
197 new MtpRoot(
198 1 /* deviceId */,
199 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900200 "Storage B" /* volume description */,
201 2048 /* free space */,
202 4096 /* total space */,
203 "Identifier B" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900204 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900205 new int[0] /* No operations supported */,
Daichi Hirono148954a2016-01-21 19:55:45 +0900206 null));
Daichi Hironod5152422015-07-15 13:31:51 +0900207
Daichi Hirono09109562015-07-28 20:57:05 +0900208 {
Daichi Hironobeac2182015-07-29 16:28:57 +0900209 mProvider.openDevice(0);
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900210 mResolver.waitForNotification(ROOTS_URI, 1);
Daichi Hironobeac2182015-07-29 16:28:57 +0900211 final Cursor cursor = mProvider.queryRoots(null);
Daichi Hirono81d48532015-12-16 15:03:19 +0900212 assertEquals(2, cursor.getCount());
Daichi Hirono09109562015-07-28 20:57:05 +0900213 cursor.moveToNext();
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900214 assertEquals("1", cursor.getString(0));
Daichi Hirono83c679e72016-08-18 15:13:36 +0900215 assertEquals(
216 Root.FLAG_SUPPORTS_IS_CHILD |
217 Root.FLAG_SUPPORTS_CREATE |
218 Root.FLAG_LOCAL_ONLY,
219 cursor.getInt(1));
Daichi Hirono39795da2015-12-02 10:56:44 +0900220 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
Daichi Hirono17c8d8b2015-10-12 11:28:46 -0700221 assertEquals("Device A Storage A", cursor.getString(3));
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900222 assertEquals("1", cursor.getString(4));
Daichi Hirono09109562015-07-28 20:57:05 +0900223 assertEquals(1024, cursor.getInt(5));
Daichi Hironod5152422015-07-15 13:31:51 +0900224 }
225
Daichi Hirono09109562015-07-28 20:57:05 +0900226 {
Daichi Hironobeac2182015-07-29 16:28:57 +0900227 mProvider.openDevice(1);
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900228 mResolver.waitForNotification(ROOTS_URI, 2);
Daichi Hironobeac2182015-07-29 16:28:57 +0900229 final Cursor cursor = mProvider.queryRoots(null);
Daichi Hirono09109562015-07-28 20:57:05 +0900230 assertEquals(2, cursor.getCount());
231 cursor.moveToNext();
232 cursor.moveToNext();
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900233 assertEquals("2", cursor.getString(0));
Daichi Hirono83c679e72016-08-18 15:13:36 +0900234 assertEquals(
235 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_LOCAL_ONLY, cursor.getInt(1));
Daichi Hirono39795da2015-12-02 10:56:44 +0900236 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
Daichi Hirono17c8d8b2015-10-12 11:28:46 -0700237 assertEquals("Device B Storage B", cursor.getString(3));
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900238 assertEquals("2", cursor.getString(4));
Daichi Hirono09109562015-07-28 20:57:05 +0900239 assertEquals(2048, cursor.getInt(5));
Daichi Hironod5152422015-07-15 13:31:51 +0900240 }
Daichi Hirono09109562015-07-28 20:57:05 +0900241 }
Daichi Hironod5152422015-07-15 13:31:51 +0900242
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900243 public void testQueryRoots_error() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900244 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900245 mMtpManager.addValidDevice(new MtpDeviceRecord(
Daichi Hironoebd24052016-02-06 21:05:57 +0900246 0,
247 "Device A",
248 "Device key A",
249 false /* unopened */,
250 new MtpRoot[0],
Daichi Hirono0f325372016-02-21 15:50:30 +0900251 OPERATIONS_SUPPORTED,
Daichi Hironoebd24052016-02-06 21:05:57 +0900252 null));
Daichi Hirono20754c52015-12-15 18:52:26 +0900253 mMtpManager.addValidDevice(new MtpDeviceRecord(
254 1,
Daichi Hironofda74742016-02-01 13:00:31 +0900255 "Device B",
Daichi Hironoebd24052016-02-06 21:05:57 +0900256 "Device key B",
Daichi Hirono20754c52015-12-15 18:52:26 +0900257 false /* unopened */,
258 new MtpRoot[] {
259 new MtpRoot(
260 1 /* deviceId */,
261 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900262 "Storage B" /* volume description */,
263 2048 /* free space */,
264 4096 /* total space */,
265 "Identifier B" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900266 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900267 OPERATIONS_SUPPORTED,
Daichi Hirono148954a2016-01-21 19:55:45 +0900268 null));
Daichi Hirono09109562015-07-28 20:57:05 +0900269 {
Daichi Hironobeac2182015-07-29 16:28:57 +0900270 mProvider.openDevice(0);
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900271 mResolver.waitForNotification(ROOTS_URI, 1);
272
Daichi Hironofda74742016-02-01 13:00:31 +0900273 mProvider.openDevice(1);
Daichi Hironofda74742016-02-01 13:00:31 +0900274 mResolver.waitForNotification(ROOTS_URI, 2);
275
Daichi Hironobeac2182015-07-29 16:28:57 +0900276 final Cursor cursor = mProvider.queryRoots(null);
Daichi Hirono81d48532015-12-16 15:03:19 +0900277 assertEquals(2, cursor.getCount());
278
279 cursor.moveToNext();
280 assertEquals("1", cursor.getString(0));
Daichi Hirono83c679e72016-08-18 15:13:36 +0900281 assertEquals(
282 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY,
283 cursor.getInt(1));
Daichi Hirono81d48532015-12-16 15:03:19 +0900284 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
285 assertEquals("Device A", cursor.getString(3));
286 assertEquals("1", cursor.getString(4));
287 assertEquals(0, cursor.getInt(5));
288
Daichi Hirono09109562015-07-28 20:57:05 +0900289 cursor.moveToNext();
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900290 assertEquals("2", cursor.getString(0));
Daichi Hirono83c679e72016-08-18 15:13:36 +0900291 assertEquals(
292 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY,
293 cursor.getInt(1));
Daichi Hirono39795da2015-12-02 10:56:44 +0900294 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
Daichi Hirono17c8d8b2015-10-12 11:28:46 -0700295 assertEquals("Device B Storage B", cursor.getString(3));
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900296 assertEquals("2", cursor.getString(4));
Daichi Hirono09109562015-07-28 20:57:05 +0900297 assertEquals(2048, cursor.getInt(5));
Daichi Hironod5152422015-07-15 13:31:51 +0900298 }
299 }
300
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900301 public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900302 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900303 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900304 setupDocuments(
305 0,
306 0,
307 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
308 "1",
309 new MtpObjectInfo[] {
310 new MtpObjectInfo.Builder()
311 .setObjectHandle(100)
312 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
313 .setName("image.jpg")
314 .setDateModified(1422716400000L)
315 .setCompressedSize(1024 * 1024 * 5)
316 .setThumbCompressedSize(50 * 1024)
317 .build()
318 });
319
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000320 final Cursor cursor = mProvider.queryDocument("3", null);
Daichi Hironoe5323b72015-07-29 16:10:47 +0900321 assertEquals(1, cursor.getCount());
322
323 cursor.moveToNext();
Daichi Hirono47eb1922015-11-16 13:01:31 +0900324
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000325 assertEquals("3", cursor.getString(0));
Daichi Hironoe5323b72015-07-29 16:10:47 +0900326 assertEquals("image/jpeg", cursor.getString(1));
327 assertEquals("image.jpg", cursor.getString(2));
328 assertEquals(1422716400000L, cursor.getLong(3));
329 assertEquals(
330 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900331 DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
Daichi Hironoe5323b72015-07-29 16:10:47 +0900332 DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL,
333 cursor.getInt(4));
334 assertEquals(1024 * 1024 * 5, cursor.getInt(5));
335 }
336
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900337 public void testQueryDocument_directory()
338 throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900339 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900340 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900341 setupDocuments(
342 0,
343 0,
344 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
345 "1",
346 new MtpObjectInfo[] {
347 new MtpObjectInfo.Builder()
348 .setObjectHandle(2)
349 .setStorageId(1)
350 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
351 .setName("directory")
352 .setDateModified(1422716400000L)
353 .build()
354 });
355
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000356 final Cursor cursor = mProvider.queryDocument("3", null);
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900357 assertEquals(1, cursor.getCount());
358
359 cursor.moveToNext();
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000360 assertEquals("3", cursor.getString(0));
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900361 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
362 assertEquals("directory", cursor.getString(2));
363 assertEquals(1422716400000L, cursor.getLong(3));
364 assertEquals(
365 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900366 DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE,
367 cursor.getInt(4));
368 assertEquals(0, cursor.getInt(5));
369 }
370
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900371 public void testQueryDocument_forRoot()
372 throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900373 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900374 setupRoots(0, new MtpRoot[] {
Daichi Hironoe5323b72015-07-29 16:10:47 +0900375 new MtpRoot(
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900376 0 /* deviceId */,
Daichi Hironoe5323b72015-07-29 16:10:47 +0900377 1 /* storageId */,
378 "Storage A" /* volume description */,
379 1024 /* free space */,
380 4096 /* total space */,
381 "" /* no volume identifier */)
382 });
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000383 final Cursor cursor = mProvider.queryDocument("2", null);
Daichi Hironoe5323b72015-07-29 16:10:47 +0900384 assertEquals(1, cursor.getCount());
385
386 cursor.moveToNext();
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000387 assertEquals("2", cursor.getString(0));
Daichi Hironoe5323b72015-07-29 16:10:47 +0900388 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900389 assertEquals("Storage A", cursor.getString(2));
Daichi Hironoe5323b72015-07-29 16:10:47 +0900390 assertTrue(cursor.isNull(3));
391 assertEquals(0, cursor.getInt(4));
392 assertEquals(3072, cursor.getInt(5));
393 }
394
Daichi Hirono124d0602015-08-11 17:08:35 +0900395 public void testQueryChildDocuments() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900396 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900397 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900398 setupDocuments(
399 0,
400 0,
401 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
402 "1",
403 new MtpObjectInfo[] {
404 new MtpObjectInfo.Builder()
405 .setObjectHandle(100)
406 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
407 .setName("image.jpg")
408 .setCompressedSize(1024 * 1024 * 5)
409 .setThumbCompressedSize(5 * 1024)
410 .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
411 .build()
412 });
Daichi Hirono47eb1922015-11-16 13:01:31 +0900413
Steve McKay29c3f682016-12-16 14:52:59 -0800414 final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null);
Daichi Hirono124d0602015-08-11 17:08:35 +0900415 assertEquals(1, cursor.getCount());
416
417 assertTrue(cursor.moveToNext());
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000418 assertEquals("3", cursor.getString(0));
Daichi Hirono124d0602015-08-11 17:08:35 +0900419 assertEquals("image/jpeg", cursor.getString(1));
420 assertEquals("image.jpg", cursor.getString(2));
421 assertEquals(0, cursor.getLong(3));
Daichi Hirono61ba9232016-02-26 12:58:39 +0900422 assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
Daichi Hirono124d0602015-08-11 17:08:35 +0900423 assertEquals(1024 * 1024 * 5, cursor.getInt(5));
424
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900425 cursor.close();
Daichi Hirono124d0602015-08-11 17:08:35 +0900426 }
427
428 public void testQueryChildDocuments_cursorError() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900429 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono124d0602015-08-11 17:08:35 +0900430 try {
Steve McKay29c3f682016-12-16 14:52:59 -0800431 mProvider.queryChildDocuments("1", null, (String) null);
Daichi Hirono124d0602015-08-11 17:08:35 +0900432 fail();
Daichi Hirono678ed362016-03-18 15:05:53 +0900433 } catch (FileNotFoundException error) {}
Daichi Hirono124d0602015-08-11 17:08:35 +0900434 }
435
436 public void testQueryChildDocuments_documentError() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900437 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900438 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono124d0602015-08-11 17:08:35 +0900439 mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
Steve McKay29c3f682016-12-16 14:52:59 -0800440 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
Daichi Hirono678ed362016-03-18 15:05:53 +0900441 assertEquals(0, cursor.getCount());
442 assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
Daichi Hirono124d0602015-08-11 17:08:35 +0900443 }
444 }
445
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900446 public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900447 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900448 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900449 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900450 });
451 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
452 new MtpObjectInfo.Builder()
453 .setName("test.txt")
454 .setObjectHandle(1)
455 .setParent(-1)
456 .build()
457 });
458
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000459 mProvider.deleteDocument("3");
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900460 assertEquals(1, mResolver.getChangeCount(
461 DocumentsContract.buildChildDocumentsUri(
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900462 MtpDocumentsProvider.AUTHORITY, "1")));
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900463 }
464
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900465 public void testDeleteDocument_error()
466 throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900467 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900468 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900469 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900470 });
471 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
472 new MtpObjectInfo.Builder()
473 .setName("test.txt")
474 .setObjectHandle(1)
475 .setParent(-1)
476 .build()
477 });
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900478 try {
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000479 mProvider.deleteDocument("4");
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900480 fail();
481 } catch (Throwable e) {
482 assertTrue(e instanceof IOException);
483 }
484 assertEquals(0, mResolver.getChangeCount(
485 DocumentsContract.buildChildDocumentsUri(
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900486 MtpDocumentsProvider.AUTHORITY, "1")));
487 }
488
Daichi Hironob36b1552016-01-25 14:26:14 +0900489 public void testOpenDocument() throws Exception {
490 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
491 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900492 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hironob36b1552016-01-25 14:26:14 +0900493 });
494 final byte[] bytes = "Hello world".getBytes();
495 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
496 new MtpObjectInfo.Builder()
497 .setName("test.txt")
498 .setObjectHandle(1)
499 .setCompressedSize(bytes.length)
500 .setParent(-1)
501 .build()
502 });
503 mMtpManager.setImportFileBytes(0, 1, bytes);
504 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
505 final byte[] readBytes = new byte[5];
506 assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET));
507 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
508 assertTrue(Arrays.equals("world".getBytes(), readBytes));
509
510 assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET));
511 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
512 assertTrue(Arrays.equals("Hello".getBytes(), readBytes));
513 }
514 }
515
516 public void testOpenDocument_shortBytes() throws Exception {
517 mMtpManager = new TestMtpManager(getContext()) {
518 @Override
519 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
520 if (objectHandle == 1) {
521 return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle))
522 .setObjectHandle(1).setCompressedSize(1024 * 1024).build();
523 }
524
525 return super.getObjectInfo(deviceId, objectHandle);
526 }
527 };
528 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
529 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900530 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hironob36b1552016-01-25 14:26:14 +0900531 });
532 final byte[] bytes = "Hello world".getBytes();
533 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
534 new MtpObjectInfo.Builder()
535 .setName("test.txt")
536 .setObjectHandle(1)
537 .setCompressedSize(bytes.length)
538 .setParent(-1)
539 .build()
540 });
541 mMtpManager.setImportFileBytes(0, 1, bytes);
542 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
543 final byte[] readBytes = new byte[1024 * 1024];
544 assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length));
545 }
546 }
547
Daichi Hironof4e7fa82016-03-28 16:07:45 +0900548 public void testOpenDocument_writing() throws Exception {
549 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
550 setupRoots(0, new MtpRoot[] {
Daichi Hirono35b2ec52016-11-02 14:51:26 +0900551 new MtpRoot(0, 100, "Storage", 0, 0, "")
Daichi Hironof4e7fa82016-03-28 16:07:45 +0900552 });
553 final String documentId = mProvider.createDocument("2", "text/plain", "test.txt");
554 {
555 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null);
556 try (ParcelFileDescriptor.AutoCloseOutputStream stream =
557 new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
558 stream.write("Hello".getBytes());
Daichi Hironoe80ea382016-11-15 13:07:01 +0900559 fd.getFileDescriptor().sync();
Daichi Hironof4e7fa82016-03-28 16:07:45 +0900560 }
561 }
562 {
563 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null);
564 try (ParcelFileDescriptor.AutoCloseInputStream stream =
565 new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
566 final byte[] bytes = new byte[5];
567 stream.read(bytes);
568 assertTrue(Arrays.equals("Hello".getBytes(), bytes));
569 }
570 }
571 }
572
Daichi Hironoc18f8072016-02-10 14:59:52 -0800573 public void testBusyDevice() throws Exception {
574 mMtpManager = new TestMtpManager(getContext()) {
575 @Override
Daichi Hironof20e49ec2016-06-08 16:20:00 +0900576 synchronized MtpDeviceRecord openDevice(int deviceId)
577 throws IOException {
Daichi Hironoc18f8072016-02-10 14:59:52 -0800578 throw new BusyDeviceException();
579 }
580 };
581 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
582 mMtpManager.addValidDevice(new MtpDeviceRecord(
Daichi Hirono0f325372016-02-21 15:50:30 +0900583 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0],
584 OPERATIONS_SUPPORTED, null));
Daichi Hironoc18f8072016-02-10 14:59:52 -0800585
586 mProvider.resumeRootScanner();
587 mResolver.waitForNotification(ROOTS_URI, 1);
588
589 try (final Cursor cursor = mProvider.queryRoots(null)) {
590 assertEquals(1, cursor.getCount());
591 }
592
Steve McKay29c3f682016-12-16 14:52:59 -0800593 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
Daichi Hironoc18f8072016-02-10 14:59:52 -0800594 assertEquals(0, cursor.getCount());
595 assertEquals(
596 "error_busy_device",
597 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
598 }
599 }
600
Daichi Hirono29657762016-02-10 16:55:37 -0800601 public void testLockedDevice() throws Exception {
602 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
603 mMtpManager.addValidDevice(new MtpDeviceRecord(
Daichi Hirono0f325372016-02-21 15:50:30 +0900604 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED,
605 null));
Daichi Hirono29657762016-02-10 16:55:37 -0800606
607 mProvider.resumeRootScanner();
608 mResolver.waitForNotification(ROOTS_URI, 1);
609
610 try (final Cursor cursor = mProvider.queryRoots(null)) {
611 assertEquals(1, cursor.getCount());
612 }
613
Steve McKay29c3f682016-12-16 14:52:59 -0800614 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
Daichi Hirono29657762016-02-10 16:55:37 -0800615 assertEquals(0, cursor.getCount());
616 assertEquals(
617 "error_locked_device",
618 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
619 }
620 }
621
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900622 public void testMappingDisconnectedDocuments() throws Exception {
623 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
624 mMtpManager.addValidDevice(new MtpDeviceRecord(
625 0,
626 "Device A",
627 "device key",
Daichi Hirono37a655a2016-03-04 18:43:21 +0900628 true /* opened */,
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900629 new MtpRoot[] {
630 new MtpRoot(
631 0 /* deviceId */,
632 1 /* storageId */,
633 "Storage A" /* volume description */,
634 1024 /* free space */,
635 2048 /* total space */,
636 "" /* no volume identifier */)
637 },
Daichi Hirono37a655a2016-03-04 18:43:21 +0900638 OPERATIONS_SUPPORTED,
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900639 null));
640
641 final String[] names = strings("Directory A", "Directory B", "Directory C");
642 final int objectHandleOffset = 100;
643 for (int i = 0; i < names.length; i++) {
644 final int parentHandle = i == 0 ?
645 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1;
646 final int objectHandle = i + objectHandleOffset;
647 mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle });
648 mMtpManager.setObjectInfo(
649 0,
650 new MtpObjectInfo.Builder()
651 .setName(names[i])
652 .setObjectHandle(objectHandle)
653 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
654 .setStorageId(1)
655 .build());
656 }
657
658 mProvider.resumeRootScanner();
659 mResolver.waitForNotification(ROOTS_URI, 1);
660
661 final int documentIdOffset = 2;
662 for (int i = 0; i < names.length; i++) {
663 try (final Cursor cursor = mProvider.queryChildDocuments(
664 String.valueOf(documentIdOffset + i),
665 strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME),
Steve McKay29c3f682016-12-16 14:52:59 -0800666 (String) null)) {
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900667 assertEquals(1, cursor.getCount());
668 cursor.moveToNext();
669 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
670 assertEquals(names[i], cursor.getString(1));
671 }
672 }
673
674 mProvider.closeDevice(0);
675 mResolver.waitForNotification(ROOTS_URI, 2);
676
677 mProvider.openDevice(0);
678 mResolver.waitForNotification(ROOTS_URI, 3);
679
680 for (int i = 0; i < names.length; i++) {
681 mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri(
682 MtpDocumentsProvider.AUTHORITY,
683 String.valueOf(documentIdOffset + i)), 1);
684 try (final Cursor cursor = mProvider.queryChildDocuments(
685 String.valueOf(documentIdOffset + i),
686 strings(Document.COLUMN_DOCUMENT_ID),
Steve McKay29c3f682016-12-16 14:52:59 -0800687 (String) null)) {
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900688 assertEquals(1, cursor.getCount());
689 cursor.moveToNext();
690 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
691 }
692 }
693 }
694
Daichi Hirono35b2ec52016-11-02 14:51:26 +0900695 public void testCreateDocument() throws Exception {
696 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
697 setupRoots(0, new MtpRoot[] {
698 new MtpRoot(0, 100, "Storage A", 100, 100, null)
699 });
700 final String documentId = mProvider.createDocument("1", "text/plain", "note.txt");
701 final Uri deviceUri = DocumentsContract.buildChildDocumentsUri(
702 MtpDocumentsProvider.AUTHORITY, "1");
703 final Uri storageUri = DocumentsContract.buildChildDocumentsUri(
704 MtpDocumentsProvider.AUTHORITY, "2");
705 mResolver.waitForNotification(storageUri, 1);
706 mResolver.waitForNotification(deviceUri, 1);
707 try (final Cursor cursor = mProvider.queryDocument(documentId, null)) {
708 assertTrue(cursor.moveToNext());
709 assertEquals(
710 "note.txt",
711 cursor.getString(cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)));
712 assertEquals(
713 "text/plain",
714 cursor.getString(cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)));
715 }
716 }
717
Daichi Hirono0f325372016-02-21 15:50:30 +0900718 public void testCreateDocument_noWritingSupport() throws Exception {
719 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
720 mMtpManager.addValidDevice(new MtpDeviceRecord(
721 0, "Device A", null /* deviceKey */, false /* unopened */,
722 new MtpRoot[] {
723 new MtpRoot(
724 0 /* deviceId */,
725 1 /* storageId */,
726 "Storage A" /* volume description */,
727 1024 /* free space */,
728 2048 /* total space */,
729 "" /* no volume identifier */)
730 },
731 new int[0] /* no operations supported */, null));
732 mProvider.resumeRootScanner();
733 mResolver.waitForNotification(ROOTS_URI, 1);
734 try {
735 mProvider.createDocument("1", "text/palin", "note.txt");
736 fail();
737 } catch (UnsupportedOperationException exception) {}
738 }
739
740 public void testOpenDocument_noWritingSupport() throws Exception {
741 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
742 mMtpManager.addValidDevice(new MtpDeviceRecord(
743 0, "Device A", null /* deviceKey */, false /* unopened */,
744 new MtpRoot[] {
745 new MtpRoot(
746 0 /* deviceId */,
747 1 /* storageId */,
748 "Storage A" /* volume description */,
749 1024 /* free space */,
750 2048 /* total space */,
751 "" /* no volume identifier */)
752 },
753 new int[0] /* no operations supported */, null));
754 mMtpManager.setObjectHandles(
755 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 });
756 mMtpManager.setObjectInfo(
757 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build());
758 mProvider.resumeRootScanner();
759 mResolver.waitForNotification(ROOTS_URI, 1);
760 try (final Cursor cursor = mProvider.queryChildDocuments(
Steve McKay29c3f682016-12-16 14:52:59 -0800761 "1", strings(Document.COLUMN_DOCUMENT_ID), (String) null)) {
Daichi Hirono0f325372016-02-21 15:50:30 +0900762 assertEquals(1, cursor.getCount());
763 cursor.moveToNext();
764 assertEquals("3", cursor.getString(0));
765 }
766 try {
767 mProvider.openDocument("3", "w", null);
768 fail();
769 } catch (UnsupportedOperationException exception) {}
770 }
771
Daichi Hirono64111e02016-03-24 21:07:38 +0900772 public void testObjectSizeLong() throws Exception {
773 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
774 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
775 mMtpManager.setObjectSizeLong(0, 100, MtpConstants.FORMAT_EXIF_JPEG, 0x400000000L);
776 setupDocuments(
777 0,
778 0,
779 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
780 "1",
781 new MtpObjectInfo[] {
782 new MtpObjectInfo.Builder()
783 .setObjectHandle(100)
784 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
785 .setName("image.jpg")
786 .setCompressedSize(0xffffffffl)
787 .build()
788 });
789
790 final Cursor cursor = mProvider.queryDocument("3", new String[] {
791 DocumentsContract.Document.COLUMN_SIZE
792 });
793 assertEquals(1, cursor.getCount());
794
795 cursor.moveToNext();
796 assertEquals(0x400000000L, cursor.getLong(0));
797 }
798
Garfield Tanf46a4632016-11-02 16:00:17 -0700799 public void testFindDocumentPath_singleStorage_toRoot() throws Exception {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900800 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
801 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
802 setupHierarchyDocuments("1");
803
Garfield Tanb690b4d2017-03-01 16:05:23 -0800804 final Path path = mProvider.findDocumentPath(null, "15");
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900805 assertEquals("1", path.getRootId());
806 assertEquals(4, path.getPath().size());
807 assertEquals("1", path.getPath().get(0));
808 assertEquals("3", path.getPath().get(1));
809 assertEquals("6", path.getPath().get(2));
810 assertEquals("15", path.getPath().get(3));
811 }
812
Garfield Tanf46a4632016-11-02 16:00:17 -0700813 public void testFindDocumentPath_singleStorage_toDoc() throws Exception {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900814 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
815 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
816 setupHierarchyDocuments("1");
817
Garfield Tanb690b4d2017-03-01 16:05:23 -0800818 final Path path = mProvider.findDocumentPath("3", "18");
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900819 assertNull(path.getRootId());
820 assertEquals(3, path.getPath().size());
821 assertEquals("3", path.getPath().get(0));
822 assertEquals("7", path.getPath().get(1));
823 assertEquals("18", path.getPath().get(2));
824 }
825
Garfield Tanf46a4632016-11-02 16:00:17 -0700826 public void testFindDocumentPath_multiStorage_toRoot() throws Exception {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900827 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
828 setupRoots(0, new MtpRoot[] {
829 new MtpRoot(0, 0, "Storage A", 1000, 1000, ""),
830 new MtpRoot(0, 1, "Storage B", 1000, 1000, "") });
831 setupHierarchyDocuments("2");
832
Garfield Tanb690b4d2017-03-01 16:05:23 -0800833 final Path path = mProvider.findDocumentPath(null, "16");
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900834 assertEquals("2", path.getRootId());
835 assertEquals(4, path.getPath().size());
836 assertEquals("2", path.getPath().get(0));
837 assertEquals("4", path.getPath().get(1));
838 assertEquals("7", path.getPath().get(2));
839 assertEquals("16", path.getPath().get(3));
840 }
841
Garfield Tanf46a4632016-11-02 16:00:17 -0700842 public void testFindDocumentPath_multiStorage_toDoc() throws Exception {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900843 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
844 setupRoots(0, new MtpRoot[] {
845 new MtpRoot(0, 0, "Storage A", 1000, 1000, ""),
846 new MtpRoot(0, 1, "Storage B", 1000, 1000, "") });
847 setupHierarchyDocuments("2");
848
Garfield Tanb690b4d2017-03-01 16:05:23 -0800849 final Path path = mProvider.findDocumentPath("4", "19");
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900850 assertNull(path.getRootId());
851 assertEquals(3, path.getPath().size());
852 assertEquals("4", path.getPath().get(0));
853 assertEquals("8", path.getPath().get(1));
854 assertEquals("19", path.getPath().get(2));
855 }
856
Daichi Hirono29de7692016-11-07 10:58:50 +0900857 public void testIsChildDocument() throws Exception {
858 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
859 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
860 setupHierarchyDocuments("1");
861 assertTrue(mProvider.isChildDocument("1", "1"));
862 assertTrue(mProvider.isChildDocument("1", "14"));
863 assertTrue(mProvider.isChildDocument("2", "14"));
864 assertTrue(mProvider.isChildDocument("5", "14"));
865 assertFalse(mProvider.isChildDocument("3", "14"));
866 assertFalse(mProvider.isChildDocument("6", "14"));
867 }
868
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900869 private void setupProvider(int flag) {
870 mDatabase = new MtpDatabase(getContext(), flag);
871 mProvider = new MtpDocumentsProvider();
Daichi Hironob36b1552016-01-25 14:26:14 +0900872 final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
873 assertTrue(mProvider.onCreateForTesting(
Daichi Hironof4e7fa82016-03-28 16:07:45 +0900874 getContext(),
Daichi Hironofda74742016-02-01 13:00:31 +0900875 mResources,
876 mMtpManager,
877 mResolver,
878 mDatabase,
879 storageManager,
880 new TestServiceIntentSender()));
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900881 }
882
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900883 private String[] getStrings(Cursor cursor) {
884 try {
885 final String[] results = new String[cursor.getCount()];
886 for (int i = 0; cursor.moveToNext(); i++) {
887 results[i] = cursor.getString(0);
888 }
889 return results;
890 } finally {
891 cursor.close();
892 }
893 }
894
895 private String[] setupRoots(int deviceId, MtpRoot[] roots)
Daichi Hirono20754c52015-12-15 18:52:26 +0900896 throws InterruptedException, TimeoutException, IOException {
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900897 final int changeCount = mResolver.getChangeCount(ROOTS_URI);
Daichi Hirono20754c52015-12-15 18:52:26 +0900898 mMtpManager.addValidDevice(
Daichi Hironoebd24052016-02-06 21:05:57 +0900899 new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */,
Daichi Hirono0f325372016-02-21 15:50:30 +0900900 roots, OPERATIONS_SUPPORTED, null));
Daichi Hirono20754c52015-12-15 18:52:26 +0900901 mProvider.openDevice(deviceId);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900902 mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
903 return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
904 }
905
906 private String[] setupDocuments(
907 int deviceId,
908 int storageId,
909 int parentHandle,
910 String parentDocumentId,
911 MtpObjectInfo[] objects) throws FileNotFoundException {
912 final int[] handles = new int[objects.length];
913 int i = 0;
914 for (final MtpObjectInfo info : objects) {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900915 handles[i++] = info.getObjectHandle();
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900916 mMtpManager.setObjectInfo(deviceId, info);
917 }
918 mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
919 return getStrings(mProvider.queryChildDocuments(
Steve McKay29c3f682016-12-16 14:52:59 -0800920 parentDocumentId,
921 strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
922 (String) null));
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900923 }
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900924
925 static class HierarchyDocument {
926 int depth;
927 String documentId;
928 int objectHandle;
929 int parentHandle;
930
931 HierarchyDocument createChildDocument(int newHandle) {
932 final HierarchyDocument doc = new HierarchyDocument();
933 doc.depth = depth - 1;
934 doc.objectHandle = newHandle;
935 doc.parentHandle = objectHandle;
936 return doc;
937 }
938
939 MtpObjectInfo toObjectInfo() {
940 return new MtpObjectInfo.Builder()
941 .setName("doc_" + documentId)
942 .setFormat(depth > 0 ?
943 MtpConstants.FORMAT_ASSOCIATION : MtpConstants.FORMAT_TEXT)
944 .setObjectHandle(objectHandle)
945 .setParent(parentHandle)
946 .build();
947 }
948 }
949
950 private void setupHierarchyDocuments(String documentId) throws Exception {
951 final Queue<HierarchyDocument> ids = new LinkedList<>();
952 final HierarchyDocument firstDocument = new HierarchyDocument();
953 firstDocument.depth = 3;
954 firstDocument.documentId = documentId;
955 firstDocument.objectHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
956 ids.add(firstDocument);
957
958 int objectHandle = 100;
959 while (!ids.isEmpty()) {
960 final HierarchyDocument document = ids.remove();
961 final HierarchyDocument[] children = new HierarchyDocument[] {
962 document.createChildDocument(objectHandle++),
963 document.createChildDocument(objectHandle++),
964 document.createChildDocument(objectHandle++),
965 };
966 final String[] childDocIds = setupDocuments(
967 0, 0, document.objectHandle, document.documentId, new MtpObjectInfo[] {
968 children[0].toObjectInfo(),
969 children[1].toObjectInfo(),
970 children[2].toObjectInfo(),
971 });
972 children[0].documentId = childDocIds[0];
973 children[1].documentId = childDocIds[1];
974 children[2].documentId = childDocIds[2];
975
976 if (children[0].depth > 0) {
977 ids.add(children[0]);
978 ids.add(children[1]);
979 ids.add(children[2]);
980 }
981 }
982 }
Daichi Hirono9a05d6b2015-07-09 15:36:42 +0900983}