1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.mtp; 18 19 import android.database.Cursor; 20 import android.mtp.MtpConstants; 21 import android.mtp.MtpObjectInfo; 22 import android.net.Uri; 23 import android.os.ParcelFileDescriptor; 24 import android.os.storage.StorageManager; 25 import android.provider.DocumentsContract.Document; 26 import android.provider.DocumentsContract.Path; 27 import android.provider.DocumentsContract.Root; 28 import android.system.Os; 29 import android.system.OsConstants; 30 import android.provider.DocumentsContract; 31 import android.test.AndroidTestCase; 32 import android.test.suitebuilder.annotation.MediumTest; 33 34 import java.io.FileNotFoundException; 35 import java.io.IOException; 36 import java.util.Arrays; 37 import java.util.LinkedList; 38 import java.util.Queue; 39 import java.util.concurrent.TimeoutException; 40 41 import static com.android.mtp.MtpDatabase.strings; 42 import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED; 43 44 @MediumTest 45 public class MtpDocumentsProviderTest extends AndroidTestCase { 46 private final static Uri ROOTS_URI = 47 DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY); 48 private TestContentResolver mResolver; 49 private MtpDocumentsProvider mProvider; 50 private TestMtpManager mMtpManager; 51 private final TestResources mResources = new TestResources(); 52 private MtpDatabase mDatabase; 53 54 @Override setUp()55 public void setUp() throws IOException { 56 mResolver = new TestContentResolver(); 57 mMtpManager = new TestMtpManager(getContext()); 58 } 59 60 @Override tearDown()61 public void tearDown() { 62 mProvider.shutdown(); 63 MtpDatabase.deleteDatabase(getContext()); 64 } 65 testOpenAndCloseDevice()66 public void testOpenAndCloseDevice() throws Exception { 67 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 68 mMtpManager.addValidDevice(new MtpDeviceRecord( 69 0, 70 "Device A", 71 null /* deviceKey */, 72 false /* unopened */, 73 new MtpRoot[] { 74 new MtpRoot( 75 0 /* deviceId */, 76 1 /* storageId */, 77 "Storage A" /* volume description */, 78 1024 /* free space */, 79 2048 /* total space */, 80 "" /* no volume identifier */) 81 }, 82 OPERATIONS_SUPPORTED, 83 null)); 84 85 mProvider.resumeRootScanner(); 86 mResolver.waitForNotification(ROOTS_URI, 1); 87 88 mProvider.openDevice(0); 89 mResolver.waitForNotification(ROOTS_URI, 2); 90 91 mProvider.closeDevice(0); 92 mResolver.waitForNotification(ROOTS_URI, 3); 93 } 94 testOpenAndCloseErrorDevice()95 public void testOpenAndCloseErrorDevice() throws Exception { 96 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 97 try { 98 mProvider.openDevice(1); 99 fail(); 100 } catch (Throwable error) { 101 assertTrue(error instanceof IOException); 102 } 103 assertEquals(0, mProvider.getOpenedDeviceRecordsCache().length); 104 105 // Check if the following notification is the first one or not. 106 mMtpManager.addValidDevice(new MtpDeviceRecord( 107 0, 108 "Device A", 109 null /* deviceKey */, 110 false /* unopened */, 111 new MtpRoot[] { 112 new MtpRoot( 113 0 /* deviceId */, 114 1 /* storageId */, 115 "Storage A" /* volume description */, 116 1024 /* free space */, 117 2048 /* total space */, 118 "" /* no volume identifier */) 119 }, 120 OPERATIONS_SUPPORTED, 121 null)); 122 mProvider.resumeRootScanner(); 123 mResolver.waitForNotification(ROOTS_URI, 1); 124 mProvider.openDevice(0); 125 mResolver.waitForNotification(ROOTS_URI, 2); 126 } 127 testOpenDeviceOnDemand()128 public void testOpenDeviceOnDemand() throws Exception { 129 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 130 mMtpManager.addValidDevice(new MtpDeviceRecord( 131 0, 132 "Device A", 133 null /* deviceKey */, 134 false /* unopened */, 135 new MtpRoot[] { 136 new MtpRoot( 137 0 /* deviceId */, 138 1 /* storageId */, 139 "Storage A" /* volume description */, 140 1024 /* free space */, 141 2048 /* total space */, 142 "" /* no volume identifier */) 143 }, 144 OPERATIONS_SUPPORTED, 145 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 { 160 final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache(); 161 assertEquals(0, openedDevice.length); 162 } 163 // Device is opened automatically when querying its children. 164 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {} 165 166 { 167 final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache(); 168 assertEquals(1, openedDevice.length); 169 assertEquals(0, openedDevice[0].deviceId); 170 } 171 } 172 testQueryRoots()173 public void testQueryRoots() throws Exception { 174 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 175 mMtpManager.addValidDevice(new MtpDeviceRecord( 176 0, 177 "Device A", 178 "Device key A", 179 false /* unopened */, 180 new MtpRoot[] { 181 new MtpRoot( 182 0 /* deviceId */, 183 1 /* storageId */, 184 "Storage A" /* volume description */, 185 1024 /* free space */, 186 2048 /* total space */, 187 "" /* no volume identifier */) 188 }, 189 OPERATIONS_SUPPORTED, 190 null)); 191 mMtpManager.addValidDevice(new MtpDeviceRecord( 192 1, 193 "Device B", 194 "Device key B", 195 false /* unopened */, 196 new MtpRoot[] { 197 new MtpRoot( 198 1 /* deviceId */, 199 1 /* storageId */, 200 "Storage B" /* volume description */, 201 2048 /* free space */, 202 4096 /* total space */, 203 "Identifier B" /* no volume identifier */) 204 }, 205 new int[0] /* No operations supported */, 206 null)); 207 208 { 209 mProvider.openDevice(0); 210 mResolver.waitForNotification(ROOTS_URI, 1); 211 final Cursor cursor = mProvider.queryRoots(null); 212 assertEquals(2, cursor.getCount()); 213 cursor.moveToNext(); 214 assertEquals("1", cursor.getString(0)); 215 assertEquals( 216 Root.FLAG_SUPPORTS_IS_CHILD | 217 Root.FLAG_SUPPORTS_CREATE | 218 Root.FLAG_LOCAL_ONLY, 219 cursor.getInt(1)); 220 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 221 assertEquals("Device A Storage A", cursor.getString(3)); 222 assertEquals("1", cursor.getString(4)); 223 assertEquals(1024, cursor.getInt(5)); 224 } 225 226 { 227 mProvider.openDevice(1); 228 mResolver.waitForNotification(ROOTS_URI, 2); 229 final Cursor cursor = mProvider.queryRoots(null); 230 assertEquals(2, cursor.getCount()); 231 cursor.moveToNext(); 232 cursor.moveToNext(); 233 assertEquals("2", cursor.getString(0)); 234 assertEquals( 235 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_LOCAL_ONLY, cursor.getInt(1)); 236 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 237 assertEquals("Device B Storage B", cursor.getString(3)); 238 assertEquals("2", cursor.getString(4)); 239 assertEquals(2048, cursor.getInt(5)); 240 } 241 } 242 testQueryRoots_error()243 public void testQueryRoots_error() throws Exception { 244 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 245 mMtpManager.addValidDevice(new MtpDeviceRecord( 246 0, 247 "Device A", 248 "Device key A", 249 false /* unopened */, 250 new MtpRoot[0], 251 OPERATIONS_SUPPORTED, 252 null)); 253 mMtpManager.addValidDevice(new MtpDeviceRecord( 254 1, 255 "Device B", 256 "Device key B", 257 false /* unopened */, 258 new MtpRoot[] { 259 new MtpRoot( 260 1 /* deviceId */, 261 1 /* storageId */, 262 "Storage B" /* volume description */, 263 2048 /* free space */, 264 4096 /* total space */, 265 "Identifier B" /* no volume identifier */) 266 }, 267 OPERATIONS_SUPPORTED, 268 null)); 269 { 270 mProvider.openDevice(0); 271 mResolver.waitForNotification(ROOTS_URI, 1); 272 273 mProvider.openDevice(1); 274 mResolver.waitForNotification(ROOTS_URI, 2); 275 276 final Cursor cursor = mProvider.queryRoots(null); 277 assertEquals(2, cursor.getCount()); 278 279 cursor.moveToNext(); 280 assertEquals("1", cursor.getString(0)); 281 assertEquals( 282 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, 283 cursor.getInt(1)); 284 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 289 cursor.moveToNext(); 290 assertEquals("2", cursor.getString(0)); 291 assertEquals( 292 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, 293 cursor.getInt(1)); 294 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 295 assertEquals("Device B Storage B", cursor.getString(3)); 296 assertEquals("2", cursor.getString(4)); 297 assertEquals(2048, cursor.getInt(5)); 298 } 299 } 300 testQueryDocument()301 public void testQueryDocument() throws IOException, InterruptedException, TimeoutException { 302 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 303 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 304 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 320 final Cursor cursor = mProvider.queryDocument("3", null); 321 assertEquals(1, cursor.getCount()); 322 323 cursor.moveToNext(); 324 325 assertEquals("3", cursor.getString(0)); 326 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 | 331 DocumentsContract.Document.FLAG_SUPPORTS_WRITE | 332 DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL | 333 DocumentsContract.Document.FLAG_SUPPORTS_METADATA, 334 cursor.getInt(4)); 335 assertEquals(1024 * 1024 * 5, cursor.getInt(5)); 336 } 337 testQueryDocument_directory()338 public void testQueryDocument_directory() 339 throws IOException, InterruptedException, TimeoutException { 340 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 341 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 342 setupDocuments( 343 0, 344 0, 345 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 346 "1", 347 new MtpObjectInfo[] { 348 new MtpObjectInfo.Builder() 349 .setObjectHandle(2) 350 .setStorageId(1) 351 .setFormat(MtpConstants.FORMAT_ASSOCIATION) 352 .setName("directory") 353 .setDateModified(1422716400000L) 354 .build() 355 }); 356 357 final Cursor cursor = mProvider.queryDocument("3", null); 358 assertEquals(1, cursor.getCount()); 359 360 cursor.moveToNext(); 361 assertEquals("3", cursor.getString(0)); 362 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); 363 assertEquals("directory", cursor.getString(2)); 364 assertEquals(1422716400000L, cursor.getLong(3)); 365 assertEquals( 366 DocumentsContract.Document.FLAG_SUPPORTS_DELETE | 367 DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, 368 cursor.getInt(4)); 369 assertEquals(0, cursor.getInt(5)); 370 } 371 testQueryDocument_forStorage()372 public void testQueryDocument_forStorage() 373 throws IOException, InterruptedException, TimeoutException { 374 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 375 setupRoots(0, new MtpRoot[] { 376 new MtpRoot( 377 0 /* deviceId */, 378 1 /* storageId */, 379 "Storage A" /* volume description */, 380 1024 /* free space */, 381 4096 /* total space */, 382 "" /* no volume identifier */) 383 }); 384 final Cursor cursor = mProvider.queryDocument("2", null); 385 assertEquals(1, cursor.getCount()); 386 387 cursor.moveToNext(); 388 assertEquals("2", cursor.getString(0)); 389 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); 390 assertEquals("Storage A", cursor.getString(2)); 391 assertTrue(cursor.isNull(3)); 392 assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4)); 393 assertEquals(3072, cursor.getInt(5)); 394 } 395 testQueryDocument_forDeviceWithSingleStorage()396 public void testQueryDocument_forDeviceWithSingleStorage() 397 throws IOException, InterruptedException, TimeoutException { 398 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 399 setupRoots(0, new MtpRoot[] { 400 new MtpRoot( 401 0 /* deviceId */, 402 1 /* storageId */, 403 "Storage A" /* volume description */, 404 1024 /* free space */, 405 4096 /* total space */, 406 "" /* no volume identifier */) 407 }); 408 final Cursor cursor = mProvider.queryDocument("1", null); 409 assertEquals(1, cursor.getCount()); 410 411 cursor.moveToNext(); 412 assertEquals("1", cursor.getString(0)); 413 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); 414 assertEquals("Device Storage A", cursor.getString(2)); 415 assertTrue(cursor.isNull(3)); 416 assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4)); 417 assertTrue(cursor.isNull(5)); 418 } 419 testQueryDocument_forDeviceWithTwoStorages()420 public void testQueryDocument_forDeviceWithTwoStorages() 421 throws IOException, InterruptedException, TimeoutException { 422 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 423 setupRoots(0, new MtpRoot[] { 424 new MtpRoot( 425 0 /* deviceId */, 426 1 /* storageId */, 427 "Storage A" /* volume description */, 428 1024 /* free space */, 429 4096 /* total space */, 430 "" /* no volume identifier */), 431 new MtpRoot( 432 0 /* deviceId */, 433 2 /* storageId */, 434 "Storage B" /* volume description */, 435 1024 /* free space */, 436 4096 /* total space */, 437 "" /* no volume identifier */) 438 }); 439 final Cursor cursor = mProvider.queryDocument("1", null); 440 assertEquals(1, cursor.getCount()); 441 442 cursor.moveToNext(); 443 assertEquals("1", cursor.getString(0)); 444 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); 445 assertEquals("Device", cursor.getString(2)); 446 assertTrue(cursor.isNull(3)); 447 assertEquals(0, cursor.getInt(4)); 448 assertTrue(cursor.isNull(5)); 449 } 450 testQueryChildDocuments()451 public void testQueryChildDocuments() throws Exception { 452 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 453 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 454 setupDocuments( 455 0, 456 0, 457 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 458 "1", 459 new MtpObjectInfo[] { 460 new MtpObjectInfo.Builder() 461 .setObjectHandle(100) 462 .setFormat(MtpConstants.FORMAT_EXIF_JPEG) 463 .setName("image.jpg") 464 .setCompressedSize(1024 * 1024 * 5) 465 .setThumbCompressedSize(5 * 1024) 466 .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY) 467 .build() 468 }); 469 470 final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null); 471 assertEquals(1, cursor.getCount()); 472 473 assertTrue(cursor.moveToNext()); 474 assertEquals("3", cursor.getString(0)); 475 assertEquals("image/jpeg", cursor.getString(1)); 476 assertEquals("image.jpg", cursor.getString(2)); 477 assertEquals(0, cursor.getLong(3)); 478 assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL 479 | Document.FLAG_SUPPORTS_METADATA, cursor.getInt(4)); 480 assertEquals(1024 * 1024 * 5, cursor.getInt(5)); 481 482 cursor.close(); 483 } 484 testQueryChildDocuments_cursorError()485 public void testQueryChildDocuments_cursorError() throws Exception { 486 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 487 try { 488 mProvider.queryChildDocuments("1", null, (String) null); 489 fail(); 490 } catch (FileNotFoundException error) {} 491 } 492 testQueryChildDocuments_documentError()493 public void testQueryChildDocuments_documentError() throws Exception { 494 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 495 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 496 mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); 497 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) { 498 assertEquals(0, cursor.getCount()); 499 assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); 500 } 501 } 502 testDeleteDocument()503 public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException { 504 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 505 setupRoots(0, new MtpRoot[] { 506 new MtpRoot(0, 0, "Storage", 0, 0, "") 507 }); 508 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 509 new MtpObjectInfo.Builder() 510 .setName("test.txt") 511 .setObjectHandle(1) 512 .setParent(-1) 513 .build() 514 }); 515 516 mProvider.deleteDocument("3"); 517 assertEquals(1, mResolver.getChangeCount( 518 DocumentsContract.buildChildDocumentsUri( 519 MtpDocumentsProvider.AUTHORITY, "1"))); 520 } 521 testDeleteDocument_error()522 public void testDeleteDocument_error() 523 throws IOException, InterruptedException, TimeoutException { 524 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 525 setupRoots(0, new MtpRoot[] { 526 new MtpRoot(0, 0, "Storage", 0, 0, "") 527 }); 528 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 529 new MtpObjectInfo.Builder() 530 .setName("test.txt") 531 .setObjectHandle(1) 532 .setParent(-1) 533 .build() 534 }); 535 try { 536 mProvider.deleteDocument("4"); 537 fail(); 538 } catch (Throwable e) { 539 assertTrue(e instanceof IOException); 540 } 541 assertEquals(0, mResolver.getChangeCount( 542 DocumentsContract.buildChildDocumentsUri( 543 MtpDocumentsProvider.AUTHORITY, "1"))); 544 } 545 testOpenDocument()546 public void testOpenDocument() throws Exception { 547 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 548 setupRoots(0, new MtpRoot[] { 549 new MtpRoot(0, 0, "Storage", 0, 0, "") 550 }); 551 final byte[] bytes = "Hello world".getBytes(); 552 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 553 new MtpObjectInfo.Builder() 554 .setName("test.txt") 555 .setObjectHandle(1) 556 .setCompressedSize(bytes.length) 557 .setParent(-1) 558 .build() 559 }); 560 mMtpManager.setImportFileBytes(0, 1, bytes); 561 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) { 562 final byte[] readBytes = new byte[5]; 563 assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET)); 564 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5)); 565 assertTrue(Arrays.equals("world".getBytes(), readBytes)); 566 567 assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET)); 568 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5)); 569 assertTrue(Arrays.equals("Hello".getBytes(), readBytes)); 570 } 571 } 572 testOpenDocument_shortBytes()573 public void testOpenDocument_shortBytes() throws Exception { 574 mMtpManager = new TestMtpManager(getContext()) { 575 @Override 576 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { 577 if (objectHandle == 1) { 578 return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle)) 579 .setObjectHandle(1).setCompressedSize(1024 * 1024).build(); 580 } 581 582 return super.getObjectInfo(deviceId, objectHandle); 583 } 584 }; 585 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 586 setupRoots(0, new MtpRoot[] { 587 new MtpRoot(0, 0, "Storage", 0, 0, "") 588 }); 589 final byte[] bytes = "Hello world".getBytes(); 590 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 591 new MtpObjectInfo.Builder() 592 .setName("test.txt") 593 .setObjectHandle(1) 594 .setCompressedSize(bytes.length) 595 .setParent(-1) 596 .build() 597 }); 598 mMtpManager.setImportFileBytes(0, 1, bytes); 599 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) { 600 final byte[] readBytes = new byte[1024 * 1024]; 601 assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length)); 602 } 603 } 604 testOpenDocument_writing()605 public void testOpenDocument_writing() throws Exception { 606 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 607 setupRoots(0, new MtpRoot[] { 608 new MtpRoot(0, 100, "Storage", 0, 0, "") 609 }); 610 final String documentId = mProvider.createDocument("2", "text/plain", "test.txt"); 611 { 612 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null); 613 try (ParcelFileDescriptor.AutoCloseOutputStream stream = 614 new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { 615 stream.write("Hello".getBytes()); 616 fd.getFileDescriptor().sync(); 617 } 618 } 619 { 620 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null); 621 try (ParcelFileDescriptor.AutoCloseInputStream stream = 622 new ParcelFileDescriptor.AutoCloseInputStream(fd)) { 623 final byte[] bytes = new byte[5]; 624 stream.read(bytes); 625 assertTrue(Arrays.equals("Hello".getBytes(), bytes)); 626 } 627 } 628 } 629 testBusyDevice()630 public void testBusyDevice() throws Exception { 631 mMtpManager = new TestMtpManager(getContext()) { 632 @Override 633 synchronized MtpDeviceRecord openDevice(int deviceId) 634 throws IOException { 635 throw new BusyDeviceException(); 636 } 637 }; 638 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 639 mMtpManager.addValidDevice(new MtpDeviceRecord( 640 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0], 641 OPERATIONS_SUPPORTED, null)); 642 643 mProvider.resumeRootScanner(); 644 mResolver.waitForNotification(ROOTS_URI, 1); 645 646 try (final Cursor cursor = mProvider.queryRoots(null)) { 647 assertEquals(1, cursor.getCount()); 648 } 649 650 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) { 651 assertEquals(0, cursor.getCount()); 652 assertEquals( 653 "error_busy_device", 654 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); 655 } 656 } 657 testLockedDevice()658 public void testLockedDevice() throws Exception { 659 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 660 mMtpManager.addValidDevice(new MtpDeviceRecord( 661 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED, 662 null)); 663 664 mProvider.resumeRootScanner(); 665 mResolver.waitForNotification(ROOTS_URI, 1); 666 667 try (final Cursor cursor = mProvider.queryRoots(null)) { 668 assertEquals(1, cursor.getCount()); 669 } 670 671 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) { 672 assertEquals(0, cursor.getCount()); 673 assertEquals( 674 "error_locked_device", 675 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); 676 } 677 } 678 testMappingDisconnectedDocuments()679 public void testMappingDisconnectedDocuments() throws Exception { 680 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 681 mMtpManager.addValidDevice(new MtpDeviceRecord( 682 0, 683 "Device A", 684 "device key", 685 true /* opened */, 686 new MtpRoot[] { 687 new MtpRoot( 688 0 /* deviceId */, 689 1 /* storageId */, 690 "Storage A" /* volume description */, 691 1024 /* free space */, 692 2048 /* total space */, 693 "" /* no volume identifier */) 694 }, 695 OPERATIONS_SUPPORTED, 696 null)); 697 698 final String[] names = strings("Directory A", "Directory B", "Directory C"); 699 final int objectHandleOffset = 100; 700 for (int i = 0; i < names.length; i++) { 701 final int parentHandle = i == 0 ? 702 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1; 703 final int objectHandle = i + objectHandleOffset; 704 mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle }); 705 mMtpManager.setObjectInfo( 706 0, 707 new MtpObjectInfo.Builder() 708 .setName(names[i]) 709 .setObjectHandle(objectHandle) 710 .setFormat(MtpConstants.FORMAT_ASSOCIATION) 711 .setStorageId(1) 712 .build()); 713 } 714 715 mProvider.resumeRootScanner(); 716 mResolver.waitForNotification(ROOTS_URI, 1); 717 718 final int documentIdOffset = 2; 719 for (int i = 0; i < names.length; i++) { 720 try (final Cursor cursor = mProvider.queryChildDocuments( 721 String.valueOf(documentIdOffset + i), 722 strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME), 723 (String) null)) { 724 assertEquals(1, cursor.getCount()); 725 cursor.moveToNext(); 726 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0)); 727 assertEquals(names[i], cursor.getString(1)); 728 } 729 } 730 731 mProvider.closeDevice(0); 732 mResolver.waitForNotification(ROOTS_URI, 2); 733 734 mProvider.openDevice(0); 735 mResolver.waitForNotification(ROOTS_URI, 3); 736 737 for (int i = 0; i < names.length; i++) { 738 mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri( 739 MtpDocumentsProvider.AUTHORITY, 740 String.valueOf(documentIdOffset + i)), 1); 741 try (final Cursor cursor = mProvider.queryChildDocuments( 742 String.valueOf(documentIdOffset + i), 743 strings(Document.COLUMN_DOCUMENT_ID), 744 (String) null)) { 745 assertEquals(1, cursor.getCount()); 746 cursor.moveToNext(); 747 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0)); 748 } 749 } 750 } 751 testCreateDocument()752 public void testCreateDocument() throws Exception { 753 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 754 setupRoots(0, new MtpRoot[] { 755 new MtpRoot(0, 100, "Storage A", 100, 100, null) 756 }); 757 final String documentId = mProvider.createDocument("1", "text/plain", "note.txt"); 758 final Uri deviceUri = DocumentsContract.buildChildDocumentsUri( 759 MtpDocumentsProvider.AUTHORITY, "1"); 760 final Uri storageUri = DocumentsContract.buildChildDocumentsUri( 761 MtpDocumentsProvider.AUTHORITY, "2"); 762 mResolver.waitForNotification(storageUri, 1); 763 mResolver.waitForNotification(deviceUri, 1); 764 try (final Cursor cursor = mProvider.queryDocument(documentId, null)) { 765 assertTrue(cursor.moveToNext()); 766 assertEquals( 767 "note.txt", 768 cursor.getString(cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME))); 769 assertEquals( 770 "text/plain", 771 cursor.getString(cursor.getColumnIndex(Document.COLUMN_MIME_TYPE))); 772 } 773 } 774 testCreateDocument_noWritingSupport()775 public void testCreateDocument_noWritingSupport() throws Exception { 776 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 777 mMtpManager.addValidDevice(new MtpDeviceRecord( 778 0, "Device A", null /* deviceKey */, false /* unopened */, 779 new MtpRoot[] { 780 new MtpRoot( 781 0 /* deviceId */, 782 1 /* storageId */, 783 "Storage A" /* volume description */, 784 1024 /* free space */, 785 2048 /* total space */, 786 "" /* no volume identifier */) 787 }, 788 new int[0] /* no operations supported */, null)); 789 mProvider.resumeRootScanner(); 790 mResolver.waitForNotification(ROOTS_URI, 1); 791 try { 792 mProvider.createDocument("1", "text/palin", "note.txt"); 793 fail(); 794 } catch (UnsupportedOperationException exception) {} 795 } 796 testOpenDocument_noWritingSupport()797 public void testOpenDocument_noWritingSupport() throws Exception { 798 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 799 mMtpManager.addValidDevice(new MtpDeviceRecord( 800 0, "Device A", null /* deviceKey */, false /* unopened */, 801 new MtpRoot[] { 802 new MtpRoot( 803 0 /* deviceId */, 804 1 /* storageId */, 805 "Storage A" /* volume description */, 806 1024 /* free space */, 807 2048 /* total space */, 808 "" /* no volume identifier */) 809 }, 810 new int[0] /* no operations supported */, null)); 811 mMtpManager.setObjectHandles( 812 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 }); 813 mMtpManager.setObjectInfo( 814 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build()); 815 mProvider.resumeRootScanner(); 816 mResolver.waitForNotification(ROOTS_URI, 1); 817 try (final Cursor cursor = mProvider.queryChildDocuments( 818 "1", strings(Document.COLUMN_DOCUMENT_ID), (String) null)) { 819 assertEquals(1, cursor.getCount()); 820 cursor.moveToNext(); 821 assertEquals("3", cursor.getString(0)); 822 } 823 try { 824 mProvider.openDocument("3", "w", null); 825 fail(); 826 } catch (UnsupportedOperationException exception) {} 827 } 828 testObjectSizeLong()829 public void testObjectSizeLong() throws Exception { 830 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 831 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 832 mMtpManager.setObjectSizeLong(0, 100, MtpConstants.FORMAT_EXIF_JPEG, 0x400000000L); 833 setupDocuments( 834 0, 835 0, 836 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 837 "1", 838 new MtpObjectInfo[] { 839 new MtpObjectInfo.Builder() 840 .setObjectHandle(100) 841 .setFormat(MtpConstants.FORMAT_EXIF_JPEG) 842 .setName("image.jpg") 843 .setCompressedSize(0xffffffffl) 844 .build() 845 }); 846 847 final Cursor cursor = mProvider.queryDocument("3", new String[] { 848 DocumentsContract.Document.COLUMN_SIZE 849 }); 850 assertEquals(1, cursor.getCount()); 851 852 cursor.moveToNext(); 853 assertEquals(0x400000000L, cursor.getLong(0)); 854 } 855 testFindDocumentPath_singleStorage_toRoot()856 public void testFindDocumentPath_singleStorage_toRoot() throws Exception { 857 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 858 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 859 setupHierarchyDocuments("1"); 860 861 final Path path = mProvider.findDocumentPath(null, "15"); 862 assertEquals("1", path.getRootId()); 863 assertEquals(4, path.getPath().size()); 864 assertEquals("1", path.getPath().get(0)); 865 assertEquals("3", path.getPath().get(1)); 866 assertEquals("6", path.getPath().get(2)); 867 assertEquals("15", path.getPath().get(3)); 868 } 869 testFindDocumentPath_singleStorage_toDoc()870 public void testFindDocumentPath_singleStorage_toDoc() throws Exception { 871 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 872 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 873 setupHierarchyDocuments("1"); 874 875 final Path path = mProvider.findDocumentPath("3", "18"); 876 assertNull(path.getRootId()); 877 assertEquals(3, path.getPath().size()); 878 assertEquals("3", path.getPath().get(0)); 879 assertEquals("7", path.getPath().get(1)); 880 assertEquals("18", path.getPath().get(2)); 881 } 882 testFindDocumentPath_multiStorage_toRoot()883 public void testFindDocumentPath_multiStorage_toRoot() throws Exception { 884 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 885 setupRoots(0, new MtpRoot[] { 886 new MtpRoot(0, 0, "Storage A", 1000, 1000, ""), 887 new MtpRoot(0, 1, "Storage B", 1000, 1000, "") }); 888 setupHierarchyDocuments("2"); 889 890 final Path path = mProvider.findDocumentPath(null, "16"); 891 assertEquals("2", path.getRootId()); 892 assertEquals(4, path.getPath().size()); 893 assertEquals("2", path.getPath().get(0)); 894 assertEquals("4", path.getPath().get(1)); 895 assertEquals("7", path.getPath().get(2)); 896 assertEquals("16", path.getPath().get(3)); 897 } 898 testFindDocumentPath_multiStorage_toDoc()899 public void testFindDocumentPath_multiStorage_toDoc() throws Exception { 900 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 901 setupRoots(0, new MtpRoot[] { 902 new MtpRoot(0, 0, "Storage A", 1000, 1000, ""), 903 new MtpRoot(0, 1, "Storage B", 1000, 1000, "") }); 904 setupHierarchyDocuments("2"); 905 906 final Path path = mProvider.findDocumentPath("4", "19"); 907 assertNull(path.getRootId()); 908 assertEquals(3, path.getPath().size()); 909 assertEquals("4", path.getPath().get(0)); 910 assertEquals("8", path.getPath().get(1)); 911 assertEquals("19", path.getPath().get(2)); 912 } 913 testIsChildDocument()914 public void testIsChildDocument() throws Exception { 915 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 916 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 917 setupHierarchyDocuments("1"); 918 assertTrue(mProvider.isChildDocument("1", "1")); 919 assertTrue(mProvider.isChildDocument("1", "14")); 920 assertTrue(mProvider.isChildDocument("2", "14")); 921 assertTrue(mProvider.isChildDocument("5", "14")); 922 assertFalse(mProvider.isChildDocument("3", "14")); 923 assertFalse(mProvider.isChildDocument("6", "14")); 924 } 925 setupProvider(int flag)926 private void setupProvider(int flag) { 927 mDatabase = new MtpDatabase(getContext(), flag); 928 mProvider = new MtpDocumentsProvider(); 929 final StorageManager storageManager = getContext().getSystemService(StorageManager.class); 930 assertTrue(mProvider.onCreateForTesting( 931 getContext(), 932 mResources, 933 mMtpManager, 934 mResolver, 935 mDatabase, 936 storageManager, 937 new TestServiceIntentSender())); 938 } 939 getStrings(Cursor cursor)940 private String[] getStrings(Cursor cursor) { 941 try { 942 final String[] results = new String[cursor.getCount()]; 943 for (int i = 0; cursor.moveToNext(); i++) { 944 results[i] = cursor.getString(0); 945 } 946 return results; 947 } finally { 948 cursor.close(); 949 } 950 } 951 setupRoots(int deviceId, MtpRoot[] roots)952 private String[] setupRoots(int deviceId, MtpRoot[] roots) 953 throws InterruptedException, TimeoutException, IOException { 954 final int changeCount = mResolver.getChangeCount(ROOTS_URI); 955 mMtpManager.addValidDevice( 956 new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */, 957 roots, OPERATIONS_SUPPORTED, null)); 958 mProvider.openDevice(deviceId); 959 mResolver.waitForNotification(ROOTS_URI, changeCount + 1); 960 return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID))); 961 } 962 setupDocuments( int deviceId, int storageId, int parentHandle, String parentDocumentId, MtpObjectInfo[] objects)963 private String[] setupDocuments( 964 int deviceId, 965 int storageId, 966 int parentHandle, 967 String parentDocumentId, 968 MtpObjectInfo[] objects) throws FileNotFoundException { 969 final int[] handles = new int[objects.length]; 970 int i = 0; 971 for (final MtpObjectInfo info : objects) { 972 handles[i++] = info.getObjectHandle(); 973 mMtpManager.setObjectInfo(deviceId, info); 974 } 975 mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles); 976 return getStrings(mProvider.queryChildDocuments( 977 parentDocumentId, 978 strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), 979 (String) null)); 980 } 981 982 static class HierarchyDocument { 983 int depth; 984 String documentId; 985 int objectHandle; 986 int parentHandle; 987 createChildDocument(int newHandle)988 HierarchyDocument createChildDocument(int newHandle) { 989 final HierarchyDocument doc = new HierarchyDocument(); 990 doc.depth = depth - 1; 991 doc.objectHandle = newHandle; 992 doc.parentHandle = objectHandle; 993 return doc; 994 } 995 toObjectInfo()996 MtpObjectInfo toObjectInfo() { 997 return new MtpObjectInfo.Builder() 998 .setName("doc_" + documentId) 999 .setFormat(depth > 0 ? 1000 MtpConstants.FORMAT_ASSOCIATION : MtpConstants.FORMAT_TEXT) 1001 .setObjectHandle(objectHandle) 1002 .setParent(parentHandle) 1003 .build(); 1004 } 1005 } 1006 setupHierarchyDocuments(String documentId)1007 private void setupHierarchyDocuments(String documentId) throws Exception { 1008 final Queue<HierarchyDocument> ids = new LinkedList<>(); 1009 final HierarchyDocument firstDocument = new HierarchyDocument(); 1010 firstDocument.depth = 3; 1011 firstDocument.documentId = documentId; 1012 firstDocument.objectHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; 1013 ids.add(firstDocument); 1014 1015 int objectHandle = 100; 1016 while (!ids.isEmpty()) { 1017 final HierarchyDocument document = ids.remove(); 1018 final HierarchyDocument[] children = new HierarchyDocument[] { 1019 document.createChildDocument(objectHandle++), 1020 document.createChildDocument(objectHandle++), 1021 document.createChildDocument(objectHandle++), 1022 }; 1023 final String[] childDocIds = setupDocuments( 1024 0, 0, document.objectHandle, document.documentId, new MtpObjectInfo[] { 1025 children[0].toObjectInfo(), 1026 children[1].toObjectInfo(), 1027 children[2].toObjectInfo(), 1028 }); 1029 children[0].documentId = childDocIds[0]; 1030 children[1].documentId = childDocIds[1]; 1031 children[2].documentId = childDocIds[2]; 1032 1033 if (children[0].depth > 0) { 1034 ids.add(children[0]); 1035 ids.add(children[1]); 1036 ids.add(children[2]); 1037 } 1038 } 1039 } 1040 } 1041