From 0f32537e40ee2662d4f0b7b625ee280ca9c02066 Mon Sep 17 00:00:00 2001 From: Daichi Hirono Date: Sun, 21 Feb 2016 15:50:30 +0900 Subject: [PATCH] Stops performing operations that does not supported by MTP device. MTP devices can return supported operation list. The CL sets root flag by referring it. BUG=26147375 Change-Id: I02397821e208cf5a8fcf7457aa279d2818ce24c7 --- .../src/com/android/mtp/AppFuse.java | 2 + .../src/com/android/mtp/Mapper.java | 9 ++- .../src/com/android/mtp/MtpDatabase.java | 20 +++-- .../src/com/android/mtp/MtpDeviceRecord.java | 26 +++++++ .../src/com/android/mtp/MtpDocumentsProvider.java | 61 +++++++++------ .../src/com/android/mtp/MtpDocumentsService.java | 45 ++++------- .../src/com/android/mtp/MtpManager.java | 72 ++++++++--------- .../src/com/android/mtp/RootScanner.java | 3 +- .../src/com/android/mtp/DocumentLoaderTest.java | 2 +- .../tests/src/com/android/mtp/MtpDatabaseTest.java | 48 ++++++------ .../com/android/mtp/MtpDocumentsProviderTest.java | 90 ++++++++++++++++++---- .../tests/src/com/android/mtp/TestMtpManager.java | 26 ++----- .../tests/src/com/android/mtp/TestUtil.java | 13 +++- 13 files changed, 254 insertions(+), 163 deletions(-) diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java index 6a98405d5026..38435f4afa47 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java @@ -133,6 +133,8 @@ public class AppFuse { return mCallback.readObjectBytes(inode, offset, size, mBuffer); } catch (IOException e) { return -OsConstants.EIO; + } catch (UnsupportedOperationException e) { + return -OsConstants.ENOTSUP; } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java index 9578e6bd1d5e..4bed0039039f 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java @@ -89,7 +89,8 @@ class Mapper { * @return If roots are added or removed from the database. * @throws FileNotFoundException */ - synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) + synchronized boolean putStorageDocuments( + String parentDocumentId, int[] operationsSupported, MtpRoot[] roots) throws FileNotFoundException { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); @@ -100,7 +101,11 @@ class Mapper { valuesList[i] = new ContentValues(); extraValuesList[i] = new ContentValues(); MtpDatabase.getStorageDocumentValues( - valuesList[i], extraValuesList[i], parentDocumentId, roots[i]); + valuesList[i], + extraValuesList[i], + parentDocumentId, + operationsSupported, + roots[i]); } final boolean changed = putDocuments( parentDocumentId, diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index e14109abfe54..701543bf330c 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -689,9 +689,7 @@ class MtpDatabase { values.putNull(Document.COLUMN_SIZE); extraValues.clear(); - extraValues.put( - Root.COLUMN_FLAGS, - Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); + extraValues.put(Root.COLUMN_FLAGS, getRootFlags(device.operationsSupported)); extraValues.putNull(Root.COLUMN_AVAILABLE_BYTES); extraValues.putNull(Root.COLUMN_CAPACITY_BYTES); extraValues.put(Root.COLUMN_MIME_TYPES, ""); @@ -700,12 +698,16 @@ class MtpDatabase { /** * Gets {@link ContentValues} for the given root. * @param values {@link ContentValues} that receives values. + * @param extraValues {@link ContentValues} that receives extra values for roots. + * @param parentDocumentId Parent document ID. + * @param supportedOperations Array of Operation code supported by the device. * @param root Root to be converted {@link ContentValues}. */ static void getStorageDocumentValues( ContentValues values, ContentValues extraValues, String parentDocumentId, + int[] operationsSupported, MtpRoot root) { values.clear(); values.put(COLUMN_DEVICE_ID, root.mDeviceId); @@ -722,9 +724,7 @@ class MtpDatabase { values.put(Document.COLUMN_FLAGS, 0); values.put(Document.COLUMN_SIZE, root.mMaxCapacity - root.mFreeSpace); - extraValues.put( - Root.COLUMN_FLAGS, - Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); + extraValues.put(Root.COLUMN_FLAGS, getRootFlags(operationsSupported)); extraValues.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace); extraValues.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity); extraValues.put(Root.COLUMN_MIME_TYPES, ""); @@ -785,6 +785,14 @@ class MtpDatabase { return "application/octet-stream"; } + private static int getRootFlags(int[] operationsSupported) { + int rootFlag = Root.FLAG_SUPPORTS_IS_CHILD; + if (MtpDeviceRecord.isWritingSupported(operationsSupported)) { + rootFlag |= Root.FLAG_SUPPORTS_CREATE; + } + return rootFlag; + } + static String[] strings(Object... args) { final String[] results = new String[args.length]; for (int i = 0; i < args.length; i++) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java index 71716bd849ad..393c4de89ea4 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java @@ -17,6 +17,7 @@ package com.android.mtp; import android.annotation.Nullable; +import android.mtp.MtpConstants; class MtpDeviceRecord { public final int deviceId; @@ -38,4 +39,29 @@ class MtpDeviceRecord { this.operationsSupported = operationsSupported; this.eventsSupported = eventsSupported; } + + /** + * Helper method to check operations/events are supported by the device or not. + */ + static boolean isSupported(@Nullable int[] supportedList, int code) { + if (supportedList == null) { + return false; + } + for (int i = 0; i < supportedList.length; i++) { + if (supportedList[i] == code) { + return true; + } + } + return false; + } + + static boolean isPartialReadSupported(@Nullable int[] supportedList, long fileSize) { + return fileSize <= 0xffffffffl && + isSupported(supportedList, MtpConstants.OPERATION_GET_PARTIAL_OBJECT); + } + + static boolean isWritingSupported(@Nullable int[] supportedList) { + return isSupported(supportedList, MtpConstants.OPERATION_SEND_OBJECT_INFO) && + isSupported(supportedList, MtpConstants.OPERATION_SEND_OBJECT); + } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 48499787c3b9..a1c5c9bde372 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -201,6 +201,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { final Identifier identifier = mDatabase.createIdentifier(documentId); try { openDevice(identifier.mDeviceId); + final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; switch (mode) { case "r": final long fileSize = getFileSize(documentId); @@ -208,7 +209,8 @@ public class MtpDocumentsProvider extends DocumentsProvider { // 4GB. Fallback to non-seekable file descriptor. // TODO: Use getPartialObject64 for MTP devices that support Android vendor // extension. - if (fileSize <= 0xffffffffl) { + if (MtpDeviceRecord.isPartialReadSupported( + device.operationsSupported, fileSize)) { return mAppFuse.openFile(Integer.parseInt(documentId)); } else { return getPipeManager(identifier).readDocument(mMtpManager, identifier); @@ -216,8 +218,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { case "w": // TODO: Clear the parent document loader task (if exists) and call notify // when writing is completed. - return getPipeManager(identifier).writeDocument( - getContext(), mMtpManager, identifier); + if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { + return getPipeManager(identifier).writeDocument( + getContext(), mMtpManager, identifier); + } else { + throw new UnsupportedOperationException( + "The device does not support writing operation."); + } case "rw": // TODO: Add support for "rw" mode. throw new UnsupportedOperationException( @@ -290,6 +297,10 @@ public class MtpDocumentsProvider extends DocumentsProvider { try { final Identifier parentId = mDatabase.createIdentifier(parentDocumentId); openDevice(parentId.mDeviceId); + final MtpDeviceRecord record = getDeviceToolkit(parentId.mDeviceId).mDeviceRecord; + if (!MtpDeviceRecord.isWritingSupported(record.operationsSupported)) { + throw new UnsupportedOperationException(); + } final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe(); pipe[0].close(); // 0 bytes for a new document. final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? @@ -323,9 +334,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { if (DEBUG) { Log.d(TAG, "Open device " + deviceId); } - mMtpManager.openDevice(deviceId); + final MtpDeviceRecord device = mMtpManager.openDevice(deviceId); final DeviceToolkit toolkit = - new DeviceToolkit(deviceId, mMtpManager, mResolver, mDatabase); + new DeviceToolkit(deviceId, mMtpManager, mResolver, mDatabase, device); mDeviceToolkits.put(deviceId, toolkit); mIntentSender.sendUpdateNotificationIntent(); try { @@ -347,20 +358,15 @@ public class MtpDocumentsProvider extends DocumentsProvider { mIntentSender.sendUpdateNotificationIntent(); } - int[] getOpenedDeviceIds() { + MtpDeviceRecord[] getOpenedDeviceRecordsCache() { synchronized (mDeviceListLock) { - return mMtpManager.getOpenedDeviceIds(); - } - } - - String getDeviceName(int deviceId) throws IOException { - synchronized (mDeviceListLock) { - for (final MtpDeviceRecord device : mMtpManager.getDevices()) { - if (device.deviceId == deviceId) { - return device.name; - } + final MtpDeviceRecord[] records = new MtpDeviceRecord[mDeviceToolkits.size()]; + int i = 0; + for (final DeviceToolkit toolkit : mDeviceToolkits.values()) { + records[i] = toolkit.mDeviceRecord; + i++; } - throw new IOException("Not found the device: " + Integer.toString(deviceId)); + return records; } } @@ -391,7 +397,10 @@ public class MtpDocumentsProvider extends DocumentsProvider { public void shutdown() { synchronized (mDeviceListLock) { try { - for (final int id : mMtpManager.getOpenedDeviceIds()) { + // Copy the opened key set because it will be modified when closing devices. + final Integer[] keySet = + mDeviceToolkits.keySet().toArray(new Integer[mDeviceToolkits.size()]); + for (final int id : keySet) { closeDeviceInternal(id); } } catch (InterruptedException|IOException e) { @@ -432,7 +441,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { getDeviceToolkit(deviceId).mDocumentLoader.close(); mDeviceToolkits.remove(deviceId); mMtpManager.closeDevice(deviceId); - if (getOpenedDeviceIds().length == 0) { + if (mDeviceToolkits.size() == 0) { mRootScanner.pause(); } } @@ -488,11 +497,14 @@ public class MtpDocumentsProvider extends DocumentsProvider { private static class DeviceToolkit { public final PipeManager mPipeManager; public final DocumentLoader mDocumentLoader; + public final MtpDeviceRecord mDeviceRecord; public DeviceToolkit( - int deviceId, MtpManager manager, ContentResolver resolver, MtpDatabase database) { + int deviceId, MtpManager manager, ContentResolver resolver, MtpDatabase database, + MtpDeviceRecord record) { mPipeManager = new PipeManager(database); mDocumentLoader = new DocumentLoader(deviceId, manager, resolver, database); + mDeviceRecord = record; } } @@ -501,8 +513,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { public long readObjectBytes( int inode, long offset, long size, byte[] buffer) throws IOException { final Identifier identifier = mDatabase.createIdentifier(Integer.toString(inode)); - return mMtpManager.getPartialObject( - identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer); + final MtpDeviceRecord record = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; + if (MtpDeviceRecord.isPartialReadSupported(record.operationsSupported, offset)) { + return mMtpManager.getPartialObject( + identifier.mDeviceId, identifier.mObjectHandle, offset, size, buffer); + } else { + throw new UnsupportedOperationException(); + } } @Override diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java index 9c4952bc5759..9b42b7828893 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java @@ -67,38 +67,25 @@ public class MtpDocumentsService extends Service { */ private boolean updateForegroundState() { final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance(); - final int[] deviceIds = provider.getOpenedDeviceIds(); int notificationId = 0; Notification notification = null; // TODO: Hide notification if the device has already been removed. - for (final int deviceId : deviceIds) { - try { - final String title = getResources().getString( - R.string.accessing_notification_title, - provider.getDeviceName(deviceIds[0])); - final String description = getResources().getString( - R.string.accessing_notification_description); - notificationId = deviceId; - notification = new Notification.Builder(this) - .setLocalOnly(true) - .setContentTitle(title) - .setContentText(description) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_data_usb) - .setCategory(Notification.CATEGORY_SYSTEM) - .setPriority(Notification.PRIORITY_LOW) - .build(); - mNotificationManager.notify(deviceId, notification); - } catch (IOException exp) { - logErrorMessage(exp); - // If we failed to obtain device name, it looks the device is unusable. - // Because this is the last device we opened, we should hide the notification - // for the case. - try { - provider.closeDevice(deviceIds[0]); - } catch (IOException | InterruptedException closeError) { - logErrorMessage(closeError); - } - } + for (final MtpDeviceRecord record : provider.getOpenedDeviceRecordsCache()) { + final String title = getResources().getString( + R.string.accessing_notification_title, + record.name); + final String description = getResources().getString( + R.string.accessing_notification_description); + notificationId = record.deviceId; + notification = new Notification.Builder(this) + .setLocalOnly(true) + .setContentTitle(title) + .setContentText(description) + .setSmallIcon(com.android.internal.R.drawable.stat_sys_data_usb) + .setCategory(Notification.CATEGORY_SYSTEM) + .setPriority(Notification.PRIORITY_LOW) + .build(); + mNotificationManager.notify(record.deviceId, notification); } if (notification != null) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java index 37dc7616b510..c49005f2beed 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -65,16 +65,14 @@ class MtpManager { */ private static final int PROTOCOL_MTP = 0; - private final UsbManager mManager; - // TODO: Save and restore the set of opened device. private final SparseArray mDevices = new SparseArray<>(); MtpManager(Context context) { mManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); } - synchronized void openDevice(int deviceId) throws IOException { + synchronized MtpDeviceRecord openDevice(int deviceId) throws IOException { UsbDevice rawDevice = null; for (final UsbDevice candidate : mManager.getDeviceList().values()) { if (candidate.getDeviceId() == deviceId) { @@ -113,6 +111,8 @@ class MtpManager { } mDevices.put(deviceId, device); + + return createDeviceRecord(rawDevice); } synchronized void closeDevice(int deviceId) throws IOException { @@ -126,45 +126,11 @@ class MtpManager { if (!isMtpDevice(device)) { continue; } - final MtpDevice mtpDevice = mDevices.get(device.getDeviceId()); - final boolean opened = mtpDevice != null; - final String name = device.getProductName(); - MtpRoot[] roots; - int[] operationsSupported = null; - int[] eventsSupported = null; - if (opened) { - try { - roots = getRoots(device.getDeviceId()); - } catch (IOException exp) { - Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exp); - // If we failed to fetch roots for the device, we still returns device model - // with an empty set of roots so that the device is shown DocumentsUI as long as - // the device is physically connected. - roots = new MtpRoot[0]; - } - final MtpDeviceInfo info = mtpDevice.getDeviceInfo(); - if (info != null) { - operationsSupported = mtpDevice.getDeviceInfo().getOperationsSupported(); - eventsSupported = mtpDevice.getDeviceInfo().getEventsSupported(); - } - } else { - roots = new MtpRoot[0]; - } - devices.add(new MtpDeviceRecord( - device.getDeviceId(), name, device.getSerialNumber(), opened, roots, - operationsSupported, eventsSupported)); + devices.add(createDeviceRecord(device)); } return devices.toArray(new MtpDeviceRecord[devices.size()]); } - synchronized int[] getOpenedDeviceIds() { - final int[] result = new int[mDevices.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = mDevices.keyAt(i); - } - return result; - } - MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); @@ -281,6 +247,36 @@ class MtpManager { } } + private MtpDeviceRecord createDeviceRecord(UsbDevice device) { + final MtpDevice mtpDevice = mDevices.get(device.getDeviceId()); + final boolean opened = mtpDevice != null; + final String name = device.getProductName(); + MtpRoot[] roots; + int[] operationsSupported = null; + int[] eventsSupported = null; + if (opened) { + try { + roots = getRoots(device.getDeviceId()); + } catch (IOException exp) { + Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exp); + // If we failed to fetch roots for the device, we still returns device model + // with an empty set of roots so that the device is shown DocumentsUI as long as + // the device is physically connected. + roots = new MtpRoot[0]; + } + final MtpDeviceInfo info = mtpDevice.getDeviceInfo(); + if (info != null) { + operationsSupported = mtpDevice.getDeviceInfo().getOperationsSupported(); + eventsSupported = mtpDevice.getDeviceInfo().getEventsSupported(); + } + } else { + roots = new MtpRoot[0]; + } + return new MtpDeviceRecord( + device.getDeviceId(), name, device.getSerialNumber(), opened, roots, + operationsSupported, eventsSupported); + } + static boolean isMtpDevice(UsbDevice device) { for (int i = 0; i < device.getInterfaceCount(); i++) { final UsbInterface usbInterface = device.getInterface(i); diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java index a48bf12b308f..82ba21f659d1 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java @@ -149,7 +149,8 @@ final class RootScanner { } try { mDatabase.getMapper().startAddingDocuments(documentId); - if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) { + if (mDatabase.getMapper().putStorageDocuments( + documentId, device.eventsSupported, device.roots)) { changed = true; } if (mDatabase.getMapper().stopAddingDocuments(documentId)) { diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java index b75a9e63a17e..a000895ab056 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java @@ -48,7 +48,7 @@ public class DocumentLoaderTest extends AndroidTestCase { mDatabase.getMapper().stopAddingDocuments(null); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", new int[0], new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); mDatabase.getMapper().stopAddingDocuments("1"); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index 05c9c57e2409..48cde4c18c26 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -30,10 +30,11 @@ import java.io.FileNotFoundException; import static android.provider.DocumentsContract.Document.*; import static com.android.mtp.MtpDatabase.strings; import static com.android.mtp.MtpDatabaseConstants.*; +import static com.android.mtp.TestUtil.OPERATIONS_SUPPORTED; @SmallTest public class MtpDatabaseTest extends AndroidTestCase { - private final String[] COLUMN_NAMES = new String[] { + private static final String[] COLUMN_NAMES = new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID, MtpDatabaseConstants.COLUMN_DEVICE_ID, MtpDatabaseConstants.COLUMN_STORAGE_ID, @@ -75,13 +76,10 @@ public class MtpDatabaseTest extends AndroidTestCase { } public void testPutSingleStorageDocuments() throws Exception { - mDatabase.getMapper().startAddingDocuments(null); - mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord( - 0, "Device", null /* deviceKey */, true, new MtpRoot[0], null, null)); - mDatabase.getMapper().stopAddingDocuments(null); + addTestDevice(); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 1, "Storage", 1000, 2000, "") }); mDatabase.getMapper().stopAddingDocuments("1"); @@ -143,7 +141,7 @@ public class MtpDatabaseTest extends AndroidTestCase { addTestDevice(); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 1, "Storage", 1000, 2000, ""), new MtpRoot(0, 2, "Storage", 2000, 4000, ""), new MtpRoot(0, 3, "/@#%&<>Storage", 3000, 6000,"") @@ -273,7 +271,7 @@ public class MtpDatabaseTest extends AndroidTestCase { // Add device and two storages. addTestDevice(); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage A", 1000, 0, ""), new MtpRoot(0, 101, "Storage B", 1001, 0, "") }); @@ -304,7 +302,7 @@ public class MtpDatabaseTest extends AndroidTestCase { // Add two storages, but one's name is different from previous one. mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 200, "Storage A", 2000, 0, ""), new MtpRoot(0, 202, "Storage C", 2002, 0, "") }); @@ -398,10 +396,10 @@ public class MtpDatabaseTest extends AndroidTestCase { mDatabase.getMapper().startAddingDocuments("1"); mDatabase.getMapper().startAddingDocuments("2"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage", 0, 0, "") }); - mDatabase.getMapper().putStorageDocuments("2", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("2", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(1, 100, "Storage", 0, 0, "") }); @@ -442,10 +440,10 @@ public class MtpDatabaseTest extends AndroidTestCase { mDatabase.getMapper().startAddingDocuments("1"); mDatabase.getMapper().startAddingDocuments("2"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 200, "Storage", 2000, 0, "") }); - mDatabase.getMapper().putStorageDocuments("2", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("2", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(1, 300, "Storage", 3000, 0, "") }); mDatabase.getMapper().stopAddingDocuments("1"); @@ -562,7 +560,7 @@ public class MtpDatabaseTest extends AndroidTestCase { addTestDevice(); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage", 0, 0, ""), }); mDatabase.getMapper().clearMapping(); @@ -576,7 +574,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 200, "Storage", 2000, 0, ""), }); mDatabase.getMapper().clearMapping(); @@ -584,7 +582,7 @@ public class MtpDatabaseTest extends AndroidTestCase { addTestDevice(); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 300, "Storage", 3000, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("1"); @@ -625,7 +623,7 @@ public class MtpDatabaseTest extends AndroidTestCase { // Add a device and two storages that has same name. addTestDevice(); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 200, "Storage", 2000, 0, ""), new MtpRoot(0, 201, "Storage", 2001, 0, ""), }); @@ -658,13 +656,13 @@ public class MtpDatabaseTest extends AndroidTestCase { // The client code should be able to replace existing rows with new information. // Add one. mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("1"); // Replace it. mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage B", 1000, 1000, ""), }); mDatabase.getMapper().stopAddingDocuments("1"); @@ -703,7 +701,7 @@ public class MtpDatabaseTest extends AndroidTestCase { // Add one. addTestDevice(); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage A", 0, 0, ""), }); mDatabase.getMapper().clearMapping(); @@ -717,11 +715,11 @@ public class MtpDatabaseTest extends AndroidTestCase { // Add one. mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 101, "Storage B", 1000, 1000, ""), }); // Add one more before resolving unmapped documents. - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 102, "Storage B", 1000, 1000, ""), }); mDatabase.getMapper().stopAddingDocuments("1"); @@ -763,7 +761,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage A", 0, 0, "") }); mDatabase.getMapper().stopAddingDocuments("1"); @@ -778,7 +776,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage A", 0, 0, ""), new MtpRoot(0, 101, "Storage B", 0, 0, "") }); @@ -798,7 +796,7 @@ public class MtpDatabaseTest extends AndroidTestCase { addTestDevice(); mDatabase.getMapper().startAddingDocuments("1"); - mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { + mDatabase.getMapper().putStorageDocuments("1", OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage A", 0, 0, ""), }); mDatabase.getMapper().stopAddingDocuments("1"); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 884d132d2b42..db82bcba97e3 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -38,6 +38,7 @@ import java.util.Arrays; 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 { @@ -77,7 +78,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { 2048 /* total space */, "" /* no volume identifier */) }, - null, + OPERATIONS_SUPPORTED, null)); mProvider.resumeRootScanner(); @@ -98,7 +99,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } catch (Throwable error) { assertTrue(error instanceof IOException); } - assertEquals(0, mProvider.getOpenedDeviceIds().length); + assertEquals(0, mProvider.getOpenedDeviceRecordsCache().length); // Check if the following notification is the first one or not. mMtpManager.addValidDevice(new MtpDeviceRecord( @@ -115,7 +116,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { 2048 /* total space */, "" /* no volume identifier */) }, - null, + OPERATIONS_SUPPORTED, null)); mProvider.resumeRootScanner(); mResolver.waitForNotification(ROOTS_URI, 1); @@ -139,7 +140,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { 2048 /* total space */, "" /* no volume identifier */) }, - null, + OPERATIONS_SUPPORTED, null)); mMtpManager.setObjectHandles(0, 1, -1, new int[0]); mProvider.resumeRootScanner(); @@ -155,16 +156,16 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { assertEquals(1, cursor.getLong(1)); } { - final int [] openedDevice = mProvider.getOpenedDeviceIds(); + 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, null)) {} { - final int [] openedDevice = mProvider.getOpenedDeviceIds(); + final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache(); assertEquals(1, openedDevice.length); - assertEquals(0, openedDevice[0]); + assertEquals(0, openedDevice[0].deviceId); } } @@ -184,7 +185,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { 2048 /* total space */, "" /* no volume identifier */) }, - null, + OPERATIONS_SUPPORTED, null)); mMtpManager.addValidDevice(new MtpDeviceRecord( 1, @@ -200,7 +201,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { 4096 /* total space */, "Identifier B" /* no volume identifier */) }, - null, + new int[0] /* No operations supported */, null)); { @@ -225,7 +226,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { cursor.moveToNext(); cursor.moveToNext(); assertEquals("2", cursor.getString(0)); - assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); + assertEquals(Root.FLAG_SUPPORTS_IS_CHILD, 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)); @@ -241,7 +242,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { "Device key A", false /* unopened */, new MtpRoot[0], - null, + OPERATIONS_SUPPORTED, null)); mMtpManager.addValidDevice(new MtpDeviceRecord( 1, @@ -257,7 +258,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { 4096 /* total space */, "Identifier B" /* no volume identifier */) }, - null, + OPERATIONS_SUPPORTED, null)); { mProvider.openDevice(0); @@ -544,14 +545,14 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testBusyDevice() throws Exception { mMtpManager = new TestMtpManager(getContext()) { @Override - void openDevice(int deviceId) throws IOException { + 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], null, - null)); + 0, "Device A", null /* deviceKey */, false /* unopened */, new MtpRoot[0], + OPERATIONS_SUPPORTED, null)); mProvider.resumeRootScanner(); mResolver.waitForNotification(ROOTS_URI, 1); @@ -571,7 +572,8 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testLockedDevice() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); mMtpManager.addValidDevice(new MtpDeviceRecord( - 0, "Device A", null, false /* unopened */, new MtpRoot[0], null, null)); + 0, "Device A", null, false /* unopened */, new MtpRoot[0], OPERATIONS_SUPPORTED, + null)); mProvider.resumeRootScanner(); mResolver.waitForNotification(ROOTS_URI, 1); @@ -661,6 +663,60 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } } + 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), null)) { + assertEquals(1, cursor.getCount()); + cursor.moveToNext(); + assertEquals("3", cursor.getString(0)); + } + try { + mProvider.openDocument("3", "w", null); + fail(); + } catch (UnsupportedOperationException exception) {} + } + private void setupProvider(int flag) { mDatabase = new MtpDatabase(getContext(), flag); mProvider = new MtpDocumentsProvider(); @@ -691,7 +747,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { final int changeCount = mResolver.getChangeCount(ROOTS_URI); mMtpManager.addValidDevice( new MtpDeviceRecord(deviceId, "Device", null /* deviceKey */, false /* unopened */, - roots, null, null)); + roots, OPERATIONS_SUPPORTED, null)); mProvider.openDevice(deviceId); mResolver.waitForNotification(ROOTS_URI, changeCount + 1); return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID))); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java index 3043ce896a7e..5e0ee1e3e1fa 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java @@ -22,6 +22,7 @@ import android.os.ParcelFileDescriptor; import android.util.SparseArray; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -84,16 +85,16 @@ public class TestMtpManager extends MtpManager { } @Override - void openDevice(int deviceId) throws IOException { + MtpDeviceRecord openDevice(int deviceId) throws IOException { final MtpDeviceRecord device = mDevices.get(deviceId); if (device == null) { throw new IOException(); } - mDevices.put( - deviceId, - new MtpDeviceRecord( - device.deviceId, device.name, device.deviceKey, true, device.roots, null, - null)); + final MtpDeviceRecord record = new MtpDeviceRecord( + device.deviceId, device.name, device.deviceKey, true, device.roots, + device.operationsSupported, device.eventsSupported); + mDevices.put(deviceId, record); + return record; } @Override @@ -198,19 +199,6 @@ public class TestMtpManager extends MtpManager { } @Override - int[] getOpenedDeviceIds() { - final int[] result = new int[mDevices.size()]; - int count = 0; - for (int i = 0; i < mDevices.size(); i++) { - final MtpDeviceRecord device = mDevices.valueAt(i); - if (device.opened) { - result[count++] = device.deviceId; - } - } - return Arrays.copyOf(result, count); - } - - @Override byte[] getObject(int deviceId, int objectHandle, int expectedSize) throws IOException { return mImportFileBytes.get(pack(deviceId, objectHandle)); } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java index 34dd77bd3747..8adb68fe676e 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java @@ -19,6 +19,7 @@ package com.android.mtp; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; import android.hardware.usb.UsbManager; +import android.mtp.MtpConstants; import android.os.SystemClock; import java.io.FileNotFoundException; @@ -32,6 +33,12 @@ import java.util.Objects; final class TestUtil { private TestUtil() {} + static final int[] OPERATIONS_SUPPORTED = new int[] { + MtpConstants.OPERATION_GET_PARTIAL_OBJECT, + MtpConstants.OPERATION_SEND_OBJECT, + MtpConstants.OPERATION_SEND_OBJECT_INFO, + }; + /** * Requests permission for a MTP device and returns the first MTP device that has at least one * storage. @@ -59,14 +66,14 @@ final class TestUtil { static void addTestDevice(MtpDatabase database) throws FileNotFoundException { database.getMapper().startAddingDocuments(null); database.getMapper().putDeviceDocument(new MtpDeviceRecord( - 0, "Device", "device_key", /* opened is */ true, new MtpRoot[0], null, - null)); + 0, "Device", "device_key", /* opened is */ true, new MtpRoot[0], + OPERATIONS_SUPPORTED, null)); database.getMapper().stopAddingDocuments(null); } static void addTestStorage(MtpDatabase database, String parentId) throws FileNotFoundException { database.getMapper().startAddingDocuments(parentId); - database.getMapper().putStorageDocuments(parentId, new MtpRoot[] { + database.getMapper().putStorageDocuments(parentId, OPERATIONS_SUPPORTED, new MtpRoot[] { new MtpRoot(0, 100, "Storage", 1024, 1024, ""), }); database.getMapper().stopAddingDocuments(parentId); -- 2.11.0