| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.mtp; |
| |
| import android.database.Cursor; |
| import android.mtp.MtpConstants; |
| import android.mtp.MtpObjectInfo; |
| import android.net.Uri; |
| import android.os.ParcelFileDescriptor; |
| import android.os.storage.StorageManager; |
| import android.provider.DocumentsContract.Document; |
| import android.provider.DocumentsContract.Path; |
| import android.provider.DocumentsContract.Root; |
| import android.system.Os; |
| import android.system.OsConstants; |
| import android.provider.DocumentsContract; |
| import android.test.AndroidTestCase; |
| import android.test.suitebuilder.annotation.MediumTest; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.LinkedList; |
| import java.util.Queue; |
| import java.util.concurrent.TimeoutException; |
| |
| import static com.android.mtp.MtpDatabase.strings; |
| import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED; |
| |
| @MediumTest |
| public class MtpDocumentsProviderTest extends AndroidTestCase { |
| private final static Uri ROOTS_URI = |
| DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY); |
| private TestContentResolver mResolver; |
| private MtpDocumentsProvider mProvider; |
| private TestMtpManager mMtpManager; |
| private final TestResources mResources = new TestResources(); |
| private MtpDatabase mDatabase; |
| |
| @Override |
| public void setUp() throws IOException { |
| mResolver = new TestContentResolver(); |
| mMtpManager = new TestMtpManager(getContext()); |
| } |
| |
| @Override |
| public void tearDown() { |
| mProvider.shutdown(); |
| MtpDatabase.deleteDatabase(getContext()); |
| } |
| |
| public void testOpenAndCloseDevice() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, |
| "Device A", |
| null /* deviceKey */, |
| false /* unopened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 2048 /* total space */, |
| "" /* no volume identifier */) |
| }, |
| OPERATIONS_SUPPORTED, |
| null)); |
| |
| mProvider.resumeRootScanner(); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| |
| mProvider.openDevice(0); |
| mResolver.waitForNotification(ROOTS_URI, 2); |
| |
| mProvider.closeDevice(0); |
| mResolver.waitForNotification(ROOTS_URI, 3); |
| } |
| |
| public void testOpenAndCloseErrorDevice() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| try { |
| mProvider.openDevice(1); |
| fail(); |
| } catch (Throwable error) { |
| assertTrue(error instanceof IOException); |
| } |
| assertEquals(0, mProvider.getOpenedDeviceRecordsCache().length); |
| |
| // Check if the following notification is the first one or not. |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, |
| "Device A", |
| null /* deviceKey */, |
| false /* unopened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 2048 /* total space */, |
| "" /* no volume identifier */) |
| }, |
| OPERATIONS_SUPPORTED, |
| null)); |
| mProvider.resumeRootScanner(); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| mProvider.openDevice(0); |
| mResolver.waitForNotification(ROOTS_URI, 2); |
| } |
| |
| public void testOpenDeviceOnDemand() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, |
| "Device A", |
| null /* deviceKey */, |
| false /* unopened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 2048 /* total space */, |
| "" /* no volume identifier */) |
| }, |
| OPERATIONS_SUPPORTED, |
| null)); |
| mMtpManager.setObjectHandles(0, 1, -1, new int[0]); |
| mProvider.resumeRootScanner(); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| final String[] columns = new String[] { |
| DocumentsContract.Root.COLUMN_TITLE, |
| DocumentsContract.Root.COLUMN_DOCUMENT_ID |
| }; |
| try (final Cursor cursor = mProvider.queryRoots(columns)) { |
| assertEquals(1, cursor.getCount()); |
| assertTrue(cursor.moveToNext()); |
| assertEquals("Device A", cursor.getString(0)); |
| assertEquals(1, cursor.getLong(1)); |
| } |
| { |
| final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache(); |
| assertEquals(0, openedDevice.length); |
| } |
| // Device is opened automatically when querying its children. |
| try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {} |
| |
| { |
| final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache(); |
| assertEquals(1, openedDevice.length); |
| assertEquals(0, openedDevice[0].deviceId); |
| } |
| } |
| |
| public void testQueryRoots() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, |
| "Device A", |
| "Device key A", |
| false /* unopened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 2048 /* total space */, |
| "" /* no volume identifier */) |
| }, |
| OPERATIONS_SUPPORTED, |
| null)); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 1, |
| "Device B", |
| "Device key B", |
| false /* unopened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 1 /* deviceId */, |
| 1 /* storageId */, |
| "Storage B" /* volume description */, |
| 2048 /* free space */, |
| 4096 /* total space */, |
| "Identifier B" /* no volume identifier */) |
| }, |
| new int[0] /* No operations supported */, |
| null)); |
| |
| { |
| mProvider.openDevice(0); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| final Cursor cursor = mProvider.queryRoots(null); |
| assertEquals(2, cursor.getCount()); |
| cursor.moveToNext(); |
| assertEquals("1", cursor.getString(0)); |
| assertEquals( |
| Root.FLAG_SUPPORTS_IS_CHILD | |
| Root.FLAG_SUPPORTS_CREATE | |
| Root.FLAG_LOCAL_ONLY, |
| cursor.getInt(1)); |
| assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); |
| assertEquals("Device A Storage A", cursor.getString(3)); |
| assertEquals("1", cursor.getString(4)); |
| assertEquals(1024, cursor.getInt(5)); |
| } |
| |
| { |
| mProvider.openDevice(1); |
| mResolver.waitForNotification(ROOTS_URI, 2); |
| final Cursor cursor = mProvider.queryRoots(null); |
| assertEquals(2, cursor.getCount()); |
| cursor.moveToNext(); |
| cursor.moveToNext(); |
| assertEquals("2", cursor.getString(0)); |
| assertEquals( |
| Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_LOCAL_ONLY, cursor.getInt(1)); |
| assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); |
| assertEquals("Device B Storage B", cursor.getString(3)); |
| assertEquals("2", cursor.getString(4)); |
| assertEquals(2048, cursor.getInt(5)); |
| } |
| } |
| |
| public void testQueryRoots_error() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, |
| "Device A", |
| "Device key A", |
| false /* unopened */, |
| new MtpRoot[0], |
| OPERATIONS_SUPPORTED, |
| null)); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 1, |
| "Device B", |
| "Device key B", |
| false /* unopened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 1 /* deviceId */, |
| 1 /* storageId */, |
| "Storage B" /* volume description */, |
| 2048 /* free space */, |
| 4096 /* total space */, |
| "Identifier B" /* no volume identifier */) |
| }, |
| OPERATIONS_SUPPORTED, |
| null)); |
| { |
| mProvider.openDevice(0); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| |
| mProvider.openDevice(1); |
| mResolver.waitForNotification(ROOTS_URI, 2); |
| |
| final Cursor cursor = mProvider.queryRoots(null); |
| assertEquals(2, cursor.getCount()); |
| |
| cursor.moveToNext(); |
| assertEquals("1", cursor.getString(0)); |
| assertEquals( |
| Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, |
| cursor.getInt(1)); |
| assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); |
| assertEquals("Device A", cursor.getString(3)); |
| assertEquals("1", cursor.getString(4)); |
| assertEquals(0, cursor.getInt(5)); |
| |
| cursor.moveToNext(); |
| assertEquals("2", cursor.getString(0)); |
| assertEquals( |
| Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY, |
| cursor.getInt(1)); |
| assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); |
| assertEquals("Device B Storage B", cursor.getString(3)); |
| assertEquals("2", cursor.getString(4)); |
| assertEquals(2048, cursor.getInt(5)); |
| } |
| } |
| |
| public void testQueryDocument() throws IOException, InterruptedException, TimeoutException { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); |
| setupDocuments( |
| 0, |
| 0, |
| MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, |
| "1", |
| new MtpObjectInfo[] { |
| new MtpObjectInfo.Builder() |
| .setObjectHandle(100) |
| .setFormat(MtpConstants.FORMAT_EXIF_JPEG) |
| .setName("image.jpg") |
| .setDateModified(1422716400000L) |
| .setCompressedSize(1024 * 1024 * 5) |
| .setThumbCompressedSize(50 * 1024) |
| .build() |
| }); |
| |
| final Cursor cursor = mProvider.queryDocument("3", null); |
| assertEquals(1, cursor.getCount()); |
| |
| cursor.moveToNext(); |
| |
| assertEquals("3", cursor.getString(0)); |
| assertEquals("image/jpeg", cursor.getString(1)); |
| assertEquals("image.jpg", cursor.getString(2)); |
| assertEquals(1422716400000L, cursor.getLong(3)); |
| assertEquals( |
| DocumentsContract.Document.FLAG_SUPPORTS_DELETE | |
| DocumentsContract.Document.FLAG_SUPPORTS_WRITE | |
| DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL | |
| DocumentsContract.Document.FLAG_SUPPORTS_METADATA, |
| cursor.getInt(4)); |
| assertEquals(1024 * 1024 * 5, cursor.getInt(5)); |
| } |
| |
| public void testQueryDocument_directory() |
| throws IOException, InterruptedException, TimeoutException { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); |
| setupDocuments( |
| 0, |
| 0, |
| MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, |
| "1", |
| new MtpObjectInfo[] { |
| new MtpObjectInfo.Builder() |
| .setObjectHandle(2) |
| .setStorageId(1) |
| .setFormat(MtpConstants.FORMAT_ASSOCIATION) |
| .setName("directory") |
| .setDateModified(1422716400000L) |
| .build() |
| }); |
| |
| final Cursor cursor = mProvider.queryDocument("3", null); |
| assertEquals(1, cursor.getCount()); |
| |
| cursor.moveToNext(); |
| assertEquals("3", cursor.getString(0)); |
| assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); |
| assertEquals("directory", cursor.getString(2)); |
| assertEquals(1422716400000L, cursor.getLong(3)); |
| assertEquals( |
| DocumentsContract.Document.FLAG_SUPPORTS_DELETE | |
| DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, |
| cursor.getInt(4)); |
| assertEquals(0, cursor.getInt(5)); |
| } |
| |
| public void testQueryDocument_forStorage() |
| throws IOException, InterruptedException, TimeoutException { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 4096 /* total space */, |
| "" /* no volume identifier */) |
| }); |
| final Cursor cursor = mProvider.queryDocument("2", null); |
| assertEquals(1, cursor.getCount()); |
| |
| cursor.moveToNext(); |
| assertEquals("2", cursor.getString(0)); |
| assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); |
| assertEquals("Storage A", cursor.getString(2)); |
| assertTrue(cursor.isNull(3)); |
| assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4)); |
| assertEquals(3072, cursor.getInt(5)); |
| } |
| |
| public void testQueryDocument_forDeviceWithSingleStorage() |
| throws IOException, InterruptedException, TimeoutException { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 4096 /* total space */, |
| "" /* no volume identifier */) |
| }); |
| final Cursor cursor = mProvider.queryDocument("1", null); |
| assertEquals(1, cursor.getCount()); |
| |
| cursor.moveToNext(); |
| assertEquals("1", cursor.getString(0)); |
| assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); |
| assertEquals("Device Storage A", cursor.getString(2)); |
| assertTrue(cursor.isNull(3)); |
| assertEquals(DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, cursor.getInt(4)); |
| assertTrue(cursor.isNull(5)); |
| } |
| |
| public void testQueryDocument_forDeviceWithTwoStorages() |
| throws IOException, InterruptedException, TimeoutException { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 4096 /* total space */, |
| "" /* no volume identifier */), |
| new MtpRoot( |
| 0 /* deviceId */, |
| 2 /* storageId */, |
| "Storage B" /* volume description */, |
| 1024 /* free space */, |
| 4096 /* total space */, |
| "" /* no volume identifier */) |
| }); |
| final Cursor cursor = mProvider.queryDocument("1", null); |
| assertEquals(1, cursor.getCount()); |
| |
| cursor.moveToNext(); |
| assertEquals("1", cursor.getString(0)); |
| assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); |
| assertEquals("Device", cursor.getString(2)); |
| assertTrue(cursor.isNull(3)); |
| assertEquals(0, cursor.getInt(4)); |
| assertTrue(cursor.isNull(5)); |
| } |
| |
| public void testQueryChildDocuments() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); |
| setupDocuments( |
| 0, |
| 0, |
| MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, |
| "1", |
| new MtpObjectInfo[] { |
| new MtpObjectInfo.Builder() |
| .setObjectHandle(100) |
| .setFormat(MtpConstants.FORMAT_EXIF_JPEG) |
| .setName("image.jpg") |
| .setCompressedSize(1024 * 1024 * 5) |
| .setThumbCompressedSize(5 * 1024) |
| .setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY) |
| .build() |
| }); |
| |
| final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null); |
| assertEquals(1, cursor.getCount()); |
| |
| assertTrue(cursor.moveToNext()); |
| assertEquals("3", cursor.getString(0)); |
| assertEquals("image/jpeg", cursor.getString(1)); |
| assertEquals("image.jpg", cursor.getString(2)); |
| assertEquals(0, cursor.getLong(3)); |
| assertEquals(Document.FLAG_SUPPORTS_THUMBNAIL |
| | Document.FLAG_SUPPORTS_METADATA, cursor.getInt(4)); |
| assertEquals(1024 * 1024 * 5, cursor.getInt(5)); |
| |
| cursor.close(); |
| } |
| |
| public void testQueryChildDocuments_cursorError() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| try { |
| mProvider.queryChildDocuments("1", null, (String) null); |
| fail(); |
| } catch (FileNotFoundException error) {} |
| } |
| |
| public void testQueryChildDocuments_documentError() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); |
| mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); |
| try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) { |
| assertEquals(0, cursor.getCount()); |
| assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); |
| } |
| } |
| |
| public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot(0, 0, "Storage", 0, 0, "") |
| }); |
| setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { |
| new MtpObjectInfo.Builder() |
| .setName("test.txt") |
| .setObjectHandle(1) |
| .setParent(-1) |
| .build() |
| }); |
| |
| mProvider.deleteDocument("3"); |
| assertEquals(1, mResolver.getChangeCount( |
| DocumentsContract.buildChildDocumentsUri( |
| MtpDocumentsProvider.AUTHORITY, "1"))); |
| } |
| |
| public void testDeleteDocument_error() |
| throws IOException, InterruptedException, TimeoutException { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot(0, 0, "Storage", 0, 0, "") |
| }); |
| setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { |
| new MtpObjectInfo.Builder() |
| .setName("test.txt") |
| .setObjectHandle(1) |
| .setParent(-1) |
| .build() |
| }); |
| try { |
| mProvider.deleteDocument("4"); |
| fail(); |
| } catch (Throwable e) { |
| assertTrue(e instanceof IOException); |
| } |
| assertEquals(0, mResolver.getChangeCount( |
| DocumentsContract.buildChildDocumentsUri( |
| MtpDocumentsProvider.AUTHORITY, "1"))); |
| } |
| |
| public void testOpenDocument() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot(0, 0, "Storage", 0, 0, "") |
| }); |
| final byte[] bytes = "Hello world".getBytes(); |
| setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { |
| new MtpObjectInfo.Builder() |
| .setName("test.txt") |
| .setObjectHandle(1) |
| .setCompressedSize(bytes.length) |
| .setParent(-1) |
| .build() |
| }); |
| mMtpManager.setImportFileBytes(0, 1, bytes); |
| try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) { |
| final byte[] readBytes = new byte[5]; |
| assertEquals(6, Os.lseek(fd.getFileDescriptor(), 6, OsConstants.SEEK_SET)); |
| assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5)); |
| assertTrue(Arrays.equals("world".getBytes(), readBytes)); |
| |
| assertEquals(0, Os.lseek(fd.getFileDescriptor(), 0, OsConstants.SEEK_SET)); |
| assertEquals(5, Os.read(fd.getFileDescriptor(), readBytes, 0, 5)); |
| assertTrue(Arrays.equals("Hello".getBytes(), readBytes)); |
| } |
| } |
| |
| public void testOpenDocument_shortBytes() throws Exception { |
| mMtpManager = new TestMtpManager(getContext()) { |
| @Override |
| MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { |
| if (objectHandle == 1) { |
| return new MtpObjectInfo.Builder(super.getObjectInfo(deviceId, objectHandle)) |
| .setObjectHandle(1).setCompressedSize(1024 * 1024).build(); |
| } |
| |
| return super.getObjectInfo(deviceId, objectHandle); |
| } |
| }; |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot(0, 0, "Storage", 0, 0, "") |
| }); |
| final byte[] bytes = "Hello world".getBytes(); |
| setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] { |
| new MtpObjectInfo.Builder() |
| .setName("test.txt") |
| .setObjectHandle(1) |
| .setCompressedSize(bytes.length) |
| .setParent(-1) |
| .build() |
| }); |
| mMtpManager.setImportFileBytes(0, 1, bytes); |
| try (final ParcelFileDescriptor fd = mProvider.openDocument("3", "r", null)) { |
| final byte[] readBytes = new byte[1024 * 1024]; |
| assertEquals(11, Os.read(fd.getFileDescriptor(), readBytes, 0, readBytes.length)); |
| } |
| } |
| |
| public void testOpenDocument_writing() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot(0, 100, "Storage", 0, 0, "") |
| }); |
| final String documentId = mProvider.createDocument("2", "text/plain", "test.txt"); |
| { |
| final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "w", null); |
| try (ParcelFileDescriptor.AutoCloseOutputStream stream = |
| new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { |
| stream.write("Hello".getBytes()); |
| fd.getFileDescriptor().sync(); |
| } |
| } |
| { |
| final ParcelFileDescriptor fd = mProvider.openDocument(documentId, "r", null); |
| try (ParcelFileDescriptor.AutoCloseInputStream stream = |
| new ParcelFileDescriptor.AutoCloseInputStream(fd)) { |
| final byte[] bytes = new byte[5]; |
| stream.read(bytes); |
| assertTrue(Arrays.equals("Hello".getBytes(), bytes)); |
| } |
| } |
| } |
| |
| public void testBusyDevice() throws Exception { |
| mMtpManager = new TestMtpManager(getContext()) { |
| @Override |
| synchronized MtpDeviceRecord openDevice(int deviceId) |
| throws IOException { |
| throw new BusyDeviceException(); |
| } |
| }; |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0], |
| OPERATIONS_SUPPORTED, null)); |
| |
| mProvider.resumeRootScanner(); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| |
| try (final Cursor cursor = mProvider.queryRoots(null)) { |
| assertEquals(1, cursor.getCount()); |
| } |
| |
| try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) { |
| assertEquals(0, cursor.getCount()); |
| assertEquals( |
| "error_busy_device", |
| cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); |
| } |
| } |
| |
| public void testLockedDevice() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED, |
| null)); |
| |
| mProvider.resumeRootScanner(); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| |
| try (final Cursor cursor = mProvider.queryRoots(null)) { |
| assertEquals(1, cursor.getCount()); |
| } |
| |
| try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) { |
| assertEquals(0, cursor.getCount()); |
| assertEquals( |
| "error_locked_device", |
| cursor.getExtras().getString(DocumentsContract.EXTRA_ERROR)); |
| } |
| } |
| |
| public void testMappingDisconnectedDocuments() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, |
| "Device A", |
| "device key", |
| true /* opened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 2048 /* total space */, |
| "" /* no volume identifier */) |
| }, |
| OPERATIONS_SUPPORTED, |
| null)); |
| |
| final String[] names = strings("Directory A", "Directory B", "Directory C"); |
| final int objectHandleOffset = 100; |
| for (int i = 0; i < names.length; i++) { |
| final int parentHandle = i == 0 ? |
| MtpManager.OBJECT_HANDLE_ROOT_CHILDREN : objectHandleOffset + i - 1; |
| final int objectHandle = i + objectHandleOffset; |
| mMtpManager.setObjectHandles(0, 1, parentHandle, new int[] { objectHandle }); |
| mMtpManager.setObjectInfo( |
| 0, |
| new MtpObjectInfo.Builder() |
| .setName(names[i]) |
| .setObjectHandle(objectHandle) |
| .setFormat(MtpConstants.FORMAT_ASSOCIATION) |
| .setStorageId(1) |
| .build()); |
| } |
| |
| mProvider.resumeRootScanner(); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| |
| final int documentIdOffset = 2; |
| for (int i = 0; i < names.length; i++) { |
| try (final Cursor cursor = mProvider.queryChildDocuments( |
| String.valueOf(documentIdOffset + i), |
| strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME), |
| (String) null)) { |
| assertEquals(1, cursor.getCount()); |
| cursor.moveToNext(); |
| assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0)); |
| assertEquals(names[i], cursor.getString(1)); |
| } |
| } |
| |
| mProvider.closeDevice(0); |
| mResolver.waitForNotification(ROOTS_URI, 2); |
| |
| mProvider.openDevice(0); |
| mResolver.waitForNotification(ROOTS_URI, 3); |
| |
| for (int i = 0; i < names.length; i++) { |
| mResolver.waitForNotification(DocumentsContract.buildChildDocumentsUri( |
| MtpDocumentsProvider.AUTHORITY, |
| String.valueOf(documentIdOffset + i)), 1); |
| try (final Cursor cursor = mProvider.queryChildDocuments( |
| String.valueOf(documentIdOffset + i), |
| strings(Document.COLUMN_DOCUMENT_ID), |
| (String) null)) { |
| assertEquals(1, cursor.getCount()); |
| cursor.moveToNext(); |
| assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0)); |
| } |
| } |
| } |
| |
| public void testCreateDocument() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot(0, 100, "Storage A", 100, 100, null) |
| }); |
| final String documentId = mProvider.createDocument("1", "text/plain", "note.txt"); |
| final Uri deviceUri = DocumentsContract.buildChildDocumentsUri( |
| MtpDocumentsProvider.AUTHORITY, "1"); |
| final Uri storageUri = DocumentsContract.buildChildDocumentsUri( |
| MtpDocumentsProvider.AUTHORITY, "2"); |
| mResolver.waitForNotification(storageUri, 1); |
| mResolver.waitForNotification(deviceUri, 1); |
| try (final Cursor cursor = mProvider.queryDocument(documentId, null)) { |
| assertTrue(cursor.moveToNext()); |
| assertEquals( |
| "note.txt", |
| cursor.getString(cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME))); |
| assertEquals( |
| "text/plain", |
| cursor.getString(cursor.getColumnIndex(Document.COLUMN_MIME_TYPE))); |
| } |
| } |
| |
| public void testCreateDocument_noWritingSupport() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, "Device A", null /* deviceKey */, false /* unopened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 2048 /* total space */, |
| "" /* no volume identifier */) |
| }, |
| new int[0] /* no operations supported */, null)); |
| mProvider.resumeRootScanner(); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| try { |
| mProvider.createDocument("1", "text/palin", "note.txt"); |
| fail(); |
| } catch (UnsupportedOperationException exception) {} |
| } |
| |
| public void testOpenDocument_noWritingSupport() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| mMtpManager.addValidDevice(new MtpDeviceRecord( |
| 0, "Device A", null /* deviceKey */, false /* unopened */, |
| new MtpRoot[] { |
| new MtpRoot( |
| 0 /* deviceId */, |
| 1 /* storageId */, |
| "Storage A" /* volume description */, |
| 1024 /* free space */, |
| 2048 /* total space */, |
| "" /* no volume identifier */) |
| }, |
| new int[0] /* no operations supported */, null)); |
| mMtpManager.setObjectHandles( |
| 0, 1, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, new int[] { 100 }); |
| mMtpManager.setObjectInfo( |
| 0, new MtpObjectInfo.Builder().setObjectHandle(100).setName("note.txt").build()); |
| mProvider.resumeRootScanner(); |
| mResolver.waitForNotification(ROOTS_URI, 1); |
| try (final Cursor cursor = mProvider.queryChildDocuments( |
| "1", strings(Document.COLUMN_DOCUMENT_ID), (String) null)) { |
| assertEquals(1, cursor.getCount()); |
| cursor.moveToNext(); |
| assertEquals("3", cursor.getString(0)); |
| } |
| try { |
| mProvider.openDocument("3", "w", null); |
| fail(); |
| } catch (UnsupportedOperationException exception) {} |
| } |
| |
| public void testObjectSizeLong() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); |
| mMtpManager.setObjectSizeLong(0, 100, MtpConstants.FORMAT_EXIF_JPEG, 0x400000000L); |
| setupDocuments( |
| 0, |
| 0, |
| MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, |
| "1", |
| new MtpObjectInfo[] { |
| new MtpObjectInfo.Builder() |
| .setObjectHandle(100) |
| .setFormat(MtpConstants.FORMAT_EXIF_JPEG) |
| .setName("image.jpg") |
| .setCompressedSize(0xffffffffl) |
| .build() |
| }); |
| |
| final Cursor cursor = mProvider.queryDocument("3", new String[] { |
| DocumentsContract.Document.COLUMN_SIZE |
| }); |
| assertEquals(1, cursor.getCount()); |
| |
| cursor.moveToNext(); |
| assertEquals(0x400000000L, cursor.getLong(0)); |
| } |
| |
| public void testFindDocumentPath_singleStorage_toRoot() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); |
| setupHierarchyDocuments("1"); |
| |
| final Path path = mProvider.findDocumentPath(null, "15"); |
| assertEquals("1", path.getRootId()); |
| assertEquals(4, path.getPath().size()); |
| assertEquals("1", path.getPath().get(0)); |
| assertEquals("3", path.getPath().get(1)); |
| assertEquals("6", path.getPath().get(2)); |
| assertEquals("15", path.getPath().get(3)); |
| } |
| |
| public void testFindDocumentPath_singleStorage_toDoc() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); |
| setupHierarchyDocuments("1"); |
| |
| final Path path = mProvider.findDocumentPath("3", "18"); |
| assertNull(path.getRootId()); |
| assertEquals(3, path.getPath().size()); |
| assertEquals("3", path.getPath().get(0)); |
| assertEquals("7", path.getPath().get(1)); |
| assertEquals("18", path.getPath().get(2)); |
| } |
| |
| public void testFindDocumentPath_multiStorage_toRoot() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot(0, 0, "Storage A", 1000, 1000, ""), |
| new MtpRoot(0, 1, "Storage B", 1000, 1000, "") }); |
| setupHierarchyDocuments("2"); |
| |
| final Path path = mProvider.findDocumentPath(null, "16"); |
| assertEquals("2", path.getRootId()); |
| assertEquals(4, path.getPath().size()); |
| assertEquals("2", path.getPath().get(0)); |
| assertEquals("4", path.getPath().get(1)); |
| assertEquals("7", path.getPath().get(2)); |
| assertEquals("16", path.getPath().get(3)); |
| } |
| |
| public void testFindDocumentPath_multiStorage_toDoc() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { |
| new MtpRoot(0, 0, "Storage A", 1000, 1000, ""), |
| new MtpRoot(0, 1, "Storage B", 1000, 1000, "") }); |
| setupHierarchyDocuments("2"); |
| |
| final Path path = mProvider.findDocumentPath("4", "19"); |
| assertNull(path.getRootId()); |
| assertEquals(3, path.getPath().size()); |
| assertEquals("4", path.getPath().get(0)); |
| assertEquals("8", path.getPath().get(1)); |
| assertEquals("19", path.getPath().get(2)); |
| } |
| |
| public void testIsChildDocument() throws Exception { |
| setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); |
| setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); |
| setupHierarchyDocuments("1"); |
| assertTrue(mProvider.isChildDocument("1", "1")); |
| assertTrue(mProvider.isChildDocument("1", "14")); |
| assertTrue(mProvider.isChildDocument("2", "14")); |
| assertTrue(mProvider.isChildDocument("5", "14")); |
| assertFalse(mProvider.isChildDocument("3", "14")); |
| assertFalse(mProvider.isChildDocument("6", "14")); |
| } |
| |
| private void setupProvider(int flag) { |
| mDatabase = new MtpDatabase(getContext(), flag); |
| mProvider = new MtpDocumentsProvider(); |
| final StorageManager storageManager = getContext().getSystemService(StorageManager.class); |
| assertTrue(mProvider.onCreateForTesting( |
| getContext(), |
| mResources, |
| mMtpManager, |
| mResolver, |
| mDatabase, |
| storageManager, |
| new TestServiceIntentSender())); |
| } |
| |
| private String[] getStrings(Cursor cursor) { |
| try { |
| final String[] results = new String[cursor.getCount()]; |
| for (int i = 0; cursor.moveToNext(); i++) { |
| results[i] = cursor.getString(0); |
| } |
| return results; |
| } finally { |
| cursor.close(); |
| } |
| } |
| |
| private String[] setupRoots(int deviceId, MtpRoot[] roots) |
| throws InterruptedException, TimeoutException, IOException { |
| final int changeCount = mResolver.getChangeCount(ROOTS_URI); |
| mMtpManager.addValidDevice( |
| new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */, |
| roots, OPERATIONS_SUPPORTED, null)); |
| mProvider.openDevice(deviceId); |
| mResolver.waitForNotification(ROOTS_URI, changeCount + 1); |
| return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID))); |
| } |
| |
| private String[] setupDocuments( |
| int deviceId, |
| int storageId, |
| int parentHandle, |
| String parentDocumentId, |
| MtpObjectInfo[] objects) throws FileNotFoundException { |
| final int[] handles = new int[objects.length]; |
| int i = 0; |
| for (final MtpObjectInfo info : objects) { |
| handles[i++] = info.getObjectHandle(); |
| mMtpManager.setObjectInfo(deviceId, info); |
| } |
| mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles); |
| return getStrings(mProvider.queryChildDocuments( |
| parentDocumentId, |
| strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), |
| (String) null)); |
| } |
| |
| static class HierarchyDocument { |
| int depth; |
| String documentId; |
| int objectHandle; |
| int parentHandle; |
| |
| HierarchyDocument createChildDocument(int newHandle) { |
| final HierarchyDocument doc = new HierarchyDocument(); |
| doc.depth = depth - 1; |
| doc.objectHandle = newHandle; |
| doc.parentHandle = objectHandle; |
| return doc; |
| } |
| |
| MtpObjectInfo toObjectInfo() { |
| return new MtpObjectInfo.Builder() |
| .setName("doc_" + documentId) |
| .setFormat(depth > 0 ? |
| MtpConstants.FORMAT_ASSOCIATION : MtpConstants.FORMAT_TEXT) |
| .setObjectHandle(objectHandle) |
| .setParent(parentHandle) |
| .build(); |
| } |
| } |
| |
| private void setupHierarchyDocuments(String documentId) throws Exception { |
| final Queue<HierarchyDocument> ids = new LinkedList<>(); |
| final HierarchyDocument firstDocument = new HierarchyDocument(); |
| firstDocument.depth = 3; |
| firstDocument.documentId = documentId; |
| firstDocument.objectHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; |
| ids.add(firstDocument); |
| |
| int objectHandle = 100; |
| while (!ids.isEmpty()) { |
| final HierarchyDocument document = ids.remove(); |
| final HierarchyDocument[] children = new HierarchyDocument[] { |
| document.createChildDocument(objectHandle++), |
| document.createChildDocument(objectHandle++), |
| document.createChildDocument(objectHandle++), |
| }; |
| final String[] childDocIds = setupDocuments( |
| 0, 0, document.objectHandle, document.documentId, new MtpObjectInfo[] { |
| children[0].toObjectInfo(), |
| children[1].toObjectInfo(), |
| children[2].toObjectInfo(), |
| }); |
| children[0].documentId = childDocIds[0]; |
| children[1].documentId = childDocIds[1]; |
| children[2].documentId = childDocIds[2]; |
| |
| if (children[0].depth > 0) { |
| ids.add(children[0]); |
| ids.add(children[1]); |
| ids.add(children[2]); |
| } |
| } |
| } |
| } |