blob: 3fa5eb581ef74ae0e40ffa58fea25538fe508cec [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 Hirono66fcb4b2017-03-23 15:24:13 +0900371 public void testQueryDocument_forStorage()
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900372 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));
Daichi Hirono4e4ec162017-03-22 15:57:48 +0900391 assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4));
Daichi Hironoe5323b72015-07-29 16:10:47 +0900392 assertEquals(3072, cursor.getInt(5));
393 }
394
Daichi Hirono66fcb4b2017-03-23 15:24:13 +0900395 public void testQueryDocument_forDeviceWithSingleStorage()
396 throws IOException, InterruptedException, TimeoutException {
397 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
398 setupRoots(0, new MtpRoot[] {
399 new MtpRoot(
400 0 /* deviceId */,
401 1 /* storageId */,
402 "Storage A" /* volume description */,
403 1024 /* free space */,
404 4096 /* total space */,
405 "" /* no volume identifier */)
406 });
407 final Cursor cursor = mProvider.queryDocument("1", null);
408 assertEquals(1, cursor.getCount());
409
410 cursor.moveToNext();
411 assertEquals("1", cursor.getString(0));
412 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
413 assertEquals("Device Storage A", cursor.getString(2));
414 assertTrue(cursor.isNull(3));
415 assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4));
416 assertTrue(cursor.isNull(5));
417 }
418
419 public void testQueryDocument_forDeviceWithTwoStorages()
420 throws IOException, InterruptedException, TimeoutException {
421 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
422 setupRoots(0, new MtpRoot[] {
423 new MtpRoot(
424 0 /* deviceId */,
425 1 /* storageId */,
426 "Storage A" /* volume description */,
427 1024 /* free space */,
428 4096 /* total space */,
429 "" /* no volume identifier */),
430 new MtpRoot(
431 0 /* deviceId */,
432 2 /* storageId */,
433 "Storage B" /* volume description */,
434 1024 /* free space */,
435 4096 /* total space */,
436 "" /* no volume identifier */)
437 });
438 final Cursor cursor = mProvider.queryDocument("1", null);
439 assertEquals(1, cursor.getCount());
440
441 cursor.moveToNext();
442 assertEquals("1", cursor.getString(0));
443 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
444 assertEquals("Device", cursor.getString(2));
445 assertTrue(cursor.isNull(3));
446 assertEquals(0, cursor.getInt(4));
447 assertTrue(cursor.isNull(5));
448 }
449
Daichi Hirono124d0602015-08-11 17:08:35 +0900450 public void testQueryChildDocuments() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900451 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900452 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900453 setupDocuments(
454 0,
455 0,
456 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
457 "1",
458 new MtpObjectInfo[] {
459 new MtpObjectInfo.Builder()
460 .setObjectHandle(100)
461 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
462 .setName("image.jpg")
463 .setCompressedSize(1024 * 1024 * 5)
464 .setThumbCompressedSize(5 * 1024)
465 .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
466 .build()
467 });
Daichi Hirono47eb1922015-11-16 13:01:31 +0900468
Steve McKay29c3f682016-12-16 14:52:59 -0800469 final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null);
Daichi Hirono124d0602015-08-11 17:08:35 +0900470 assertEquals(1, cursor.getCount());
471
472 assertTrue(cursor.moveToNext());
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000473 assertEquals("3", cursor.getString(0));
Daichi Hirono124d0602015-08-11 17:08:35 +0900474 assertEquals("image/jpeg", cursor.getString(1));
475 assertEquals("image.jpg", cursor.getString(2));
476 assertEquals(0, cursor.getLong(3));
Daichi Hirono61ba9232016-02-26 12:58:39 +0900477 assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
Daichi Hirono124d0602015-08-11 17:08:35 +0900478 assertEquals(1024 * 1024 * 5, cursor.getInt(5));
479
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900480 cursor.close();
Daichi Hirono124d0602015-08-11 17:08:35 +0900481 }
482
483 public void testQueryChildDocuments_cursorError() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900484 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono124d0602015-08-11 17:08:35 +0900485 try {
Steve McKay29c3f682016-12-16 14:52:59 -0800486 mProvider.queryChildDocuments("1", null, (String) null);
Daichi Hirono124d0602015-08-11 17:08:35 +0900487 fail();
Daichi Hirono678ed362016-03-18 15:05:53 +0900488 } catch (FileNotFoundException error) {}
Daichi Hirono124d0602015-08-11 17:08:35 +0900489 }
490
491 public void testQueryChildDocuments_documentError() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900492 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900493 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono124d0602015-08-11 17:08:35 +0900494 mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
Steve McKay29c3f682016-12-16 14:52:59 -0800495 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
Daichi Hirono678ed362016-03-18 15:05:53 +0900496 assertEquals(0, cursor.getCount());
497 assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
Daichi Hirono124d0602015-08-11 17:08:35 +0900498 }
499 }
500
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900501 public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900502 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900503 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900504 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900505 });
506 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
507 new MtpObjectInfo.Builder()
508 .setName("test.txt")
509 .setObjectHandle(1)
510 .setParent(-1)
511 .build()
512 });
513
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000514 mProvider.deleteDocument("3");
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900515 assertEquals(1, mResolver.getChangeCount(
516 DocumentsContract.buildChildDocumentsUri(
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900517 MtpDocumentsProvider.AUTHORITY, "1")));
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900518 }
519
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900520 public void testDeleteDocument_error()
521 throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900522 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900523 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900524 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900525 });
526 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
527 new MtpObjectInfo.Builder()
528 .setName("test.txt")
529 .setObjectHandle(1)
530 .setParent(-1)
531 .build()
532 });
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900533 try {
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000534 mProvider.deleteDocument("4");
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900535 fail();
536 } catch (Throwable e) {
537 assertTrue(e instanceof IOException);
538 }
539 assertEquals(0, mResolver.getChangeCount(
540 DocumentsContract.buildChildDocumentsUri(
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900541 MtpDocumentsProvider.AUTHORITY, "1")));
542 }
543
Daichi Hironob36b1552016-01-25 14:26:14 +0900544 public void testOpenDocument() throws Exception {
545 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
546 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900547 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hironob36b1552016-01-25 14:26:14 +0900548 });
549 final byte[] bytes = "Hello world".getBytes();
550 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
551 new MtpObjectInfo.Builder()
552 .setName("test.txt")
553 .setObjectHandle(1)
554 .setCompressedSize(bytes.length)
555 .setParent(-1)
556 .build()
557 });
558 mMtpManager.setImportFileBytes(0, 1, bytes);
559 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
560 final byte[] readBytes = new byte[5];
561 assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET));
562 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
563 assertTrue(Arrays.equals("world".getBytes(), readBytes));
564
565 assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET));
566 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
567 assertTrue(Arrays.equals("Hello".getBytes(), readBytes));
568 }
569 }
570
571 public void testOpenDocument_shortBytes() throws Exception {
572 mMtpManager = new TestMtpManager(getContext()) {
573 @Override
574 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
575 if (objectHandle == 1) {
576 return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle))
577 .setObjectHandle(1).setCompressedSize(1024 * 1024).build();
578 }
579
580 return super.getObjectInfo(deviceId, objectHandle);
581 }
582 };
583 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
584 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900585 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hironob36b1552016-01-25 14:26:14 +0900586 });
587 final byte[] bytes = "Hello world".getBytes();
588 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
589 new MtpObjectInfo.Builder()
590 .setName("test.txt")
591 .setObjectHandle(1)
592 .setCompressedSize(bytes.length)
593 .setParent(-1)
594 .build()
595 });
596 mMtpManager.setImportFileBytes(0, 1, bytes);
597 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
598 final byte[] readBytes = new byte[1024 * 1024];
599 assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length));
600 }
601 }
602
Daichi Hironof4e7fa82016-03-28 16:07:45 +0900603 public void testOpenDocument_writing() throws Exception {
604 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
605 setupRoots(0, new MtpRoot[] {
Daichi Hirono35b2ec52016-11-02 14:51:26 +0900606 new MtpRoot(0, 100, "Storage", 0, 0, "")
Daichi Hironof4e7fa82016-03-28 16:07:45 +0900607 });
608 final String documentId = mProvider.createDocument("2", "text/plain", "test.txt");
609 {
610 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null);
611 try (ParcelFileDescriptor.AutoCloseOutputStream stream =
612 new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
613 stream.write("Hello".getBytes());
Daichi Hironoe80ea382016-11-15 13:07:01 +0900614 fd.getFileDescriptor().sync();
Daichi Hironof4e7fa82016-03-28 16:07:45 +0900615 }
616 }
617 {
618 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null);
619 try (ParcelFileDescriptor.AutoCloseInputStream stream =
620 new ParcelFileDescriptor.AutoCloseInputStream(fd)) {
621 final byte[] bytes = new byte[5];
622 stream.read(bytes);
623 assertTrue(Arrays.equals("Hello".getBytes(), bytes));
624 }
625 }
626 }
627
Daichi Hironoc18f8072016-02-10 14:59:52 -0800628 public void testBusyDevice() throws Exception {
629 mMtpManager = new TestMtpManager(getContext()) {
630 @Override
Daichi Hironof20e49ec2016-06-08 16:20:00 +0900631 synchronized MtpDeviceRecord openDevice(int deviceId)
632 throws IOException {
Daichi Hironoc18f8072016-02-10 14:59:52 -0800633 throw new BusyDeviceException();
634 }
635 };
636 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
637 mMtpManager.addValidDevice(new MtpDeviceRecord(
Daichi Hirono0f325372016-02-21 15:50:30 +0900638 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0],
639 OPERATIONS_SUPPORTED, null));
Daichi Hironoc18f8072016-02-10 14:59:52 -0800640
641 mProvider.resumeRootScanner();
642 mResolver.waitForNotification(ROOTS_URI, 1);
643
644 try (final Cursor cursor = mProvider.queryRoots(null)) {
645 assertEquals(1, cursor.getCount());
646 }
647
Steve McKay29c3f682016-12-16 14:52:59 -0800648 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
Daichi Hironoc18f8072016-02-10 14:59:52 -0800649 assertEquals(0, cursor.getCount());
650 assertEquals(
651 "error_busy_device",
652 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
653 }
654 }
655
Daichi Hirono29657762016-02-10 16:55:37 -0800656 public void testLockedDevice() throws Exception {
657 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
658 mMtpManager.addValidDevice(new MtpDeviceRecord(
Daichi Hirono0f325372016-02-21 15:50:30 +0900659 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED,
660 null));
Daichi Hirono29657762016-02-10 16:55:37 -0800661
662 mProvider.resumeRootScanner();
663 mResolver.waitForNotification(ROOTS_URI, 1);
664
665 try (final Cursor cursor = mProvider.queryRoots(null)) {
666 assertEquals(1, cursor.getCount());
667 }
668
Steve McKay29c3f682016-12-16 14:52:59 -0800669 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
Daichi Hirono29657762016-02-10 16:55:37 -0800670 assertEquals(0, cursor.getCount());
671 assertEquals(
672 "error_locked_device",
673 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
674 }
675 }
676
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900677 public void testMappingDisconnectedDocuments() throws Exception {
678 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
679 mMtpManager.addValidDevice(new MtpDeviceRecord(
680 0,
681 "Device A",
682 "device key",
Daichi Hirono37a655a2016-03-04 18:43:21 +0900683 true /* opened */,
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900684 new MtpRoot[] {
685 new MtpRoot(
686 0 /* deviceId */,
687 1 /* storageId */,
688 "Storage A" /* volume description */,
689 1024 /* free space */,
690 2048 /* total space */,
691 "" /* no volume identifier */)
692 },
Daichi Hirono37a655a2016-03-04 18:43:21 +0900693 OPERATIONS_SUPPORTED,
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900694 null));
695
696 final String[] names = strings("Directory A", "Directory B", "Directory C");
697 final int objectHandleOffset = 100;
698 for (int i = 0; i < names.length; i++) {
699 final int parentHandle = i == 0 ?
700 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1;
701 final int objectHandle = i + objectHandleOffset;
702 mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle });
703 mMtpManager.setObjectInfo(
704 0,
705 new MtpObjectInfo.Builder()
706 .setName(names[i])
707 .setObjectHandle(objectHandle)
708 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
709 .setStorageId(1)
710 .build());
711 }
712
713 mProvider.resumeRootScanner();
714 mResolver.waitForNotification(ROOTS_URI, 1);
715
716 final int documentIdOffset = 2;
717 for (int i = 0; i < names.length; i++) {
718 try (final Cursor cursor = mProvider.queryChildDocuments(
719 String.valueOf(documentIdOffset + i),
720 strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME),
Steve McKay29c3f682016-12-16 14:52:59 -0800721 (String) null)) {
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900722 assertEquals(1, cursor.getCount());
723 cursor.moveToNext();
724 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
725 assertEquals(names[i], cursor.getString(1));
726 }
727 }
728
729 mProvider.closeDevice(0);
730 mResolver.waitForNotification(ROOTS_URI, 2);
731
732 mProvider.openDevice(0);
733 mResolver.waitForNotification(ROOTS_URI, 3);
734
735 for (int i = 0; i < names.length; i++) {
736 mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri(
737 MtpDocumentsProvider.AUTHORITY,
738 String.valueOf(documentIdOffset + i)), 1);
739 try (final Cursor cursor = mProvider.queryChildDocuments(
740 String.valueOf(documentIdOffset + i),
741 strings(Document.COLUMN_DOCUMENT_ID),
Steve McKay29c3f682016-12-16 14:52:59 -0800742 (String) null)) {
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900743 assertEquals(1, cursor.getCount());
744 cursor.moveToNext();
745 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
746 }
747 }
748 }
749
Daichi Hirono35b2ec52016-11-02 14:51:26 +0900750 public void testCreateDocument() throws Exception {
751 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
752 setupRoots(0, new MtpRoot[] {
753 new MtpRoot(0, 100, "Storage A", 100, 100, null)
754 });
755 final String documentId = mProvider.createDocument("1", "text/plain", "note.txt");
756 final Uri deviceUri = DocumentsContract.buildChildDocumentsUri(
757 MtpDocumentsProvider.AUTHORITY, "1");
758 final Uri storageUri = DocumentsContract.buildChildDocumentsUri(
759 MtpDocumentsProvider.AUTHORITY, "2");
760 mResolver.waitForNotification(storageUri, 1);
761 mResolver.waitForNotification(deviceUri, 1);
762 try (final Cursor cursor = mProvider.queryDocument(documentId, null)) {
763 assertTrue(cursor.moveToNext());
764 assertEquals(
765 "note.txt",
766 cursor.getString(cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)));
767 assertEquals(
768 "text/plain",
769 cursor.getString(cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)));
770 }
771 }
772
Daichi Hirono0f325372016-02-21 15:50:30 +0900773 public void testCreateDocument_noWritingSupport() throws Exception {
774 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
775 mMtpManager.addValidDevice(new MtpDeviceRecord(
776 0, "Device A", null /* deviceKey */, false /* unopened */,
777 new MtpRoot[] {
778 new MtpRoot(
779 0 /* deviceId */,
780 1 /* storageId */,
781 "Storage A" /* volume description */,
782 1024 /* free space */,
783 2048 /* total space */,
784 "" /* no volume identifier */)
785 },
786 new int[0] /* no operations supported */, null));
787 mProvider.resumeRootScanner();
788 mResolver.waitForNotification(ROOTS_URI, 1);
789 try {
790 mProvider.createDocument("1", "text/palin", "note.txt");
791 fail();
792 } catch (UnsupportedOperationException exception) {}
793 }
794
795 public void testOpenDocument_noWritingSupport() throws Exception {
796 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
797 mMtpManager.addValidDevice(new MtpDeviceRecord(
798 0, "Device A", null /* deviceKey */, false /* unopened */,
799 new MtpRoot[] {
800 new MtpRoot(
801 0 /* deviceId */,
802 1 /* storageId */,
803 "Storage A" /* volume description */,
804 1024 /* free space */,
805 2048 /* total space */,
806 "" /* no volume identifier */)
807 },
808 new int[0] /* no operations supported */, null));
809 mMtpManager.setObjectHandles(
810 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 });
811 mMtpManager.setObjectInfo(
812 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build());
813 mProvider.resumeRootScanner();
814 mResolver.waitForNotification(ROOTS_URI, 1);
815 try (final Cursor cursor = mProvider.queryChildDocuments(
Steve McKay29c3f682016-12-16 14:52:59 -0800816 "1", strings(Document.COLUMN_DOCUMENT_ID), (String) null)) {
Daichi Hirono0f325372016-02-21 15:50:30 +0900817 assertEquals(1, cursor.getCount());
818 cursor.moveToNext();
819 assertEquals("3", cursor.getString(0));
820 }
821 try {
822 mProvider.openDocument("3", "w", null);
823 fail();
824 } catch (UnsupportedOperationException exception) {}
825 }
826
Daichi Hirono64111e02016-03-24 21:07:38 +0900827 public void testObjectSizeLong() throws Exception {
828 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
829 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
830 mMtpManager.setObjectSizeLong(0, 100, MtpConstants.FORMAT_EXIF_JPEG, 0x400000000L);
831 setupDocuments(
832 0,
833 0,
834 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
835 "1",
836 new MtpObjectInfo[] {
837 new MtpObjectInfo.Builder()
838 .setObjectHandle(100)
839 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
840 .setName("image.jpg")
841 .setCompressedSize(0xffffffffl)
842 .build()
843 });
844
845 final Cursor cursor = mProvider.queryDocument("3", new String[] {
846 DocumentsContract.Document.COLUMN_SIZE
847 });
848 assertEquals(1, cursor.getCount());
849
850 cursor.moveToNext();
851 assertEquals(0x400000000L, cursor.getLong(0));
852 }
853
Garfield Tanf46a4632016-11-02 16:00:17 -0700854 public void testFindDocumentPath_singleStorage_toRoot() throws Exception {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900855 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
856 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
857 setupHierarchyDocuments("1");
858
Garfield Tanb690b4d2017-03-01 16:05:23 -0800859 final Path path = mProvider.findDocumentPath(null, "15");
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900860 assertEquals("1", path.getRootId());
861 assertEquals(4, path.getPath().size());
862 assertEquals("1", path.getPath().get(0));
863 assertEquals("3", path.getPath().get(1));
864 assertEquals("6", path.getPath().get(2));
865 assertEquals("15", path.getPath().get(3));
866 }
867
Garfield Tanf46a4632016-11-02 16:00:17 -0700868 public void testFindDocumentPath_singleStorage_toDoc() throws Exception {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900869 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
870 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
871 setupHierarchyDocuments("1");
872
Garfield Tanb690b4d2017-03-01 16:05:23 -0800873 final Path path = mProvider.findDocumentPath("3", "18");
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900874 assertNull(path.getRootId());
875 assertEquals(3, path.getPath().size());
876 assertEquals("3", path.getPath().get(0));
877 assertEquals("7", path.getPath().get(1));
878 assertEquals("18", path.getPath().get(2));
879 }
880
Garfield Tanf46a4632016-11-02 16:00:17 -0700881 public void testFindDocumentPath_multiStorage_toRoot() throws Exception {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900882 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
883 setupRoots(0, new MtpRoot[] {
884 new MtpRoot(0, 0, "Storage A", 1000, 1000, ""),
885 new MtpRoot(0, 1, "Storage B", 1000, 1000, "") });
886 setupHierarchyDocuments("2");
887
Garfield Tanb690b4d2017-03-01 16:05:23 -0800888 final Path path = mProvider.findDocumentPath(null, "16");
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900889 assertEquals("2", path.getRootId());
890 assertEquals(4, path.getPath().size());
891 assertEquals("2", path.getPath().get(0));
892 assertEquals("4", path.getPath().get(1));
893 assertEquals("7", path.getPath().get(2));
894 assertEquals("16", path.getPath().get(3));
895 }
896
Garfield Tanf46a4632016-11-02 16:00:17 -0700897 public void testFindDocumentPath_multiStorage_toDoc() throws Exception {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900898 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
899 setupRoots(0, new MtpRoot[] {
900 new MtpRoot(0, 0, "Storage A", 1000, 1000, ""),
901 new MtpRoot(0, 1, "Storage B", 1000, 1000, "") });
902 setupHierarchyDocuments("2");
903
Garfield Tanb690b4d2017-03-01 16:05:23 -0800904 final Path path = mProvider.findDocumentPath("4", "19");
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900905 assertNull(path.getRootId());
906 assertEquals(3, path.getPath().size());
907 assertEquals("4", path.getPath().get(0));
908 assertEquals("8", path.getPath().get(1));
909 assertEquals("19", path.getPath().get(2));
910 }
911
Daichi Hirono29de7692016-11-07 10:58:50 +0900912 public void testIsChildDocument() throws Exception {
913 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
914 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
915 setupHierarchyDocuments("1");
916 assertTrue(mProvider.isChildDocument("1", "1"));
917 assertTrue(mProvider.isChildDocument("1", "14"));
918 assertTrue(mProvider.isChildDocument("2", "14"));
919 assertTrue(mProvider.isChildDocument("5", "14"));
920 assertFalse(mProvider.isChildDocument("3", "14"));
921 assertFalse(mProvider.isChildDocument("6", "14"));
922 }
923
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900924 private void setupProvider(int flag) {
925 mDatabase = new MtpDatabase(getContext(), flag);
926 mProvider = new MtpDocumentsProvider();
Daichi Hironob36b1552016-01-25 14:26:14 +0900927 final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
928 assertTrue(mProvider.onCreateForTesting(
Daichi Hironof4e7fa82016-03-28 16:07:45 +0900929 getContext(),
Daichi Hironofda74742016-02-01 13:00:31 +0900930 mResources,
931 mMtpManager,
932 mResolver,
933 mDatabase,
934 storageManager,
935 new TestServiceIntentSender()));
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900936 }
937
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900938 private String[] getStrings(Cursor cursor) {
939 try {
940 final String[] results = new String[cursor.getCount()];
941 for (int i = 0; cursor.moveToNext(); i++) {
942 results[i] = cursor.getString(0);
943 }
944 return results;
945 } finally {
946 cursor.close();
947 }
948 }
949
950 private String[] setupRoots(int deviceId, MtpRoot[] roots)
Daichi Hirono20754c52015-12-15 18:52:26 +0900951 throws InterruptedException, TimeoutException, IOException {
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900952 final int changeCount = mResolver.getChangeCount(ROOTS_URI);
Daichi Hirono20754c52015-12-15 18:52:26 +0900953 mMtpManager.addValidDevice(
Daichi Hironoebd24052016-02-06 21:05:57 +0900954 new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */,
Daichi Hirono0f325372016-02-21 15:50:30 +0900955 roots, OPERATIONS_SUPPORTED, null));
Daichi Hirono20754c52015-12-15 18:52:26 +0900956 mProvider.openDevice(deviceId);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900957 mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
958 return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
959 }
960
961 private String[] setupDocuments(
962 int deviceId,
963 int storageId,
964 int parentHandle,
965 String parentDocumentId,
966 MtpObjectInfo[] objects) throws FileNotFoundException {
967 final int[] handles = new int[objects.length];
968 int i = 0;
969 for (final MtpObjectInfo info : objects) {
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900970 handles[i++] = info.getObjectHandle();
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900971 mMtpManager.setObjectInfo(deviceId, info);
972 }
973 mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
974 return getStrings(mProvider.queryChildDocuments(
Steve McKay29c3f682016-12-16 14:52:59 -0800975 parentDocumentId,
976 strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
977 (String) null));
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900978 }
Daichi Hironob9ffa2a2016-11-01 18:41:43 +0900979
980 static class HierarchyDocument {
981 int depth;
982 String documentId;
983 int objectHandle;
984 int parentHandle;
985
986 HierarchyDocument createChildDocument(int newHandle) {
987 final HierarchyDocument doc = new HierarchyDocument();
988 doc.depth = depth - 1;
989 doc.objectHandle = newHandle;
990 doc.parentHandle = objectHandle;
991 return doc;
992 }
993
994 MtpObjectInfo toObjectInfo() {
995 return new MtpObjectInfo.Builder()
996 .setName("doc_" + documentId)
997 .setFormat(depth > 0 ?
998 MtpConstants.FORMAT_ASSOCIATION : MtpConstants.FORMAT_TEXT)
999 .setObjectHandle(objectHandle)
1000 .setParent(parentHandle)
1001 .build();
1002 }
1003 }
1004
1005 private void setupHierarchyDocuments(String documentId) throws Exception {
1006 final Queue<HierarchyDocument> ids = new LinkedList<>();
1007 final HierarchyDocument firstDocument = new HierarchyDocument();
1008 firstDocument.depth = 3;
1009 firstDocument.documentId = documentId;
1010 firstDocument.objectHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
1011 ids.add(firstDocument);
1012
1013 int objectHandle = 100;
1014 while (!ids.isEmpty()) {
1015 final HierarchyDocument document = ids.remove();
1016 final HierarchyDocument[] children = new HierarchyDocument[] {
1017 document.createChildDocument(objectHandle++),
1018 document.createChildDocument(objectHandle++),
1019 document.createChildDocument(objectHandle++),
1020 };
1021 final String[] childDocIds = setupDocuments(
1022 0, 0, document.objectHandle, document.documentId, new MtpObjectInfo[] {
1023 children[0].toObjectInfo(),
1024 children[1].toObjectInfo(),
1025 children[2].toObjectInfo(),
1026 });
1027 children[0].documentId = childDocIds[0];
1028 children[1].documentId = childDocIds[1];
1029 children[2].documentId = childDocIds[2];
1030
1031 if (children[0].depth > 0) {
1032 ids.add(children[0]);
1033 ids.add(children[1]);
1034 ids.add(children[2]);
1035 }
1036 }
1037 }
Daichi Hirono9a05d6b2015-07-09 15:36:42 +09001038}