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
diff --git a/src/com/android/mtp/AppFuse.java b/src/com/android/mtp/AppFuse.java
index 6a98405..38435f4 100644
--- a/src/com/android/mtp/AppFuse.java
+++ b/src/com/android/mtp/AppFuse.java
@@ -133,6 +133,8 @@
return mCallback.readObjectBytes(inode, offset, size, mBuffer);
} catch (IOException e) {
return -OsConstants.EIO;
+ } catch (UnsupportedOperationException e) {
+ return -OsConstants.ENOTSUP;
}
}
diff --git a/src/com/android/mtp/Mapper.java b/src/com/android/mtp/Mapper.java
index 9578e6b..4bed003 100644
--- a/src/com/android/mtp/Mapper.java
+++ b/src/com/android/mtp/Mapper.java
@@ -89,7 +89,8 @@
* @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 @@
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/src/com/android/mtp/MtpDatabase.java b/src/com/android/mtp/MtpDatabase.java
index e14109a..701543b 100644
--- a/src/com/android/mtp/MtpDatabase.java
+++ b/src/com/android/mtp/MtpDatabase.java
@@ -689,9 +689,7 @@
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 @@
/**
* 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 @@
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 @@
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/src/com/android/mtp/MtpDeviceRecord.java b/src/com/android/mtp/MtpDeviceRecord.java
index 71716bd..393c4de 100644
--- a/src/com/android/mtp/MtpDeviceRecord.java
+++ b/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 @@
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/src/com/android/mtp/MtpDocumentsProvider.java b/src/com/android/mtp/MtpDocumentsProvider.java
index 4849978..a1c5c9b 100644
--- a/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/src/com/android/mtp/MtpDocumentsProvider.java
@@ -201,6 +201,7 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 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 @@
getDeviceToolkit(deviceId).mDocumentLoader.close();
mDeviceToolkits.remove(deviceId);
mMtpManager.closeDevice(deviceId);
- if (getOpenedDeviceIds().length == 0) {
+ if (mDeviceToolkits.size() == 0) {
mRootScanner.pause();
}
}
@@ -488,11 +497,14 @@
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 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/src/com/android/mtp/MtpDocumentsService.java b/src/com/android/mtp/MtpDocumentsService.java
index 9c4952b..9b42b78 100644
--- a/src/com/android/mtp/MtpDocumentsService.java
+++ b/src/com/android/mtp/MtpDocumentsService.java
@@ -67,38 +67,25 @@
*/
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/src/com/android/mtp/MtpManager.java b/src/com/android/mtp/MtpManager.java
index 37dc761..c49005f 100644
--- a/src/com/android/mtp/MtpManager.java
+++ b/src/com/android/mtp/MtpManager.java
@@ -65,16 +65,14 @@
*/
private static final int PROTOCOL_MTP = 0;
-
private final UsbManager mManager;
- // TODO: Save and restore the set of opened device.
private final SparseArray<MtpDevice> 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 @@
}
mDevices.put(deviceId, device);
+
+ return createDeviceRecord(rawDevice);
}
synchronized void closeDevice(int deviceId) throws IOException {
@@ -126,45 +126,11 @@
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 @@
}
}
+ 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/src/com/android/mtp/RootScanner.java b/src/com/android/mtp/RootScanner.java
index a48bf12..82ba21f 100644
--- a/src/com/android/mtp/RootScanner.java
+++ b/src/com/android/mtp/RootScanner.java
@@ -149,7 +149,8 @@
}
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/tests/src/com/android/mtp/DocumentLoaderTest.java b/tests/src/com/android/mtp/DocumentLoaderTest.java
index b75a9e6..a000895 100644
--- a/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -48,7 +48,7 @@
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/tests/src/com/android/mtp/MtpDatabaseTest.java b/tests/src/com/android/mtp/MtpDatabaseTest.java
index 05c9c57..48cde4c 100644
--- a/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -30,10 +30,11 @@
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 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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
}
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 @@
}
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 @@
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/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 884d132..db82bcb 100644
--- a/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -38,6 +38,7 @@
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 @@
2048 /* total space */,
"" /* no volume identifier */)
},
- null,
+ OPERATIONS_SUPPORTED,
null));
mProvider.resumeRootScanner();
@@ -98,7 +99,7 @@
} 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 @@
2048 /* total space */,
"" /* no volume identifier */)
},
- null,
+ OPERATIONS_SUPPORTED,
null));
mProvider.resumeRootScanner();
mResolver.waitForNotification(ROOTS_URI, 1);
@@ -139,7 +140,7 @@
2048 /* total space */,
"" /* no volume identifier */)
},
- null,
+ OPERATIONS_SUPPORTED,
null));
mMtpManager.setObjectHandles(0, 1, -1, new int[0]);
mProvider.resumeRootScanner();
@@ -155,16 +156,16 @@
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 @@
2048 /* total space */,
"" /* no volume identifier */)
},
- null,
+ OPERATIONS_SUPPORTED,
null));
mMtpManager.addValidDevice(new MtpDeviceRecord(
1,
@@ -200,7 +201,7 @@
4096 /* total space */,
"Identifier B" /* no volume identifier */)
},
- null,
+ new int[0] /* No operations supported */,
null));
{
@@ -225,7 +226,7 @@
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 @@
"Device key A",
false /* unopened */,
new MtpRoot[0],
- null,
+ OPERATIONS_SUPPORTED,
null));
mMtpManager.addValidDevice(new MtpDeviceRecord(
1,
@@ -257,7 +258,7 @@
4096 /* total space */,
"Identifier B" /* no volume identifier */)
},
- null,
+ OPERATIONS_SUPPORTED,
null));
{
mProvider.openDevice(0);
@@ -544,14 +545,14 @@
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 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 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 @@
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/tests/src/com/android/mtp/TestMtpManager.java b/tests/src/com/android/mtp/TestMtpManager.java
index 3043ce8..5e0ee1e 100644
--- a/tests/src/com/android/mtp/TestMtpManager.java
+++ b/tests/src/com/android/mtp/TestMtpManager.java
@@ -22,6 +22,7 @@
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 @@
}
@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 @@
}
@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/tests/src/com/android/mtp/TestUtil.java b/tests/src/com/android/mtp/TestUtil.java
index 34dd77b..8adb68f 100644
--- a/tests/src/com/android/mtp/TestUtil.java
+++ b/tests/src/com/android/mtp/TestUtil.java
@@ -19,6 +19,7 @@
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 @@
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 @@
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);