blob: 3dfa4ade6eb5078137c1146811d173f3544a627e [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 Hirono09109562015-07-28 20:57:05 +090026import android.provider.DocumentsContract.Root;
Daichi Hironob36b1552016-01-25 14:26:14 +090027import android.system.Os;
28import android.system.OsConstants;
Tomasz Mikolajewskibb430fa2015-08-25 18:34:30 +090029import android.provider.DocumentsContract;
Daichi Hirono9a05d6b2015-07-09 15:36:42 +090030import android.test.AndroidTestCase;
Daichi Hironob36b1552016-01-25 14:26:14 +090031import android.test.suitebuilder.annotation.MediumTest;
Daichi Hirono9a05d6b2015-07-09 15:36:42 +090032
Daichi Hironoc18f8072016-02-10 14:59:52 -080033import com.android.mtp.exceptions.BusyDeviceException;
34
Daichi Hirono5fecc6c2015-08-04 17:45:51 +090035import java.io.FileNotFoundException;
Daichi Hironod5152422015-07-15 13:31:51 +090036import java.io.IOException;
Daichi Hironob36b1552016-01-25 14:26:14 +090037import java.util.Arrays;
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +090038import java.util.concurrent.TimeoutException;
39
Daichi Hirono259ce802015-11-20 17:51:53 +090040import static com.android.mtp.MtpDatabase.strings;
Daichi Hirono0f325372016-02-21 15:50:30 +090041import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED;
Daichi Hironod5152422015-07-15 13:31:51 +090042
Daichi Hironob36b1552016-01-25 14:26:14 +090043@MediumTest
Daichi Hirono9a05d6b2015-07-09 15:36:42 +090044public class MtpDocumentsProviderTest extends AndroidTestCase {
Daichi Hirono8b9024f2015-08-12 12:59:09 +090045 private final static Uri ROOTS_URI =
46 DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
Daichi Hirono6baa16e2015-08-12 13:51:59 +090047 private TestContentResolver mResolver;
Daichi Hironobeac2182015-07-29 16:28:57 +090048 private MtpDocumentsProvider mProvider;
Daichi Hirono6ee4a1c2015-07-31 10:30:05 +090049 private TestMtpManager mMtpManager;
Daichi Hironob999b0c2015-10-27 16:29:18 +090050 private final TestResources mResources = new TestResources();
Daichi Hironodc473442015-11-13 15:42:28 +090051 private MtpDatabase mDatabase;
Daichi Hironobeac2182015-07-29 16:28:57 +090052
53 @Override
Tomasz Mikolajewski4c1d3dd2015-09-02 13:27:46 +090054 public void setUp() throws IOException {
Daichi Hirono6baa16e2015-08-12 13:51:59 +090055 mResolver = new TestContentResolver();
Daichi Hirono6ee4a1c2015-07-31 10:30:05 +090056 mMtpManager = new TestMtpManager(getContext());
Daichi Hironodc473442015-11-13 15:42:28 +090057 }
58
59 @Override
60 public void tearDown() {
Daichi Hironoe1d57712015-11-17 10:55:45 +090061 mProvider.shutdown();
Daichi Hironoe0282dd2015-11-26 15:20:08 +090062 MtpDatabase.deleteDatabase(getContext());
Daichi Hironobeac2182015-07-29 16:28:57 +090063 }
64
Daichi Hironod5152422015-07-15 13:31:51 +090065 public void testOpenAndCloseDevice() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +090066 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono20754c52015-12-15 18:52:26 +090067 mMtpManager.addValidDevice(new MtpDeviceRecord(
68 0,
Daichi Hironof83ccbd2016-02-04 16:58:55 +090069 "Device A",
Daichi Hironoebd24052016-02-06 21:05:57 +090070 null /* deviceKey */,
Daichi Hirono20754c52015-12-15 18:52:26 +090071 false /* unopened */,
72 new MtpRoot[] {
73 new MtpRoot(
74 0 /* deviceId */,
75 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +090076 "Storage A" /* volume description */,
77 1024 /* free space */,
78 2048 /* total space */,
79 "" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +090080 },
Daichi Hirono0f325372016-02-21 15:50:30 +090081 OPERATIONS_SUPPORTED,
Daichi Hirono148954a2016-01-21 19:55:45 +090082 null));
Daichi Hironod5152422015-07-15 13:31:51 +090083
Daichi Hironofda74742016-02-01 13:00:31 +090084 mProvider.resumeRootScanner();
Daichi Hirono8b9024f2015-08-12 12:59:09 +090085 mResolver.waitForNotification(ROOTS_URI, 1);
Daichi Hirono09109562015-07-28 20:57:05 +090086
Daichi Hironofda74742016-02-01 13:00:31 +090087 mProvider.openDevice(0);
Daichi Hirono8b9024f2015-08-12 12:59:09 +090088 mResolver.waitForNotification(ROOTS_URI, 2);
Daichi Hironofda74742016-02-01 13:00:31 +090089
90 mProvider.closeDevice(0);
91 mResolver.waitForNotification(ROOTS_URI, 3);
Daichi Hironod5152422015-07-15 13:31:51 +090092 }
93
Daichi Hirono8b9024f2015-08-12 12:59:09 +090094 public void testOpenAndCloseErrorDevice() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +090095 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono8b9024f2015-08-12 12:59:09 +090096 try {
97 mProvider.openDevice(1);
98 fail();
99 } catch (Throwable error) {
100 assertTrue(error instanceof IOException);
101 }
Daichi Hirono0f325372016-02-21 15:50:30 +0900102 assertEquals(0, mProvider.getOpenedDeviceRecordsCache().length);
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900103
104 // Check if the following notification is the first one or not.
Daichi Hirono20754c52015-12-15 18:52:26 +0900105 mMtpManager.addValidDevice(new MtpDeviceRecord(
106 0,
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900107 "Device A",
Daichi Hironoebd24052016-02-06 21:05:57 +0900108 null /* deviceKey */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900109 false /* unopened */,
110 new MtpRoot[] {
111 new MtpRoot(
112 0 /* deviceId */,
113 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900114 "Storage A" /* volume description */,
115 1024 /* free space */,
116 2048 /* total space */,
117 "" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900118 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900119 OPERATIONS_SUPPORTED,
Daichi Hirono148954a2016-01-21 19:55:45 +0900120 null));
Daichi Hironofda74742016-02-01 13:00:31 +0900121 mProvider.resumeRootScanner();
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900122 mResolver.waitForNotification(ROOTS_URI, 1);
Daichi Hironofda74742016-02-01 13:00:31 +0900123 mProvider.openDevice(0);
124 mResolver.waitForNotification(ROOTS_URI, 2);
125 }
126
127 public void testOpenDeviceOnDemand() throws Exception {
128 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
129 mMtpManager.addValidDevice(new MtpDeviceRecord(
130 0,
131 "Device A",
Daichi Hironoebd24052016-02-06 21:05:57 +0900132 null /* deviceKey */,
Daichi Hironofda74742016-02-01 13:00:31 +0900133 false /* unopened */,
134 new MtpRoot[] {
135 new MtpRoot(
136 0 /* deviceId */,
137 1 /* storageId */,
Daichi Hironofda74742016-02-01 13:00:31 +0900138 "Storage A" /* volume description */,
139 1024 /* free space */,
140 2048 /* total space */,
141 "" /* no volume identifier */)
142 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900143 OPERATIONS_SUPPORTED,
Daichi Hironofda74742016-02-01 13:00:31 +0900144 null));
145 mMtpManager.setObjectHandles(0, 1, -1, new int[0]);
146 mProvider.resumeRootScanner();
147 mResolver.waitForNotification(ROOTS_URI, 1);
148 final String[] columns = new String[] {
149 DocumentsContract.Root.COLUMN_TITLE,
150 DocumentsContract.Root.COLUMN_DOCUMENT_ID
151 };
152 try (final Cursor cursor = mProvider.queryRoots(columns)) {
153 assertEquals(1, cursor.getCount());
154 assertTrue(cursor.moveToNext());
155 assertEquals("Device A", cursor.getString(0));
156 assertEquals(1, cursor.getLong(1));
157 }
158 {
Daichi Hirono0f325372016-02-21 15:50:30 +0900159 final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
Daichi Hironofda74742016-02-01 13:00:31 +0900160 assertEquals(0, openedDevice.length);
161 }
162 // Device is opened automatically when querying its children.
163 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {}
164
165 {
Daichi Hirono0f325372016-02-21 15:50:30 +0900166 final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
Daichi Hironofda74742016-02-01 13:00:31 +0900167 assertEquals(1, openedDevice.length);
Daichi Hirono0f325372016-02-21 15:50:30 +0900168 assertEquals(0, openedDevice[0].deviceId);
Daichi Hironofda74742016-02-01 13:00:31 +0900169 }
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900170 }
171
Daichi Hirono09109562015-07-28 20:57:05 +0900172 public void testQueryRoots() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900173 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono20754c52015-12-15 18:52:26 +0900174 mMtpManager.addValidDevice(new MtpDeviceRecord(
175 0,
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900176 "Device A",
Daichi Hironoebd24052016-02-06 21:05:57 +0900177 "Device key A",
Daichi Hirono20754c52015-12-15 18:52:26 +0900178 false /* unopened */,
179 new MtpRoot[] {
180 new MtpRoot(
181 0 /* deviceId */,
182 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900183 "Storage A" /* volume description */,
184 1024 /* free space */,
185 2048 /* total space */,
186 "" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900187 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900188 OPERATIONS_SUPPORTED,
Daichi Hirono148954a2016-01-21 19:55:45 +0900189 null));
Daichi Hirono20754c52015-12-15 18:52:26 +0900190 mMtpManager.addValidDevice(new MtpDeviceRecord(
191 1,
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900192 "Device B",
Daichi Hironoebd24052016-02-06 21:05:57 +0900193 "Device key B",
Daichi Hirono20754c52015-12-15 18:52:26 +0900194 false /* unopened */,
195 new MtpRoot[] {
196 new MtpRoot(
197 1 /* deviceId */,
198 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900199 "Storage B" /* volume description */,
200 2048 /* free space */,
201 4096 /* total space */,
202 "Identifier B" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900203 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900204 new int[0] /* No operations supported */,
Daichi Hirono148954a2016-01-21 19:55:45 +0900205 null));
Daichi Hironod5152422015-07-15 13:31:51 +0900206
Daichi Hirono09109562015-07-28 20:57:05 +0900207 {
Daichi Hironobeac2182015-07-29 16:28:57 +0900208 mProvider.openDevice(0);
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900209 mResolver.waitForNotification(ROOTS_URI, 1);
Daichi Hironobeac2182015-07-29 16:28:57 +0900210 final Cursor cursor = mProvider.queryRoots(null);
Daichi Hirono81d48532015-12-16 15:03:19 +0900211 assertEquals(2, cursor.getCount());
Daichi Hirono09109562015-07-28 20:57:05 +0900212 cursor.moveToNext();
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900213 assertEquals("1", cursor.getString(0));
Tomasz Mikolajewskic0834cd2015-08-26 12:31:45 +0900214 assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
Daichi Hirono39795da2015-12-02 10:56:44 +0900215 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
Daichi Hirono17c8d8b2015-10-12 11:28:46 -0700216 assertEquals("Device A Storage A", cursor.getString(3));
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900217 assertEquals("1", cursor.getString(4));
Daichi Hirono09109562015-07-28 20:57:05 +0900218 assertEquals(1024, cursor.getInt(5));
Daichi Hironod5152422015-07-15 13:31:51 +0900219 }
220
Daichi Hirono09109562015-07-28 20:57:05 +0900221 {
Daichi Hironobeac2182015-07-29 16:28:57 +0900222 mProvider.openDevice(1);
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900223 mResolver.waitForNotification(ROOTS_URI, 2);
Daichi Hironobeac2182015-07-29 16:28:57 +0900224 final Cursor cursor = mProvider.queryRoots(null);
Daichi Hirono09109562015-07-28 20:57:05 +0900225 assertEquals(2, cursor.getCount());
226 cursor.moveToNext();
227 cursor.moveToNext();
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900228 assertEquals("2", cursor.getString(0));
Daichi Hirono0f325372016-02-21 15:50:30 +0900229 assertEquals(Root.FLAG_SUPPORTS_IS_CHILD, cursor.getInt(1));
Daichi Hirono39795da2015-12-02 10:56:44 +0900230 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
Daichi Hirono17c8d8b2015-10-12 11:28:46 -0700231 assertEquals("Device B Storage B", cursor.getString(3));
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900232 assertEquals("2", cursor.getString(4));
Daichi Hirono09109562015-07-28 20:57:05 +0900233 assertEquals(2048, cursor.getInt(5));
Daichi Hironod5152422015-07-15 13:31:51 +0900234 }
Daichi Hirono09109562015-07-28 20:57:05 +0900235 }
Daichi Hironod5152422015-07-15 13:31:51 +0900236
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900237 public void testQueryRoots_error() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900238 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900239 mMtpManager.addValidDevice(new MtpDeviceRecord(
Daichi Hironoebd24052016-02-06 21:05:57 +0900240 0,
241 "Device A",
242 "Device key A",
243 false /* unopened */,
244 new MtpRoot[0],
Daichi Hirono0f325372016-02-21 15:50:30 +0900245 OPERATIONS_SUPPORTED,
Daichi Hironoebd24052016-02-06 21:05:57 +0900246 null));
Daichi Hirono20754c52015-12-15 18:52:26 +0900247 mMtpManager.addValidDevice(new MtpDeviceRecord(
248 1,
Daichi Hironofda74742016-02-01 13:00:31 +0900249 "Device B",
Daichi Hironoebd24052016-02-06 21:05:57 +0900250 "Device key B",
Daichi Hirono20754c52015-12-15 18:52:26 +0900251 false /* unopened */,
252 new MtpRoot[] {
253 new MtpRoot(
254 1 /* deviceId */,
255 1 /* storageId */,
Daichi Hirono20754c52015-12-15 18:52:26 +0900256 "Storage B" /* volume description */,
257 2048 /* free space */,
258 4096 /* total space */,
259 "Identifier B" /* no volume identifier */)
Daichi Hirono1d4779c2016-01-06 16:43:32 +0900260 },
Daichi Hirono0f325372016-02-21 15:50:30 +0900261 OPERATIONS_SUPPORTED,
Daichi Hirono148954a2016-01-21 19:55:45 +0900262 null));
Daichi Hirono09109562015-07-28 20:57:05 +0900263 {
Daichi Hironobeac2182015-07-29 16:28:57 +0900264 mProvider.openDevice(0);
Daichi Hironofda74742016-02-01 13:00:31 +0900265 mProvider.resumeRootScanner();
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900266 mResolver.waitForNotification(ROOTS_URI, 1);
267
Daichi Hironofda74742016-02-01 13:00:31 +0900268 mProvider.openDevice(1);
269 mProvider.resumeRootScanner();
270 mResolver.waitForNotification(ROOTS_URI, 2);
271
Daichi Hironobeac2182015-07-29 16:28:57 +0900272 final Cursor cursor = mProvider.queryRoots(null);
Daichi Hirono81d48532015-12-16 15:03:19 +0900273 assertEquals(2, cursor.getCount());
274
275 cursor.moveToNext();
276 assertEquals("1", cursor.getString(0));
277 assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
278 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
279 assertEquals("Device A", cursor.getString(3));
280 assertEquals("1", cursor.getString(4));
281 assertEquals(0, cursor.getInt(5));
282
Daichi Hirono09109562015-07-28 20:57:05 +0900283 cursor.moveToNext();
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900284 assertEquals("2", cursor.getString(0));
Tomasz Mikolajewskic0834cd2015-08-26 12:31:45 +0900285 assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1));
Daichi Hirono39795da2015-12-02 10:56:44 +0900286 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2));
Daichi Hirono17c8d8b2015-10-12 11:28:46 -0700287 assertEquals("Device B Storage B", cursor.getString(3));
Daichi Hirono6a5ea7e2016-02-02 16:35:03 +0900288 assertEquals("2", cursor.getString(4));
Daichi Hirono09109562015-07-28 20:57:05 +0900289 assertEquals(2048, cursor.getInt(5));
Daichi Hironod5152422015-07-15 13:31:51 +0900290 }
291 }
292
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900293 public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900294 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900295 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900296 setupDocuments(
297 0,
298 0,
299 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
300 "1",
301 new MtpObjectInfo[] {
302 new MtpObjectInfo.Builder()
303 .setObjectHandle(100)
304 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
305 .setName("image.jpg")
306 .setDateModified(1422716400000L)
307 .setCompressedSize(1024 * 1024 * 5)
308 .setThumbCompressedSize(50 * 1024)
309 .build()
310 });
311
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000312 final Cursor cursor = mProvider.queryDocument("3", null);
Daichi Hironoe5323b72015-07-29 16:10:47 +0900313 assertEquals(1, cursor.getCount());
314
315 cursor.moveToNext();
Daichi Hirono47eb1922015-11-16 13:01:31 +0900316
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000317 assertEquals("3", cursor.getString(0));
Daichi Hironoe5323b72015-07-29 16:10:47 +0900318 assertEquals("image/jpeg", cursor.getString(1));
319 assertEquals("image.jpg", cursor.getString(2));
320 assertEquals(1422716400000L, cursor.getLong(3));
321 assertEquals(
322 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900323 DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
Daichi Hironoe5323b72015-07-29 16:10:47 +0900324 DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL,
325 cursor.getInt(4));
326 assertEquals(1024 * 1024 * 5, cursor.getInt(5));
327 }
328
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900329 public void testQueryDocument_directory()
330 throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900331 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900332 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900333 setupDocuments(
334 0,
335 0,
336 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
337 "1",
338 new MtpObjectInfo[] {
339 new MtpObjectInfo.Builder()
340 .setObjectHandle(2)
341 .setStorageId(1)
342 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
343 .setName("directory")
344 .setDateModified(1422716400000L)
345 .build()
346 });
347
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000348 final Cursor cursor = mProvider.queryDocument("3", null);
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900349 assertEquals(1, cursor.getCount());
350
351 cursor.moveToNext();
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000352 assertEquals("3", cursor.getString(0));
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900353 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
354 assertEquals("directory", cursor.getString(2));
355 assertEquals(1422716400000L, cursor.getLong(3));
356 assertEquals(
357 DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
358 DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
359 DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE,
360 cursor.getInt(4));
361 assertEquals(0, cursor.getInt(5));
362 }
363
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900364 public void testQueryDocument_forRoot()
365 throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900366 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900367 setupRoots(0, new MtpRoot[] {
Daichi Hironoe5323b72015-07-29 16:10:47 +0900368 new MtpRoot(
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900369 0 /* deviceId */,
Daichi Hironoe5323b72015-07-29 16:10:47 +0900370 1 /* storageId */,
371 "Storage A" /* volume description */,
372 1024 /* free space */,
373 4096 /* total space */,
374 "" /* no volume identifier */)
375 });
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000376 final Cursor cursor = mProvider.queryDocument("2", null);
Daichi Hironoe5323b72015-07-29 16:10:47 +0900377 assertEquals(1, cursor.getCount());
378
379 cursor.moveToNext();
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000380 assertEquals("2", cursor.getString(0));
Daichi Hironoe5323b72015-07-29 16:10:47 +0900381 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900382 assertEquals("Storage A", cursor.getString(2));
Daichi Hironoe5323b72015-07-29 16:10:47 +0900383 assertTrue(cursor.isNull(3));
384 assertEquals(0, cursor.getInt(4));
385 assertEquals(3072, cursor.getInt(5));
386 }
387
Daichi Hirono124d0602015-08-11 17:08:35 +0900388 public void testQueryChildDocuments() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900389 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900390 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900391 setupDocuments(
392 0,
393 0,
394 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
395 "1",
396 new MtpObjectInfo[] {
397 new MtpObjectInfo.Builder()
398 .setObjectHandle(100)
399 .setFormat(MtpConstants.FORMAT_EXIF_JPEG)
400 .setName("image.jpg")
401 .setCompressedSize(1024 * 1024 * 5)
402 .setThumbCompressedSize(5 * 1024)
403 .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
404 .build()
405 });
Daichi Hirono47eb1922015-11-16 13:01:31 +0900406
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900407 final Cursor cursor = mProvider.queryChildDocuments("1", null, null);
Daichi Hirono124d0602015-08-11 17:08:35 +0900408 assertEquals(1, cursor.getCount());
409
410 assertTrue(cursor.moveToNext());
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000411 assertEquals("3", cursor.getString(0));
Daichi Hirono124d0602015-08-11 17:08:35 +0900412 assertEquals("image/jpeg", cursor.getString(1));
413 assertEquals("image.jpg", cursor.getString(2));
414 assertEquals(0, cursor.getLong(3));
Daichi Hirono61ba9232016-02-26 12:58:39 +0900415 assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
Daichi Hirono124d0602015-08-11 17:08:35 +0900416 assertEquals(1024 * 1024 * 5, cursor.getInt(5));
417
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900418 cursor.close();
Daichi Hirono124d0602015-08-11 17:08:35 +0900419 }
420
421 public void testQueryChildDocuments_cursorError() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900422 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono124d0602015-08-11 17:08:35 +0900423 try {
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900424 mProvider.queryChildDocuments("1", null, null);
Daichi Hirono124d0602015-08-11 17:08:35 +0900425 fail();
426 } catch (Throwable error) {
427 assertTrue(error instanceof FileNotFoundException);
428 }
429 }
430
431 public void testQueryChildDocuments_documentError() throws Exception {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900432 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900433 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
Daichi Hirono124d0602015-08-11 17:08:35 +0900434 mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
435 try {
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900436 mProvider.queryChildDocuments("1", null, null);
Daichi Hirono124d0602015-08-11 17:08:35 +0900437 fail();
438 } catch (Throwable error) {
439 assertTrue(error instanceof FileNotFoundException);
440 }
441 }
442
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900443 public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900444 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900445 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900446 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900447 });
448 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
449 new MtpObjectInfo.Builder()
450 .setName("test.txt")
451 .setObjectHandle(1)
452 .setParent(-1)
453 .build()
454 });
455
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000456 mProvider.deleteDocument("3");
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900457 assertEquals(1, mResolver.getChangeCount(
458 DocumentsContract.buildChildDocumentsUri(
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900459 MtpDocumentsProvider.AUTHORITY, "1")));
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900460 }
461
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900462 public void testDeleteDocument_error()
463 throws IOException, InterruptedException, TimeoutException {
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900464 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900465 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900466 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900467 });
468 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
469 new MtpObjectInfo.Builder()
470 .setName("test.txt")
471 .setObjectHandle(1)
472 .setParent(-1)
473 .build()
474 });
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900475 try {
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000476 mProvider.deleteDocument("4");
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900477 fail();
478 } catch (Throwable e) {
479 assertTrue(e instanceof IOException);
480 }
481 assertEquals(0, mResolver.getChangeCount(
482 DocumentsContract.buildChildDocumentsUri(
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900483 MtpDocumentsProvider.AUTHORITY, "1")));
484 }
485
Daichi Hironob36b1552016-01-25 14:26:14 +0900486 public void testOpenDocument() throws Exception {
487 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
488 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900489 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hironob36b1552016-01-25 14:26:14 +0900490 });
491 final byte[] bytes = "Hello world".getBytes();
492 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
493 new MtpObjectInfo.Builder()
494 .setName("test.txt")
495 .setObjectHandle(1)
496 .setCompressedSize(bytes.length)
497 .setParent(-1)
498 .build()
499 });
500 mMtpManager.setImportFileBytes(0, 1, bytes);
501 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
502 final byte[] readBytes = new byte[5];
503 assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET));
504 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
505 assertTrue(Arrays.equals("world".getBytes(), readBytes));
506
507 assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET));
508 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5));
509 assertTrue(Arrays.equals("Hello".getBytes(), readBytes));
510 }
511 }
512
513 public void testOpenDocument_shortBytes() throws Exception {
514 mMtpManager = new TestMtpManager(getContext()) {
515 @Override
516 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
517 if (objectHandle == 1) {
518 return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle))
519 .setObjectHandle(1).setCompressedSize(1024 * 1024).build();
520 }
521
522 return super.getObjectInfo(deviceId, objectHandle);
523 }
524 };
525 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
526 setupRoots(0, new MtpRoot[] {
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900527 new MtpRoot(0, 0, "Storage", 0, 0, "")
Daichi Hironob36b1552016-01-25 14:26:14 +0900528 });
529 final byte[] bytes = "Hello world".getBytes();
530 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
531 new MtpObjectInfo.Builder()
532 .setName("test.txt")
533 .setObjectHandle(1)
534 .setCompressedSize(bytes.length)
535 .setParent(-1)
536 .build()
537 });
538 mMtpManager.setImportFileBytes(0, 1, bytes);
539 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) {
540 final byte[] readBytes = new byte[1024 * 1024];
541 assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length));
542 }
543 }
544
Daichi Hironoc18f8072016-02-10 14:59:52 -0800545 public void testBusyDevice() throws Exception {
546 mMtpManager = new TestMtpManager(getContext()) {
547 @Override
Daichi Hirono0f325372016-02-21 15:50:30 +0900548 MtpDeviceRecord openDevice(int deviceId) throws IOException {
Daichi Hironoc18f8072016-02-10 14:59:52 -0800549 throw new BusyDeviceException();
550 }
551 };
552 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
553 mMtpManager.addValidDevice(new MtpDeviceRecord(
Daichi Hirono0f325372016-02-21 15:50:30 +0900554 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0],
555 OPERATIONS_SUPPORTED, null));
Daichi Hironoc18f8072016-02-10 14:59:52 -0800556
557 mProvider.resumeRootScanner();
558 mResolver.waitForNotification(ROOTS_URI, 1);
559
560 try (final Cursor cursor = mProvider.queryRoots(null)) {
561 assertEquals(1, cursor.getCount());
562 }
563
564 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
565 assertEquals(0, cursor.getCount());
566 assertEquals(
567 "error_busy_device",
568 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
569 }
570 }
571
Daichi Hirono29657762016-02-10 16:55:37 -0800572 public void testLockedDevice() throws Exception {
573 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
574 mMtpManager.addValidDevice(new MtpDeviceRecord(
Daichi Hirono0f325372016-02-21 15:50:30 +0900575 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED,
576 null));
Daichi Hirono29657762016-02-10 16:55:37 -0800577
578 mProvider.resumeRootScanner();
579 mResolver.waitForNotification(ROOTS_URI, 1);
580
581 try (final Cursor cursor = mProvider.queryRoots(null)) {
582 assertEquals(1, cursor.getCount());
583 }
584
585 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
586 assertEquals(0, cursor.getCount());
587 assertEquals(
588 "error_locked_device",
589 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR));
590 }
591 }
592
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900593 public void testMappingDisconnectedDocuments() throws Exception {
594 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
595 mMtpManager.addValidDevice(new MtpDeviceRecord(
596 0,
597 "Device A",
598 "device key",
599 true /* unopened */,
600 new MtpRoot[] {
601 new MtpRoot(
602 0 /* deviceId */,
603 1 /* storageId */,
604 "Storage A" /* volume description */,
605 1024 /* free space */,
606 2048 /* total space */,
607 "" /* no volume identifier */)
608 },
609 null,
610 null));
611
612 final String[] names = strings("Directory A", "Directory B", "Directory C");
613 final int objectHandleOffset = 100;
614 for (int i = 0; i < names.length; i++) {
615 final int parentHandle = i == 0 ?
616 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1;
617 final int objectHandle = i + objectHandleOffset;
618 mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle });
619 mMtpManager.setObjectInfo(
620 0,
621 new MtpObjectInfo.Builder()
622 .setName(names[i])
623 .setObjectHandle(objectHandle)
624 .setFormat(MtpConstants.FORMAT_ASSOCIATION)
625 .setStorageId(1)
626 .build());
627 }
628
629 mProvider.resumeRootScanner();
630 mResolver.waitForNotification(ROOTS_URI, 1);
631
632 final int documentIdOffset = 2;
633 for (int i = 0; i < names.length; i++) {
634 try (final Cursor cursor = mProvider.queryChildDocuments(
635 String.valueOf(documentIdOffset + i),
636 strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME),
637 null)) {
638 assertEquals(1, cursor.getCount());
639 cursor.moveToNext();
640 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
641 assertEquals(names[i], cursor.getString(1));
642 }
643 }
644
645 mProvider.closeDevice(0);
646 mResolver.waitForNotification(ROOTS_URI, 2);
647
648 mProvider.openDevice(0);
649 mResolver.waitForNotification(ROOTS_URI, 3);
650
651 for (int i = 0; i < names.length; i++) {
652 mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri(
653 MtpDocumentsProvider.AUTHORITY,
654 String.valueOf(documentIdOffset + i)), 1);
655 try (final Cursor cursor = mProvider.queryChildDocuments(
656 String.valueOf(documentIdOffset + i),
657 strings(Document.COLUMN_DOCUMENT_ID),
658 null)) {
659 assertEquals(1, cursor.getCount());
660 cursor.moveToNext();
661 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
662 }
663 }
664 }
665
Daichi Hirono0f325372016-02-21 15:50:30 +0900666 public void testCreateDocument_noWritingSupport() throws Exception {
667 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
668 mMtpManager.addValidDevice(new MtpDeviceRecord(
669 0, "Device A", null /* deviceKey */, false /* unopened */,
670 new MtpRoot[] {
671 new MtpRoot(
672 0 /* deviceId */,
673 1 /* storageId */,
674 "Storage A" /* volume description */,
675 1024 /* free space */,
676 2048 /* total space */,
677 "" /* no volume identifier */)
678 },
679 new int[0] /* no operations supported */, null));
680 mProvider.resumeRootScanner();
681 mResolver.waitForNotification(ROOTS_URI, 1);
682 try {
683 mProvider.createDocument("1", "text/palin", "note.txt");
684 fail();
685 } catch (UnsupportedOperationException exception) {}
686 }
687
688 public void testOpenDocument_noWritingSupport() throws Exception {
689 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
690 mMtpManager.addValidDevice(new MtpDeviceRecord(
691 0, "Device A", null /* deviceKey */, false /* unopened */,
692 new MtpRoot[] {
693 new MtpRoot(
694 0 /* deviceId */,
695 1 /* storageId */,
696 "Storage A" /* volume description */,
697 1024 /* free space */,
698 2048 /* total space */,
699 "" /* no volume identifier */)
700 },
701 new int[0] /* no operations supported */, null));
702 mMtpManager.setObjectHandles(
703 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 });
704 mMtpManager.setObjectInfo(
705 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build());
706 mProvider.resumeRootScanner();
707 mResolver.waitForNotification(ROOTS_URI, 1);
708 try (final Cursor cursor = mProvider.queryChildDocuments(
709 "1", strings(Document.COLUMN_DOCUMENT_ID), null)) {
710 assertEquals(1, cursor.getCount());
711 cursor.moveToNext();
712 assertEquals("3", cursor.getString(0));
713 }
714 try {
715 mProvider.openDocument("3", "w", null);
716 fail();
717 } catch (UnsupportedOperationException exception) {}
718 }
719
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900720 private void setupProvider(int flag) {
721 mDatabase = new MtpDatabase(getContext(), flag);
722 mProvider = new MtpDocumentsProvider();
Daichi Hironob36b1552016-01-25 14:26:14 +0900723 final StorageManager storageManager = getContext().getSystemService(StorageManager.class);
724 assertTrue(mProvider.onCreateForTesting(
Daichi Hironofda74742016-02-01 13:00:31 +0900725 mResources,
726 mMtpManager,
727 mResolver,
728 mDatabase,
729 storageManager,
730 new TestServiceIntentSender()));
Daichi Hironoe0282dd2015-11-26 15:20:08 +0900731 }
732
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900733 private String[] getStrings(Cursor cursor) {
734 try {
735 final String[] results = new String[cursor.getCount()];
736 for (int i = 0; cursor.moveToNext(); i++) {
737 results[i] = cursor.getString(0);
738 }
739 return results;
740 } finally {
741 cursor.close();
742 }
743 }
744
745 private String[] setupRoots(int deviceId, MtpRoot[] roots)
Daichi Hirono20754c52015-12-15 18:52:26 +0900746 throws InterruptedException, TimeoutException, IOException {
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900747 final int changeCount = mResolver.getChangeCount(ROOTS_URI);
Daichi Hirono20754c52015-12-15 18:52:26 +0900748 mMtpManager.addValidDevice(
Daichi Hironoebd24052016-02-06 21:05:57 +0900749 new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */,
Daichi Hirono0f325372016-02-21 15:50:30 +0900750 roots, OPERATIONS_SUPPORTED, null));
Daichi Hirono20754c52015-12-15 18:52:26 +0900751 mProvider.openDevice(deviceId);
Daichi Hirono9e8a4fa2015-11-19 16:13:38 +0900752 mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
753 return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
754 }
755
756 private String[] setupDocuments(
757 int deviceId,
758 int storageId,
759 int parentHandle,
760 String parentDocumentId,
761 MtpObjectInfo[] objects) throws FileNotFoundException {
762 final int[] handles = new int[objects.length];
763 int i = 0;
764 for (final MtpObjectInfo info : objects) {
765 handles[i] = info.getObjectHandle();
766 mMtpManager.setObjectInfo(deviceId, info);
767 }
768 mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
769 return getStrings(mProvider.queryChildDocuments(
770 parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
Daichi Hirono5fecc6c2015-08-04 17:45:51 +0900771 }
Daichi Hirono9a05d6b2015-07-09 15:36:42 +0900772}