Loading packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +3 −3 Original line number Diff line number Diff line Loading @@ -255,7 +255,7 @@ class DocumentLoader { return; } if (mNumLoaded == 0) { mDatabase.getMapper().startAddingChildDocuments(mIdentifier.mDocumentId); mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId); } try { mDatabase.getMapper().putChildDocuments( Loading @@ -266,7 +266,7 @@ class DocumentLoader { mNumLoaded = 0; } if (getState() != STATE_LOADING) { mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId); mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } } Loading @@ -275,7 +275,7 @@ class DocumentLoader { mError = message; mNumLoaded = 0; if (lastState == STATE_LOADING) { mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId); mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } } Loading packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +66 −124 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.mtp; import static com.android.mtp.MtpDatabaseConstants.*; import android.annotation.Nullable; import android.content.ContentValues; import android.content.res.Resources; import android.database.Cursor; Loading @@ -36,7 +37,6 @@ import java.util.Map; import static com.android.mtp.MtpDatabase.strings; /** * Mapping operations for MtpDatabase. * Also see the comments of {@link MtpDatabase}. Loading @@ -45,8 +45,9 @@ class Mapper { private final MtpDatabase mDatabase; /** * Mapping mode for roots/documents where we start adding child documents. * Mapping mode for a parent. The key is document ID of parent, or null for root documents. * Methods operate the state needs to be synchronized. * TODO: Replace this with unboxing int map. */ private final Map<String, Integer> mMappingMode = new HashMap<>(); Loading @@ -54,32 +55,6 @@ class Mapper { mDatabase = database; } /** * Invokes {@link #startAddingDocuments} for root documents. * @param deviceId Device ID. */ synchronized void startAddingRootDocuments(int deviceId) { final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId); Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey)); mMappingMode.put( mappingStateKey, startAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId))); } /** * Invokes {@link #startAddingDocuments} for child of specific documents. * @param parentDocumentId Document ID for parent document. */ @VisibleForTesting synchronized void startAddingChildDocuments(String parentDocumentId) { final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId); Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey)); mMappingMode.put( mappingStateKey, startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId)); } /** * Puts root information to database. * @param deviceId Device ID Loading @@ -93,9 +68,8 @@ class Mapper { try { final boolean heuristic; final String mapColumn; final String key = getRootDocumentsMappingStateKey(deviceId); Preconditions.checkState(mMappingMode.containsKey(key)); switch (mMappingMode.get(key)) { Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null)); switch (mMappingMode.get(/* no parent for root */ null)) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; mapColumn = COLUMN_STORAGE_ID; Loading @@ -117,8 +91,8 @@ class Mapper { } final boolean changed = putDocuments( valuesList, SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), COLUMN_PARENT_DOCUMENT_ID + " IS NULL", new String[0], heuristic, mapColumn); final ContentValues values = new ContentValues(); Loading Loading @@ -156,9 +130,8 @@ class Mapper { synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) { final boolean heuristic; final String mapColumn; final String key = getChildDocumentsMappingStateKey(parentId); Preconditions.checkState(mMappingMode.containsKey(key)); switch (mMappingMode.get(key)) { Preconditions.checkState(mMappingMode.containsKey(parentId)); switch (mMappingMode.get(parentId)) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; mapColumn = COLUMN_OBJECT_HANDLE; Loading @@ -177,59 +150,11 @@ class Mapper { valuesList[i], deviceId, parentId, documents[i]); } putDocuments( valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn); } /** * Stops adding root documents. * @param deviceId Device ID. * @return True if new rows are added/removed. */ synchronized boolean stopAddingRootDocuments(int deviceId) { final String key = getRootDocumentsMappingStateKey(deviceId); Preconditions.checkState(mMappingMode.containsKey(key)); switch (mMappingMode.get(key)) { case MAP_BY_MTP_IDENTIFIER: mMappingMode.remove(key); return stopAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), COLUMN_STORAGE_ID); case MAP_BY_NAME: mMappingMode.remove(key); return stopAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), Document.COLUMN_DISPLAY_NAME); default: throw new Error("Unexpected mapping state."); } } /** * Stops adding documents under the parent. * @param parentId Document ID of the parent. */ synchronized void stopAddingChildDocuments(String parentId) { final String key = getChildDocumentsMappingStateKey(parentId); Preconditions.checkState(mMappingMode.containsKey(key)); switch (mMappingMode.get(key)) { case MAP_BY_MTP_IDENTIFIER: stopAddingDocuments( SELECTION_CHILD_DOCUMENTS, parentId, COLUMN_OBJECT_HANDLE); break; case MAP_BY_NAME: stopAddingDocuments( SELECTION_CHILD_DOCUMENTS, parentId, Document.COLUMN_DISPLAY_NAME); break; default: throw new Error("Unexpected mapping state."); } mMappingMode.remove(key); valuesList, COLUMN_PARENT_DOCUMENT_ID + "=?", strings(parentId), heuristic, mapColumn); } @VisibleForTesting Loading Loading @@ -257,31 +182,42 @@ class Mapper { * identifier or not. If all the documents have MTP identifier, it uses the identifier to find * a corresponding existing row. Otherwise it does heuristic. * * @param selection Query matches valid documents. * @param arg Argument for selection. * @return Mapping mode. * @param parentDocumentId Parent document ID or NULL for root documents. */ private int startAddingDocuments(String selection, String arg) { void startAddingDocuments(@Nullable String parentDocumentId) { Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId)); final String selection; final String[] args; if (parentDocumentId != null) { selection = COLUMN_PARENT_DOCUMENT_ID + " = ?"; args = strings(parentDocumentId); } else { selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; args = new String[0]; } final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { // Delete all pending rows. mDatabase.deleteDocumentsAndRootsRecursively( selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING)); selection + " AND " + COLUMN_ROW_STATE + "=?", DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_PENDING))); // Set all documents as invalidated. final ContentValues values = new ContentValues(); values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED); database.update(TABLE_DOCUMENTS, values, selection, new String[] { arg }); database.update(TABLE_DOCUMENTS, values, selection, args); // If we have rows that does not have MTP identifier, do heuristic mapping by name. final boolean useNameForResolving = DatabaseUtils.queryNumEntries( database, TABLE_DOCUMENTS, selection + " AND " + COLUMN_STORAGE_ID + " IS NULL", new String[] { arg }) > 0; args) > 0; database.setTransactionSuccessful(); return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER; mMappingMode.put( parentDocumentId, useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER); } finally { database.endTransaction(); } Loading @@ -292,19 +228,19 @@ class Mapper { * If the mapping mode is not heuristic, it just adds the rows to the database or updates the * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid' * {@link #stopAddingDocuments(String, String[], String)} turns the pending rows into 'valid' * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param valuesList Values for documents to be stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param arg Argument for selection SQL. * @param args Argument for selection SQL. * @param heuristic Whether the mapping mode is heuristic. * @return Whether the method adds new rows. */ private boolean putDocuments( ContentValues[] valuesList, String selection, String arg, String[] args, boolean heuristic, String mappingKey) { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); Loading @@ -318,7 +254,9 @@ class Mapper { selection + " AND " + COLUMN_ROW_STATE + "=? AND " + mappingKey + "=?", strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)), DatabaseUtils.appendSelectionArgs( args, strings(ROW_STATE_INVALIDATED, values.getAsString(mappingKey))), null, null, null, Loading Loading @@ -362,12 +300,32 @@ class Mapper { * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey. * If the database does not find corresponding 'invalidated' document, it just removes * 'invalidated' document from the database. * @param selection Query to select rows for resolving. * @param arg Argument for selection SQL. * @param groupKey Column name used to find corresponding rows. * @param parentId Parent document ID or null for root documents. * @return Whether the methods adds or removed visible rows. */ private boolean stopAddingDocuments(String selection, String arg, String groupKey) { boolean stopAddingDocuments(@Nullable String parentId) { Preconditions.checkState(mMappingMode.containsKey(parentId)); final String selection; final String[] args; if (parentId != null) { selection = COLUMN_PARENT_DOCUMENT_ID + "=?"; args = strings(parentId); } else { selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; args = new String[0]; } final String groupKey; switch (mMappingMode.get(parentId)) { case MAP_BY_MTP_IDENTIFIER: groupKey = parentId != null ? COLUMN_OBJECT_HANDLE : COLUMN_STORAGE_ID; break; case MAP_BY_NAME: groupKey = Document.COLUMN_DISPLAY_NAME; break; default: throw new Error("Unexpected mapping state."); } mMappingMode.remove(parentId); final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { Loading @@ -390,7 +348,7 @@ class Mapper { "group_concat(" + pendingIdQuery + ")" }, selection, strings(arg), args, groupKey, "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1", null); Loading Loading @@ -439,7 +397,7 @@ class Mapper { // Delete all invalidated rows that cannot be mapped. if (mDatabase.deleteDocumentsAndRootsRecursively( COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_INVALIDATED, arg))) { DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) { changed = true; } Loading @@ -452,7 +410,7 @@ class Mapper { TABLE_DOCUMENTS, values, COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_PENDING, arg)) != 0) { DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_PENDING), args)) != 0) { changed = true; } database.setTransactionSuccessful(); Loading Loading @@ -494,20 +452,4 @@ class Mapper { return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) + " THEN " + a + " ELSE NULL END"; } /** * @param deviceId Device ID. * @return Key for {@link #mMappingMode}. */ private static String getRootDocumentsMappingStateKey(int deviceId) { return "RootDocuments/" + deviceId; } /** * @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/MtpDatabase.java +4 −4 Original line number Diff line number Diff line Loading @@ -53,13 +53,13 @@ import java.util.Set; * by comparing the directory structure and object name. * * To start putting documents into the database, the client needs to call * {@link Mapper#startAddingChildDocuments(String)} with the parent document ID. Also it * needs to call {@link Mapper#stopAddingChildDocuments(String)} after putting all child * {@link Mapper#startAddingDocuments(String)} with the parent document ID. Also it * needs to call {@link Mapper#stopAddingDocuments(String)} after putting all child * documents to the database. (All explanations are same for root documents) * * database.getMapper().startAddingChildDocuments(); * database.getMapper().startAddingDocuments(); * database.getMapper().putChildDocuments(); * database.getMapper().stopAddingChildDocuments(); * database.getMapper().stopAddingDocuments(); * * To update the existing documents, the client code can repeat to call the three methods again. * The newly added rows update corresponding existing rows that have same MTP identifier like Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +0 −3 Original line number Diff line number Diff line Loading @@ -103,9 +103,6 @@ class MtpDatabaseConstants { static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?"; static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?"; static final String SELECTION_ROOT_DOCUMENTS = COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?"; static final String QUERY_CREATE_DOCUMENTS = "CREATE TABLE " + TABLE_DOCUMENTS + " (" + Loading packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +6 −10 Original line number Diff line number Diff line Loading @@ -110,26 +110,22 @@ final class RootScanner { return; } boolean changed = false; mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */); for (int deviceId : deviceIds) { try { final MtpRoot[] roots = mManager.getRoots(deviceId); mDatabase.getMapper().startAddingRootDocuments(deviceId); try { if (mDatabase.getMapper().putRootDocuments( deviceId, mResources, roots)) { changed = true; } } finally { if (mDatabase.getMapper().stopAddingRootDocuments(deviceId)) { if (mDatabase.getMapper().putRootDocuments(deviceId, mResources, roots)) { changed = true; } } } catch (IOException | SQLiteException exception) { // The error may happen on the device. We would like to continue getting // roots for other devices. Log.e(MtpDocumentsProvider.TAG, exception.getMessage()); } } if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) { changed = true; } if (changed) { notifyChange(); } Loading Loading
packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +3 −3 Original line number Diff line number Diff line Loading @@ -255,7 +255,7 @@ class DocumentLoader { return; } if (mNumLoaded == 0) { mDatabase.getMapper().startAddingChildDocuments(mIdentifier.mDocumentId); mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId); } try { mDatabase.getMapper().putChildDocuments( Loading @@ -266,7 +266,7 @@ class DocumentLoader { mNumLoaded = 0; } if (getState() != STATE_LOADING) { mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId); mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } } Loading @@ -275,7 +275,7 @@ class DocumentLoader { mError = message; mNumLoaded = 0; if (lastState == STATE_LOADING) { mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId); mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } } Loading
packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +66 −124 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.mtp; import static com.android.mtp.MtpDatabaseConstants.*; import android.annotation.Nullable; import android.content.ContentValues; import android.content.res.Resources; import android.database.Cursor; Loading @@ -36,7 +37,6 @@ import java.util.Map; import static com.android.mtp.MtpDatabase.strings; /** * Mapping operations for MtpDatabase. * Also see the comments of {@link MtpDatabase}. Loading @@ -45,8 +45,9 @@ class Mapper { private final MtpDatabase mDatabase; /** * Mapping mode for roots/documents where we start adding child documents. * Mapping mode for a parent. The key is document ID of parent, or null for root documents. * Methods operate the state needs to be synchronized. * TODO: Replace this with unboxing int map. */ private final Map<String, Integer> mMappingMode = new HashMap<>(); Loading @@ -54,32 +55,6 @@ class Mapper { mDatabase = database; } /** * Invokes {@link #startAddingDocuments} for root documents. * @param deviceId Device ID. */ synchronized void startAddingRootDocuments(int deviceId) { final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId); Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey)); mMappingMode.put( mappingStateKey, startAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId))); } /** * Invokes {@link #startAddingDocuments} for child of specific documents. * @param parentDocumentId Document ID for parent document. */ @VisibleForTesting synchronized void startAddingChildDocuments(String parentDocumentId) { final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId); Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey)); mMappingMode.put( mappingStateKey, startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId)); } /** * Puts root information to database. * @param deviceId Device ID Loading @@ -93,9 +68,8 @@ class Mapper { try { final boolean heuristic; final String mapColumn; final String key = getRootDocumentsMappingStateKey(deviceId); Preconditions.checkState(mMappingMode.containsKey(key)); switch (mMappingMode.get(key)) { Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null)); switch (mMappingMode.get(/* no parent for root */ null)) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; mapColumn = COLUMN_STORAGE_ID; Loading @@ -117,8 +91,8 @@ class Mapper { } final boolean changed = putDocuments( valuesList, SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), COLUMN_PARENT_DOCUMENT_ID + " IS NULL", new String[0], heuristic, mapColumn); final ContentValues values = new ContentValues(); Loading Loading @@ -156,9 +130,8 @@ class Mapper { synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) { final boolean heuristic; final String mapColumn; final String key = getChildDocumentsMappingStateKey(parentId); Preconditions.checkState(mMappingMode.containsKey(key)); switch (mMappingMode.get(key)) { Preconditions.checkState(mMappingMode.containsKey(parentId)); switch (mMappingMode.get(parentId)) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; mapColumn = COLUMN_OBJECT_HANDLE; Loading @@ -177,59 +150,11 @@ class Mapper { valuesList[i], deviceId, parentId, documents[i]); } putDocuments( valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn); } /** * Stops adding root documents. * @param deviceId Device ID. * @return True if new rows are added/removed. */ synchronized boolean stopAddingRootDocuments(int deviceId) { final String key = getRootDocumentsMappingStateKey(deviceId); Preconditions.checkState(mMappingMode.containsKey(key)); switch (mMappingMode.get(key)) { case MAP_BY_MTP_IDENTIFIER: mMappingMode.remove(key); return stopAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), COLUMN_STORAGE_ID); case MAP_BY_NAME: mMappingMode.remove(key); return stopAddingDocuments( SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId), Document.COLUMN_DISPLAY_NAME); default: throw new Error("Unexpected mapping state."); } } /** * Stops adding documents under the parent. * @param parentId Document ID of the parent. */ synchronized void stopAddingChildDocuments(String parentId) { final String key = getChildDocumentsMappingStateKey(parentId); Preconditions.checkState(mMappingMode.containsKey(key)); switch (mMappingMode.get(key)) { case MAP_BY_MTP_IDENTIFIER: stopAddingDocuments( SELECTION_CHILD_DOCUMENTS, parentId, COLUMN_OBJECT_HANDLE); break; case MAP_BY_NAME: stopAddingDocuments( SELECTION_CHILD_DOCUMENTS, parentId, Document.COLUMN_DISPLAY_NAME); break; default: throw new Error("Unexpected mapping state."); } mMappingMode.remove(key); valuesList, COLUMN_PARENT_DOCUMENT_ID + "=?", strings(parentId), heuristic, mapColumn); } @VisibleForTesting Loading Loading @@ -257,31 +182,42 @@ class Mapper { * identifier or not. If all the documents have MTP identifier, it uses the identifier to find * a corresponding existing row. Otherwise it does heuristic. * * @param selection Query matches valid documents. * @param arg Argument for selection. * @return Mapping mode. * @param parentDocumentId Parent document ID or NULL for root documents. */ private int startAddingDocuments(String selection, String arg) { void startAddingDocuments(@Nullable String parentDocumentId) { Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId)); final String selection; final String[] args; if (parentDocumentId != null) { selection = COLUMN_PARENT_DOCUMENT_ID + " = ?"; args = strings(parentDocumentId); } else { selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; args = new String[0]; } final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { // Delete all pending rows. mDatabase.deleteDocumentsAndRootsRecursively( selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING)); selection + " AND " + COLUMN_ROW_STATE + "=?", DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_PENDING))); // Set all documents as invalidated. final ContentValues values = new ContentValues(); values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED); database.update(TABLE_DOCUMENTS, values, selection, new String[] { arg }); database.update(TABLE_DOCUMENTS, values, selection, args); // If we have rows that does not have MTP identifier, do heuristic mapping by name. final boolean useNameForResolving = DatabaseUtils.queryNumEntries( database, TABLE_DOCUMENTS, selection + " AND " + COLUMN_STORAGE_ID + " IS NULL", new String[] { arg }) > 0; args) > 0; database.setTransactionSuccessful(); return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER; mMappingMode.put( parentDocumentId, useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER); } finally { database.endTransaction(); } Loading @@ -292,19 +228,19 @@ class Mapper { * If the mapping mode is not heuristic, it just adds the rows to the database or updates the * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid' * {@link #stopAddingDocuments(String, String[], String)} turns the pending rows into 'valid' * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param valuesList Values for documents to be stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param arg Argument for selection SQL. * @param args Argument for selection SQL. * @param heuristic Whether the mapping mode is heuristic. * @return Whether the method adds new rows. */ private boolean putDocuments( ContentValues[] valuesList, String selection, String arg, String[] args, boolean heuristic, String mappingKey) { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); Loading @@ -318,7 +254,9 @@ class Mapper { selection + " AND " + COLUMN_ROW_STATE + "=? AND " + mappingKey + "=?", strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)), DatabaseUtils.appendSelectionArgs( args, strings(ROW_STATE_INVALIDATED, values.getAsString(mappingKey))), null, null, null, Loading Loading @@ -362,12 +300,32 @@ class Mapper { * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey. * If the database does not find corresponding 'invalidated' document, it just removes * 'invalidated' document from the database. * @param selection Query to select rows for resolving. * @param arg Argument for selection SQL. * @param groupKey Column name used to find corresponding rows. * @param parentId Parent document ID or null for root documents. * @return Whether the methods adds or removed visible rows. */ private boolean stopAddingDocuments(String selection, String arg, String groupKey) { boolean stopAddingDocuments(@Nullable String parentId) { Preconditions.checkState(mMappingMode.containsKey(parentId)); final String selection; final String[] args; if (parentId != null) { selection = COLUMN_PARENT_DOCUMENT_ID + "=?"; args = strings(parentId); } else { selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; args = new String[0]; } final String groupKey; switch (mMappingMode.get(parentId)) { case MAP_BY_MTP_IDENTIFIER: groupKey = parentId != null ? COLUMN_OBJECT_HANDLE : COLUMN_STORAGE_ID; break; case MAP_BY_NAME: groupKey = Document.COLUMN_DISPLAY_NAME; break; default: throw new Error("Unexpected mapping state."); } mMappingMode.remove(parentId); final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { Loading @@ -390,7 +348,7 @@ class Mapper { "group_concat(" + pendingIdQuery + ")" }, selection, strings(arg), args, groupKey, "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1", null); Loading Loading @@ -439,7 +397,7 @@ class Mapper { // Delete all invalidated rows that cannot be mapped. if (mDatabase.deleteDocumentsAndRootsRecursively( COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_INVALIDATED, arg))) { DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) { changed = true; } Loading @@ -452,7 +410,7 @@ class Mapper { TABLE_DOCUMENTS, values, COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_PENDING, arg)) != 0) { DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_PENDING), args)) != 0) { changed = true; } database.setTransactionSuccessful(); Loading Loading @@ -494,20 +452,4 @@ class Mapper { return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) + " THEN " + a + " ELSE NULL END"; } /** * @param deviceId Device ID. * @return Key for {@link #mMappingMode}. */ private static String getRootDocumentsMappingStateKey(int deviceId) { return "RootDocuments/" + deviceId; } /** * @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/MtpDatabase.java +4 −4 Original line number Diff line number Diff line Loading @@ -53,13 +53,13 @@ import java.util.Set; * by comparing the directory structure and object name. * * To start putting documents into the database, the client needs to call * {@link Mapper#startAddingChildDocuments(String)} with the parent document ID. Also it * needs to call {@link Mapper#stopAddingChildDocuments(String)} after putting all child * {@link Mapper#startAddingDocuments(String)} with the parent document ID. Also it * needs to call {@link Mapper#stopAddingDocuments(String)} after putting all child * documents to the database. (All explanations are same for root documents) * * database.getMapper().startAddingChildDocuments(); * database.getMapper().startAddingDocuments(); * database.getMapper().putChildDocuments(); * database.getMapper().stopAddingChildDocuments(); * database.getMapper().stopAddingDocuments(); * * To update the existing documents, the client code can repeat to call the three methods again. * The newly added rows update corresponding existing rows that have same MTP identifier like Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +0 −3 Original line number Diff line number Diff line Loading @@ -103,9 +103,6 @@ class MtpDatabaseConstants { static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?"; static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?"; static final String SELECTION_ROOT_DOCUMENTS = COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?"; static final String QUERY_CREATE_DOCUMENTS = "CREATE TABLE " + TABLE_DOCUMENTS + " (" + Loading
packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +6 −10 Original line number Diff line number Diff line Loading @@ -110,26 +110,22 @@ final class RootScanner { return; } boolean changed = false; mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */); for (int deviceId : deviceIds) { try { final MtpRoot[] roots = mManager.getRoots(deviceId); mDatabase.getMapper().startAddingRootDocuments(deviceId); try { if (mDatabase.getMapper().putRootDocuments( deviceId, mResources, roots)) { changed = true; } } finally { if (mDatabase.getMapper().stopAddingRootDocuments(deviceId)) { if (mDatabase.getMapper().putRootDocuments(deviceId, mResources, roots)) { changed = true; } } } catch (IOException | SQLiteException exception) { // The error may happen on the device. We would like to continue getting // roots for other devices. Log.e(MtpDocumentsProvider.TAG, exception.getMessage()); } } if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) { changed = true; } if (changed) { notifyChange(); } Loading