Merge "Cleans up the metadata in MtpDatabase at the first launch after booting." into nyc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
7b5479f22e
@@ -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;
|
||||
@@ -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.
|
||||
@@ -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 " +
|
||||
@@ -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,
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -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));
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user