Loading packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +22 −15 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.mtp; import android.content.ContentResolver; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.os.Bundle; Loading Loading @@ -262,29 +261,37 @@ class DocumentLoader { if (objectInfoList.length == 0 || getState() != STATE_LOADING) { return; } try{ if (mNumLoaded == 0) { mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId); } try { mDatabase.getMapper().putChildDocuments( mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList); mNumLoaded += objectInfoList.length; } catch (SQLiteException exp) { mError = exp; mNumLoaded = 0; } if (getState() != STATE_LOADING) { mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } } catch (FileNotFoundException exception) { setErrorInternal(exception); } } void setError(Exception message) { void setError(Exception error) { final int lastState = getState(); mError = message; mNumLoaded = 0; setErrorInternal(error); if (lastState == STATE_LOADING) { try { mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } catch (FileNotFoundException exception) { setErrorInternal(exception); } } } private void setErrorInternal(Exception error) { Log.e(MtpDocumentsProvider.TAG, "Error in DocumentLoader thread", error); mError = error; mNumLoaded = 0; } private Uri createUri() { Loading packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +56 −13 Original line number Diff line number Diff line Loading @@ -27,9 +27,9 @@ import android.mtp.MtpObjectInfo; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.util.HashMap; import java.util.Map; Loading @@ -56,11 +56,12 @@ class Mapper { /** * Puts device information to database. * * @return If device is added to the database. * @throws FileNotFoundException */ synchronized boolean putDeviceDocument(MtpDeviceRecord device) { synchronized boolean putDeviceDocument(MtpDeviceRecord device) throws FileNotFoundException { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null)); database.beginTransaction(); try { final ContentValues[] valuesList = new ContentValues[1]; Loading @@ -69,6 +70,7 @@ class Mapper { extraValuesList[0] = new ContentValues(); MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device); final boolean changed = putDocuments( null, valuesList, extraValuesList, COLUMN_PARENT_DOCUMENT_ID + " IS NULL", Loading @@ -83,11 +85,14 @@ class Mapper { /** * Puts root information to database. * * @param parentDocumentId Document ID of device document. * @param roots List of root information. * @return If roots are added or removed from the database. * @throws FileNotFoundException */ synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) { synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) throws FileNotFoundException { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { Loading @@ -112,6 +117,7 @@ class Mapper { valuesList[i], extraValuesList[i], parentDocumentId, roots[i]); } final boolean changed = putDocuments( parentDocumentId, valuesList, extraValuesList, COLUMN_PARENT_DOCUMENT_ID + "=?", Loading @@ -127,11 +133,14 @@ class Mapper { /** * Puts document information to database. * * @param deviceId Device ID * @param parentId Parent document ID. * @param documents List of document information. * @throws FileNotFoundException */ synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) { synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) throws FileNotFoundException { final String mapColumn; Preconditions.checkState(mMappingMode.containsKey(parentId)); switch (mMappingMode.get(parentId)) { Loading @@ -151,6 +160,7 @@ class Mapper { valuesList[i], deviceId, parentId, documents[i]); } putDocuments( parentId, valuesList, null, COLUMN_PARENT_DOCUMENT_ID + "=?", Loading Loading @@ -181,9 +191,9 @@ class Mapper { * a corresponding existing row. Otherwise it does heuristic. * * @param parentDocumentId Parent document ID or NULL for root documents. * @throws FileNotFoundException */ void startAddingDocuments(@Nullable String parentDocumentId) { Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId)); void startAddingDocuments(@Nullable String parentDocumentId) throws FileNotFoundException { final String selection; final String[] args; if (parentDocumentId != null) { Loading @@ -197,6 +207,9 @@ class Mapper { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { getParentOrHaltMapping(parentDocumentId); Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId)); // Set all documents as invalidated. final ContentValues values = new ContentValues(); values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED); Loading Loading @@ -224,22 +237,27 @@ class Mapper { * {@link #stopAddingDocuments(String)} turns the pending rows into 'valid' * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param parentId Parent document ID. * @param valuesList Values for documents to be stored in the database. * @param rootExtraValuesList Values for root extra to be stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param args Argument for selection SQL. * @return Whether the method adds new rows. * @return Whether it adds at least one new row that is not mapped with existing document ID. * @throws FileNotFoundException When parentId is not registered in the database. */ private boolean putDocuments( String parentId, ContentValues[] valuesList, @Nullable ContentValues[] rootExtraValuesList, String selection, String[] args, String mappingKey) { String mappingKey) throws FileNotFoundException { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); boolean added = false; database.beginTransaction(); try { getParentOrHaltMapping(parentId); Preconditions.checkState(mMappingMode.containsKey(parentId)); for (int i = 0; i < valuesList.length; i++) { final ContentValues values = valuesList[i]; final ContentValues rootExtraValues; Loading Loading @@ -298,11 +316,12 @@ 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 parentId Parent document ID or null for root documents. * @return Whether the methods adds or removed visible rows. * @throws FileNotFoundException */ boolean stopAddingDocuments(@Nullable String parentId) { Preconditions.checkState(mMappingMode.containsKey(parentId)); boolean stopAddingDocuments(@Nullable String parentId) throws FileNotFoundException { final String selection; final String[] args; if (parentId != null) { Loading @@ -312,12 +331,15 @@ class Mapper { selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; args = EMPTY_ARGS; } mMappingMode.remove(parentId); final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { boolean changed = false; getParentOrHaltMapping(parentId); Preconditions.checkState(mMappingMode.containsKey(parentId)); mMappingMode.remove(parentId); boolean changed = false; // Delete all invalidated rows that cannot be mapped. if (mDatabase.deleteDocumentsAndRootsRecursively( COLUMN_ROW_STATE + " = ? AND " + selection, Loading @@ -331,4 +353,25 @@ class Mapper { database.endTransaction(); } } /** * Returns the parent identifier from parent document ID if the parent ID is found in the * database. Otherwise it halts mapping and throws FileNotFoundException. * * @param parentId Parent document ID * @return Parent identifier * @throws FileNotFoundException */ private @Nullable Identifier getParentOrHaltMapping( @Nullable String parentId) throws FileNotFoundException { if (parentId == null) { return null; } try { return mDatabase.createIdentifier(parentId); } catch (FileNotFoundException error) { mMappingMode.remove(parentId); throw error; } } } packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +26 −12 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; import java.io.FileNotFoundException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; Loading Loading @@ -123,15 +124,23 @@ final class RootScanner { // Update devices. final MtpDeviceRecord[] devices = mManager.getDevices(); try { mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */); for (final MtpDeviceRecord device : devices) { if (mDatabase.getMapper().putDeviceDocument(device)) { changed = true; } } if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) { if (mDatabase.getMapper().stopAddingDocuments( null /* parentDocumentId */)) { changed = true; } } catch (FileNotFoundException exception) { // The top root (ID is null) must exist always. // FileNotFoundException is unexpected. Log.e(MtpDocumentsProvider.TAG, "Unexpected FileNotFoundException", exception); throw new AssertionError("Unexpected exception for the top parent", exception); } // Update roots. for (final MtpDeviceRecord device : devices) { Loading @@ -139,6 +148,7 @@ final class RootScanner { if (documentId == null) { continue; } try { mDatabase.getMapper().startAddingDocuments(documentId); if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) { changed = true; Loading @@ -146,6 +156,10 @@ final class RootScanner { if (mDatabase.getMapper().stopAddingDocuments(documentId)) { changed = true; } } catch (FileNotFoundException exception) { Log.e(MtpDocumentsProvider.TAG, "Parent document is gone.", exception); continue; } } if (changed) { Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +12 −5 Original line number Diff line number Diff line Loading @@ -36,16 +36,23 @@ public class DocumentLoaderTest extends AndroidTestCase { private TestContentResolver mResolver; private DocumentLoader mLoader; final private Identifier mParentIdentifier = new Identifier( 0, 0, 0, "1", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE); 0, 0, 0, "2", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE); @Override public void setUp() { public void setUp() throws Exception { mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); mDatabase.getMapper().startAddingDocuments("deviceDocId"); mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] { mDatabase.getMapper().startAddingDocuments(null); mDatabase.getMapper().putDeviceDocument( new MtpDeviceRecord(1, "Device", true, new MtpRoot[0], null, null)); mDatabase.getMapper().stopAddingDocuments(null); mDatabase.getMapper().startAddingDocuments("1"); mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); mDatabase.getMapper().stopAddingDocuments("1"); mManager = new BlockableTestMtpManager(getContext()); mResolver = new TestContentResolver(); mLoader = new DocumentLoader(mManager, mResolver, mDatabase); Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +235 −169 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +22 −15 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.mtp; import android.content.ContentResolver; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.os.Bundle; Loading Loading @@ -262,29 +261,37 @@ class DocumentLoader { if (objectInfoList.length == 0 || getState() != STATE_LOADING) { return; } try{ if (mNumLoaded == 0) { mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId); } try { mDatabase.getMapper().putChildDocuments( mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList); mNumLoaded += objectInfoList.length; } catch (SQLiteException exp) { mError = exp; mNumLoaded = 0; } if (getState() != STATE_LOADING) { mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } } catch (FileNotFoundException exception) { setErrorInternal(exception); } } void setError(Exception message) { void setError(Exception error) { final int lastState = getState(); mError = message; mNumLoaded = 0; setErrorInternal(error); if (lastState == STATE_LOADING) { try { mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId); } catch (FileNotFoundException exception) { setErrorInternal(exception); } } } private void setErrorInternal(Exception error) { Log.e(MtpDocumentsProvider.TAG, "Error in DocumentLoader thread", error); mError = error; mNumLoaded = 0; } private Uri createUri() { Loading
packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java +56 −13 Original line number Diff line number Diff line Loading @@ -27,9 +27,9 @@ import android.mtp.MtpObjectInfo; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.util.HashMap; import java.util.Map; Loading @@ -56,11 +56,12 @@ class Mapper { /** * Puts device information to database. * * @return If device is added to the database. * @throws FileNotFoundException */ synchronized boolean putDeviceDocument(MtpDeviceRecord device) { synchronized boolean putDeviceDocument(MtpDeviceRecord device) throws FileNotFoundException { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null)); database.beginTransaction(); try { final ContentValues[] valuesList = new ContentValues[1]; Loading @@ -69,6 +70,7 @@ class Mapper { extraValuesList[0] = new ContentValues(); MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device); final boolean changed = putDocuments( null, valuesList, extraValuesList, COLUMN_PARENT_DOCUMENT_ID + " IS NULL", Loading @@ -83,11 +85,14 @@ class Mapper { /** * Puts root information to database. * * @param parentDocumentId Document ID of device document. * @param roots List of root information. * @return If roots are added or removed from the database. * @throws FileNotFoundException */ synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) { synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) throws FileNotFoundException { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { Loading @@ -112,6 +117,7 @@ class Mapper { valuesList[i], extraValuesList[i], parentDocumentId, roots[i]); } final boolean changed = putDocuments( parentDocumentId, valuesList, extraValuesList, COLUMN_PARENT_DOCUMENT_ID + "=?", Loading @@ -127,11 +133,14 @@ class Mapper { /** * Puts document information to database. * * @param deviceId Device ID * @param parentId Parent document ID. * @param documents List of document information. * @throws FileNotFoundException */ synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) { synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) throws FileNotFoundException { final String mapColumn; Preconditions.checkState(mMappingMode.containsKey(parentId)); switch (mMappingMode.get(parentId)) { Loading @@ -151,6 +160,7 @@ class Mapper { valuesList[i], deviceId, parentId, documents[i]); } putDocuments( parentId, valuesList, null, COLUMN_PARENT_DOCUMENT_ID + "=?", Loading Loading @@ -181,9 +191,9 @@ class Mapper { * a corresponding existing row. Otherwise it does heuristic. * * @param parentDocumentId Parent document ID or NULL for root documents. * @throws FileNotFoundException */ void startAddingDocuments(@Nullable String parentDocumentId) { Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId)); void startAddingDocuments(@Nullable String parentDocumentId) throws FileNotFoundException { final String selection; final String[] args; if (parentDocumentId != null) { Loading @@ -197,6 +207,9 @@ class Mapper { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { getParentOrHaltMapping(parentDocumentId); Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId)); // Set all documents as invalidated. final ContentValues values = new ContentValues(); values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED); Loading Loading @@ -224,22 +237,27 @@ class Mapper { * {@link #stopAddingDocuments(String)} turns the pending rows into 'valid' * rows. If the methods adds rows to database, it updates valueList with correct document ID. * * @param parentId Parent document ID. * @param valuesList Values for documents to be stored in the database. * @param rootExtraValuesList Values for root extra to be stored in the database. * @param selection SQL where closure to select rows that shares the same parent. * @param args Argument for selection SQL. * @return Whether the method adds new rows. * @return Whether it adds at least one new row that is not mapped with existing document ID. * @throws FileNotFoundException When parentId is not registered in the database. */ private boolean putDocuments( String parentId, ContentValues[] valuesList, @Nullable ContentValues[] rootExtraValuesList, String selection, String[] args, String mappingKey) { String mappingKey) throws FileNotFoundException { final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); boolean added = false; database.beginTransaction(); try { getParentOrHaltMapping(parentId); Preconditions.checkState(mMappingMode.containsKey(parentId)); for (int i = 0; i < valuesList.length; i++) { final ContentValues values = valuesList[i]; final ContentValues rootExtraValues; Loading Loading @@ -298,11 +316,12 @@ 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 parentId Parent document ID or null for root documents. * @return Whether the methods adds or removed visible rows. * @throws FileNotFoundException */ boolean stopAddingDocuments(@Nullable String parentId) { Preconditions.checkState(mMappingMode.containsKey(parentId)); boolean stopAddingDocuments(@Nullable String parentId) throws FileNotFoundException { final String selection; final String[] args; if (parentId != null) { Loading @@ -312,12 +331,15 @@ class Mapper { selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; args = EMPTY_ARGS; } mMappingMode.remove(parentId); final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); database.beginTransaction(); try { boolean changed = false; getParentOrHaltMapping(parentId); Preconditions.checkState(mMappingMode.containsKey(parentId)); mMappingMode.remove(parentId); boolean changed = false; // Delete all invalidated rows that cannot be mapped. if (mDatabase.deleteDocumentsAndRootsRecursively( COLUMN_ROW_STATE + " = ? AND " + selection, Loading @@ -331,4 +353,25 @@ class Mapper { database.endTransaction(); } } /** * Returns the parent identifier from parent document ID if the parent ID is found in the * database. Otherwise it halts mapping and throws FileNotFoundException. * * @param parentId Parent document ID * @return Parent identifier * @throws FileNotFoundException */ private @Nullable Identifier getParentOrHaltMapping( @Nullable String parentId) throws FileNotFoundException { if (parentId == null) { return null; } try { return mDatabase.createIdentifier(parentId); } catch (FileNotFoundException error) { mMappingMode.remove(parentId); throw error; } } }
packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +26 −12 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; import java.io.FileNotFoundException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; Loading Loading @@ -123,15 +124,23 @@ final class RootScanner { // Update devices. final MtpDeviceRecord[] devices = mManager.getDevices(); try { mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */); for (final MtpDeviceRecord device : devices) { if (mDatabase.getMapper().putDeviceDocument(device)) { changed = true; } } if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) { if (mDatabase.getMapper().stopAddingDocuments( null /* parentDocumentId */)) { changed = true; } } catch (FileNotFoundException exception) { // The top root (ID is null) must exist always. // FileNotFoundException is unexpected. Log.e(MtpDocumentsProvider.TAG, "Unexpected FileNotFoundException", exception); throw new AssertionError("Unexpected exception for the top parent", exception); } // Update roots. for (final MtpDeviceRecord device : devices) { Loading @@ -139,6 +148,7 @@ final class RootScanner { if (documentId == null) { continue; } try { mDatabase.getMapper().startAddingDocuments(documentId); if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) { changed = true; Loading @@ -146,6 +156,10 @@ final class RootScanner { if (mDatabase.getMapper().stopAddingDocuments(documentId)) { changed = true; } } catch (FileNotFoundException exception) { Log.e(MtpDocumentsProvider.TAG, "Parent document is gone.", exception); continue; } } if (changed) { Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java +12 −5 Original line number Diff line number Diff line Loading @@ -36,16 +36,23 @@ public class DocumentLoaderTest extends AndroidTestCase { private TestContentResolver mResolver; private DocumentLoader mLoader; final private Identifier mParentIdentifier = new Identifier( 0, 0, 0, "1", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE); 0, 0, 0, "2", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE); @Override public void setUp() { public void setUp() throws Exception { mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY); mDatabase.getMapper().startAddingDocuments("deviceDocId"); mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] { mDatabase.getMapper().startAddingDocuments(null); mDatabase.getMapper().putDeviceDocument( new MtpDeviceRecord(1, "Device", true, new MtpRoot[0], null, null)); mDatabase.getMapper().stopAddingDocuments(null); mDatabase.getMapper().startAddingDocuments("1"); mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") }); mDatabase.getMapper().stopAddingDocuments("deviceDocId"); mDatabase.getMapper().stopAddingDocuments("1"); mManager = new BlockableTestMtpManager(getContext()); mResolver = new TestContentResolver(); mLoader = new DocumentLoader(mManager, mResolver, mDatabase); Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +235 −169 File changed.Preview size limit exceeded, changes collapsed. Show changes