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