Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +107 −38 Original line number Diff line number Diff line Loading @@ -71,6 +71,11 @@ import java.util.Map; @VisibleForTesting class MtpDatabase { private final MtpDatabaseInternal mDatabase; /** * Mapping mode for roots/documents where we start adding child documents. * Methods operate the state needs to be synchronized. */ private final Map<String, Integer> mMappingMode = new HashMap<>(); @VisibleForTesting Loading @@ -78,23 +83,49 @@ class MtpDatabase { mDatabase = new MtpDatabaseInternal(context); } /** * Closes the database. */ @VisibleForTesting void close() { mDatabase.close(); } /** * {@link MtpDatabaseInternal#queryRoots} */ Cursor queryRoots(String[] columnNames) { return mDatabase.queryRoots(columnNames); } /** * {@link MtpDatabaseInternal#queryRootDocuments} */ @VisibleForTesting Cursor queryRootDocuments(String[] columnNames) { return mDatabase.queryRootDocuments(columnNames); } /** * {@link MtpDatabaseInternal#queryChildDocuments} */ @VisibleForTesting Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) { return mDatabase.queryChildDocuments(columnNames, parentDocumentId); } @VisibleForTesting void startAddingRootDocuments(int deviceId) { /** * {@link MtpDatabaseInternal#removeDeviceRows} */ void removeDeviceRows(int deviceId) { mDatabase.removeDeviceRows(deviceId); } /** * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents. * @param deviceId Device ID. */ synchronized void startAddingRootDocuments(int deviceId) { final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId); if (mMappingMode.containsKey(mappingStateKey)) { throw new Error("Mapping for the root has already started."); Loading @@ -105,8 +136,12 @@ class MtpDatabase { SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId))); } /** * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for child of specific documents. * @param parentDocumentId Document ID for parent document. */ @VisibleForTesting void startAddingChildDocuments(String parentDocumentId) { synchronized void startAddingChildDocuments(String parentDocumentId) { final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId); if (mMappingMode.containsKey(mappingStateKey)) { throw new Error("Mapping for the root has already started."); Loading @@ -116,20 +151,18 @@ class MtpDatabase { mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId)); } @VisibleForTesting void putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) { /** * Puts root information to database. * @param deviceId Device ID * @param resources Resources required to localize root name. * @param roots List of root information. * @return If roots are added or removed from the database. */ synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) { mDatabase.beginTransaction(); try { final ContentValues[] valuesList = new ContentValues[roots.length]; for (int i = 0; i < roots.length; i++) { if (roots[i].mDeviceId != deviceId) { throw new IllegalArgumentException(); } valuesList[i] = new ContentValues(); getRootDocumentValues(valuesList[i], resources, roots[i]); } boolean heuristic; String mapColumn; final boolean heuristic; final String mapColumn; switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; Loading @@ -142,7 +175,15 @@ class MtpDatabase { default: throw new Error("Unexpected map mode."); } final long[] documentIds = mDatabase.putDocuments( final ContentValues[] valuesList = new ContentValues[roots.length]; for (int i = 0; i < roots.length; i++) { if (roots[i].mDeviceId != deviceId) { throw new IllegalArgumentException(); } valuesList[i] = new ContentValues(); getRootDocumentValues(valuesList[i], resources, roots[i]); } final boolean changed = mDatabase.putDocuments( valuesList, SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), Loading @@ -152,33 +193,38 @@ class MtpDatabase { int i = 0; for (final MtpRoot root : roots) { // Use the same value for the root ID and the corresponding document ID. values.put(Root.COLUMN_ROOT_ID, documentIds[i++]); values.put( Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); Root.COLUMN_ROOT_ID, valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID)); values.put( Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace); values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity); values.put(Root.COLUMN_MIME_TYPES, ""); mDatabase.putRootExtra(values); } mDatabase.setTransactionSuccessful(); return changed; } finally { mDatabase.endTransaction(); } } /** * Puts document information to database. * @param deviceId Device ID * @param parentId Parent document ID. * @param documents List of document information. */ @VisibleForTesting void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) { final ContentValues[] valuesList = new ContentValues[documents.length]; for (int i = 0; i < documents.length; i++) { valuesList[i] = new ContentValues(); getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]); } boolean heuristic; String mapColumn; synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) { final boolean heuristic; final String mapColumn; switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; mapColumn = COLUMN_STORAGE_ID; mapColumn = COLUMN_OBJECT_HANDLE; break; case MAP_BY_NAME: heuristic = true; Loading @@ -187,40 +233,55 @@ class MtpDatabase { default: throw new Error("Unexpected map mode."); } final ContentValues[] valuesList = new ContentValues[documents.length]; for (int i = 0; i < documents.length; i++) { valuesList[i] = new ContentValues(); getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]); } mDatabase.putDocuments( valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn); } /** * Clears mapping between MTP identifier and document/root ID. */ @VisibleForTesting void clearMapping() { synchronized void clearMapping() { mDatabase.clearMapping(); mMappingMode.clear(); } @VisibleForTesting void stopAddingRootDocuments(int deviceId) { /** * Stops adding root documents. * @param deviceId Device ID. * @return True if new rows are added/removed. */ synchronized boolean stopAddingRootDocuments(int deviceId) { final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId); switch (mMappingMode.get(mappingModeKey)) { case MAP_BY_MTP_IDENTIFIER: mDatabase.stopAddingDocuments( mMappingMode.remove(mappingModeKey); return mDatabase.stopAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), COLUMN_STORAGE_ID); break; case MAP_BY_NAME: mDatabase.stopAddingDocuments( mMappingMode.remove(mappingModeKey); return mDatabase.stopAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), Document.COLUMN_DISPLAY_NAME); break; default: throw new Error("Unexpected mapping state."); } mMappingMode.remove(mappingModeKey); } /** * Stops adding documents under the parent. * @param parentId Document ID of the parent. */ @VisibleForTesting void stopAddingChildDocuments(String parentId) { synchronized void stopAddingChildDocuments(String parentId) { final String mappingModeKey = getChildDocumentsMappingStateKey(parentId); switch (mMappingMode.get(mappingModeKey)) { case MAP_BY_MTP_IDENTIFIER: Loading Loading @@ -303,11 +364,19 @@ class MtpDatabase { values.put(Document.COLUMN_SIZE, info.getCompressedSize()); } private String getRootDocumentsMappingStateKey(int deviceId) { /** * @param deviceId Device ID. * @return Key for {@link #mMappingMode}. */ private static String getRootDocumentsMappingStateKey(int deviceId) { return "RootDocuments/" + deviceId; } private String getChildDocumentsMappingStateKey(String parentDocumentId) { /** * @param parentDocumentId Document ID for the parent document. * @return Key for {@link #mMappingMode}. */ private static String getChildDocumentsMappingStateKey(String parentDocumentId) { return "ChildDocuments/" + parentDocumentId; } } packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +4 −0 Original line number Diff line number Diff line Loading @@ -126,7 +126,11 @@ class MtpDatabaseConstants { Root.COLUMN_TITLE + "," + TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " + Root.COLUMN_SUMMARY + "," + // Temporary replace COLUMN_DOCUMENT_ID with old format. TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " + Root.COLUMN_DOCUMENT_ID + "_," + TABLE_DOCUMENTS + "." + COLUMN_DEVICE_ID + "|| '_' ||" + TABLE_DOCUMENTS + "." + COLUMN_STORAGE_ID + "||'_0' AS " + Root.COLUMN_DOCUMENT_ID + "," + TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," + TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," + Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java +68 −15 Original line number Diff line number Diff line Loading @@ -59,6 +59,15 @@ class MtpDatabaseInternal { mDatabase = helper.getWritableDatabase(); } void close() { mDatabase.close(); } /** * Queries roots information. * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}. * @return Database cursor. */ Cursor queryRoots(String[] columnNames) { return mDatabase.query( VIEW_ROOTS, Loading @@ -70,6 +79,12 @@ class MtpDatabaseInternal { null); } /** * Queries root documents information. * @param columnNames Column names defined in * {@link android.provider.DocumentsContract.Document}. * @return Database cursor. */ Cursor queryRootDocuments(String[] columnNames) { return mDatabase.query( TABLE_DOCUMENTS, Loading @@ -81,6 +96,12 @@ class MtpDatabaseInternal { null); } /** * Queries documents information. * @param columnNames Column names defined in * {@link android.provider.DocumentsContract.Document}. * @return Database cursor. */ Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) { return mDatabase.query( TABLE_DOCUMENTS, Loading @@ -92,6 +113,14 @@ class MtpDatabaseInternal { null); } /** * Remove all rows belong to a device. * @param deviceId Device ID. */ void removeDeviceRows(int deviceId) { deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId)); } /** * Starts adding new documents. * The methods decides mapping mode depends on if all documents under the given parent have MTP Loading Loading @@ -133,24 +162,23 @@ class MtpDatabaseInternal { * 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' * rows. * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param valuesList Values that are stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param arg Argument for selection SQL. * @param heuristic Whether the mapping mode is heuristic. * @return List of Document ID inserted to the table. * @return Whether the method adds new rows. */ long[] putDocuments( boolean putDocuments( ContentValues[] valuesList, String selection, String arg, boolean heuristic, String mappingKey) { boolean added = false; mDatabase.beginTransaction(); try { final long[] documentIds = new long[valuesList.length]; int i = 0; for (final ContentValues values : valuesList) { final Cursor candidateCursor = mDatabase.query( TABLE_DOCUMENTS, Loading @@ -166,6 +194,7 @@ class MtpDatabaseInternal { final long rowId; if (candidateCursor.getCount() == 0) { rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values); added = true; } else if (!heuristic) { candidateCursor.moveToNext(); final String documentId = candidateCursor.getString(0); Loading @@ -177,17 +206,21 @@ class MtpDatabaseInternal { } // Document ID is a primary integer key of the table. So the returned row // IDs should be same with the document ID. documentIds[i++] = rowId; values.put(Document.COLUMN_DOCUMENT_ID, rowId); candidateCursor.close(); } mDatabase.setTransactionSuccessful(); return documentIds; return added; } finally { mDatabase.endTransaction(); } } /** * Puts extra information for root documents. * @param values Values containing extra information. */ void putRootExtra(ContentValues values) { mDatabase.replace(TABLE_ROOT_EXTRA, null, values); } Loading @@ -199,8 +232,9 @@ class MtpDatabaseInternal { * @param selection Query to select rows for resolving. * @param arg Argument for selection SQL. * @param groupKey Column name used to find corresponding rows. * @return Whether the methods adds or removed visible rows. */ void stopAddingDocuments(String selection, String arg, String groupKey) { boolean stopAddingDocuments(String selection, String arg, String groupKey) { mDatabase.beginTransaction(); try { // Get 1-to-1 mapping of invalidated document and pending document. Loading Loading @@ -265,22 +299,29 @@ class MtpDatabaseInternal { } mergingCursor.close(); boolean changed = false; // Delete all invalidated rows that cannot be mapped. deleteDocumentsAndRoots( if (deleteDocumentsAndRoots( COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_INVALIDATED, arg)); strings(ROW_STATE_INVALIDATED, arg))) { changed = true; } // The database cannot find old document ID for the pending rows. // Turn the all pending rows into valid state, which means the rows become to be // valid with new document ID. values.clear(); values.put(COLUMN_ROW_STATE, ROW_STATE_VALID); mDatabase.update( if (mDatabase.update( TABLE_DOCUMENTS, values, COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_PENDING, arg)); strings(ROW_STATE_PENDING, arg)) != 0) { changed = true; } mDatabase.setTransactionSuccessful(); return changed; } finally { mDatabase.endTransaction(); } Loading @@ -307,14 +348,23 @@ class MtpDatabaseInternal { } } /** * {@link android.database.sqlite.SQLiteDatabase#beginTransaction()} */ void beginTransaction() { mDatabase.beginTransaction(); } /** * {@link android.database.sqlite.SQLiteDatabase#setTransactionSuccessful()} */ void setTransactionSuccessful() { mDatabase.setTransactionSuccessful(); } /** * {@link android.database.sqlite.SQLiteDatabase#endTransaction()} */ void endTransaction() { mDatabase.endTransaction(); } Loading @@ -323,11 +373,13 @@ class MtpDatabaseInternal { * Deletes a document, and its root information if the document is a root document. * @param selection Query to select documents. * @param args Arguments for selection. * @return Whether the method deletes rows. */ private void deleteDocumentsAndRoots(String selection, String[] args) { private boolean deleteDocumentsAndRoots(String selection, String[] args) { mDatabase.beginTransaction(); try { mDatabase.delete( int deleted = 0; deleted += mDatabase.delete( TABLE_ROOT_EXTRA, Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString( false, Loading @@ -339,8 +391,9 @@ class MtpDatabaseInternal { null, null) + ")", args); mDatabase.delete(TABLE_DOCUMENTS, selection, args); deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args); mDatabase.setTransactionSuccessful(); return deleted != 0; } finally { mDatabase.endTransaction(); } Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +18 −17 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private Map<Integer, DeviceToolkit> mDeviceToolkits; private RootScanner mRootScanner; private Resources mResources; private MtpDatabase mDatabase; /** * Provides singleton instance to MtpDocumentsService. Loading @@ -77,17 +78,23 @@ public class MtpDocumentsProvider extends DocumentsProvider { mMtpManager = new MtpManager(getContext()); mResolver = getContext().getContentResolver(); mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); mRootScanner = new RootScanner(mResolver, mMtpManager); mDatabase = new MtpDatabase(getContext()); mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); return true; } @VisibleForTesting void onCreateForTesting(Resources resources, MtpManager mtpManager, ContentResolver resolver) { void onCreateForTesting( Resources resources, MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) { mResources = resources; mMtpManager = mtpManager; mResolver = resolver; mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); mRootScanner = new RootScanner(mResolver, mMtpManager); mDatabase = database; mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); } @Override Loading @@ -95,17 +102,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { if (projection == null) { projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; } final MatrixCursor cursor = new MatrixCursor(projection); final MtpRoot[] roots = mRootScanner.getRoots(); for (final MtpRoot root : roots) { final Identifier rootIdentifier = new Identifier(root.mDeviceId, root.mStorageId); final MatrixCursor.RowBuilder builder = cursor.newRow(); builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId()); builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); builder.add(Root.COLUMN_TITLE, root.getRootName(mResources)); builder.add(Root.COLUMN_DOCUMENT_ID, rootIdentifier.toDocumentId()); builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace); } final Cursor cursor = mDatabase.queryRoots(projection); cursor.setNotificationUri( mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); return cursor; Loading Loading @@ -266,14 +263,16 @@ public class MtpDocumentsProvider extends DocumentsProvider { // TODO: Flush the device before closing (if not closed externally). getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); mDeviceToolkits.remove(deviceId); mDatabase.removeDeviceRows(deviceId); mMtpManager.closeDevice(deviceId); mRootScanner.scanNow(); mRootScanner.notifyChange(); } void closeAllDevices() { void close() throws InterruptedException { boolean closed = false; for (int deviceId : mMtpManager.getOpenedDeviceIds()) { try { mDatabase.removeDeviceRows(deviceId); mMtpManager.closeDevice(deviceId); getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); closed = true; Loading @@ -282,8 +281,10 @@ public class MtpDocumentsProvider extends DocumentsProvider { } } if (closed) { mRootScanner.scanNow(); mRootScanner.notifyChange(); } mRootScanner.close(); mDatabase.close(); } boolean hasOpenedDevices() { Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java +5 −1 Original line number Diff line number Diff line Loading @@ -79,9 +79,13 @@ public class MtpDocumentsService extends Service { @Override public void onDestroy() { final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance(); provider.closeAllDevices(); unregisterReceiver(mReceiver); mReceiver = null; try { provider.close(); } catch (InterruptedException e) { Log.e(MtpDocumentsProvider.TAG, e.getMessage()); } super.onDestroy(); } Loading Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +107 −38 Original line number Diff line number Diff line Loading @@ -71,6 +71,11 @@ import java.util.Map; @VisibleForTesting class MtpDatabase { private final MtpDatabaseInternal mDatabase; /** * Mapping mode for roots/documents where we start adding child documents. * Methods operate the state needs to be synchronized. */ private final Map<String, Integer> mMappingMode = new HashMap<>(); @VisibleForTesting Loading @@ -78,23 +83,49 @@ class MtpDatabase { mDatabase = new MtpDatabaseInternal(context); } /** * Closes the database. */ @VisibleForTesting void close() { mDatabase.close(); } /** * {@link MtpDatabaseInternal#queryRoots} */ Cursor queryRoots(String[] columnNames) { return mDatabase.queryRoots(columnNames); } /** * {@link MtpDatabaseInternal#queryRootDocuments} */ @VisibleForTesting Cursor queryRootDocuments(String[] columnNames) { return mDatabase.queryRootDocuments(columnNames); } /** * {@link MtpDatabaseInternal#queryChildDocuments} */ @VisibleForTesting Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) { return mDatabase.queryChildDocuments(columnNames, parentDocumentId); } @VisibleForTesting void startAddingRootDocuments(int deviceId) { /** * {@link MtpDatabaseInternal#removeDeviceRows} */ void removeDeviceRows(int deviceId) { mDatabase.removeDeviceRows(deviceId); } /** * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents. * @param deviceId Device ID. */ synchronized void startAddingRootDocuments(int deviceId) { final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId); if (mMappingMode.containsKey(mappingStateKey)) { throw new Error("Mapping for the root has already started."); Loading @@ -105,8 +136,12 @@ class MtpDatabase { SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId))); } /** * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for child of specific documents. * @param parentDocumentId Document ID for parent document. */ @VisibleForTesting void startAddingChildDocuments(String parentDocumentId) { synchronized void startAddingChildDocuments(String parentDocumentId) { final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId); if (mMappingMode.containsKey(mappingStateKey)) { throw new Error("Mapping for the root has already started."); Loading @@ -116,20 +151,18 @@ class MtpDatabase { mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId)); } @VisibleForTesting void putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) { /** * Puts root information to database. * @param deviceId Device ID * @param resources Resources required to localize root name. * @param roots List of root information. * @return If roots are added or removed from the database. */ synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) { mDatabase.beginTransaction(); try { final ContentValues[] valuesList = new ContentValues[roots.length]; for (int i = 0; i < roots.length; i++) { if (roots[i].mDeviceId != deviceId) { throw new IllegalArgumentException(); } valuesList[i] = new ContentValues(); getRootDocumentValues(valuesList[i], resources, roots[i]); } boolean heuristic; String mapColumn; final boolean heuristic; final String mapColumn; switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; Loading @@ -142,7 +175,15 @@ class MtpDatabase { default: throw new Error("Unexpected map mode."); } final long[] documentIds = mDatabase.putDocuments( final ContentValues[] valuesList = new ContentValues[roots.length]; for (int i = 0; i < roots.length; i++) { if (roots[i].mDeviceId != deviceId) { throw new IllegalArgumentException(); } valuesList[i] = new ContentValues(); getRootDocumentValues(valuesList[i], resources, roots[i]); } final boolean changed = mDatabase.putDocuments( valuesList, SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), Loading @@ -152,33 +193,38 @@ class MtpDatabase { int i = 0; for (final MtpRoot root : roots) { // Use the same value for the root ID and the corresponding document ID. values.put(Root.COLUMN_ROOT_ID, documentIds[i++]); values.put( Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); Root.COLUMN_ROOT_ID, valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID)); values.put( Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace); values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity); values.put(Root.COLUMN_MIME_TYPES, ""); mDatabase.putRootExtra(values); } mDatabase.setTransactionSuccessful(); return changed; } finally { mDatabase.endTransaction(); } } /** * Puts document information to database. * @param deviceId Device ID * @param parentId Parent document ID. * @param documents List of document information. */ @VisibleForTesting void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) { final ContentValues[] valuesList = new ContentValues[documents.length]; for (int i = 0; i < documents.length; i++) { valuesList[i] = new ContentValues(); getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]); } boolean heuristic; String mapColumn; synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) { final boolean heuristic; final String mapColumn; switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; mapColumn = COLUMN_STORAGE_ID; mapColumn = COLUMN_OBJECT_HANDLE; break; case MAP_BY_NAME: heuristic = true; Loading @@ -187,40 +233,55 @@ class MtpDatabase { default: throw new Error("Unexpected map mode."); } final ContentValues[] valuesList = new ContentValues[documents.length]; for (int i = 0; i < documents.length; i++) { valuesList[i] = new ContentValues(); getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]); } mDatabase.putDocuments( valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn); } /** * Clears mapping between MTP identifier and document/root ID. */ @VisibleForTesting void clearMapping() { synchronized void clearMapping() { mDatabase.clearMapping(); mMappingMode.clear(); } @VisibleForTesting void stopAddingRootDocuments(int deviceId) { /** * Stops adding root documents. * @param deviceId Device ID. * @return True if new rows are added/removed. */ synchronized boolean stopAddingRootDocuments(int deviceId) { final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId); switch (mMappingMode.get(mappingModeKey)) { case MAP_BY_MTP_IDENTIFIER: mDatabase.stopAddingDocuments( mMappingMode.remove(mappingModeKey); return mDatabase.stopAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), COLUMN_STORAGE_ID); break; case MAP_BY_NAME: mDatabase.stopAddingDocuments( mMappingMode.remove(mappingModeKey); return mDatabase.stopAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), Document.COLUMN_DISPLAY_NAME); break; default: throw new Error("Unexpected mapping state."); } mMappingMode.remove(mappingModeKey); } /** * Stops adding documents under the parent. * @param parentId Document ID of the parent. */ @VisibleForTesting void stopAddingChildDocuments(String parentId) { synchronized void stopAddingChildDocuments(String parentId) { final String mappingModeKey = getChildDocumentsMappingStateKey(parentId); switch (mMappingMode.get(mappingModeKey)) { case MAP_BY_MTP_IDENTIFIER: Loading Loading @@ -303,11 +364,19 @@ class MtpDatabase { values.put(Document.COLUMN_SIZE, info.getCompressedSize()); } private String getRootDocumentsMappingStateKey(int deviceId) { /** * @param deviceId Device ID. * @return Key for {@link #mMappingMode}. */ private static String getRootDocumentsMappingStateKey(int deviceId) { return "RootDocuments/" + deviceId; } private String getChildDocumentsMappingStateKey(String parentDocumentId) { /** * @param parentDocumentId Document ID for the parent document. * @return Key for {@link #mMappingMode}. */ private static String getChildDocumentsMappingStateKey(String parentDocumentId) { return "ChildDocuments/" + parentDocumentId; } }
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +4 −0 Original line number Diff line number Diff line Loading @@ -126,7 +126,11 @@ class MtpDatabaseConstants { Root.COLUMN_TITLE + "," + TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " + Root.COLUMN_SUMMARY + "," + // Temporary replace COLUMN_DOCUMENT_ID with old format. TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " + Root.COLUMN_DOCUMENT_ID + "_," + TABLE_DOCUMENTS + "." + COLUMN_DEVICE_ID + "|| '_' ||" + TABLE_DOCUMENTS + "." + COLUMN_STORAGE_ID + "||'_0' AS " + Root.COLUMN_DOCUMENT_ID + "," + TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," + TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," + Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java +68 −15 Original line number Diff line number Diff line Loading @@ -59,6 +59,15 @@ class MtpDatabaseInternal { mDatabase = helper.getWritableDatabase(); } void close() { mDatabase.close(); } /** * Queries roots information. * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}. * @return Database cursor. */ Cursor queryRoots(String[] columnNames) { return mDatabase.query( VIEW_ROOTS, Loading @@ -70,6 +79,12 @@ class MtpDatabaseInternal { null); } /** * Queries root documents information. * @param columnNames Column names defined in * {@link android.provider.DocumentsContract.Document}. * @return Database cursor. */ Cursor queryRootDocuments(String[] columnNames) { return mDatabase.query( TABLE_DOCUMENTS, Loading @@ -81,6 +96,12 @@ class MtpDatabaseInternal { null); } /** * Queries documents information. * @param columnNames Column names defined in * {@link android.provider.DocumentsContract.Document}. * @return Database cursor. */ Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) { return mDatabase.query( TABLE_DOCUMENTS, Loading @@ -92,6 +113,14 @@ class MtpDatabaseInternal { null); } /** * Remove all rows belong to a device. * @param deviceId Device ID. */ void removeDeviceRows(int deviceId) { deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId)); } /** * Starts adding new documents. * The methods decides mapping mode depends on if all documents under the given parent have MTP Loading Loading @@ -133,24 +162,23 @@ class MtpDatabaseInternal { * 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' * rows. * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param valuesList Values that are stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param arg Argument for selection SQL. * @param heuristic Whether the mapping mode is heuristic. * @return List of Document ID inserted to the table. * @return Whether the method adds new rows. */ long[] putDocuments( boolean putDocuments( ContentValues[] valuesList, String selection, String arg, boolean heuristic, String mappingKey) { boolean added = false; mDatabase.beginTransaction(); try { final long[] documentIds = new long[valuesList.length]; int i = 0; for (final ContentValues values : valuesList) { final Cursor candidateCursor = mDatabase.query( TABLE_DOCUMENTS, Loading @@ -166,6 +194,7 @@ class MtpDatabaseInternal { final long rowId; if (candidateCursor.getCount() == 0) { rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values); added = true; } else if (!heuristic) { candidateCursor.moveToNext(); final String documentId = candidateCursor.getString(0); Loading @@ -177,17 +206,21 @@ class MtpDatabaseInternal { } // Document ID is a primary integer key of the table. So the returned row // IDs should be same with the document ID. documentIds[i++] = rowId; values.put(Document.COLUMN_DOCUMENT_ID, rowId); candidateCursor.close(); } mDatabase.setTransactionSuccessful(); return documentIds; return added; } finally { mDatabase.endTransaction(); } } /** * Puts extra information for root documents. * @param values Values containing extra information. */ void putRootExtra(ContentValues values) { mDatabase.replace(TABLE_ROOT_EXTRA, null, values); } Loading @@ -199,8 +232,9 @@ class MtpDatabaseInternal { * @param selection Query to select rows for resolving. * @param arg Argument for selection SQL. * @param groupKey Column name used to find corresponding rows. * @return Whether the methods adds or removed visible rows. */ void stopAddingDocuments(String selection, String arg, String groupKey) { boolean stopAddingDocuments(String selection, String arg, String groupKey) { mDatabase.beginTransaction(); try { // Get 1-to-1 mapping of invalidated document and pending document. Loading Loading @@ -265,22 +299,29 @@ class MtpDatabaseInternal { } mergingCursor.close(); boolean changed = false; // Delete all invalidated rows that cannot be mapped. deleteDocumentsAndRoots( if (deleteDocumentsAndRoots( COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_INVALIDATED, arg)); strings(ROW_STATE_INVALIDATED, arg))) { changed = true; } // The database cannot find old document ID for the pending rows. // Turn the all pending rows into valid state, which means the rows become to be // valid with new document ID. values.clear(); values.put(COLUMN_ROW_STATE, ROW_STATE_VALID); mDatabase.update( if (mDatabase.update( TABLE_DOCUMENTS, values, COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_PENDING, arg)); strings(ROW_STATE_PENDING, arg)) != 0) { changed = true; } mDatabase.setTransactionSuccessful(); return changed; } finally { mDatabase.endTransaction(); } Loading @@ -307,14 +348,23 @@ class MtpDatabaseInternal { } } /** * {@link android.database.sqlite.SQLiteDatabase#beginTransaction()} */ void beginTransaction() { mDatabase.beginTransaction(); } /** * {@link android.database.sqlite.SQLiteDatabase#setTransactionSuccessful()} */ void setTransactionSuccessful() { mDatabase.setTransactionSuccessful(); } /** * {@link android.database.sqlite.SQLiteDatabase#endTransaction()} */ void endTransaction() { mDatabase.endTransaction(); } Loading @@ -323,11 +373,13 @@ class MtpDatabaseInternal { * Deletes a document, and its root information if the document is a root document. * @param selection Query to select documents. * @param args Arguments for selection. * @return Whether the method deletes rows. */ private void deleteDocumentsAndRoots(String selection, String[] args) { private boolean deleteDocumentsAndRoots(String selection, String[] args) { mDatabase.beginTransaction(); try { mDatabase.delete( int deleted = 0; deleted += mDatabase.delete( TABLE_ROOT_EXTRA, Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString( false, Loading @@ -339,8 +391,9 @@ class MtpDatabaseInternal { null, null) + ")", args); mDatabase.delete(TABLE_DOCUMENTS, selection, args); deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args); mDatabase.setTransactionSuccessful(); return deleted != 0; } finally { mDatabase.endTransaction(); } Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +18 −17 Original line number Diff line number Diff line Loading @@ -62,6 +62,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private Map<Integer, DeviceToolkit> mDeviceToolkits; private RootScanner mRootScanner; private Resources mResources; private MtpDatabase mDatabase; /** * Provides singleton instance to MtpDocumentsService. Loading @@ -77,17 +78,23 @@ public class MtpDocumentsProvider extends DocumentsProvider { mMtpManager = new MtpManager(getContext()); mResolver = getContext().getContentResolver(); mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); mRootScanner = new RootScanner(mResolver, mMtpManager); mDatabase = new MtpDatabase(getContext()); mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); return true; } @VisibleForTesting void onCreateForTesting(Resources resources, MtpManager mtpManager, ContentResolver resolver) { void onCreateForTesting( Resources resources, MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) { mResources = resources; mMtpManager = mtpManager; mResolver = resolver; mDeviceToolkits = new HashMap<Integer, DeviceToolkit>(); mRootScanner = new RootScanner(mResolver, mMtpManager); mDatabase = database; mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); } @Override Loading @@ -95,17 +102,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { if (projection == null) { projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION; } final MatrixCursor cursor = new MatrixCursor(projection); final MtpRoot[] roots = mRootScanner.getRoots(); for (final MtpRoot root : roots) { final Identifier rootIdentifier = new Identifier(root.mDeviceId, root.mStorageId); final MatrixCursor.RowBuilder builder = cursor.newRow(); builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId()); builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE); builder.add(Root.COLUMN_TITLE, root.getRootName(mResources)); builder.add(Root.COLUMN_DOCUMENT_ID, rootIdentifier.toDocumentId()); builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace); } final Cursor cursor = mDatabase.queryRoots(projection); cursor.setNotificationUri( mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY)); return cursor; Loading Loading @@ -266,14 +263,16 @@ public class MtpDocumentsProvider extends DocumentsProvider { // TODO: Flush the device before closing (if not closed externally). getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); mDeviceToolkits.remove(deviceId); mDatabase.removeDeviceRows(deviceId); mMtpManager.closeDevice(deviceId); mRootScanner.scanNow(); mRootScanner.notifyChange(); } void closeAllDevices() { void close() throws InterruptedException { boolean closed = false; for (int deviceId : mMtpManager.getOpenedDeviceIds()) { try { mDatabase.removeDeviceRows(deviceId); mMtpManager.closeDevice(deviceId); getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); closed = true; Loading @@ -282,8 +281,10 @@ public class MtpDocumentsProvider extends DocumentsProvider { } } if (closed) { mRootScanner.scanNow(); mRootScanner.notifyChange(); } mRootScanner.close(); mDatabase.close(); } boolean hasOpenedDevices() { Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java +5 −1 Original line number Diff line number Diff line Loading @@ -79,9 +79,13 @@ public class MtpDocumentsService extends Service { @Override public void onDestroy() { final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance(); provider.closeAllDevices(); unregisterReceiver(mReceiver); mReceiver = null; try { provider.close(); } catch (InterruptedException e) { Log.e(MtpDocumentsProvider.TAG, e.getMessage()); } super.onDestroy(); } Loading