Loading packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +106 −134 Original line number Diff line number Diff line Loading @@ -81,11 +81,9 @@ class DocumentLoader implements AutoCloseable { // 3. startAddingChildDocuemnts. // 4. stopAddingChildDocuments - It removes the new document added at the step 2, // because it is not updated between start/stopAddingChildDocuments. task = LoaderTask.create(mDatabase, mMtpManager, mDevice.operationsSupported, parent); task.fillDocuments(loadDocuments( mMtpManager, parent.mDeviceId, task.getUnloadedObjectHandles(NUM_INITIAL_ENTRIES))); task = new LoaderTask(mMtpManager, mDatabase, mDevice.operationsSupported, parent); task.loadObjectHandles(); task.loadObjectInfoList(NUM_INITIAL_ENTRIES); } else { // Once remove the existing task in order to add it to the head of the list. mTaskList.remove(task); Loading Loading @@ -130,15 +128,11 @@ class DocumentLoader implements AutoCloseable { Preconditions.checkState(existingTask.getState() != LoaderTask.STATE_LOADING); mTaskList.remove(existingTask); } try { final LoaderTask newTask = LoaderTask.create( mDatabase, mMtpManager, mDevice.operationsSupported, identifier); final LoaderTask newTask = new LoaderTask( mMtpManager, mDatabase, mDevice.operationsSupported, identifier); newTask.loadObjectHandles(); mTaskList.addFirst(newTask); return newTask; } catch (IOException exception) { Log.e(MtpDocumentsProvider.TAG, "Failed to create a task for mapping", exception); // Continue to release the background thread. } } mBackgroundThread = null; Loading Loading @@ -169,24 +163,6 @@ class DocumentLoader implements AutoCloseable { mTaskList.clearTask(parentIdentifier); } /** * Helper method to loads multiple object info. */ private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles) throws IOException { final ArrayList<MtpObjectInfo> objects = new ArrayList<>(); for (int i = 0; i < handles.length; i++) { final MtpObjectInfo info = manager.getObjectInfo(deviceId, handles[i]); if (info == null) { Log.e(MtpDocumentsProvider.TAG, "Failed to obtain object info handle=" + handles[i]); continue; } objects.add(info); } return objects.toArray(new MtpObjectInfo[objects.size()]); } /** * Background thread to fetch object info. */ Loading @@ -203,12 +179,7 @@ class DocumentLoader implements AutoCloseable { if (task == null) { return; } try { final MtpObjectInfo[] objectInfos = loadDocuments( mMtpManager, task.mIdentifier.mDeviceId, task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES)); task.fillDocuments(objectInfos); task.loadObjectInfoList(NUM_LOADING_ENTRIES); final boolean shouldNotify = task.mLastNotified.getTime() < new Date().getTime() - NOTIFY_PERIOD_MS || Loading @@ -216,9 +187,6 @@ class DocumentLoader implements AutoCloseable { if (shouldNotify) { task.notify(mResolver); } } catch (IOException exception) { task.setError(exception); } } } } Loading Loading @@ -271,43 +239,66 @@ class DocumentLoader implements AutoCloseable { * Each task is responsible for fetching child documents for the given parent document. */ private static class LoaderTask { static final int STATE_LOADING = 0; static final int STATE_COMPLETED = 1; static final int STATE_ERROR = 2; static final int STATE_START = 0; static final int STATE_LOADING = 1; static final int STATE_COMPLETED = 2; static final int STATE_ERROR = 3; final MtpManager mManager; final MtpDatabase mDatabase; final int[] mOperationsSupported; final Identifier mIdentifier; final int[] mObjectHandles; int[] mObjectHandles; int mState; Date mLastNotified; int mNumLoaded; Exception mError; int mPosition; IOException mError; LoaderTask(MtpDatabase database, int[] operationsSupported, Identifier identifier, int[] objectHandles) { LoaderTask(MtpManager manager, MtpDatabase database, int[] operationsSupported, Identifier identifier) { Preconditions.checkNotNull(operationsSupported); Preconditions.checkNotNull(objectHandles); mManager = manager; mDatabase = database; mOperationsSupported = operationsSupported; mIdentifier = identifier; mObjectHandles = objectHandles; mNumLoaded = 0; mObjectHandles = null; mState = STATE_START; mPosition = 0; mLastNotified = new Date(); } synchronized void loadObjectHandles() { assert mState == STATE_START; int parentHandle = mIdentifier.mObjectHandle; // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to // getObjectHandles if we would like to obtain children under the root. if (mIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; } try { mObjectHandles = mManager.getObjectHandles( mIdentifier.mDeviceId, mIdentifier.mStorageId, parentHandle); mState = STATE_LOADING; } catch (IOException error) { mError = error; mState = STATE_ERROR; } } /** * Returns a cursor that traverses the child document of the parent document handled by the * task. * The returned task may have a EXTRA_LOADING flag. */ Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException { synchronized Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException { final Bundle extras = new Bundle(); switch (getState()) { case STATE_LOADING: extras.putBoolean(DocumentsContract.EXTRA_LOADING, true); break; case STATE_ERROR: throw new IOException(mError); throw mError; } final Cursor cursor = Loading @@ -318,100 +309,81 @@ class DocumentLoader implements AutoCloseable { return cursor; } /** * Returns a state of the task. */ int getState() { if (mError != null) { return STATE_ERROR; } else if (mNumLoaded == mObjectHandles.length) { return STATE_COMPLETED; } else { return STATE_LOADING; } } /** * Obtains object handles that have not been loaded yet. */ int[] getUnloadedObjectHandles(int count) { return Arrays.copyOfRange( mObjectHandles, mNumLoaded, Math.min(mNumLoaded + count, mObjectHandles.length)); } /** * Notifies a change of child list of the document. */ void notify(ContentResolver resolver) { resolver.notifyChange(createUri(), null, false); mLastNotified = new Date(); } /** * Stores object information into database. */ void fillDocuments(MtpObjectInfo[] objectInfoList) { if (objectInfoList.length == 0 || getState() != STATE_LOADING) { void loadObjectInfoList(int count) { synchronized (this) { if (mState != STATE_LOADING) { return; } if (mPosition == 0) { try{ if (mNumLoaded == 0) { mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId); } catch (FileNotFoundException error) { mError = error; mState = STATE_ERROR; return; } mDatabase.getMapper().putChildDocuments( mIdentifier.mDeviceId, mIdentifier.mDocumentId, mOperationsSupported, objectInfoList); mNumLoaded += objectInfoList.length; if (getState() != STATE_LOADING) { mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } } catch (FileNotFoundException exception) { setErrorInternal(exception); } final ArrayList<MtpObjectInfo> infoList = new ArrayList<>(); for (int chunkEnd = mPosition + count; mPosition < mObjectHandles.length && mPosition < chunkEnd; mPosition++) { try { infoList.add(mManager.getObjectInfo( mIdentifier.mDeviceId, mObjectHandles[mPosition])); } catch (IOException error) { Log.e(MtpDocumentsProvider.TAG, "Failed to load object info", error); } /** * Marks the loading task as error. */ void setError(Exception error) { final int lastState = getState(); setErrorInternal(error); if (lastState == STATE_LOADING) { } synchronized (this) { try { mDatabase.getMapper().putChildDocuments( mIdentifier.mDeviceId, mIdentifier.mDocumentId, mOperationsSupported, infoList.toArray(new MtpObjectInfo[infoList.size()])); } catch (FileNotFoundException error) { // Looks like the parent document information is removed. // Adding documents has already cancelled in Mapper so we don't need to invoke // stopAddingDocuments. mError = error; mState = STATE_ERROR; return; } if (mPosition >= mObjectHandles.length) { try{ mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } catch (FileNotFoundException exception) { setErrorInternal(exception); mState = STATE_COMPLETED; } catch (FileNotFoundException error) { mError = error; mState = STATE_ERROR; return; } } } private void setErrorInternal(Exception error) { Log.e(MtpDocumentsProvider.TAG, "Error in DocumentLoader thread", error); mError = error; mNumLoaded = 0; } private Uri createUri() { return DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId); /** * Returns a state of the task. */ int getState() { return mState; } /** * Creates a LoaderTask that loads children of the given document. * Notifies a change of child list of the document. */ static LoaderTask create(MtpDatabase database, MtpManager manager, int[] operationsSupported, Identifier parent) throws IOException { int parentHandle = parent.mObjectHandle; // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to // getObjectHandles if we would like to obtain children under the root. if (parent.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; void notify(ContentResolver resolver) { resolver.notifyChange(createUri(), null, false); mLastNotified = new Date(); } return new LoaderTask(database, operationsSupported, parent, manager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); private Uri createUri() { return DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId); } } } packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +6 −3 Original line number Diff line number Diff line Loading @@ -130,11 +130,14 @@ class MtpManager { return devices.toArray(new MtpDeviceRecord[devices.size()]); } MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); synchronized (device) { return device.getObjectInfo(objectHandle); final MtpObjectInfo info = device.getObjectInfo(objectHandle); if (info == null) { throw new IOException("Failed to get object info: " + objectHandle); } return info; } } Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +51 −7 Original line number Diff line number Diff line Loading @@ -55,13 +55,6 @@ public class DocumentLoaderTest extends AndroidTestCase { mManager = new BlockableTestMtpManager(getContext()); mResolver = new TestContentResolver(); mLoader = new DocumentLoader( new MtpDeviceRecord( 0, "Device", "Key", true, new MtpRoot[0], TestUtil.OPERATIONS_SUPPORTED, new int[0]), mManager, mResolver, mDatabase); } @Override Loading @@ -71,6 +64,8 @@ public class DocumentLoaderTest extends AndroidTestCase { } public void testBasic() throws Exception { setUpLoader(); final Uri uri = DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId); setUpDocument(mManager, 40); Loading Loading @@ -107,6 +102,55 @@ public class DocumentLoaderTest extends AndroidTestCase { assertEquals(2, mResolver.getChangeCount(uri)); } public void testError_GetObjectHandles() throws Exception { mManager = new BlockableTestMtpManager(getContext()) { @Override int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle) throws IOException { throw new IOException(); } }; setUpLoader(); mManager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, null); try { try (final Cursor cursor = mLoader.queryChildDocuments( MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {} fail(); } catch (IOException exception) { // Expect exception. } } public void testError_GetObjectInfo() throws Exception { mManager = new BlockableTestMtpManager(getContext()) { @Override MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { if (objectHandle == DocumentLoader.NUM_INITIAL_ENTRIES) { throw new IOException(); } else { return super.getObjectInfo(deviceId, objectHandle); } } }; setUpLoader(); setUpDocument(mManager, DocumentLoader.NUM_INITIAL_ENTRIES); try (final Cursor cursor = mLoader.queryChildDocuments( MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) { // Even if MtpManager returns an error for a document, loading must complete. assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); } } private void setUpLoader() { mLoader = new DocumentLoader( new MtpDeviceRecord( 0, "Device", "Key", true, new MtpRoot[0], TestUtil.OPERATIONS_SUPPORTED, new int[0]), mManager, mResolver, mDatabase); } private void setUpDocument(TestMtpManager manager, int count) { int[] childDocuments = new int[count]; for (int i = 0; i < childDocuments.length; i++) { Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +4 −8 Original line number Diff line number Diff line Loading @@ -419,20 +419,16 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { try { mProvider.queryChildDocuments("1", null, null); fail(); } catch (Throwable error) { assertTrue(error instanceof FileNotFoundException); } } catch (FileNotFoundException error) {} } public void testQueryChildDocuments_documentError() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); try { mProvider.queryChildDocuments("1", null, null); fail(); } catch (Throwable error) { assertTrue(error instanceof FileNotFoundException); try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { assertEquals(0, cursor.getCount()); assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); } } Loading Loading
packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +106 −134 Original line number Diff line number Diff line Loading @@ -81,11 +81,9 @@ class DocumentLoader implements AutoCloseable { // 3. startAddingChildDocuemnts. // 4. stopAddingChildDocuments - It removes the new document added at the step 2, // because it is not updated between start/stopAddingChildDocuments. task = LoaderTask.create(mDatabase, mMtpManager, mDevice.operationsSupported, parent); task.fillDocuments(loadDocuments( mMtpManager, parent.mDeviceId, task.getUnloadedObjectHandles(NUM_INITIAL_ENTRIES))); task = new LoaderTask(mMtpManager, mDatabase, mDevice.operationsSupported, parent); task.loadObjectHandles(); task.loadObjectInfoList(NUM_INITIAL_ENTRIES); } else { // Once remove the existing task in order to add it to the head of the list. mTaskList.remove(task); Loading Loading @@ -130,15 +128,11 @@ class DocumentLoader implements AutoCloseable { Preconditions.checkState(existingTask.getState() != LoaderTask.STATE_LOADING); mTaskList.remove(existingTask); } try { final LoaderTask newTask = LoaderTask.create( mDatabase, mMtpManager, mDevice.operationsSupported, identifier); final LoaderTask newTask = new LoaderTask( mMtpManager, mDatabase, mDevice.operationsSupported, identifier); newTask.loadObjectHandles(); mTaskList.addFirst(newTask); return newTask; } catch (IOException exception) { Log.e(MtpDocumentsProvider.TAG, "Failed to create a task for mapping", exception); // Continue to release the background thread. } } mBackgroundThread = null; Loading Loading @@ -169,24 +163,6 @@ class DocumentLoader implements AutoCloseable { mTaskList.clearTask(parentIdentifier); } /** * Helper method to loads multiple object info. */ private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles) throws IOException { final ArrayList<MtpObjectInfo> objects = new ArrayList<>(); for (int i = 0; i < handles.length; i++) { final MtpObjectInfo info = manager.getObjectInfo(deviceId, handles[i]); if (info == null) { Log.e(MtpDocumentsProvider.TAG, "Failed to obtain object info handle=" + handles[i]); continue; } objects.add(info); } return objects.toArray(new MtpObjectInfo[objects.size()]); } /** * Background thread to fetch object info. */ Loading @@ -203,12 +179,7 @@ class DocumentLoader implements AutoCloseable { if (task == null) { return; } try { final MtpObjectInfo[] objectInfos = loadDocuments( mMtpManager, task.mIdentifier.mDeviceId, task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES)); task.fillDocuments(objectInfos); task.loadObjectInfoList(NUM_LOADING_ENTRIES); final boolean shouldNotify = task.mLastNotified.getTime() < new Date().getTime() - NOTIFY_PERIOD_MS || Loading @@ -216,9 +187,6 @@ class DocumentLoader implements AutoCloseable { if (shouldNotify) { task.notify(mResolver); } } catch (IOException exception) { task.setError(exception); } } } } Loading Loading @@ -271,43 +239,66 @@ class DocumentLoader implements AutoCloseable { * Each task is responsible for fetching child documents for the given parent document. */ private static class LoaderTask { static final int STATE_LOADING = 0; static final int STATE_COMPLETED = 1; static final int STATE_ERROR = 2; static final int STATE_START = 0; static final int STATE_LOADING = 1; static final int STATE_COMPLETED = 2; static final int STATE_ERROR = 3; final MtpManager mManager; final MtpDatabase mDatabase; final int[] mOperationsSupported; final Identifier mIdentifier; final int[] mObjectHandles; int[] mObjectHandles; int mState; Date mLastNotified; int mNumLoaded; Exception mError; int mPosition; IOException mError; LoaderTask(MtpDatabase database, int[] operationsSupported, Identifier identifier, int[] objectHandles) { LoaderTask(MtpManager manager, MtpDatabase database, int[] operationsSupported, Identifier identifier) { Preconditions.checkNotNull(operationsSupported); Preconditions.checkNotNull(objectHandles); mManager = manager; mDatabase = database; mOperationsSupported = operationsSupported; mIdentifier = identifier; mObjectHandles = objectHandles; mNumLoaded = 0; mObjectHandles = null; mState = STATE_START; mPosition = 0; mLastNotified = new Date(); } synchronized void loadObjectHandles() { assert mState == STATE_START; int parentHandle = mIdentifier.mObjectHandle; // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to // getObjectHandles if we would like to obtain children under the root. if (mIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; } try { mObjectHandles = mManager.getObjectHandles( mIdentifier.mDeviceId, mIdentifier.mStorageId, parentHandle); mState = STATE_LOADING; } catch (IOException error) { mError = error; mState = STATE_ERROR; } } /** * Returns a cursor that traverses the child document of the parent document handled by the * task. * The returned task may have a EXTRA_LOADING flag. */ Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException { synchronized Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException { final Bundle extras = new Bundle(); switch (getState()) { case STATE_LOADING: extras.putBoolean(DocumentsContract.EXTRA_LOADING, true); break; case STATE_ERROR: throw new IOException(mError); throw mError; } final Cursor cursor = Loading @@ -318,100 +309,81 @@ class DocumentLoader implements AutoCloseable { return cursor; } /** * Returns a state of the task. */ int getState() { if (mError != null) { return STATE_ERROR; } else if (mNumLoaded == mObjectHandles.length) { return STATE_COMPLETED; } else { return STATE_LOADING; } } /** * Obtains object handles that have not been loaded yet. */ int[] getUnloadedObjectHandles(int count) { return Arrays.copyOfRange( mObjectHandles, mNumLoaded, Math.min(mNumLoaded + count, mObjectHandles.length)); } /** * Notifies a change of child list of the document. */ void notify(ContentResolver resolver) { resolver.notifyChange(createUri(), null, false); mLastNotified = new Date(); } /** * Stores object information into database. */ void fillDocuments(MtpObjectInfo[] objectInfoList) { if (objectInfoList.length == 0 || getState() != STATE_LOADING) { void loadObjectInfoList(int count) { synchronized (this) { if (mState != STATE_LOADING) { return; } if (mPosition == 0) { try{ if (mNumLoaded == 0) { mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId); } catch (FileNotFoundException error) { mError = error; mState = STATE_ERROR; return; } mDatabase.getMapper().putChildDocuments( mIdentifier.mDeviceId, mIdentifier.mDocumentId, mOperationsSupported, objectInfoList); mNumLoaded += objectInfoList.length; if (getState() != STATE_LOADING) { mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } } catch (FileNotFoundException exception) { setErrorInternal(exception); } final ArrayList<MtpObjectInfo> infoList = new ArrayList<>(); for (int chunkEnd = mPosition + count; mPosition < mObjectHandles.length && mPosition < chunkEnd; mPosition++) { try { infoList.add(mManager.getObjectInfo( mIdentifier.mDeviceId, mObjectHandles[mPosition])); } catch (IOException error) { Log.e(MtpDocumentsProvider.TAG, "Failed to load object info", error); } /** * Marks the loading task as error. */ void setError(Exception error) { final int lastState = getState(); setErrorInternal(error); if (lastState == STATE_LOADING) { } synchronized (this) { try { mDatabase.getMapper().putChildDocuments( mIdentifier.mDeviceId, mIdentifier.mDocumentId, mOperationsSupported, infoList.toArray(new MtpObjectInfo[infoList.size()])); } catch (FileNotFoundException error) { // Looks like the parent document information is removed. // Adding documents has already cancelled in Mapper so we don't need to invoke // stopAddingDocuments. mError = error; mState = STATE_ERROR; return; } if (mPosition >= mObjectHandles.length) { try{ mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } catch (FileNotFoundException exception) { setErrorInternal(exception); mState = STATE_COMPLETED; } catch (FileNotFoundException error) { mError = error; mState = STATE_ERROR; return; } } } private void setErrorInternal(Exception error) { Log.e(MtpDocumentsProvider.TAG, "Error in DocumentLoader thread", error); mError = error; mNumLoaded = 0; } private Uri createUri() { return DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId); /** * Returns a state of the task. */ int getState() { return mState; } /** * Creates a LoaderTask that loads children of the given document. * Notifies a change of child list of the document. */ static LoaderTask create(MtpDatabase database, MtpManager manager, int[] operationsSupported, Identifier parent) throws IOException { int parentHandle = parent.mObjectHandle; // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to // getObjectHandles if we would like to obtain children under the root. if (parent.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) { parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; void notify(ContentResolver resolver) { resolver.notifyChange(createUri(), null, false); mLastNotified = new Date(); } return new LoaderTask(database, operationsSupported, parent, manager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); private Uri createUri() { return DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId); } } }
packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +6 −3 Original line number Diff line number Diff line Loading @@ -130,11 +130,14 @@ class MtpManager { return devices.toArray(new MtpDeviceRecord[devices.size()]); } MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); synchronized (device) { return device.getObjectInfo(objectHandle); final MtpObjectInfo info = device.getObjectInfo(objectHandle); if (info == null) { throw new IOException("Failed to get object info: " + objectHandle); } return info; } } Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +51 −7 Original line number Diff line number Diff line Loading @@ -55,13 +55,6 @@ public class DocumentLoaderTest extends AndroidTestCase { mManager = new BlockableTestMtpManager(getContext()); mResolver = new TestContentResolver(); mLoader = new DocumentLoader( new MtpDeviceRecord( 0, "Device", "Key", true, new MtpRoot[0], TestUtil.OPERATIONS_SUPPORTED, new int[0]), mManager, mResolver, mDatabase); } @Override Loading @@ -71,6 +64,8 @@ public class DocumentLoaderTest extends AndroidTestCase { } public void testBasic() throws Exception { setUpLoader(); final Uri uri = DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId); setUpDocument(mManager, 40); Loading Loading @@ -107,6 +102,55 @@ public class DocumentLoaderTest extends AndroidTestCase { assertEquals(2, mResolver.getChangeCount(uri)); } public void testError_GetObjectHandles() throws Exception { mManager = new BlockableTestMtpManager(getContext()) { @Override int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle) throws IOException { throw new IOException(); } }; setUpLoader(); mManager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, null); try { try (final Cursor cursor = mLoader.queryChildDocuments( MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {} fail(); } catch (IOException exception) { // Expect exception. } } public void testError_GetObjectInfo() throws Exception { mManager = new BlockableTestMtpManager(getContext()) { @Override MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { if (objectHandle == DocumentLoader.NUM_INITIAL_ENTRIES) { throw new IOException(); } else { return super.getObjectInfo(deviceId, objectHandle); } } }; setUpLoader(); setUpDocument(mManager, DocumentLoader.NUM_INITIAL_ENTRIES); try (final Cursor cursor = mLoader.queryChildDocuments( MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) { // Even if MtpManager returns an error for a document, loading must complete. assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); } } private void setUpLoader() { mLoader = new DocumentLoader( new MtpDeviceRecord( 0, "Device", "Key", true, new MtpRoot[0], TestUtil.OPERATIONS_SUPPORTED, new int[0]), mManager, mResolver, mDatabase); } private void setUpDocument(TestMtpManager manager, int count) { int[] childDocuments = new int[count]; for (int i = 0; i < childDocuments.length; i++) { Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +4 −8 Original line number Diff line number Diff line Loading @@ -419,20 +419,16 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { try { mProvider.queryChildDocuments("1", null, null); fail(); } catch (Throwable error) { assertTrue(error instanceof FileNotFoundException); } } catch (FileNotFoundException error) {} } public void testQueryChildDocuments_documentError() throws Exception { setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 }); try { mProvider.queryChildDocuments("1", null, null); fail(); } catch (Throwable error) { assertTrue(error instanceof FileNotFoundException); try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) { assertEquals(0, cursor.getCount()); assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)); } } Loading