Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +79 −4 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.media.MediaFile; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; Loading @@ -40,8 +41,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashSet; import java.util.Objects; import java.util.Set; /** * Database for MTP objects. Loading Loading @@ -606,7 +608,7 @@ class MtpDatabase { * @param deviceId Device to find documents. * @return Identifier of found document or null. */ public @Nullable Identifier getUnmappedDocumentsParent(int deviceId) { @Nullable Identifier getUnmappedDocumentsParent(int deviceId) { final String fromClosure = TABLE_DOCUMENTS + " AS child INNER JOIN " + TABLE_DOCUMENTS + " AS parent ON " + Loading Loading @@ -643,6 +645,65 @@ class MtpDatabase { } } /** * Removes metadata except for data used by outgoingPersistedUriPermissions. */ void cleanDatabase(Uri[] outgoingPersistedUris) { mDatabase.beginTransaction(); try { final Set<String> ids = new HashSet<>(); for (final Uri uri : outgoingPersistedUris) { String documentId = DocumentsContract.getDocumentId(uri); while (documentId != null) { if (ids.contains(documentId)) { break; } ids.add(documentId); try (final Cursor cursor = mDatabase.query( TABLE_DOCUMENTS, strings(COLUMN_PARENT_DOCUMENT_ID), SELECTION_DOCUMENT_ID, strings(documentId), null, null, null)) { documentId = cursor.moveToNext() ? cursor.getString(0) : null; } } } deleteDocumentsAndRoots( Document.COLUMN_DOCUMENT_ID + " NOT IN " + getIdList(ids), null); mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } } int getLastBootCount() { try (final Cursor cursor = mDatabase.query( TABLE_LAST_BOOT_COUNT, strings(COLUMN_VALUE), null, null, null, null, null)) { if (cursor.moveToNext()) { return cursor.getInt(0); } else { return 0; } } } void setLastBootCount(int value) { Preconditions.checkArgumentNonnegative(value, "Boot count must not be negative."); mDatabase.beginTransaction(); try { final ContentValues values = new ContentValues(); values.put(COLUMN_VALUE, value); mDatabase.delete(TABLE_LAST_BOOT_COUNT, null, null); mDatabase.insert(TABLE_LAST_BOOT_COUNT, null, values); mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } } private static class OpenHelper extends SQLiteOpenHelper { public OpenHelper(Context context, int flags) { super(context, Loading @@ -655,12 +716,14 @@ class MtpDatabase { public void onCreate(SQLiteDatabase db) { db.execSQL(QUERY_CREATE_DOCUMENTS); db.execSQL(QUERY_CREATE_ROOT_EXTRA); db.execSQL(QUERY_CREATE_LAST_BOOT_COUNT); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE " + TABLE_DOCUMENTS); db.execSQL("DROP TABLE " + TABLE_ROOT_EXTRA); db.execSQL("DROP TABLE IF EXISTS " + TABLE_DOCUMENTS); db.execSQL("DROP TABLE IF EXISTS " + TABLE_ROOT_EXTRA); db.execSQL("DROP TABLE IF EXISTS " + TABLE_LAST_BOOT_COUNT); onCreate(db); } } Loading Loading @@ -818,4 +881,16 @@ class MtpDatabase { } return results; } private static String getIdList(Set<String> ids) { String result = "("; for (final String id : ids) { if (result.length() > 1) { result += ","; } result += id; } result += ")"; return result; } } packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +16 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,7 @@ import java.util.Map; * Class containing MtpDatabase constants. */ class MtpDatabaseConstants { static final int DATABASE_VERSION = 4; static final int DATABASE_VERSION = 5; static final String DATABASE_NAME = "database"; static final int FLAG_DATABASE_IN_MEMORY = 1; Loading @@ -47,6 +47,11 @@ class MtpDatabaseConstants { */ static final String TABLE_ROOT_EXTRA = "RootExtra"; /** * Table containing last boot count. */ static final String TABLE_LAST_BOOT_COUNT = "LastBootCount"; /** * 'FROM' closure of joining TABLE_DOCUMENTS and TABLE_ROOT_EXTRA. */ Loading @@ -62,7 +67,13 @@ class MtpDatabaseConstants { static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id"; static final String COLUMN_DOCUMENT_TYPE = "document_type"; static final String COLUMN_ROW_STATE = "row_state"; static final String COLUMN_MAPPING_KEY = "column_mapping_key"; static final String COLUMN_MAPPING_KEY = "mapping_key"; /** * Value for TABLE_LAST_BOOT_COUNT. * Type: INTEGER */ static final String COLUMN_VALUE = "value"; /** * The state represents that the row has a valid object handle. Loading Loading @@ -133,6 +144,9 @@ class MtpDatabaseConstants { Root.COLUMN_CAPACITY_BYTES + " INTEGER," + Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);"; static final String QUERY_CREATE_LAST_BOOT_COUNT = "CREATE TABLE " + TABLE_LAST_BOOT_COUNT + " (value INTEGER NOT NULL);"; /** * Map for columns names to provide DocumentContract.Root compatible columns. * @see SQLiteQueryBuilder#setProjectionMap(Map) Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +21 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.mtp; import android.content.ContentResolver; import android.content.UriPermission; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.database.Cursor; Loading @@ -25,6 +26,7 @@ import android.graphics.Point; import android.media.MediaFile; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; Loading @@ -33,6 +35,8 @@ import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; import com.android.internal.annotations.GuardedBy; Loading @@ -42,6 +46,7 @@ import com.android.mtp.exceptions.BusyDeviceException; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; /** Loading Loading @@ -95,6 +100,21 @@ public class MtpDocumentsProvider extends DocumentsProvider { mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); mAppFuse = new AppFuse(TAG, new AppFuseCallback()); mIntentSender = new ServiceIntentSender(getContext()); // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider // after booting. final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1); final int lastBootCount = mDatabase.getLastBootCount(); if (bootCount != -1 && bootCount != lastBootCount) { mDatabase.setLastBootCount(bootCount); final List<UriPermission> permissions = mResolver.getOutgoingPersistedUriPermissions(); final Uri[] uris = new Uri[permissions.size()]; for (int i = 0; i < permissions.size(); i++) { uris[i] = permissions.get(i).getUri(); } mDatabase.cleanDatabase(uris); } // TODO: Mount AppFuse on demands. try { mAppFuse.mount(getContext().getSystemService(StorageManager.class)); Loading Loading @@ -122,6 +142,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); mAppFuse = new AppFuse(TAG, new AppFuseCallback()); mIntentSender = intentSender; // TODO: Mount AppFuse on demands. try { mAppFuse.mount(storageManager); Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.mtp; import android.database.Cursor; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; Loading @@ -26,6 +27,7 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import java.io.FileNotFoundException; import java.util.Arrays; import static android.provider.DocumentsContract.Document.*; import static com.android.mtp.MtpDatabase.strings; Loading Loading @@ -1023,6 +1025,62 @@ public class MtpDatabaseTest extends AndroidTestCase { assertFalse(mDatabase.getMapper().stopAddingDocuments(null)); } public void testSetBootCount() { assertEquals(0, mDatabase.getLastBootCount()); mDatabase.setLastBootCount(10); assertEquals(10, mDatabase.getLastBootCount()); try { mDatabase.setLastBootCount(-1); fail(); } catch (IllegalArgumentException e) {} } public void testCleanDatabase() throws FileNotFoundException { // Add tree. addTestDevice(); addTestStorage("1"); mDatabase.getMapper().startAddingDocuments("2"); mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] { createDocument(100, "apple.txt", MtpConstants.FORMAT_TEXT, 1024), createDocument(101, "orange.txt", MtpConstants.FORMAT_TEXT, 1024), }); mDatabase.getMapper().stopAddingDocuments("2"); // Disconnect the device. mDatabase.getMapper().startAddingDocuments(null); mDatabase.getMapper().stopAddingDocuments(null); // Clean database. mDatabase.cleanDatabase(new Uri[] { DocumentsContract.buildDocumentUri(MtpDocumentsProvider.AUTHORITY, "3") }); // Add tree again. addTestDevice(); addTestStorage("1"); mDatabase.getMapper().startAddingDocuments("2"); mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] { createDocument(100, "apple.txt", MtpConstants.FORMAT_TEXT, 1024), createDocument(101, "orange.txt", MtpConstants.FORMAT_TEXT, 1024), }); mDatabase.getMapper().stopAddingDocuments("2"); try (final Cursor cursor = mDatabase.queryChildDocuments( strings(COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME), "2")) { assertEquals(2, cursor.getCount()); // Persistent uri uses the same ID. cursor.moveToNext(); assertEquals("3", cursor.getString(0)); assertEquals("apple.txt", cursor.getString(1)); // Others does not. cursor.moveToNext(); assertEquals("5", cursor.getString(0)); assertEquals("orange.txt", cursor.getString(1)); } } private void addTestDevice() throws FileNotFoundException { TestUtil.addTestDevice(mDatabase); } Loading Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +79 −4 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.media.MediaFile; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; Loading @@ -40,8 +41,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashSet; import java.util.Objects; import java.util.Set; /** * Database for MTP objects. Loading Loading @@ -606,7 +608,7 @@ class MtpDatabase { * @param deviceId Device to find documents. * @return Identifier of found document or null. */ public @Nullable Identifier getUnmappedDocumentsParent(int deviceId) { @Nullable Identifier getUnmappedDocumentsParent(int deviceId) { final String fromClosure = TABLE_DOCUMENTS + " AS child INNER JOIN " + TABLE_DOCUMENTS + " AS parent ON " + Loading Loading @@ -643,6 +645,65 @@ class MtpDatabase { } } /** * Removes metadata except for data used by outgoingPersistedUriPermissions. */ void cleanDatabase(Uri[] outgoingPersistedUris) { mDatabase.beginTransaction(); try { final Set<String> ids = new HashSet<>(); for (final Uri uri : outgoingPersistedUris) { String documentId = DocumentsContract.getDocumentId(uri); while (documentId != null) { if (ids.contains(documentId)) { break; } ids.add(documentId); try (final Cursor cursor = mDatabase.query( TABLE_DOCUMENTS, strings(COLUMN_PARENT_DOCUMENT_ID), SELECTION_DOCUMENT_ID, strings(documentId), null, null, null)) { documentId = cursor.moveToNext() ? cursor.getString(0) : null; } } } deleteDocumentsAndRoots( Document.COLUMN_DOCUMENT_ID + " NOT IN " + getIdList(ids), null); mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } } int getLastBootCount() { try (final Cursor cursor = mDatabase.query( TABLE_LAST_BOOT_COUNT, strings(COLUMN_VALUE), null, null, null, null, null)) { if (cursor.moveToNext()) { return cursor.getInt(0); } else { return 0; } } } void setLastBootCount(int value) { Preconditions.checkArgumentNonnegative(value, "Boot count must not be negative."); mDatabase.beginTransaction(); try { final ContentValues values = new ContentValues(); values.put(COLUMN_VALUE, value); mDatabase.delete(TABLE_LAST_BOOT_COUNT, null, null); mDatabase.insert(TABLE_LAST_BOOT_COUNT, null, values); mDatabase.setTransactionSuccessful(); } finally { mDatabase.endTransaction(); } } private static class OpenHelper extends SQLiteOpenHelper { public OpenHelper(Context context, int flags) { super(context, Loading @@ -655,12 +716,14 @@ class MtpDatabase { public void onCreate(SQLiteDatabase db) { db.execSQL(QUERY_CREATE_DOCUMENTS); db.execSQL(QUERY_CREATE_ROOT_EXTRA); db.execSQL(QUERY_CREATE_LAST_BOOT_COUNT); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE " + TABLE_DOCUMENTS); db.execSQL("DROP TABLE " + TABLE_ROOT_EXTRA); db.execSQL("DROP TABLE IF EXISTS " + TABLE_DOCUMENTS); db.execSQL("DROP TABLE IF EXISTS " + TABLE_ROOT_EXTRA); db.execSQL("DROP TABLE IF EXISTS " + TABLE_LAST_BOOT_COUNT); onCreate(db); } } Loading Loading @@ -818,4 +881,16 @@ class MtpDatabase { } return results; } private static String getIdList(Set<String> ids) { String result = "("; for (final String id : ids) { if (result.length() > 1) { result += ","; } result += id; } result += ")"; return result; } }
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java +16 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,7 @@ import java.util.Map; * Class containing MtpDatabase constants. */ class MtpDatabaseConstants { static final int DATABASE_VERSION = 4; static final int DATABASE_VERSION = 5; static final String DATABASE_NAME = "database"; static final int FLAG_DATABASE_IN_MEMORY = 1; Loading @@ -47,6 +47,11 @@ class MtpDatabaseConstants { */ static final String TABLE_ROOT_EXTRA = "RootExtra"; /** * Table containing last boot count. */ static final String TABLE_LAST_BOOT_COUNT = "LastBootCount"; /** * 'FROM' closure of joining TABLE_DOCUMENTS and TABLE_ROOT_EXTRA. */ Loading @@ -62,7 +67,13 @@ class MtpDatabaseConstants { static final String COLUMN_PARENT_DOCUMENT_ID = "parent_document_id"; static final String COLUMN_DOCUMENT_TYPE = "document_type"; static final String COLUMN_ROW_STATE = "row_state"; static final String COLUMN_MAPPING_KEY = "column_mapping_key"; static final String COLUMN_MAPPING_KEY = "mapping_key"; /** * Value for TABLE_LAST_BOOT_COUNT. * Type: INTEGER */ static final String COLUMN_VALUE = "value"; /** * The state represents that the row has a valid object handle. Loading Loading @@ -133,6 +144,9 @@ class MtpDatabaseConstants { Root.COLUMN_CAPACITY_BYTES + " INTEGER," + Root.COLUMN_MIME_TYPES + " TEXT NOT NULL);"; static final String QUERY_CREATE_LAST_BOOT_COUNT = "CREATE TABLE " + TABLE_LAST_BOOT_COUNT + " (value INTEGER NOT NULL);"; /** * Map for columns names to provide DocumentContract.Root compatible columns. * @see SQLiteQueryBuilder#setProjectionMap(Map) Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +21 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.mtp; import android.content.ContentResolver; import android.content.UriPermission; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.database.Cursor; Loading @@ -25,6 +26,7 @@ import android.graphics.Point; import android.media.MediaFile; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; Loading @@ -33,6 +35,8 @@ import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; import com.android.internal.annotations.GuardedBy; Loading @@ -42,6 +46,7 @@ import com.android.mtp.exceptions.BusyDeviceException; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; /** Loading Loading @@ -95,6 +100,21 @@ public class MtpDocumentsProvider extends DocumentsProvider { mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); mAppFuse = new AppFuse(TAG, new AppFuseCallback()); mIntentSender = new ServiceIntentSender(getContext()); // Check boot count and cleans database if it's first time to launch MtpDocumentsProvider // after booting. final int bootCount = Settings.Global.getInt(mResolver, Settings.Global.BOOT_COUNT, -1); final int lastBootCount = mDatabase.getLastBootCount(); if (bootCount != -1 && bootCount != lastBootCount) { mDatabase.setLastBootCount(bootCount); final List<UriPermission> permissions = mResolver.getOutgoingPersistedUriPermissions(); final Uri[] uris = new Uri[permissions.size()]; for (int i = 0; i < permissions.size(); i++) { uris[i] = permissions.get(i).getUri(); } mDatabase.cleanDatabase(uris); } // TODO: Mount AppFuse on demands. try { mAppFuse.mount(getContext().getSystemService(StorageManager.class)); Loading Loading @@ -122,6 +142,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mRootScanner = new RootScanner(mResolver, mMtpManager, mDatabase); mAppFuse = new AppFuse(TAG, new AppFuseCallback()); mIntentSender = intentSender; // TODO: Mount AppFuse on demands. try { mAppFuse.mount(storageManager); Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.mtp; import android.database.Cursor; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.net.Uri; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; Loading @@ -26,6 +27,7 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import java.io.FileNotFoundException; import java.util.Arrays; import static android.provider.DocumentsContract.Document.*; import static com.android.mtp.MtpDatabase.strings; Loading Loading @@ -1023,6 +1025,62 @@ public class MtpDatabaseTest extends AndroidTestCase { assertFalse(mDatabase.getMapper().stopAddingDocuments(null)); } public void testSetBootCount() { assertEquals(0, mDatabase.getLastBootCount()); mDatabase.setLastBootCount(10); assertEquals(10, mDatabase.getLastBootCount()); try { mDatabase.setLastBootCount(-1); fail(); } catch (IllegalArgumentException e) {} } public void testCleanDatabase() throws FileNotFoundException { // Add tree. addTestDevice(); addTestStorage("1"); mDatabase.getMapper().startAddingDocuments("2"); mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] { createDocument(100, "apple.txt", MtpConstants.FORMAT_TEXT, 1024), createDocument(101, "orange.txt", MtpConstants.FORMAT_TEXT, 1024), }); mDatabase.getMapper().stopAddingDocuments("2"); // Disconnect the device. mDatabase.getMapper().startAddingDocuments(null); mDatabase.getMapper().stopAddingDocuments(null); // Clean database. mDatabase.cleanDatabase(new Uri[] { DocumentsContract.buildDocumentUri(MtpDocumentsProvider.AUTHORITY, "3") }); // Add tree again. addTestDevice(); addTestStorage("1"); mDatabase.getMapper().startAddingDocuments("2"); mDatabase.getMapper().putChildDocuments(0, "2", OPERATIONS_SUPPORTED, new MtpObjectInfo[] { createDocument(100, "apple.txt", MtpConstants.FORMAT_TEXT, 1024), createDocument(101, "orange.txt", MtpConstants.FORMAT_TEXT, 1024), }); mDatabase.getMapper().stopAddingDocuments("2"); try (final Cursor cursor = mDatabase.queryChildDocuments( strings(COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME), "2")) { assertEquals(2, cursor.getCount()); // Persistent uri uses the same ID. cursor.moveToNext(); assertEquals("3", cursor.getString(0)); assertEquals("apple.txt", cursor.getString(1)); // Others does not. cursor.moveToNext(); assertEquals("5", cursor.getString(0)); assertEquals("orange.txt", cursor.getString(1)); } } private void addTestDevice() throws FileNotFoundException { TestUtil.addTestDevice(mDatabase); } Loading