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 17package com.android.mtp; 18 19import android.database.Cursor; 20import android.mtp.MtpConstants; 21import android.mtp.MtpObjectInfo; 22import android.net.Uri; 23import android.os.ParcelFileDescriptor; 24import android.os.ParcelFileDescriptor.AutoCloseOutputStream; 25import android.os.storage.StorageManager; 26import android.provider.DocumentsContract.Document; 27import android.provider.DocumentsContract.Root; 28import android.system.Os; 29import android.system.OsConstants; 30import android.provider.DocumentsContract; 31import android.test.AndroidTestCase; 32import android.test.suitebuilder.annotation.MediumTest; 33 34import java.io.FileNotFoundException; 35import java.io.IOException; 36import java.util.Arrays; 37import java.util.concurrent.TimeoutException; 38 39import static com.android.mtp.MtpDatabase.strings; 40import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED; 41 42@MediumTest 43public 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 53 public void setUp() throws IOException { 54 mResolver = new TestContentResolver(); 55 mMtpManager = new TestMtpManager(getContext()); 56 } 57 58 @Override 59 public void tearDown() { 60 mProvider.shutdown(); 61 MtpDatabase.deleteDatabase(getContext()); 62 } 63 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 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 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 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( 214 Root.FLAG_SUPPORTS_IS_CHILD | 215 Root.FLAG_SUPPORTS_CREATE | 216 Root.FLAG_LOCAL_ONLY, 217 cursor.getInt(1)); 218 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 219 assertEquals("Device A Storage A", cursor.getString(3)); 220 assertEquals("1", cursor.getString(4)); 221 assertEquals(1024, cursor.getInt(5)); 222 } 223 224 { 225 mProvider.openDevice(1); 226 mResolver.waitForNotification(ROOTS_URI, 2); 227 final Cursor cursor = mProvider.queryRoots(null); 228 assertEquals(2, cursor.getCount()); 229 cursor.moveToNext(); 230 cursor.moveToNext(); 231 assertEquals("2", cursor.getString(0)); 232 assertEquals( 233 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_LOCAL_ONLY, cursor.getInt(1)); 234 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 235 assertEquals("Device B Storage B", cursor.getString(3)); 236 assertEquals("2", cursor.getString(4)); 237 assertEquals(2048, cursor.getInt(5)); 238 } 239 } 240 241 public void testQueryRoots_error() throws Exception { 242 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 243 mMtpManager.addValidDevice(new MtpDeviceRecord( 244 0, 245 "Device A", 246 "Device key A", 247 false /* unopened */, 248 new MtpRoot[0], 249 OPERATIONS_SUPPORTED, 250 null)); 251 mMtpManager.addValidDevice(new MtpDeviceRecord( 252 1, 253 "Device B", 254 "Device key B", 255 false /* unopened */, 256 new MtpRoot[] { 257 new MtpRoot( 258 1 /* deviceId */, 259 1 /* storageId */, 260 "Storage B" /* volume description */, 261 2048 /* free space */, 262 4096 /* total space */, 263 "Identifier B" /* no volume identifier */) 264 }, 265 OPERATIONS_SUPPORTED, 266 null)); 267 { 268 mProvider.openDevice(0); 269 mResolver.waitForNotification(ROOTS_URI, 1); 270 271 mProvider.openDevice(1); 272 mResolver.waitForNotification(ROOTS_URI, 2); 273 274 final Cursor cursor = mProvider.queryRoots(null); 275 assertEquals(2, cursor.getCount()); 276 277 cursor.moveToNext(); 278 assertEquals("1", cursor.getString(0)); 279 assertEquals( 280 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, 281 cursor.getInt(1)); 282 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 283 assertEquals("Device A", cursor.getString(3)); 284 assertEquals("1", cursor.getString(4)); 285 assertEquals(0, cursor.getInt(5)); 286 287 cursor.moveToNext(); 288 assertEquals("2", cursor.getString(0)); 289 assertEquals( 290 Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, 291 cursor.getInt(1)); 292 assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); 293 assertEquals("Device B Storage B", cursor.getString(3)); 294 assertEquals("2", cursor.getString(4)); 295 assertEquals(2048, cursor.getInt(5)); 296 } 297 } 298 299 public void testQueryDocument() throws IOException, InterruptedException, TimeoutException { 300 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 301 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 302 setupDocuments( 303 0, 304 0, 305 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 306 "1", 307 new MtpObjectInfo[] { 308 new MtpObjectInfo.Builder() 309 .setObjectHandle(100) 310 .setFormat(MtpConstants.FORMAT_EXIF_JPEG) 311 .setName("image.jpg") 312 .setDateModified(1422716400000L) 313 .setCompressedSize(1024 * 1024 * 5) 314 .setThumbCompressedSize(50 * 1024) 315 .build() 316 }); 317 318 final Cursor cursor = mProvider.queryDocument("3", null); 319 assertEquals(1, cursor.getCount()); 320 321 cursor.moveToNext(); 322 323 assertEquals("3", cursor.getString(0)); 324 assertEquals("image/jpeg", cursor.getString(1)); 325 assertEquals("image.jpg", cursor.getString(2)); 326 assertEquals(1422716400000L, cursor.getLong(3)); 327 assertEquals( 328 DocumentsContract.Document.FLAG_SUPPORTS_DELETE | 329 DocumentsContract.Document.FLAG_SUPPORTS_WRITE | 330 DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, 331 cursor.getInt(4)); 332 assertEquals(1024 * 1024 * 5, cursor.getInt(5)); 333 } 334 335 public void testQueryDocument_directory() 336 throws IOException, InterruptedException, TimeoutException { 337 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 338 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 339 setupDocuments( 340 0, 341 0, 342 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 343 "1", 344 new MtpObjectInfo[] { 345 new MtpObjectInfo.Builder() 346 .setObjectHandle(2) 347 .setStorageId(1) 348 .setFormat(MtpConstants.FORMAT_ASSOCIATION) 349 .setName("directory") 350 .setDateModified(1422716400000L) 351 .build() 352 }); 353 354 final Cursor cursor = mProvider.queryDocument("3", null); 355 assertEquals(1, cursor.getCount()); 356 357 cursor.moveToNext(); 358 assertEquals("3", cursor.getString(0)); 359 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); 360 assertEquals("directory", cursor.getString(2)); 361 assertEquals(1422716400000L, cursor.getLong(3)); 362 assertEquals( 363 DocumentsContract.Document.FLAG_SUPPORTS_DELETE | 364 DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, 365 cursor.getInt(4)); 366 assertEquals(0, cursor.getInt(5)); 367 } 368 369 public void testQueryDocument_forRoot() 370 throws IOException, InterruptedException, TimeoutException { 371 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 372 setupRoots(0, new MtpRoot[] { 373 new MtpRoot( 374 0 /* deviceId */, 375 1 /* storageId */, 376 "Storage A" /* volume description */, 377 1024 /* free space */, 378 4096 /* total space */, 379 "" /* no volume identifier */) 380 }); 381 final Cursor cursor = mProvider.queryDocument("2", null); 382 assertEquals(1, cursor.getCount()); 383 384 cursor.moveToNext(); 385 assertEquals("2", cursor.getString(0)); 386 assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); 387 assertEquals("Storage A", cursor.getString(2)); 388 assertTrue(cursor.isNull(3)); 389 assertEquals(0, cursor.getInt(4)); 390 assertEquals(3072, cursor.getInt(5)); 391 } 392 393 public void testQueryChildDocuments() throws Exception { 394 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 395 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 396 setupDocuments( 397 0, 398 0, 399 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 400 "1", 401 new MtpObjectInfo[] { 402 new MtpObjectInfo.Builder() 403 .setObjectHandle(100) 404 .setFormat(MtpConstants.FORMAT_EXIF_JPEG) 405 .setName("image.jpg") 406 .setCompressedSize(1024 * 1024 * 5) 407 .setThumbCompressedSize(5 * 1024) 408 .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY) 409 .build() 410 }); 411 412 final Cursor cursor = mProvider.queryChildDocuments("1", null, null); 413 assertEquals(1, cursor.getCount()); 414 415 assertTrue(cursor.moveToNext()); 416 assertEquals("3", cursor.getString(0)); 417 assertEquals("image/jpeg", cursor.getString(1)); 418 assertEquals("image.jpg", cursor.getString(2)); 419 assertEquals(0, cursor.getLong(3)); 420 assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4)); 421 assertEquals(1024 * 1024 * 5, cursor.getInt(5)); 422 423 cursor.close(); 424 } 425 426 public void testQueryChildDocuments_cursorError() throws Exception { 427 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 428 try { 429 mProvider.queryChildDocuments("1", null, null); 430 fail(); 431 } catch (FileNotFoundException error) {} 432 } 433 434 public void testQueryChildDocuments_documentError() throws Exception { 435 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 436 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 437 mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); 438 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { 439 assertEquals(0, cursor.getCount()); 440 assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); 441 } 442 } 443 444 public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException { 445 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 446 setupRoots(0, new MtpRoot[] { 447 new MtpRoot(0, 0, "Storage", 0, 0, "") 448 }); 449 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 450 new MtpObjectInfo.Builder() 451 .setName("test.txt") 452 .setObjectHandle(1) 453 .setParent(-1) 454 .build() 455 }); 456 457 mProvider.deleteDocument("3"); 458 assertEquals(1, mResolver.getChangeCount( 459 DocumentsContract.buildChildDocumentsUri( 460 MtpDocumentsProvider.AUTHORITY, "1"))); 461 } 462 463 public void testDeleteDocument_error() 464 throws IOException, InterruptedException, TimeoutException { 465 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 466 setupRoots(0, new MtpRoot[] { 467 new MtpRoot(0, 0, "Storage", 0, 0, "") 468 }); 469 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 470 new MtpObjectInfo.Builder() 471 .setName("test.txt") 472 .setObjectHandle(1) 473 .setParent(-1) 474 .build() 475 }); 476 try { 477 mProvider.deleteDocument("4"); 478 fail(); 479 } catch (Throwable e) { 480 assertTrue(e instanceof IOException); 481 } 482 assertEquals(0, mResolver.getChangeCount( 483 DocumentsContract.buildChildDocumentsUri( 484 MtpDocumentsProvider.AUTHORITY, "1"))); 485 } 486 487 public void testOpenDocument() throws Exception { 488 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 489 setupRoots(0, new MtpRoot[] { 490 new MtpRoot(0, 0, "Storage", 0, 0, "") 491 }); 492 final byte[] bytes = "Hello world".getBytes(); 493 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 494 new MtpObjectInfo.Builder() 495 .setName("test.txt") 496 .setObjectHandle(1) 497 .setCompressedSize(bytes.length) 498 .setParent(-1) 499 .build() 500 }); 501 mMtpManager.setImportFileBytes(0, 1, bytes); 502 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) { 503 final byte[] readBytes = new byte[5]; 504 assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET)); 505 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5)); 506 assertTrue(Arrays.equals("world".getBytes(), readBytes)); 507 508 assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET)); 509 assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5)); 510 assertTrue(Arrays.equals("Hello".getBytes(), readBytes)); 511 } 512 } 513 514 public void testOpenDocument_shortBytes() throws Exception { 515 mMtpManager = new TestMtpManager(getContext()) { 516 @Override 517 MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { 518 if (objectHandle == 1) { 519 return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle)) 520 .setObjectHandle(1).setCompressedSize(1024 * 1024).build(); 521 } 522 523 return super.getObjectInfo(deviceId, objectHandle); 524 } 525 }; 526 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 527 setupRoots(0, new MtpRoot[] { 528 new MtpRoot(0, 0, "Storage", 0, 0, "") 529 }); 530 final byte[] bytes = "Hello world".getBytes(); 531 setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { 532 new MtpObjectInfo.Builder() 533 .setName("test.txt") 534 .setObjectHandle(1) 535 .setCompressedSize(bytes.length) 536 .setParent(-1) 537 .build() 538 }); 539 mMtpManager.setImportFileBytes(0, 1, bytes); 540 try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) { 541 final byte[] readBytes = new byte[1024 * 1024]; 542 assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length)); 543 } 544 } 545 546 public void testOpenDocument_writing() 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 String documentId = mProvider.createDocument("2", "text/plain", "test.txt"); 552 { 553 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null); 554 try (ParcelFileDescriptor.AutoCloseOutputStream stream = 555 new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { 556 stream.write("Hello".getBytes()); 557 } 558 } 559 { 560 final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null); 561 try (ParcelFileDescriptor.AutoCloseInputStream stream = 562 new ParcelFileDescriptor.AutoCloseInputStream(fd)) { 563 final byte[] bytes = new byte[5]; 564 stream.read(bytes); 565 assertTrue(Arrays.equals("Hello".getBytes(), bytes)); 566 } 567 } 568 } 569 570 public void testBusyDevice() throws Exception { 571 mMtpManager = new TestMtpManager(getContext()) { 572 @Override 573 MtpDeviceRecord openDevice(int deviceId) throws IOException { 574 throw new BusyDeviceException(); 575 } 576 }; 577 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 578 mMtpManager.addValidDevice(new MtpDeviceRecord( 579 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0], 580 OPERATIONS_SUPPORTED, null)); 581 582 mProvider.resumeRootScanner(); 583 mResolver.waitForNotification(ROOTS_URI, 1); 584 585 try (final Cursor cursor = mProvider.queryRoots(null)) { 586 assertEquals(1, cursor.getCount()); 587 } 588 589 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { 590 assertEquals(0, cursor.getCount()); 591 assertEquals( 592 "error_busy_device", 593 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); 594 } 595 } 596 597 public void testLockedDevice() throws Exception { 598 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 599 mMtpManager.addValidDevice(new MtpDeviceRecord( 600 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED, 601 null)); 602 603 mProvider.resumeRootScanner(); 604 mResolver.waitForNotification(ROOTS_URI, 1); 605 606 try (final Cursor cursor = mProvider.queryRoots(null)) { 607 assertEquals(1, cursor.getCount()); 608 } 609 610 try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { 611 assertEquals(0, cursor.getCount()); 612 assertEquals( 613 "error_locked_device", 614 cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); 615 } 616 } 617 618 public void testMappingDisconnectedDocuments() throws Exception { 619 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 620 mMtpManager.addValidDevice(new MtpDeviceRecord( 621 0, 622 "Device A", 623 "device key", 624 true /* opened */, 625 new MtpRoot[] { 626 new MtpRoot( 627 0 /* deviceId */, 628 1 /* storageId */, 629 "Storage A" /* volume description */, 630 1024 /* free space */, 631 2048 /* total space */, 632 "" /* no volume identifier */) 633 }, 634 OPERATIONS_SUPPORTED, 635 null)); 636 637 final String[] names = strings("Directory A", "Directory B", "Directory C"); 638 final int objectHandleOffset = 100; 639 for (int i = 0; i < names.length; i++) { 640 final int parentHandle = i == 0 ? 641 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1; 642 final int objectHandle = i + objectHandleOffset; 643 mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle }); 644 mMtpManager.setObjectInfo( 645 0, 646 new MtpObjectInfo.Builder() 647 .setName(names[i]) 648 .setObjectHandle(objectHandle) 649 .setFormat(MtpConstants.FORMAT_ASSOCIATION) 650 .setStorageId(1) 651 .build()); 652 } 653 654 mProvider.resumeRootScanner(); 655 mResolver.waitForNotification(ROOTS_URI, 1); 656 657 final int documentIdOffset = 2; 658 for (int i = 0; i < names.length; i++) { 659 try (final Cursor cursor = mProvider.queryChildDocuments( 660 String.valueOf(documentIdOffset + i), 661 strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME), 662 null)) { 663 assertEquals(1, cursor.getCount()); 664 cursor.moveToNext(); 665 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0)); 666 assertEquals(names[i], cursor.getString(1)); 667 } 668 } 669 670 mProvider.closeDevice(0); 671 mResolver.waitForNotification(ROOTS_URI, 2); 672 673 mProvider.openDevice(0); 674 mResolver.waitForNotification(ROOTS_URI, 3); 675 676 for (int i = 0; i < names.length; i++) { 677 mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri( 678 MtpDocumentsProvider.AUTHORITY, 679 String.valueOf(documentIdOffset + i)), 1); 680 try (final Cursor cursor = mProvider.queryChildDocuments( 681 String.valueOf(documentIdOffset + i), 682 strings(Document.COLUMN_DOCUMENT_ID), 683 null)) { 684 assertEquals(1, cursor.getCount()); 685 cursor.moveToNext(); 686 assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0)); 687 } 688 } 689 } 690 691 public void testCreateDocument_noWritingSupport() throws Exception { 692 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 693 mMtpManager.addValidDevice(new MtpDeviceRecord( 694 0, "Device A", null /* deviceKey */, false /* unopened */, 695 new MtpRoot[] { 696 new MtpRoot( 697 0 /* deviceId */, 698 1 /* storageId */, 699 "Storage A" /* volume description */, 700 1024 /* free space */, 701 2048 /* total space */, 702 "" /* no volume identifier */) 703 }, 704 new int[0] /* no operations supported */, null)); 705 mProvider.resumeRootScanner(); 706 mResolver.waitForNotification(ROOTS_URI, 1); 707 try { 708 mProvider.createDocument("1", "text/palin", "note.txt"); 709 fail(); 710 } catch (UnsupportedOperationException exception) {} 711 } 712 713 public void testOpenDocument_noWritingSupport() throws Exception { 714 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 715 mMtpManager.addValidDevice(new MtpDeviceRecord( 716 0, "Device A", null /* deviceKey */, false /* unopened */, 717 new MtpRoot[] { 718 new MtpRoot( 719 0 /* deviceId */, 720 1 /* storageId */, 721 "Storage A" /* volume description */, 722 1024 /* free space */, 723 2048 /* total space */, 724 "" /* no volume identifier */) 725 }, 726 new int[0] /* no operations supported */, null)); 727 mMtpManager.setObjectHandles( 728 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 }); 729 mMtpManager.setObjectInfo( 730 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build()); 731 mProvider.resumeRootScanner(); 732 mResolver.waitForNotification(ROOTS_URI, 1); 733 try (final Cursor cursor = mProvider.queryChildDocuments( 734 "1", strings(Document.COLUMN_DOCUMENT_ID), null)) { 735 assertEquals(1, cursor.getCount()); 736 cursor.moveToNext(); 737 assertEquals("3", cursor.getString(0)); 738 } 739 try { 740 mProvider.openDocument("3", "w", null); 741 fail(); 742 } catch (UnsupportedOperationException exception) {} 743 } 744 745 public void testObjectSizeLong() throws Exception { 746 setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); 747 setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); 748 mMtpManager.setObjectSizeLong(0, 100, MtpConstants.FORMAT_EXIF_JPEG, 0x400000000L); 749 setupDocuments( 750 0, 751 0, 752 MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, 753 "1", 754 new MtpObjectInfo[] { 755 new MtpObjectInfo.Builder() 756 .setObjectHandle(100) 757 .setFormat(MtpConstants.FORMAT_EXIF_JPEG) 758 .setName("image.jpg") 759 .setCompressedSize(0xffffffffl) 760 .build() 761 }); 762 763 final Cursor cursor = mProvider.queryDocument("3", new String[] { 764 DocumentsContract.Document.COLUMN_SIZE 765 }); 766 assertEquals(1, cursor.getCount()); 767 768 cursor.moveToNext(); 769 assertEquals(0x400000000L, cursor.getLong(0)); 770 } 771 772 private void setupProvider(int flag) { 773 mDatabase = new MtpDatabase(getContext(), flag); 774 mProvider = new MtpDocumentsProvider(); 775 final StorageManager storageManager = getContext().getSystemService(StorageManager.class); 776 assertTrue(mProvider.onCreateForTesting( 777 getContext(), 778 mResources, 779 mMtpManager, 780 mResolver, 781 mDatabase, 782 storageManager, 783 new TestServiceIntentSender())); 784 } 785 786 private String[] getStrings(Cursor cursor) { 787 try { 788 final String[] results = new String[cursor.getCount()]; 789 for (int i = 0; cursor.moveToNext(); i++) { 790 results[i] = cursor.getString(0); 791 } 792 return results; 793 } finally { 794 cursor.close(); 795 } 796 } 797 798 private String[] setupRoots(int deviceId, MtpRoot[] roots) 799 throws InterruptedException, TimeoutException, IOException { 800 final int changeCount = mResolver.getChangeCount(ROOTS_URI); 801 mMtpManager.addValidDevice( 802 new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */, 803 roots, OPERATIONS_SUPPORTED, null)); 804 mProvider.openDevice(deviceId); 805 mResolver.waitForNotification(ROOTS_URI, changeCount + 1); 806 return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID))); 807 } 808 809 private String[] setupDocuments( 810 int deviceId, 811 int storageId, 812 int parentHandle, 813 String parentDocumentId, 814 MtpObjectInfo[] objects) throws FileNotFoundException { 815 final int[] handles = new int[objects.length]; 816 int i = 0; 817 for (final MtpObjectInfo info : objects) { 818 handles[i] = info.getObjectHandle(); 819 mMtpManager.setObjectInfo(deviceId, info); 820 } 821 mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles); 822 return getStrings(mProvider.queryChildDocuments( 823 parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null)); 824 } 825} 826