Loading packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +154 −47 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.mtp; import android.annotation.Nullable; import android.annotation.WorkerThread; import android.content.ContentResolver; import android.database.Cursor; import android.mtp.MtpObjectInfo; Loading @@ -25,6 +27,8 @@ import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; Loading @@ -38,60 +42,46 @@ import java.util.LinkedList; * background thread to load the rest documents and caches its result for next requests. * TODO: Rename this class to ObjectInfoLoader */ class DocumentLoader { class DocumentLoader implements AutoCloseable { static final int NUM_INITIAL_ENTRIES = 10; static final int NUM_LOADING_ENTRIES = 20; static final int NOTIFY_PERIOD_MS = 500; private final int mDeviceId; private final MtpManager mMtpManager; private final ContentResolver mResolver; private final MtpDatabase mDatabase; private final TaskList mTaskList = new TaskList(); private boolean mHasBackgroundThread = false; private Thread mBackgroundThread; DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) { DocumentLoader(int deviceId, MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) { mDeviceId = deviceId; mMtpManager = mtpManager; mResolver = resolver; mDatabase = database; } 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()]); } /** * Queries the child documents of given parent. * It loads the first NUM_INITIAL_ENTRIES of object info, then launches the background thread * to load the rest. */ synchronized Cursor queryChildDocuments(String[] columnNames, Identifier parent) throws IOException { Preconditions.checkArgument(parent.mDeviceId == mDeviceId); LoaderTask task = mTaskList.findTask(parent); if (task == null) { if (parent.mDocumentId == null) { throw new FileNotFoundException("Parent not found."); } 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; } // TODO: Handle nit race around here. // 1. getObjectHandles. // 2. putNewDocument. // 3. startAddingChildDocuemnts. // 4. stopAddingChildDocuments - It removes the new document added at the step 2, // because it is not updated between start/stopAddingChildDocuments. task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); task = LoaderTask.create(mDatabase, mMtpManager, parent); task.fillDocuments(loadDocuments( mMtpManager, parent.mDeviceId, Loading @@ -102,15 +92,72 @@ class DocumentLoader { } mTaskList.addFirst(task); if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) { mHasBackgroundThread = true; new BackgroundLoaderThread().start(); if (task.getState() == LoaderTask.STATE_LOADING) { resume(); } return task.createCursor(mResolver, columnNames); } synchronized void clearTasks() { /** * Resumes a background thread. */ synchronized void resume() { if (mBackgroundThread == null) { mBackgroundThread = new BackgroundLoaderThread(); mBackgroundThread.start(); } } /** * Obtains next task to be run in background thread, or release the reference to background * thread. * * Worker thread that receives null task needs to exit. */ @WorkerThread synchronized @Nullable LoaderTask getNextTaskOrReleaseBackgroundThread() { Preconditions.checkState(mBackgroundThread != null); final LoaderTask task = mTaskList.findRunningTask(); if (task != null) { return task; } final Identifier identifier = mDatabase.getUnmappedDocumentsParent(mDeviceId); if (identifier != null) { final LoaderTask existingTask = mTaskList.findTask(identifier); if (existingTask != null) { Preconditions.checkState(existingTask.getState() != LoaderTask.STATE_LOADING); mTaskList.remove(existingTask); } try { final LoaderTask newTask = LoaderTask.create(mDatabase, mMtpManager, identifier); 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; return null; } /** * Terminates background thread. */ @Override public void close() throws InterruptedException { final Thread thread; synchronized (this) { mTaskList.clear(); thread = mBackgroundThread; } if (thread != null) { thread.interrupt(); thread.join(); } } synchronized void clearCompletedTasks() { Loading @@ -121,27 +168,45 @@ class DocumentLoader { 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. */ private class BackgroundLoaderThread extends Thread { /** * Finds task that needs to be processed, then loads NUM_LOADING_ENTRIES of object info and * store them to the database. If it does not find a task, exits the thread. */ @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { LoaderTask task; int deviceId; int[] handles; synchronized (DocumentLoader.this) { task = mTaskList.findRunningTask(); while (!Thread.interrupted()) { final LoaderTask task = getNextTaskOrReleaseBackgroundThread(); if (task == null) { mHasBackgroundThread = false; return; } deviceId = task.mIdentifier.mDeviceId; handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES); } try { final MtpObjectInfo[] objectInfos = loadDocuments(mMtpManager, deviceId, handles); final MtpObjectInfo[] objectInfos = loadDocuments( mMtpManager, task.mIdentifier.mDeviceId, task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES)); task.fillDocuments(objectInfos); final boolean shouldNotify = task.mLastNotified.getTime() < Loading @@ -157,6 +222,9 @@ class DocumentLoader { } } /** * Task list that has helper methods to search/clear tasks. */ private static class TaskList extends LinkedList<LoaderTask> { LoaderTask findTask(Identifier parent) { for (int i = 0; i < size(); i++) { Loading Loading @@ -197,6 +265,10 @@ class DocumentLoader { } } /** * Loader task. * 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; Loading @@ -217,6 +289,11 @@ class DocumentLoader { mLastNotified = new Date(); } /** * 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 { final Bundle extras = new Bundle(); switch (getState()) { Loading @@ -235,6 +312,9 @@ class DocumentLoader { return cursor; } /** * Returns a state of the task. */ int getState() { if (mError != null) { return STATE_ERROR; Loading @@ -245,6 +325,9 @@ class DocumentLoader { } } /** * Obtains object handles that have not been loaded yet. */ int[] getUnloadedObjectHandles(int count) { return Arrays.copyOfRange( mObjectHandles, Loading @@ -252,11 +335,17 @@ class DocumentLoader { 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) { return; Loading @@ -276,6 +365,9 @@ class DocumentLoader { } } /** * Marks the loading task as error. */ void setError(Exception error) { final int lastState = getState(); setErrorInternal(error); Loading @@ -298,5 +390,20 @@ class DocumentLoader { return DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId); } /** * Creates a LoaderTask that loads children of the given document. */ static LoaderTask create(MtpDatabase database, MtpManager manager, 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; } return new LoaderTask(database, parent, manager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); } } } packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +1 −6 Original line number Diff line number Diff line Loading @@ -410,12 +410,7 @@ class Mapper { return null; } try { final Identifier identifier = mDatabase.createIdentifier(parentId); if (mDatabase.getRowState(parentId) == ROW_STATE_DISCONNECTED) { throw new FileNotFoundException( "document: " + parentId + " is in disconnected device."); } return identifier; return mDatabase.createIdentifier(parentId); } catch (FileNotFoundException error) { mInMappingIds.remove(parentId); throw error; Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +46 −3 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Objects; /** Loading Loading @@ -406,15 +407,15 @@ class MtpDatabase { COLUMN_STORAGE_ID, COLUMN_OBJECT_HANDLE, COLUMN_DOCUMENT_TYPE), SELECTION_DOCUMENT_ID, strings(documentId), SELECTION_DOCUMENT_ID + " AND " + COLUMN_ROW_STATE + " IN (?, ?)", strings(documentId, ROW_STATE_VALID, ROW_STATE_INVALIDATED), null, null, null, "1"); try { if (cursor.getCount() == 0) { throw new FileNotFoundException("ID is not found."); throw new FileNotFoundException("ID \"" + documentId + "\" is not found."); } else { cursor.moveToNext(); return new Identifier( Loading Loading @@ -598,6 +599,48 @@ class MtpDatabase { } } /** * Obtains a document that has already mapped but has unmapped children. * @param deviceId Device to find documents. * @return Identifier of found document or null. */ public @Nullable Identifier getUnmappedDocumentsParent(int deviceId) { final String fromClosure = TABLE_DOCUMENTS + " AS child INNER JOIN " + TABLE_DOCUMENTS + " AS parent ON " + "child." + COLUMN_PARENT_DOCUMENT_ID + " = " + "parent." + Document.COLUMN_DOCUMENT_ID; final String whereClosure = "parent." + COLUMN_DEVICE_ID + " = ? AND " + "parent." + COLUMN_ROW_STATE + " IN (?, ?) AND " + "child." + COLUMN_ROW_STATE + " = ?"; try (final Cursor cursor = mDatabase.query( fromClosure, strings("parent." + COLUMN_DEVICE_ID, "parent." + COLUMN_STORAGE_ID, "parent." + COLUMN_OBJECT_HANDLE, "parent." + Document.COLUMN_DOCUMENT_ID, "parent." + COLUMN_DOCUMENT_TYPE), whereClosure, strings(deviceId, ROW_STATE_VALID, ROW_STATE_INVALIDATED, ROW_STATE_DISCONNECTED), null, null, null, "1")) { if (cursor.getCount() == 0) { return null; } cursor.moveToNext(); return new Identifier( cursor.getInt(0), cursor.getInt(1), cursor.getInt(2), cursor.getString(3), cursor.getInt(4)); } } private static class OpenHelper extends SQLiteOpenHelper { public OpenHelper(Context context, int flags) { super(context, Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +10 −5 Original line number Diff line number Diff line Loading @@ -324,14 +324,18 @@ public class MtpDocumentsProvider extends DocumentsProvider { Log.d(TAG, "Open device " + deviceId); } mMtpManager.openDevice(deviceId); mDeviceToolkits.put( deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); final DeviceToolkit toolkit = new DeviceToolkit(deviceId, mMtpManager, mResolver, mDatabase); mDeviceToolkits.put(deviceId, toolkit); mIntentSender.sendUpdateNotificationIntent(); try { mRootScanner.resume().await(); } catch (InterruptedException error) { Log.e(TAG, "openDevice", error); } // Resume document loader to remap disconnected document ID. Must be invoked after the // root scanner resumes. toolkit.mDocumentLoader.resume(); } } Loading Loading @@ -425,7 +429,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { if (DEBUG) { Log.d(TAG, "Close device " + deviceId); } getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); getDeviceToolkit(deviceId).mDocumentLoader.close(); mDeviceToolkits.remove(deviceId); mMtpManager.closeDevice(deviceId); if (getOpenedDeviceIds().length == 0) { Loading Loading @@ -485,9 +489,10 @@ public class MtpDocumentsProvider extends DocumentsProvider { public final PipeManager mPipeManager; public final DocumentLoader mDocumentLoader; public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) { public DeviceToolkit( int deviceId, MtpManager manager, ContentResolver resolver, MtpDatabase database) { mPipeManager = new PipeManager(database); mDocumentLoader = new DocumentLoader(manager, resolver, database); mDocumentLoader = new DocumentLoader(deviceId, manager, resolver, database); } } Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -44,7 +44,7 @@ public class DocumentLoaderTest extends AndroidTestCase { mDatabase.getMapper().startAddingDocuments(null); mDatabase.getMapper().putDeviceDocument( new MtpDeviceRecord(1, "Device", null, true, new MtpRoot[0], null, null)); new MtpDeviceRecord(0, "Device", null, true, new MtpRoot[0], null, null)); mDatabase.getMapper().stopAddingDocuments(null); mDatabase.getMapper().startAddingDocuments("1"); Loading @@ -55,11 +55,12 @@ public class DocumentLoaderTest extends AndroidTestCase { mManager = new BlockableTestMtpManager(getContext()); mResolver = new TestContentResolver(); mLoader = new DocumentLoader(mManager, mResolver, mDatabase); mLoader = new DocumentLoader(0, mManager, mResolver, mDatabase); } @Override public void tearDown() { public void tearDown() throws Exception { mLoader.close(); mDatabase.close(); } Loading Loading
packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +154 −47 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.mtp; import android.annotation.Nullable; import android.annotation.WorkerThread; import android.content.ContentResolver; import android.database.Cursor; import android.mtp.MtpObjectInfo; Loading @@ -25,6 +27,8 @@ import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; Loading @@ -38,60 +42,46 @@ import java.util.LinkedList; * background thread to load the rest documents and caches its result for next requests. * TODO: Rename this class to ObjectInfoLoader */ class DocumentLoader { class DocumentLoader implements AutoCloseable { static final int NUM_INITIAL_ENTRIES = 10; static final int NUM_LOADING_ENTRIES = 20; static final int NOTIFY_PERIOD_MS = 500; private final int mDeviceId; private final MtpManager mMtpManager; private final ContentResolver mResolver; private final MtpDatabase mDatabase; private final TaskList mTaskList = new TaskList(); private boolean mHasBackgroundThread = false; private Thread mBackgroundThread; DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) { DocumentLoader(int deviceId, MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) { mDeviceId = deviceId; mMtpManager = mtpManager; mResolver = resolver; mDatabase = database; } 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()]); } /** * Queries the child documents of given parent. * It loads the first NUM_INITIAL_ENTRIES of object info, then launches the background thread * to load the rest. */ synchronized Cursor queryChildDocuments(String[] columnNames, Identifier parent) throws IOException { Preconditions.checkArgument(parent.mDeviceId == mDeviceId); LoaderTask task = mTaskList.findTask(parent); if (task == null) { if (parent.mDocumentId == null) { throw new FileNotFoundException("Parent not found."); } 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; } // TODO: Handle nit race around here. // 1. getObjectHandles. // 2. putNewDocument. // 3. startAddingChildDocuemnts. // 4. stopAddingChildDocuments - It removes the new document added at the step 2, // because it is not updated between start/stopAddingChildDocuments. task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); task = LoaderTask.create(mDatabase, mMtpManager, parent); task.fillDocuments(loadDocuments( mMtpManager, parent.mDeviceId, Loading @@ -102,15 +92,72 @@ class DocumentLoader { } mTaskList.addFirst(task); if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) { mHasBackgroundThread = true; new BackgroundLoaderThread().start(); if (task.getState() == LoaderTask.STATE_LOADING) { resume(); } return task.createCursor(mResolver, columnNames); } synchronized void clearTasks() { /** * Resumes a background thread. */ synchronized void resume() { if (mBackgroundThread == null) { mBackgroundThread = new BackgroundLoaderThread(); mBackgroundThread.start(); } } /** * Obtains next task to be run in background thread, or release the reference to background * thread. * * Worker thread that receives null task needs to exit. */ @WorkerThread synchronized @Nullable LoaderTask getNextTaskOrReleaseBackgroundThread() { Preconditions.checkState(mBackgroundThread != null); final LoaderTask task = mTaskList.findRunningTask(); if (task != null) { return task; } final Identifier identifier = mDatabase.getUnmappedDocumentsParent(mDeviceId); if (identifier != null) { final LoaderTask existingTask = mTaskList.findTask(identifier); if (existingTask != null) { Preconditions.checkState(existingTask.getState() != LoaderTask.STATE_LOADING); mTaskList.remove(existingTask); } try { final LoaderTask newTask = LoaderTask.create(mDatabase, mMtpManager, identifier); 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; return null; } /** * Terminates background thread. */ @Override public void close() throws InterruptedException { final Thread thread; synchronized (this) { mTaskList.clear(); thread = mBackgroundThread; } if (thread != null) { thread.interrupt(); thread.join(); } } synchronized void clearCompletedTasks() { Loading @@ -121,27 +168,45 @@ class DocumentLoader { 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. */ private class BackgroundLoaderThread extends Thread { /** * Finds task that needs to be processed, then loads NUM_LOADING_ENTRIES of object info and * store them to the database. If it does not find a task, exits the thread. */ @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { LoaderTask task; int deviceId; int[] handles; synchronized (DocumentLoader.this) { task = mTaskList.findRunningTask(); while (!Thread.interrupted()) { final LoaderTask task = getNextTaskOrReleaseBackgroundThread(); if (task == null) { mHasBackgroundThread = false; return; } deviceId = task.mIdentifier.mDeviceId; handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES); } try { final MtpObjectInfo[] objectInfos = loadDocuments(mMtpManager, deviceId, handles); final MtpObjectInfo[] objectInfos = loadDocuments( mMtpManager, task.mIdentifier.mDeviceId, task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES)); task.fillDocuments(objectInfos); final boolean shouldNotify = task.mLastNotified.getTime() < Loading @@ -157,6 +222,9 @@ class DocumentLoader { } } /** * Task list that has helper methods to search/clear tasks. */ private static class TaskList extends LinkedList<LoaderTask> { LoaderTask findTask(Identifier parent) { for (int i = 0; i < size(); i++) { Loading Loading @@ -197,6 +265,10 @@ class DocumentLoader { } } /** * Loader task. * 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; Loading @@ -217,6 +289,11 @@ class DocumentLoader { mLastNotified = new Date(); } /** * 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 { final Bundle extras = new Bundle(); switch (getState()) { Loading @@ -235,6 +312,9 @@ class DocumentLoader { return cursor; } /** * Returns a state of the task. */ int getState() { if (mError != null) { return STATE_ERROR; Loading @@ -245,6 +325,9 @@ class DocumentLoader { } } /** * Obtains object handles that have not been loaded yet. */ int[] getUnloadedObjectHandles(int count) { return Arrays.copyOfRange( mObjectHandles, Loading @@ -252,11 +335,17 @@ class DocumentLoader { 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) { return; Loading @@ -276,6 +365,9 @@ class DocumentLoader { } } /** * Marks the loading task as error. */ void setError(Exception error) { final int lastState = getState(); setErrorInternal(error); Loading @@ -298,5 +390,20 @@ class DocumentLoader { return DocumentsContract.buildChildDocumentsUri( MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId); } /** * Creates a LoaderTask that loads children of the given document. */ static LoaderTask create(MtpDatabase database, MtpManager manager, 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; } return new LoaderTask(database, parent, manager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); } } }
packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +1 −6 Original line number Diff line number Diff line Loading @@ -410,12 +410,7 @@ class Mapper { return null; } try { final Identifier identifier = mDatabase.createIdentifier(parentId); if (mDatabase.getRowState(parentId) == ROW_STATE_DISCONNECTED) { throw new FileNotFoundException( "document: " + parentId + " is in disconnected device."); } return identifier; return mDatabase.createIdentifier(parentId); } catch (FileNotFoundException error) { mInMappingIds.remove(parentId); throw error; Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +46 −3 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Objects; /** Loading Loading @@ -406,15 +407,15 @@ class MtpDatabase { COLUMN_STORAGE_ID, COLUMN_OBJECT_HANDLE, COLUMN_DOCUMENT_TYPE), SELECTION_DOCUMENT_ID, strings(documentId), SELECTION_DOCUMENT_ID + " AND " + COLUMN_ROW_STATE + " IN (?, ?)", strings(documentId, ROW_STATE_VALID, ROW_STATE_INVALIDATED), null, null, null, "1"); try { if (cursor.getCount() == 0) { throw new FileNotFoundException("ID is not found."); throw new FileNotFoundException("ID \"" + documentId + "\" is not found."); } else { cursor.moveToNext(); return new Identifier( Loading Loading @@ -598,6 +599,48 @@ class MtpDatabase { } } /** * Obtains a document that has already mapped but has unmapped children. * @param deviceId Device to find documents. * @return Identifier of found document or null. */ public @Nullable Identifier getUnmappedDocumentsParent(int deviceId) { final String fromClosure = TABLE_DOCUMENTS + " AS child INNER JOIN " + TABLE_DOCUMENTS + " AS parent ON " + "child." + COLUMN_PARENT_DOCUMENT_ID + " = " + "parent." + Document.COLUMN_DOCUMENT_ID; final String whereClosure = "parent." + COLUMN_DEVICE_ID + " = ? AND " + "parent." + COLUMN_ROW_STATE + " IN (?, ?) AND " + "child." + COLUMN_ROW_STATE + " = ?"; try (final Cursor cursor = mDatabase.query( fromClosure, strings("parent." + COLUMN_DEVICE_ID, "parent." + COLUMN_STORAGE_ID, "parent." + COLUMN_OBJECT_HANDLE, "parent." + Document.COLUMN_DOCUMENT_ID, "parent." + COLUMN_DOCUMENT_TYPE), whereClosure, strings(deviceId, ROW_STATE_VALID, ROW_STATE_INVALIDATED, ROW_STATE_DISCONNECTED), null, null, null, "1")) { if (cursor.getCount() == 0) { return null; } cursor.moveToNext(); return new Identifier( cursor.getInt(0), cursor.getInt(1), cursor.getInt(2), cursor.getString(3), cursor.getInt(4)); } } private static class OpenHelper extends SQLiteOpenHelper { public OpenHelper(Context context, int flags) { super(context, Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +10 −5 Original line number Diff line number Diff line Loading @@ -324,14 +324,18 @@ public class MtpDocumentsProvider extends DocumentsProvider { Log.d(TAG, "Open device " + deviceId); } mMtpManager.openDevice(deviceId); mDeviceToolkits.put( deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); final DeviceToolkit toolkit = new DeviceToolkit(deviceId, mMtpManager, mResolver, mDatabase); mDeviceToolkits.put(deviceId, toolkit); mIntentSender.sendUpdateNotificationIntent(); try { mRootScanner.resume().await(); } catch (InterruptedException error) { Log.e(TAG, "openDevice", error); } // Resume document loader to remap disconnected document ID. Must be invoked after the // root scanner resumes. toolkit.mDocumentLoader.resume(); } } Loading Loading @@ -425,7 +429,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { if (DEBUG) { Log.d(TAG, "Close device " + deviceId); } getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); getDeviceToolkit(deviceId).mDocumentLoader.close(); mDeviceToolkits.remove(deviceId); mMtpManager.closeDevice(deviceId); if (getOpenedDeviceIds().length == 0) { Loading Loading @@ -485,9 +489,10 @@ public class MtpDocumentsProvider extends DocumentsProvider { public final PipeManager mPipeManager; public final DocumentLoader mDocumentLoader; public DeviceToolkit(MtpManager manager, ContentResolver resolver, MtpDatabase database) { public DeviceToolkit( int deviceId, MtpManager manager, ContentResolver resolver, MtpDatabase database) { mPipeManager = new PipeManager(database); mDocumentLoader = new DocumentLoader(manager, resolver, database); mDocumentLoader = new DocumentLoader(deviceId, manager, resolver, database); } } Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -44,7 +44,7 @@ public class DocumentLoaderTest extends AndroidTestCase { mDatabase.getMapper().startAddingDocuments(null); mDatabase.getMapper().putDeviceDocument( new MtpDeviceRecord(1, "Device", null, true, new MtpRoot[0], null, null)); new MtpDeviceRecord(0, "Device", null, true, new MtpRoot[0], null, null)); mDatabase.getMapper().stopAddingDocuments(null); mDatabase.getMapper().startAddingDocuments("1"); Loading @@ -55,11 +55,12 @@ public class DocumentLoaderTest extends AndroidTestCase { mManager = new BlockableTestMtpManager(getContext()); mResolver = new TestContentResolver(); mLoader = new DocumentLoader(mManager, mResolver, mDatabase); mLoader = new DocumentLoader(0, mManager, mResolver, mDatabase); } @Override public void tearDown() { public void tearDown() throws Exception { mLoader.close(); mDatabase.close(); } Loading