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.ParcelFileDescriptor.AutoCloseOutputStream; 25 import android.os.storage.StorageManager; 26 import android.provider.DocumentsContract.Document; 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.concurrent.TimeoutException; 38 39 import static com.android.mtp.MtpDatabase.strings; 40 import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED; 41 42 @MediumTest 43 public class MtpDocumentsProviderTest extends AndroidTestCase { 44 private final static Uri ROOTS_URI = 45 DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY); 46 private TestContentResolver mResolver; 47 private MtpDocumentsProvider mProvider; 48 private TestMtpManager mMtpManager; 49 private final TestResources mResources = new TestResources(); 50 private MtpDatabase mDatabase; 51 52 @Override setUp()53 public void setUp() throws IOException { 54 mResolver = new TestContentResolver(); 55 mMtpManager = new TestMtpManager(getContext()); 56 } 57 58 @Override tearDown()59 public void tearDown() { 60 mProvider.shutdown(); 61 MtpDatabase.deleteDatabase(getContext()); 62 } 63 testOpenAndCloseDevice()64 public void testOpenAndCloseDevice() throws Exception { 65 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 66 mMtpManager.addValidDevice(new MtpDeviceRecord( 67 0, 68 "Device A", 69 null /* deviceKey */, 70 false /* unopened */, 71 new MtpRoot[] { 72 new MtpRoot( 73 0 /* deviceId */, 74 1 /* storageId */, 75 "Storage A" /* volume description */, 76 1024 /* free space */, 77 2048 /* total space */, 78 "" /* no volume identifier */) 79 }, 80 OPERATIONS_SUPPORTED, 81 null)); 82 83 mProvider.resumeRootScanner(); 84 mResolver.waitForNotification(ROOTS_URI, 1); 85 86 mProvider.openDevice(0); 87 mResolver.waitForNotification(ROOTS_URI, 2); 88 89 mProvider.closeDevice(0); 90 mResolver.waitForNotification(ROOTS_URI, 3); 91 } 92 testOpenAndCloseErrorDevice()93 public void testOpenAndCloseErrorDevice() throws Exception { 94 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 95 try { 96 mProvider.openDevice(1); 97 fail(); 98 } catch (Throwable error) { 99 assertTrue(error instanceof IOException); 100 } 101 assertEquals(0, mProvider.getOpenedDeviceRecordsCache().length); 102 103 // Check if the following notification is the first one or not. 104 mMtpManager.addValidDevice(new MtpDeviceRecord( 105 0, 106 "Device A", 107 null /* deviceKey */, 108 false /* unopened */, 109 new MtpRoot[] { 110 new MtpRoot( 111 0 /* deviceId */, 112 1 /* storageId */, 113 "Storage A" /* volume description */, 114 1024 /* free space */, 115 2048 /* total space */, 116 "" /* no volume identifier */) 117 }, 118 OPERATIONS_SUPPORTED, 119 null)); 120 mProvider.resumeRootScanner(); 121 mResolver.waitForNotification(ROOTS_URI, 1); 122 mProvider.openDevice(0); 123 mResolver.waitForNotification(ROOTS_URI, 2); 124 } 125 testOpenDeviceOnDemand()126 public void testOpenDeviceOnDemand() throws Exception { 127 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 128 mMtpManager.addValidDevice(new MtpDeviceRecord( 129 0, 130 "Device A", 131 null /* deviceKey */, 132 false /* unopened */, 133 new MtpRoot[] { 134 new MtpRoot( 135 0 /* deviceId */, 136 1 /* storageId */, 137 "Storage A" /* volume description */, 138 1024 /* free space */, 139 2048 /* total space */, 140 "" /* no volume identifier */) 141 }, 142 OPERATIONS_SUPPORTED, 143 null)); 144 mMtpManager.setObjectHandles(0, 1, -1, new int[0]); 145 mProvider.resumeRootScanner(); 146 mResolver.waitForNotification(ROOTS_URI, 1); 147 final String[] columns = new String[] { 148 DocumentsContract.Root.COLUMN_TITLE, 149 DocumentsContract.Root.COLUMN_DOCUMENT_ID 150 }; 151 try (final Cursor cursor = mProvider.queryRoots(columns)) { 152 assertEquals(1, cursor.getCount()); 153 assertTrue(cursor.moveToNext()); 154 assertEquals("Device A", cursor.getString(0)); 155 assertEquals(1, cursor.getLong(1)); 156 } 157 { 158 final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache(); 159 assertEquals(0, openedDevice.length); 160 } 161 // Device is opened automatically when querying its children. 162 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {} 163 164 { 165 final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache(); 166 assertEquals(1, openedDevice.length); 167 assertEquals(0, openedDevice[0].deviceId); 168 } 169 } 170 testQueryRoots()171 public void testQueryRoots() throws Exception { 172 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 173 mMtpManager.addValidDevice(new MtpDeviceRecord( 174 0, 175 "Device A", 176 "Device key A", 177 false /* unopened */, 178 new MtpRoot[] { 179 new MtpRoot( 180 0 /* deviceId */, 181 1 /* storageId */, 182 "Storage A" /* volume description */, 183 1024 /* free space */, 184 2048 /* total space */, 185 "" /* no volume identifier */) 186 }, 187 OPERATIONS_SUPPORTED, 188 null)); 189 mMtpManager.addValidDevice(new MtpDeviceRecord( 190 1, 191 "Device B", 192 "Device key B", 193 false /* unopened */, 194 new MtpRoot[] { 195 new MtpRoot( 196 1 /* deviceId */, 197 1 /* storageId */, 198 "Storage B" /* volume description */, 199 2048 /* free space */, 200 4096 /* total space */, 201 "Identifier B" /* no volume identifier */) 202 }, 203 new int[0] /* No operations supported */, 204 null)); 205 206 { 207 mProvider.openDevice(0); 208 mResolver.waitForNotification(ROOTS_URI, 1); 209 final Cursor cursor = mProvider.queryRoots(null); 210 assertEquals(2, cursor.getCount()); 211 cursor.moveToNext(); 212 assertEquals("1", cursor.getString(0)); 213 assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); 214 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 215 assertEquals("Device A Storage A", cursor.getString(3)); 216 assertEquals("1", cursor.getString(4)); 217 assertEquals(1024, cursor.getInt(5)); 218 } 219 220 { 221 mProvider.openDevice(1); 222 mResolver.waitForNotification(ROOTS_URI, 2); 223 final Cursor cursor = mProvider.queryRoots(null); 224 assertEquals(2, cursor.getCount()); 225 cursor.moveToNext(); 226 cursor.moveToNext(); 227 assertEquals("2", cursor.getString(0)); 228 assertEquals(Root.FLAG_SUPPORTS_IS_CHILD, cursor.getInt(1)); 229 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 230 assertEquals("Device B Storage B", cursor.getString(3)); 231 assertEquals("2", cursor.getString(4)); 232 assertEquals(2048, cursor.getInt(5)); 233 } 234 } 235 testQueryRoots_error()236 public void testQueryRoots_error() throws Exception { 237 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 238 mMtpManager.addValidDevice(new MtpDeviceRecord( 239 0, 240 "Device A", 241 "Device key A", 242 false /* unopened */, 243 new MtpRoot[0], 244 OPERATIONS_SUPPORTED, 245 null)); 246 mMtpManager.addValidDevice(new MtpDeviceRecord( 247 1, 248 "Device B", 249 "Device key B", 250 false /* unopened */, 251 new MtpRoot[] { 252 new MtpRoot( 253 1 /* deviceId */, 254 1 /* storageId */, 255 "Storage B" /* volume description */, 256 2048 /* free space */, 257 4096 /* total space */, 258 "Identifier B" /* no volume identifier */) 259 }, 260 OPERATIONS_SUPPORTED, 261 null)); 262 { 263 mProvider.openDevice(0); 264 mResolver.waitForNotification(ROOTS_URI, 1); 265 266 mProvider.openDevice(1); 267 mResolver.waitForNotification(ROOTS_URI, 2); 268 269 final Cursor cursor = mProvider.queryRoots(null); 270 assertEquals(2, cursor.getCount()); 271 272 cursor.moveToNext(); 273 assertEquals("1", cursor.getString(0)); 274 assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); 275 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 276 assertEquals("Device A", cursor.getString(3)); 277 assertEquals("1", cursor.getString(4)); 278 assertEquals(0, cursor.getInt(5)); 279 280 cursor.moveToNext(); 281 assertEquals("2", cursor.getString(0)); 282 assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); 283 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 284 assertEquals("Device B Storage B", cursor.getString(3)); 285 assertEquals("2", cursor.getString(4)); 286 assertEquals(2048, cursor.getInt(5)); 287 } 288 } 289 testQueryDocument()290 public void testQueryDocument() throws IOException, InterruptedException, TimeoutException { 291 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 292 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 293 setupDocuments( 294 0, 295 0, 296 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 297 "1", 298 new MtpObjectInfo[] { 299 new MtpObjectInfo.Builder() 300 .setObjectHandle(100) 301 .setFormat(MtpConstants.FORMAT_EXIF_JPEG) 302 .setName("image.jpg") 303 .setDateModified(1422716400000L) 304 .setCompressedSize(1024 * 1024 * 5) 305 .setThumbCompressedSize(50 * 1024) 306 .build() 307 }); 308 309 final Cursor cursor = mProvider.queryDocument("3", null); 310 assertEquals(1, cursor.getCount()); 311 312 cursor.moveToNext(); 313 314 assertEquals("3", cursor.getString(0)); 315 assertEquals("image/jpeg", cursor.getString(1)); 316 assertEquals("image.jpg", cursor.getString(2)); 317 assertEquals(1422716400000L, cursor.getLong(3)); 318 assertEquals( 319 DocumentsContract.Document.FLAG_SUPPORTS_DELETE | 320 DocumentsContract.Document.FLAG_SUPPORTS_WRITE | 321 DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, 322 cursor.getInt(4)); 323 assertEquals(1024 * 1024 * 5, cursor.getInt(5)); 324 } 325 testQueryDocument_directory()326 public void testQueryDocument_directory() 327 throws IOException, InterruptedException, TimeoutException { 328 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 329 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 330 setupDocuments( 331 0, 332 0, 333 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 334 "1", 335 new MtpObjectInfo[] { 336 new MtpObjectInfo.Builder() 337 .setObjectHandle(2) 338 .setStorageId(1) 339 .setFormat(MtpConstants.FORMAT_ASSOCIATION) 340 .setName("directory") 341 .setDateModified(1422716400000L) 342 .build() 343 }); 344 345 final Cursor cursor = mProvider.queryDocument("3", null); 346 assertEquals(1, cursor.getCount()); 347 348 cursor.moveToNext(); 349 assertEquals("3", cursor.getString(0)); 350 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); 351 assertEquals("directory", cursor.getString(2)); 352 assertEquals(1422716400000L, cursor.getLong(3)); 353 assertEquals( 354 DocumentsContract.Document.FLAG_SUPPORTS_DELETE | 355 DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, 356 cursor.getInt(4)); 357 assertEquals(0, cursor.getInt(5)); 358 } 359 testQueryDocument_forRoot()360 public void testQueryDocument_forRoot() 361 throws IOException, InterruptedException, TimeoutException { 362 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 363 setupRoots(0, new MtpRoot[] { 364 new MtpRoot( 365 0 /* deviceId */, 366 1 /* storageId */, 367 "Storage A" /* volume description */, 368 1024 /* free space */, 369 4096 /* total space */, 370 "" /* no volume identifier */) 371 }); 372 final Cursor cursor = mProvider.queryDocument("2", null); 373 assertEquals(1, cursor.getCount()); 374 375 cursor.moveToNext(); 376 assertEquals("2", cursor.getString(0)); 377 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); 378 assertEquals("Storage A", cursor.getString(2)); 379 assertTrue(cursor.isNull(3)); 380 assertEquals(0, cursor.getInt(4)); 381 assertEquals(3072, cursor.getInt(5)); 382 } 383 testQueryChildDocuments()384 public void testQueryChildDocuments() throws Exception { 385 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 386 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 387 setupDocuments( 388 0, 389 0, 390 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 391 "1", 392 new MtpObjectInfo[] { 393 new MtpObjectInfo.Builder() 394 .setObjectHandle(100) 395 .setFormat(MtpConstants.FORMAT_EXIF_JPEG) 396 .setName("image.jpg") 397 .setCompressedSize(1024 * 1024 * 5) 398 .setThumbCompressedSize(5 * 1024) 399 .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY) 400 .build() 401 }); 402 403 final Cursor cursor = mProvider.queryChildDocuments("1", null, null); 404 assertEquals(1, cursor.getCount()); 405 406 assertTrue(cursor.moveToNext()); 407 assertEquals("3", cursor.getString(0)); 408 assertEquals("image/jpeg", cursor.getString(1)); 409 assertEquals("image.jpg", cursor.getString(2)); 410 assertEquals(0, cursor.getLong(3)); 411 assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4)); 412 assertEquals(1024 * 1024 * 5, cursor.getInt(5)); 413 414 cursor.close(); 415 } 416 testQueryChildDocuments_cursorError()417 public void testQueryChildDocuments_cursorError() throws Exception { 418 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 419 try { 420 mProvider.queryChildDocuments("1", null, null); 421 fail(); 422 } catch (FileNotFoundException error) {} 423 } 424 testQueryChildDocuments_documentError()425 public void testQueryChildDocuments_documentError() throws Exception { 426 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 427 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 428 mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); 429 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { 430 assertEquals(0, cursor.getCount()); 431 assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); 432 } 433 } 434 testDeleteDocument()435 public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException { 436 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 437 setupRoots(0, new MtpRoot[] { 438 new MtpRoot(0, 0, "Storage", 0, 0, "") 439 }); 440 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 441 new MtpObjectInfo.Builder() 442 .setName("test.txt") 443 .setObjectHandle(1) 444 .setParent(-1) 445 .build() 446 }); 447 448 mProvider.deleteDocument("3"); 449 assertEquals(1, mResolver.getChangeCount( 450 DocumentsContract.buildChildDocumentsUri( 451 MtpDocumentsProvider.AUTHORITY, "1"))); 452 } 453 testDeleteDocument_error()454 public void testDeleteDocument_error() 455 throws IOException, InterruptedException, TimeoutException { 456 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 457 setupRoots(0, new MtpRoot[] { 458 new MtpRoot(0, 0, "Storage", 0, 0, "") 459 }); 460 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 461 new MtpObjectInfo.Builder() 462 .setName("test.txt") 463 .setObjectHandle(1) 464 .setParent(-1) 465 .build() 466 }); 467 try { 468 mProvider.deleteDocument("4"); 469 fail(); 470 } catch (Throwable e) { 471 assertTrue(e instanceof IOException); 472 } 473 assertEquals(0, mResolver.getChangeCount( 474 DocumentsContract.buildChildDocumentsUri( 475 MtpDocumentsProvider.AUTHORITY, "1"))); 476 } 477 testOpenDocument()478 public void testOpenDocument() throws Exception { 479 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 480 setupRoots(0, new MtpRoot[] { 481 new MtpRoot(0, 0, "Storage", 0, 0, "") 482 }); 483 final byte[] bytes = "Hello world".getBytes(); 484 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 485 new MtpObjectInfo.Builder() 486 .setName("test.txt") 487 .setObjectHandle(1) 488 .setCompressedSize(bytes.length) 489 .setParent(-1) 490 .build() 491 }); 492 mMtpManager.setImportFileBytes(0, 1, bytes); 493 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) { 494 final byte[] readBytes = new byte[5]; 495 assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET)); 496 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5)); 497 assertTrue(Arrays.equals("world".getBytes(), readBytes)); 498 499 assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET)); 500 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5)); 501 assertTrue(Arrays.equals("Hello".getBytes(), readBytes)); 502 } 503 } 504 testOpenDocument_shortBytes()505 public void testOpenDocument_shortBytes() throws Exception { 506 mMtpManager = new TestMtpManager(getContext()) { 507 @Override 508 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { 509 if (objectHandle == 1) { 510 return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle)) 511 .setObjectHandle(1).setCompressedSize(1024 * 1024).build(); 512 } 513 514 return super.getObjectInfo(deviceId, objectHandle); 515 } 516 }; 517 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 518 setupRoots(0, new MtpRoot[] { 519 new MtpRoot(0, 0, "Storage", 0, 0, "") 520 }); 521 final byte[] bytes = "Hello world".getBytes(); 522 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 523 new MtpObjectInfo.Builder() 524 .setName("test.txt") 525 .setObjectHandle(1) 526 .setCompressedSize(bytes.length) 527 .setParent(-1) 528 .build() 529 }); 530 mMtpManager.setImportFileBytes(0, 1, bytes); 531 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) { 532 final byte[] readBytes = new byte[1024 * 1024]; 533 assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length)); 534 } 535 } 536 testOpenDocument_writing()537 public void testOpenDocument_writing() throws Exception { 538 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 539 setupRoots(0, new MtpRoot[] { 540 new MtpRoot(0, 0, "Storage", 0, 0, "") 541 }); 542 final String documentId = mProvider.createDocument("2", "text/plain", "test.txt"); 543 { 544 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null); 545 try (ParcelFileDescriptor.AutoCloseOutputStream stream = 546 new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { 547 stream.write("Hello".getBytes()); 548 } 549 } 550 { 551 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null); 552 try (ParcelFileDescriptor.AutoCloseInputStream stream = 553 new ParcelFileDescriptor.AutoCloseInputStream(fd)) { 554 final byte[] bytes = new byte[5]; 555 stream.read(bytes); 556 assertTrue(Arrays.equals("Hello".getBytes(), bytes)); 557 } 558 } 559 } 560 testBusyDevice()561 public void testBusyDevice() throws Exception { 562 mMtpManager = new TestMtpManager(getContext()) { 563 @Override 564 MtpDeviceRecord openDevice(int deviceId) throws IOException { 565 throw new BusyDeviceException(); 566 } 567 }; 568 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 569 mMtpManager.addValidDevice(new MtpDeviceRecord( 570 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0], 571 OPERATIONS_SUPPORTED, null)); 572 573 mProvider.resumeRootScanner(); 574 mResolver.waitForNotification(ROOTS_URI, 1); 575 576 try (final Cursor cursor = mProvider.queryRoots(null)) { 577 assertEquals(1, cursor.getCount()); 578 } 579 580 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { 581 assertEquals(0, cursor.getCount()); 582 assertEquals( 583 "error_busy_device", 584 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); 585 } 586 } 587 testLockedDevice()588 public void testLockedDevice() throws Exception { 589 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 590 mMtpManager.addValidDevice(new MtpDeviceRecord( 591 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED, 592 null)); 593 594 mProvider.resumeRootScanner(); 595 mResolver.waitForNotification(ROOTS_URI, 1); 596 597 try (final Cursor cursor = mProvider.queryRoots(null)) { 598 assertEquals(1, cursor.getCount()); 599 } 600 601 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { 602 assertEquals(0, cursor.getCount()); 603 assertEquals( 604 "error_locked_device", 605 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); 606 } 607 } 608 testMappingDisconnectedDocuments()609 public void testMappingDisconnectedDocuments() throws Exception { 610 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 611 mMtpManager.addValidDevice(new MtpDeviceRecord( 612 0, 613 "Device A", 614 "device key", 615 true /* opened */, 616 new MtpRoot[] { 617 new MtpRoot( 618 0 /* deviceId */, 619 1 /* storageId */, 620 "Storage A" /* volume description */, 621 1024 /* free space */, 622 2048 /* total space */, 623 "" /* no volume identifier */) 624 }, 625 OPERATIONS_SUPPORTED, 626 null)); 627 628 final String[] names = strings("Directory A", "Directory B", "Directory C"); 629 final int objectHandleOffset = 100; 630 for (int i = 0; i < names.length; i++) { 631 final int parentHandle = i == 0 ? 632 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1; 633 final int objectHandle = i + objectHandleOffset; 634 mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle }); 635 mMtpManager.setObjectInfo( 636 0, 637 new MtpObjectInfo.Builder() 638 .setName(names[i]) 639 .setObjectHandle(objectHandle) 640 .setFormat(MtpConstants.FORMAT_ASSOCIATION) 641 .setStorageId(1) 642 .build()); 643 } 644 645 mProvider.resumeRootScanner(); 646 mResolver.waitForNotification(ROOTS_URI, 1); 647 648 final int documentIdOffset = 2; 649 for (int i = 0; i < names.length; i++) { 650 try (final Cursor cursor = mProvider.queryChildDocuments( 651 String.valueOf(documentIdOffset + i), 652 strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME), 653 null)) { 654 assertEquals(1, cursor.getCount()); 655 cursor.moveToNext(); 656 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0)); 657 assertEquals(names[i], cursor.getString(1)); 658 } 659 } 660 661 mProvider.closeDevice(0); 662 mResolver.waitForNotification(ROOTS_URI, 2); 663 664 mProvider.openDevice(0); 665 mResolver.waitForNotification(ROOTS_URI, 3); 666 667 for (int i = 0; i < names.length; i++) { 668 mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri( 669 MtpDocumentsProvider.AUTHORITY, 670 String.valueOf(documentIdOffset + i)), 1); 671 try (final Cursor cursor = mProvider.queryChildDocuments( 672 String.valueOf(documentIdOffset + i), 673 strings(Document.COLUMN_DOCUMENT_ID), 674 null)) { 675 assertEquals(1, cursor.getCount()); 676 cursor.moveToNext(); 677 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0)); 678 } 679 } 680 } 681 testCreateDocument_noWritingSupport()682 public void testCreateDocument_noWritingSupport() throws Exception { 683 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 684 mMtpManager.addValidDevice(new MtpDeviceRecord( 685 0, "Device A", null /* deviceKey */, false /* unopened */, 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 new int[0] /* no operations supported */, null)); 696 mProvider.resumeRootScanner(); 697 mResolver.waitForNotification(ROOTS_URI, 1); 698 try { 699 mProvider.createDocument("1", "text/palin", "note.txt"); 700 fail(); 701 } catch (UnsupportedOperationException exception) {} 702 } 703 testOpenDocument_noWritingSupport()704 public void testOpenDocument_noWritingSupport() throws Exception { 705 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 706 mMtpManager.addValidDevice(new MtpDeviceRecord( 707 0, "Device A", null /* deviceKey */, false /* unopened */, 708 new MtpRoot[] { 709 new MtpRoot( 710 0 /* deviceId */, 711 1 /* storageId */, 712 "Storage A" /* volume description */, 713 1024 /* free space */, 714 2048 /* total space */, 715 "" /* no volume identifier */) 716 }, 717 new int[0] /* no operations supported */, null)); 718 mMtpManager.setObjectHandles( 719 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 }); 720 mMtpManager.setObjectInfo( 721 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build()); 722 mProvider.resumeRootScanner(); 723 mResolver.waitForNotification(ROOTS_URI, 1); 724 try (final Cursor cursor = mProvider.queryChildDocuments( 725 "1", strings(Document.COLUMN_DOCUMENT_ID), null)) { 726 assertEquals(1, cursor.getCount()); 727 cursor.moveToNext(); 728 assertEquals("3", cursor.getString(0)); 729 } 730 try { 731 mProvider.openDocument("3", "w", null); 732 fail(); 733 } catch (UnsupportedOperationException exception) {} 734 } 735 testObjectSizeLong()736 public void testObjectSizeLong() throws Exception { 737 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 738 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 739 mMtpManager.setObjectSizeLong(0, 100, MtpConstants.FORMAT_EXIF_JPEG, 0x400000000L); 740 setupDocuments( 741 0, 742 0, 743 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 744 "1", 745 new MtpObjectInfo[] { 746 new MtpObjectInfo.Builder() 747 .setObjectHandle(100) 748 .setFormat(MtpConstants.FORMAT_EXIF_JPEG) 749 .setName("image.jpg") 750 .setCompressedSize(0xffffffffl) 751 .build() 752 }); 753 754 final Cursor cursor = mProvider.queryDocument("3", new String[] { 755 DocumentsContract.Document.COLUMN_SIZE 756 }); 757 assertEquals(1, cursor.getCount()); 758 759 cursor.moveToNext(); 760 assertEquals(0x400000000L, cursor.getLong(0)); 761 } 762 setupProvider(int flag)763 private void setupProvider(int flag) { 764 mDatabase = new MtpDatabase(getContext(), flag); 765 mProvider = new MtpDocumentsProvider(); 766 final StorageManager storageManager = getContext().getSystemService(StorageManager.class); 767 assertTrue(mProvider.onCreateForTesting( 768 getContext(), 769 mResources, 770 mMtpManager, 771 mResolver, 772 mDatabase, 773 storageManager, 774 new TestServiceIntentSender())); 775 } 776 getStrings(Cursor cursor)777 private String[] getStrings(Cursor cursor) { 778 try { 779 final String[] results = new String[cursor.getCount()]; 780 for (int i = 0; cursor.moveToNext(); i++) { 781 results[i] = cursor.getString(0); 782 } 783 return results; 784 } finally { 785 cursor.close(); 786 } 787 } 788 setupRoots(int deviceId, MtpRoot[] roots)789 private String[] setupRoots(int deviceId, MtpRoot[] roots) 790 throws InterruptedException, TimeoutException, IOException { 791 final int changeCount = mResolver.getChangeCount(ROOTS_URI); 792 mMtpManager.addValidDevice( 793 new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */, 794 roots, OPERATIONS_SUPPORTED, null)); 795 mProvider.openDevice(deviceId); 796 mResolver.waitForNotification(ROOTS_URI, changeCount + 1); 797 return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID))); 798 } 799 setupDocuments( int deviceId, int storageId, int parentHandle, String parentDocumentId, MtpObjectInfo[] objects)800 private String[] setupDocuments( 801 int deviceId, 802 int storageId, 803 int parentHandle, 804 String parentDocumentId, 805 MtpObjectInfo[] objects) throws FileNotFoundException { 806 final int[] handles = new int[objects.length]; 807 int i = 0; 808 for (final MtpObjectInfo info : objects) { 809 handles[i] = info.getObjectHandle(); 810 mMtpManager.setObjectInfo(deviceId, info); 811 } 812 mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles); 813 return getStrings(mProvider.queryChildDocuments( 814 parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null)); 815 } 816 } 817