From 20754c5a112e418c11cc88176283db2c4bf2efd6 Mon Sep 17 00:00:00 2001 From: Daichi Hirono Date: Tue, 15 Dec 2015 18:52:26 +0900 Subject: [PATCH] Store device document even if the device is not opened. BUG=26197156 Change-Id: I2a80bb3e85310cf63253173b1110a155ee1391ba --- .../src/com/android/mtp/Mapper.java | 27 +-- .../src/com/android/mtp/MtpDatabase.java | 34 +++- .../src/com/android/mtp/MtpDeviceRecord.java | 31 ++++ .../com/android/mtp/MtpDocumentsProvider.java | 9 +- .../src/com/android/mtp/MtpManager.java | 110 +++++++++--- .../src/com/android/mtp/RootScanner.java | 35 ++-- .../src/com/android/mtp/MtpDatabaseTest.java | 8 + .../android/mtp/MtpDocumentsProviderTest.java | 164 +++++++++--------- .../src/com/android/mtp/TestMtpManager.java | 72 ++++---- .../tests/src/com/android/mtp/TestUtil.java | 14 +- 10 files changed, 319 insertions(+), 185 deletions(-) create mode 100644 packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java index 322246ac2ccba..cb49535ea55db 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java @@ -56,22 +56,26 @@ class Mapper { mDatabase = database; } - synchronized String putDeviceDocument(int deviceId, String name, MtpRoot[] roots) { + /** + * Puts device information to database. + * @return If device is added to the database. + */ + synchronized boolean putDeviceDocument(MtpDeviceRecord device) { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null)); database.beginTransaction(); try { final ContentValues[] valuesList = new ContentValues[1]; valuesList[0] = new ContentValues(); - MtpDatabase.getDeviceDocumentValues(valuesList[0], deviceId, name, roots); - putDocuments( + MtpDatabase.getDeviceDocumentValues(valuesList[0], device); + final boolean changed = putDocuments( valuesList, COLUMN_PARENT_DOCUMENT_ID + " IS NULL", EMPTY_ARGS, /* heuristic */ false, COLUMN_DEVICE_ID); database.setTransactionSuccessful(); - return valuesList[0].getAsString(Document.COLUMN_DOCUMENT_ID); + return changed; } finally { database.endTransaction(); } @@ -249,7 +253,7 @@ class Mapper { * If the mapping mode is not heuristic, it just adds the rows to the database or updates the * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then - * {@link #stopAddingDocuments(String, String[], String)} turns the pending rows into 'valid' + * {@link #stopAddingDocuments(String)} turns the pending rows into 'valid' * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param valuesList Values for documents to be stored in the database. @@ -452,12 +456,15 @@ class Mapper { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); values.clear(); final Cursor cursor = database.query(table, null, selection, args, null, null, null, "1"); - if (cursor.getCount() == 0) { - return; + try { + if (cursor.getCount() == 0) { + return; + } + cursor.moveToNext(); + DatabaseUtils.cursorRowToContentValues(cursor, values); + } finally { + cursor.close(); } - cursor.moveToNext(); - DatabaseUtils.cursorRowToContentValues(cursor, values); - cursor.close(); } /** diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index 1ba3e314ff048..10941eb02a0ab 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -18,11 +18,11 @@ package com.android.mtp; import static com.android.mtp.MtpDatabaseConstants.*; +import android.annotation.Nullable; import android.content.ContentValues; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; -import android.database.MatrixCursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; @@ -36,8 +36,6 @@ import android.provider.DocumentsContract.Root; import com.android.internal.annotations.VisibleForTesting; import java.io.FileNotFoundException; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; /** @@ -183,6 +181,27 @@ class MtpDatabase { deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId)); } + @Nullable String getDocumentIdForDevice(int deviceId) { + final Cursor cursor = mDatabase.query( + TABLE_DOCUMENTS, + strings(Document.COLUMN_DOCUMENT_ID), + COLUMN_DOCUMENT_TYPE + " = ? AND " + COLUMN_DEVICE_ID + " = ?", + strings(DOCUMENT_TYPE_DEVICE, deviceId), + null, + null, + null, + "1"); + try { + if (cursor.moveToNext()) { + return cursor.getString(0); + } else { + return null; + } + } finally { + cursor.close(); + } + } + /** * Obtains parent document ID. * @param documentId @@ -361,23 +380,22 @@ class MtpDatabase { context.deleteDatabase(DATABASE_NAME); } - static void getDeviceDocumentValues( - ContentValues values, int deviceId, String name, MtpRoot[] roots) { + static void getDeviceDocumentValues(ContentValues values, MtpDeviceRecord device) { values.clear(); - values.put(COLUMN_DEVICE_ID, deviceId); + values.put(COLUMN_DEVICE_ID, device.deviceId); values.putNull(COLUMN_STORAGE_ID); values.putNull(COLUMN_OBJECT_HANDLE); values.putNull(COLUMN_PARENT_DOCUMENT_ID); values.put(COLUMN_ROW_STATE, ROW_STATE_VALID); values.put(COLUMN_DOCUMENT_TYPE, DOCUMENT_TYPE_DEVICE); values.put(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); - values.put(Document.COLUMN_DISPLAY_NAME, name); + values.put(Document.COLUMN_DISPLAY_NAME, device.name); values.putNull(Document.COLUMN_SUMMARY); values.putNull(Document.COLUMN_LAST_MODIFIED); values.put(Document.COLUMN_ICON, R.drawable.ic_root_mtp); values.put(Document.COLUMN_FLAGS, 0); long size = 0; - for (final MtpRoot root : roots) { + for (final MtpRoot root : device.roots) { size += root.mMaxCapacity - root.mFreeSpace; } values.put(Document.COLUMN_SIZE, size); diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java new file mode 100644 index 0000000000000..71df5c15bed4e --- /dev/null +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDeviceRecord.java @@ -0,0 +1,31 @@ +/* + * 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; + +class MtpDeviceRecord { + public final int deviceId; + public final String name; + public final boolean opened; + public final MtpRoot[] roots; + + MtpDeviceRecord(int deviceId, String name, boolean opened, MtpRoot[] roots) { + this.deviceId = deviceId; + this.name = name; + this.opened = opened; + this.roots = roots; + } +} diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index caa2024027915..57a68ba7c9eed 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -247,7 +247,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { closeDeviceInternal(deviceId); mDatabase.removeDeviceRows(deviceId); } - mRootScanner.notifyChange(); + mRootScanner.resume(); } int[] getOpenedDeviceIds() { @@ -258,7 +258,12 @@ public class MtpDocumentsProvider extends DocumentsProvider { String getDeviceName(int deviceId) throws IOException { synchronized (mDeviceListLock) { - return mMtpManager.getDeviceName(deviceId); + for (final MtpDeviceRecord device : mMtpManager.getDevices()) { + if (device.deviceId == deviceId) { + return device.name; + } + } + throw new IOException("Not found the device: " + Integer.toString(deviceId)); } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java index e7f94b7a7bca3..88cab8b30a803 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -17,8 +17,10 @@ package com.android.mtp; import android.content.Context; +import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.mtp.MtpConstants; import android.mtp.MtpDevice; @@ -26,12 +28,14 @@ import android.mtp.MtpEvent; import android.mtp.MtpObjectInfo; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; +import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; /** * The model wrapping android.mtp API. @@ -39,6 +43,27 @@ import java.io.IOException; class MtpManager { final static int OBJECT_HANDLE_ROOT_CHILDREN = -1; + /** + * Subclass for PTP. + */ + private static final int SUBCLASS_STILL_IMAGE_CAPTURE = 1; + + /** + * Subclass for Android style MTP. + */ + private static final int SUBCLASS_MTP = 0xff; + + /** + * Protocol for Picture Transfer Protocol (PIMA 15470). + */ + private static final int PROTOCOL_PICTURE_TRANSFER = 1; + + /** + * Protocol for Android style MTP. + */ + 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<>(); @@ -92,6 +117,33 @@ class MtpManager { mDevices.remove(deviceId); } + synchronized MtpDeviceRecord[] getDevices() { + final ArrayList devices = new ArrayList<>(); + for (UsbDevice device : mManager.getDeviceList().values()) { + if (!isMtpDevice(device)) { + continue; + } + final boolean opened = mDevices.get(device.getDeviceId()) != null; + final String name = device.getProductName(); + MtpRoot[] roots; + if (opened) { + try { + roots = getRoots(device.getDeviceId()); + } catch (IOException exp) { + Log.e(MtpDocumentsProvider.TAG, exp.getMessage()); + // 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]; + } + } else { + roots = new MtpRoot[0]; + } + devices.add(new MtpDeviceRecord(device.getDeviceId(), name, opened, roots)); + } + 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++) { @@ -100,28 +152,6 @@ class MtpManager { return result; } - String getDeviceName(int deviceId) throws IOException { - return getDevice(deviceId).getDeviceInfo().getModel(); - } - - MtpRoot[] getRoots(int deviceId) throws IOException { - final MtpDevice device = getDevice(deviceId); - synchronized (device) { - final int[] storageIds = device.getStorageIds(); - if (storageIds == null) { - throw new IOException("Failed to obtain storage IDs."); - } - final MtpRoot[] results = new MtpRoot[storageIds.length]; - for (int i = 0; i < storageIds.length; i++) { - results[i] = new MtpRoot( - device.getDeviceId(), - device.getDeviceInfo().getModel(), - device.getStorageInfo(storageIds[i])); - } - return results; - } - } - MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); @@ -212,4 +242,40 @@ class MtpManager { } return device; } + + private MtpRoot[] getRoots(int deviceId) throws IOException { + final MtpDevice device = getDevice(deviceId); + synchronized (device) { + final int[] storageIds = device.getStorageIds(); + if (storageIds == null) { + throw new IOException("Failed to obtain storage IDs."); + } + final MtpRoot[] results = new MtpRoot[storageIds.length]; + for (int i = 0; i < storageIds.length; i++) { + results[i] = new MtpRoot( + device.getDeviceId(), + device.getDeviceInfo().getModel(), + device.getStorageInfo(storageIds[i])); + } + return results; + } + } + + static boolean isMtpDevice(UsbDevice device) { + for (int i = 0; i < device.getInterfaceCount(); i++) { + final UsbInterface usbInterface = device.getInterface(i); + if ((usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE && + usbInterface.getInterfaceSubclass() == SUBCLASS_STILL_IMAGE_CAPTURE && + usbInterface.getInterfaceProtocol() == PROTOCOL_PICTURE_TRANSFER)) { + return true; + } + if (usbInterface.getInterfaceClass() == UsbConstants.USB_SUBCLASS_VENDOR_SPEC && + usbInterface.getInterfaceSubclass() == SUBCLASS_MTP && + usbInterface.getInterfaceProtocol() == PROTOCOL_MTP && + usbInterface.getName().equals("MTP")) { + return true; + } + } + return false; + } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java index 197dec868c9a8..e6c2726f531aa 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java @@ -2,12 +2,10 @@ package com.android.mtp; import android.content.ContentResolver; import android.content.res.Resources; -import android.database.sqlite.SQLiteException; import android.net.Uri; import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; -import android.util.SparseArray; import java.io.IOException; import java.util.HashMap; @@ -108,36 +106,29 @@ final class RootScanner { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); int pollingCount = 0; while (!Thread.interrupted()) { - final int[] deviceIds = mManager.getOpenedDeviceIds(); - final Map rootsMap = new HashMap<>(); boolean changed = false; // Update devices. + final MtpDeviceRecord[] devices = mManager.getDevices(); mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */); - for (final int deviceId : deviceIds) { - try { - final MtpRoot[] roots = mManager.getRoots(deviceId); - final String id = mDatabase.getMapper().putDeviceDocument( - deviceId, - mManager.getDeviceName(deviceId), - roots); - if (id != null) { - changed = true; - rootsMap.put(id, roots); - } - } catch (IOException exception) { - // The error may happen on the device. We would like to continue getting - // roots for other devices. - Log.e(MtpDocumentsProvider.TAG, exception.getMessage()); + for (final MtpDeviceRecord device : devices) { + if (mDatabase.getMapper().putDeviceDocument(device)) { + changed = true; } } - mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */); + if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) { + changed = true; + } // Update roots. - for (final String documentId : rootsMap.keySet()) { + for (final MtpDeviceRecord device : devices) { + final String documentId = mDatabase.getDocumentIdForDevice(device.deviceId); + if (documentId == null) { + continue; + } mDatabase.getMapper().startAddingDocuments(documentId); if (mDatabase.getMapper().putRootDocuments( - documentId, mResources, rootsMap.get(documentId))) { + documentId, mResources, device.roots)) { changed = true; } if (mDatabase.getMapper().stopAddingDocuments(documentId)) { diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index 6d57c5b087f89..b74517574c2ee 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -871,4 +871,12 @@ public class MtpDatabaseTest extends AndroidTestCase { cursor.close(); } } + + public void testGetDocumentIdForDevice() { + mDatabase.getMapper().startAddingDocuments(null); + mDatabase.getMapper().putDeviceDocument( + new MtpDeviceRecord(100, "Device", true, new MtpRoot[0])); + mDatabase.getMapper().stopAddingDocuments(null); + assertEquals("1", mDatabase.getDocumentIdForDevice(100)); + } } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index b0e9722c74bb8..884b1e2ccb1df 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -23,7 +23,6 @@ import android.net.Uri; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import java.io.FileNotFoundException; @@ -56,17 +55,20 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testOpenAndCloseDevice() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mMtpManager.setRoots(0, new MtpRoot[] { - new MtpRoot( - 0 /* deviceId */, - 1 /* storageId */, - "Device A" /* device model name */, - "Storage A" /* volume description */, - 1024 /* free space */, - 2048 /* total space */, - "" /* no volume identifier */) - }); + mMtpManager.addValidDevice(new MtpDeviceRecord( + 0, + "Device", + false /* unopened */, + new MtpRoot[] { + new MtpRoot( + 0 /* deviceId */, + 1 /* storageId */, + "Device A" /* device model name */, + "Storage A" /* volume description */, + 1024 /* free space */, + 2048 /* total space */, + "" /* no volume identifier */) + })); mProvider.openDevice(0); mResolver.waitForNotification(ROOTS_URI, 1); @@ -92,45 +94,54 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } // Check if the following notification is the first one or not. - mMtpManager.addValidDevice(0); - mMtpManager.setRoots(0, new MtpRoot[] { - new MtpRoot( - 0 /* deviceId */, - 1 /* storageId */, - "Device A" /* device model name */, - "Storage A" /* volume description */, - 1024 /* free space */, - 2048 /* total space */, - "" /* no volume identifier */) - }); + mMtpManager.addValidDevice(new MtpDeviceRecord( + 0, + "Device", + false /* unopened */, + new MtpRoot[] { + new MtpRoot( + 0 /* deviceId */, + 1 /* storageId */, + "Device A" /* device model name */, + "Storage A" /* volume description */, + 1024 /* free space */, + 2048 /* total space */, + "" /* no volume identifier */) + })); mProvider.openDevice(0); mResolver.waitForNotification(ROOTS_URI, 1); } public void testQueryRoots() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mMtpManager.addValidDevice(1); - mMtpManager.setRoots(0, new MtpRoot[] { - new MtpRoot( - 0 /* deviceId */, - 1 /* storageId */, - "Device A" /* device model name */, - "Storage A" /* volume description */, - 1024 /* free space */, - 2048 /* total space */, - "" /* no volume identifier */) - }); - mMtpManager.setRoots(1, new MtpRoot[] { - new MtpRoot( - 1 /* deviceId */, - 1 /* storageId */, - "Device B" /* device model name */, - "Storage B" /* volume description */, - 2048 /* free space */, - 4096 /* total space */, - "Identifier B" /* no volume identifier */) - }); + mMtpManager.addValidDevice(new MtpDeviceRecord( + 0, + "Device", + false /* unopened */, + new MtpRoot[] { + new MtpRoot( + 0 /* deviceId */, + 1 /* storageId */, + "Device A" /* device model name */, + "Storage A" /* volume description */, + 1024 /* free space */, + 2048 /* total space */, + "" /* no volume identifier */) + })); + mMtpManager.addValidDevice(new MtpDeviceRecord( + 1, + "Device", + false /* unopened */, + new MtpRoot[] { + new MtpRoot( + 1 /* deviceId */, + 1 /* storageId */, + "Device B" /* device model name */, + "Storage B" /* volume description */, + 2048 /* free space */, + 4096 /* total space */, + "Identifier B" /* no volume identifier */) + })); { mProvider.openDevice(0); @@ -138,11 +149,11 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { final Cursor cursor = mProvider.queryRoots(null); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals("2", cursor.getString(0)); + assertEquals("3", cursor.getString(0)); assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, cursor.getInt(1)); assertEquals(R.drawable.ic_root_mtp, cursor.getInt(2)); assertEquals("Device A Storage A", cursor.getString(3)); - assertEquals("2", cursor.getString(4)); + assertEquals("3", cursor.getString(4)); assertEquals(1024, cursor.getInt(5)); } @@ -164,19 +175,22 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testQueryRoots_error() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mMtpManager.addValidDevice(1); - // Not set roots for device 0 so that MtpManagerMock#getRoots throws IOException. - mMtpManager.setRoots(1, new MtpRoot[] { - new MtpRoot( - 1 /* deviceId */, - 1 /* storageId */, - "Device B" /* device model name */, - "Storage B" /* volume description */, - 2048 /* free space */, - 4096 /* total space */, - "Identifier B" /* no volume identifier */) - }); + mMtpManager.addValidDevice( + new MtpDeviceRecord(0, "Device", false /* unopened */, new MtpRoot[0])); + mMtpManager.addValidDevice(new MtpDeviceRecord( + 1, + "Device", + false /* unopened */, + new MtpRoot[] { + new MtpRoot( + 1 /* deviceId */, + 1 /* storageId */, + "Device B" /* device model name */, + "Storage B" /* volume description */, + 2048 /* free space */, + 4096 /* total space */, + "Identifier B" /* no volume identifier */) + })); { mProvider.openDevice(0); mProvider.openDevice(1); @@ -185,20 +199,17 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { final Cursor cursor = mProvider.queryRoots(null); assertEquals(1, cursor.getCount()); cursor.moveToNext(); - assertEquals("2", cursor.getString(0)); + assertEquals("3", cursor.getString(0)); assertEquals(Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE, 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("3", cursor.getString(4)); assertEquals(2048, cursor.getInt(5)); } } public void testQueryDocument() throws IOException, InterruptedException, TimeoutException { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mProvider.openDevice(0); - setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") }); setupDocuments( 0, @@ -236,9 +247,6 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testQueryDocument_directory() throws IOException, InterruptedException, TimeoutException { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mProvider.openDevice(0); - setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") }); setupDocuments( 0, @@ -274,9 +282,6 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testQueryDocument_forRoot() throws IOException, InterruptedException, TimeoutException { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mProvider.openDevice(0); - setupRoots(0, new MtpRoot[] { new MtpRoot( 0 /* deviceId */, @@ -301,9 +306,6 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testQueryChildDocuments() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mProvider.openDevice(0); - setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") }); setupDocuments( 0, @@ -337,8 +339,6 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testQueryChildDocuments_cursorError() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mProvider.openDevice(0); try { mProvider.queryChildDocuments("1", null, null); fail(); @@ -349,8 +349,6 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testQueryChildDocuments_documentError() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mProvider.openDevice(0); setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") }); mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); try { @@ -363,8 +361,6 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mProvider.openDevice(0); setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 0, 0, "") }); @@ -385,8 +381,6 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { public void testDeleteDocument_error() throws IOException, InterruptedException, TimeoutException { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); - mMtpManager.addValidDevice(0); - mProvider.openDevice(0); setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 0, 0, "") }); @@ -427,9 +421,11 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } private String[] setupRoots(int deviceId, MtpRoot[] roots) - throws FileNotFoundException, InterruptedException, TimeoutException { + throws InterruptedException, TimeoutException, IOException { final int changeCount = mResolver.getChangeCount(ROOTS_URI); - mMtpManager.setRoots(deviceId, roots); + mMtpManager.addValidDevice( + new MtpDeviceRecord(deviceId, "Device", false /* unopened */, roots)); + 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 ddc18a4a28e16..bbd0a300c6a1e 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java @@ -19,14 +19,12 @@ package com.android.mtp; import android.content.Context; import android.mtp.MtpObjectInfo; import android.os.ParcelFileDescriptor; +import android.util.SparseArray; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; -import java.util.TreeSet; public class TestMtpManager extends MtpManager { public static final int CREATED_DOCUMENT_HANDLE = 1000; @@ -35,9 +33,7 @@ public class TestMtpManager extends MtpManager { return Arrays.toString(args); } - private final Set mValidDevices = new HashSet<>(); - private final Set mOpenedDevices = new TreeSet<>(); - private final Map mRoots = new HashMap<>(); + private final SparseArray mDevices = new SparseArray<>(); private final Map mObjectInfos = new HashMap<>(); private final Map mObjectHandles = new HashMap<>(); private final Map mThumbnailBytes = new HashMap<>(); @@ -47,18 +43,14 @@ public class TestMtpManager extends MtpManager { super(context); } - void addValidDevice(int deviceId) { - mValidDevices.add(deviceId); + void addValidDevice(MtpDeviceRecord device) { + mDevices.put(device.deviceId, device); } void setObjectHandles(int deviceId, int storageId, int parentHandle, int[] objectHandles) { mObjectHandles.put(pack(deviceId, storageId, parentHandle), objectHandles); } - void setRoots(int deviceId, MtpRoot[] roots) { - mRoots.put(deviceId, roots); - } - void setObjectInfo(int deviceId, MtpObjectInfo objectInfo) { mObjectInfos.put(pack(deviceId, objectInfo.getObjectHandle()), objectInfo); } @@ -75,29 +67,41 @@ public class TestMtpManager extends MtpManager { mThumbnailBytes.put(pack(deviceId, objectHandle), bytes); } + @Override + MtpDeviceRecord[] getDevices() { + final MtpDeviceRecord[] result = new MtpDeviceRecord[mDevices.size()]; + for (int i = 0; i < mDevices.size(); i++) { + final MtpDeviceRecord device = mDevices.valueAt(i); + if (device.opened) { + result[i] = device; + } else { + result[i] = new MtpDeviceRecord( + device.deviceId, device.name, device.opened, new MtpRoot[0]); + } + } + return result; + } + @Override void openDevice(int deviceId) throws IOException { - if (!mValidDevices.contains(deviceId) || mOpenedDevices.contains(deviceId)) { + final MtpDeviceRecord device = mDevices.get(deviceId); + if (device == null || device.opened) { throw new IOException(); } - mOpenedDevices.add(deviceId); + mDevices.put( + deviceId, + new MtpDeviceRecord(device.deviceId, device.name, true, device.roots)); } @Override void closeDevice(int deviceId) throws IOException { - if (!mValidDevices.contains(deviceId) || !mOpenedDevices.contains(deviceId)) { + final MtpDeviceRecord device = mDevices.get(deviceId); + if (device == null || !device.opened) { throw new IOException(); } - mOpenedDevices.remove(deviceId); - } - - @Override - MtpRoot[] getRoots(int deviceId) throws IOException { - if (mRoots.containsKey(deviceId)) { - return mRoots.get(deviceId); - } else { - throw new IOException("getRoots error: " + Integer.toString(deviceId)); - } + mDevices.put( + deviceId, + new MtpDeviceRecord(device.deviceId, device.name, false, device.roots)); } @Override @@ -189,16 +193,14 @@ public class TestMtpManager extends MtpManager { @Override int[] getOpenedDeviceIds() { - int i = 0; - final int[] result = new int[mOpenedDevices.size()]; - for (int deviceId : mOpenedDevices) { - result[i++] = deviceId; + 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 result; - } - - @Override - String getDeviceName(int deviceId) throws IOException { - return "Device"; + return Arrays.copyOf(result, count); } } diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java index f91032147fff9..611e8317c4e45 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestUtil.java @@ -120,9 +120,19 @@ final class TestUtil { private static void waitForStorages( TestResultInstrumentation instrumentation, MtpManager manager, - int deviceId) throws IOException, InterruptedException { + int deviceId) throws InterruptedException, IOException { while (true) { - if (manager.getRoots(deviceId).length == 0) { + MtpDeviceRecord device = null; + for (final MtpDeviceRecord deviceCandidate : manager.getDevices()) { + if (deviceCandidate.deviceId == deviceId) { + device = deviceCandidate; + break; + } + } + if (device == null) { + throw new IOException("Device was detached."); + } + if (device.roots.length == 0) { instrumentation.show("Wait for storages."); Thread.sleep(1000); continue;