Keep metadata of documents as disconnected status after the device is disconnected.
am: 8e87364a67
* commit '8e87364a67ada7ce92730182719f7820886c13bd':
Keep metadata of documents as disconnected status after the device is disconnected.
This commit is contained in:
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.mtp;
|
||||
|
||||
import static com.android.mtp.MtpDatabaseConstants.*;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
@@ -26,6 +24,7 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.mtp.MtpObjectInfo;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
@@ -33,6 +32,7 @@ import java.io.FileNotFoundException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.android.mtp.MtpDatabaseConstants.*;
|
||||
import static com.android.mtp.MtpDatabase.strings;
|
||||
|
||||
/**
|
||||
@@ -75,7 +75,7 @@ class Mapper {
|
||||
extraValuesList,
|
||||
COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
|
||||
EMPTY_ARGS,
|
||||
COLUMN_DEVICE_ID);
|
||||
Document.COLUMN_DISPLAY_NAME);
|
||||
database.setTransactionSuccessful();
|
||||
return changed;
|
||||
} finally {
|
||||
@@ -172,13 +172,16 @@ class Mapper {
|
||||
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
|
||||
database.beginTransaction();
|
||||
try {
|
||||
final ContentValues values = new ContentValues();
|
||||
values.putNull(COLUMN_OBJECT_HANDLE);
|
||||
values.putNull(COLUMN_STORAGE_ID);
|
||||
values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
|
||||
database.update(TABLE_DOCUMENTS, values, null, null);
|
||||
database.setTransactionSuccessful();
|
||||
mMappingMode.clear();
|
||||
// Disconnect all device rows.
|
||||
try {
|
||||
startAddingDocuments(null);
|
||||
stopAddingDocuments(null);
|
||||
} catch (FileNotFoundException exception) {
|
||||
Log.e(MtpDocumentsProvider.TAG, "Unexpected FileNotFoundException.", exception);
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
database.setTransactionSuccessful();
|
||||
} finally {
|
||||
database.endTransaction();
|
||||
}
|
||||
@@ -210,16 +213,20 @@ class Mapper {
|
||||
getParentOrHaltMapping(parentDocumentId);
|
||||
Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
|
||||
|
||||
// Set all documents as invalidated.
|
||||
// Set all valid documents as invalidated.
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
|
||||
database.update(TABLE_DOCUMENTS, values, selection, args);
|
||||
database.update(
|
||||
TABLE_DOCUMENTS,
|
||||
values,
|
||||
selection + " AND " + COLUMN_ROW_STATE + " = ?",
|
||||
DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_VALID)));
|
||||
|
||||
// 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",
|
||||
selection + " AND " + COLUMN_DEVICE_ID + " IS NULL",
|
||||
args) > 0;
|
||||
database.setTransactionSuccessful();
|
||||
mMappingMode.put(
|
||||
@@ -270,11 +277,13 @@ class Mapper {
|
||||
TABLE_DOCUMENTS,
|
||||
strings(Document.COLUMN_DOCUMENT_ID),
|
||||
selection + " AND " +
|
||||
COLUMN_ROW_STATE + "=? AND " +
|
||||
COLUMN_ROW_STATE + " IN (?, ?) AND " +
|
||||
mappingKey + "=?",
|
||||
DatabaseUtils.appendSelectionArgs(
|
||||
args,
|
||||
strings(ROW_STATE_INVALIDATED, values.getAsString(mappingKey))),
|
||||
strings(ROW_STATE_INVALIDATED,
|
||||
ROW_STATE_DISCONNECTED,
|
||||
values.getAsString(mappingKey))),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
@@ -335,16 +344,27 @@ class Mapper {
|
||||
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
|
||||
database.beginTransaction();
|
||||
try {
|
||||
getParentOrHaltMapping(parentId);
|
||||
final Identifier parentIdentifier = 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,
|
||||
DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) {
|
||||
changed = true;
|
||||
// Delete/disconnect all invalidated rows that cannot be mapped.
|
||||
final boolean keepUnmatchedDocument =
|
||||
parentIdentifier == null ||
|
||||
parentIdentifier.mDocumentType == DOCUMENT_TYPE_DEVICE;
|
||||
if (keepUnmatchedDocument) {
|
||||
if (mDatabase.disconnectDocumentsRecursively(
|
||||
COLUMN_ROW_STATE + " = ? AND " + selection,
|
||||
DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) {
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (mDatabase.deleteDocumentsAndRootsRecursively(
|
||||
COLUMN_ROW_STATE + " = ? AND " + selection,
|
||||
DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
database.setTransactionSuccessful();
|
||||
@@ -368,7 +388,12 @@ class Mapper {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mDatabase.createIdentifier(parentId);
|
||||
final Identifier identifier = mDatabase.createIdentifier(parentId);
|
||||
if (mDatabase.getRowState(parentId) == ROW_STATE_DISCONNECTED) {
|
||||
throw new FileNotFoundException(
|
||||
"document: " + parentId + " is in disconnected device.");
|
||||
}
|
||||
return identifier;
|
||||
} catch (FileNotFoundException error) {
|
||||
mMappingMode.remove(parentId);
|
||||
throw error;
|
||||
|
||||
@@ -219,7 +219,7 @@ class MtpDatabase {
|
||||
return mDatabase.query(
|
||||
TABLE_DOCUMENTS,
|
||||
columnNames,
|
||||
COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + "=?",
|
||||
COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_DOCUMENT_TYPE + " = ?",
|
||||
strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, DOCUMENT_TYPE_STORAGE),
|
||||
null,
|
||||
null,
|
||||
@@ -294,15 +294,6 @@ class MtpDatabase {
|
||||
"1");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all rows belong to a device.
|
||||
* @param deviceId Device ID.
|
||||
*/
|
||||
void removeDeviceRows(int deviceId) {
|
||||
// Call non-recursive version because it anyway deletes all rows in the devices.
|
||||
deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
|
||||
}
|
||||
|
||||
@Nullable String getDocumentIdForDevice(int deviceId) {
|
||||
final Cursor cursor = mDatabase.query(
|
||||
TABLE_DOCUMENTS,
|
||||
@@ -344,7 +335,7 @@ class MtpDatabase {
|
||||
if (cursor.moveToNext()) {
|
||||
return createIdentifier(cursor.getString(0));
|
||||
} else {
|
||||
throw new FileNotFoundException("Cannot find a row having ID=" + documentId);
|
||||
throw new FileNotFoundException("Cannot find a row having ID = " + documentId);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
@@ -438,7 +429,7 @@ class MtpDatabase {
|
||||
try {
|
||||
while (cursor.moveToNext()) {
|
||||
if (deleteDocumentsAndRootsRecursively(
|
||||
COLUMN_PARENT_DOCUMENT_ID + "=?",
|
||||
COLUMN_PARENT_DOCUMENT_ID + " = ?",
|
||||
strings(cursor.getString(0)))) {
|
||||
changed = true;
|
||||
}
|
||||
@@ -456,7 +447,43 @@ class MtpDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deleteDocumentsAndRoots(String selection, String[] args) {
|
||||
/**
|
||||
* Marks the documents and their child as disconnected documents.
|
||||
* @param selection
|
||||
* @param args
|
||||
* @return True if at least one row is updated.
|
||||
*/
|
||||
boolean disconnectDocumentsRecursively(String selection, String[] args) {
|
||||
mDatabase.beginTransaction();
|
||||
try {
|
||||
boolean changed = false;
|
||||
try (final Cursor cursor = mDatabase.query(
|
||||
TABLE_DOCUMENTS,
|
||||
strings(Document.COLUMN_DOCUMENT_ID),
|
||||
selection,
|
||||
args,
|
||||
null,
|
||||
null,
|
||||
null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
if (disconnectDocumentsRecursively(
|
||||
COLUMN_PARENT_DOCUMENT_ID + " = ?",
|
||||
strings(cursor.getString(0)))) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (disconnectDocuments(selection, args)) {
|
||||
changed = true;
|
||||
}
|
||||
mDatabase.setTransactionSuccessful();
|
||||
return changed;
|
||||
} finally {
|
||||
mDatabase.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
boolean deleteDocumentsAndRoots(String selection, String[] args) {
|
||||
mDatabase.beginTransaction();
|
||||
try {
|
||||
int deleted = 0;
|
||||
@@ -481,6 +508,39 @@ class MtpDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
boolean disconnectDocuments(String selection, String[] args) {
|
||||
mDatabase.beginTransaction();
|
||||
try {
|
||||
final ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_ROW_STATE, ROW_STATE_DISCONNECTED);
|
||||
values.putNull(COLUMN_DEVICE_ID);
|
||||
values.putNull(COLUMN_STORAGE_ID);
|
||||
values.putNull(COLUMN_OBJECT_HANDLE);
|
||||
final boolean updated = mDatabase.update(TABLE_DOCUMENTS, values, selection, args) != 0;
|
||||
mDatabase.setTransactionSuccessful();
|
||||
return updated;
|
||||
} finally {
|
||||
mDatabase.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
int getRowState(String documentId) throws FileNotFoundException {
|
||||
try (final Cursor cursor = mDatabase.query(
|
||||
TABLE_DOCUMENTS,
|
||||
strings(COLUMN_ROW_STATE),
|
||||
SELECTION_DOCUMENT_ID,
|
||||
strings(documentId),
|
||||
null,
|
||||
null,
|
||||
null)) {
|
||||
if (cursor.getCount() == 0) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
cursor.moveToNext();
|
||||
return cursor.getInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static class OpenHelper extends SQLiteOpenHelper {
|
||||
public OpenHelper(Context context, int flags) {
|
||||
super(context,
|
||||
@@ -497,6 +557,12 @@ class MtpDatabase {
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
if (oldVersion == 1) {
|
||||
db.execSQL("DROP TABLE " + TABLE_DOCUMENTS);
|
||||
db.execSQL("DROP TABLE " + TABLE_ROOT_EXTRA);
|
||||
onCreate(db);
|
||||
return;
|
||||
}
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.Map;
|
||||
* Class containing MtpDatabase constants.
|
||||
*/
|
||||
class MtpDatabaseConstants {
|
||||
static final int DATABASE_VERSION = 1;
|
||||
static final int DATABASE_VERSION = 2;
|
||||
static final String DATABASE_NAME = "database";
|
||||
|
||||
static final int FLAG_DATABASE_IN_MEMORY = 1;
|
||||
@@ -77,6 +77,12 @@ class MtpDatabaseConstants {
|
||||
*/
|
||||
static final int ROW_STATE_INVALIDATED = 1;
|
||||
|
||||
/**
|
||||
* The documents are of device/storage that are disconnected now. The documents are invisible
|
||||
* but their document ID will be reuse when the device/storage is connected again.
|
||||
*/
|
||||
static final int ROW_STATE_DISCONNECTED = 2;
|
||||
|
||||
/**
|
||||
* Mapping mode that uses MTP identifier to find corresponding rows.
|
||||
*/
|
||||
@@ -113,7 +119,7 @@ class MtpDatabaseConstants {
|
||||
"CREATE TABLE " + TABLE_DOCUMENTS + " (" +
|
||||
Document.COLUMN_DOCUMENT_ID +
|
||||
" INTEGER PRIMARY KEY AUTOINCREMENT," +
|
||||
COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
|
||||
COLUMN_DEVICE_ID + " INTEGER," +
|
||||
COLUMN_STORAGE_ID + " INTEGER," +
|
||||
COLUMN_OBJECT_HANDLE + " INTEGER," +
|
||||
COLUMN_PARENT_DOCUMENT_ID + " INTEGER," +
|
||||
|
||||
@@ -95,7 +95,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
|
||||
assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
|
||||
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
|
||||
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
|
||||
assertEquals(
|
||||
DocumentsContract.Document.MIME_TYPE_DIR, getString(cursor, COLUMN_MIME_TYPE));
|
||||
assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
assertTrue(isNull(cursor, COLUMN_SUMMARY));
|
||||
assertTrue(isNull(cursor, COLUMN_LAST_MODIFIED));
|
||||
@@ -103,7 +104,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
assertEquals(0, getInt(cursor, COLUMN_FLAGS));
|
||||
assertEquals(1000, getInt(cursor, COLUMN_SIZE));
|
||||
assertEquals(
|
||||
MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE));
|
||||
MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE,
|
||||
getInt(cursor, COLUMN_DOCUMENT_TYPE));
|
||||
|
||||
cursor.close();
|
||||
}
|
||||
@@ -296,15 +298,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
|
||||
{
|
||||
final Cursor cursor = mDatabase.queryRootDocuments(columns);
|
||||
assertEquals(2, cursor.getCount());
|
||||
cursor.moveToNext();
|
||||
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
|
||||
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
|
||||
assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
cursor.moveToNext();
|
||||
assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
|
||||
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
|
||||
assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
@@ -314,28 +308,10 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
new MtpRoot(0, 200, "Storage A", 2000, 0, ""),
|
||||
new MtpRoot(0, 202, "Storage C", 2002, 0, "")
|
||||
});
|
||||
|
||||
{
|
||||
final Cursor cursor = mDatabase.queryRootDocuments(columns);
|
||||
assertEquals(3, cursor.getCount());
|
||||
cursor.moveToNext();
|
||||
assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
|
||||
assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
|
||||
assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
cursor.moveToNext();
|
||||
assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
|
||||
assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
|
||||
assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
cursor.moveToNext();
|
||||
assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
|
||||
assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
|
||||
assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
mDatabase.getMapper().stopAddingDocuments("1");
|
||||
|
||||
{
|
||||
// After compeleting mapping, Storage A can be obtained with new storage ID.
|
||||
final Cursor cursor = mDatabase.queryRootDocuments(columns);
|
||||
assertEquals(2, cursor.getCount());
|
||||
cursor.moveToNext();
|
||||
@@ -372,24 +348,9 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
addTestStorage("1");
|
||||
|
||||
{
|
||||
// Don't return objects that lost MTP object handles.
|
||||
final Cursor cursor = mDatabase.queryChildDocuments(columns, "2");
|
||||
assertEquals(3, cursor.getCount());
|
||||
|
||||
cursor.moveToNext();
|
||||
assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
|
||||
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
|
||||
assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
|
||||
cursor.moveToNext();
|
||||
assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
|
||||
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
|
||||
assertEquals("image.jpg", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
|
||||
cursor.moveToNext();
|
||||
assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
|
||||
assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
|
||||
assertEquals("music.mp3", getString(cursor, COLUMN_DISPLAY_NAME));
|
||||
|
||||
assertEquals(0, cursor.getCount());
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
@@ -598,10 +559,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
Root.COLUMN_AVAILABLE_BYTES
|
||||
};
|
||||
|
||||
mDatabase.getMapper().startAddingDocuments(null);
|
||||
mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
|
||||
0, "Device", false, new MtpRoot[0], null, null));
|
||||
mDatabase.getMapper().stopAddingDocuments(null);
|
||||
addTestDevice();
|
||||
|
||||
mDatabase.getMapper().startAddingDocuments("1");
|
||||
mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
|
||||
@@ -701,10 +659,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
public void testReplaceExistingRoots() throws Exception {
|
||||
mDatabase.getMapper().startAddingDocuments(null);
|
||||
mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
|
||||
0, "Device", true, new MtpRoot[0], null, null));
|
||||
mDatabase.getMapper().stopAddingDocuments(null);
|
||||
addTestDevice();
|
||||
|
||||
// The client code should be able to replace existing rows with new information.
|
||||
// Add one.
|
||||
@@ -802,10 +757,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
|
||||
public void testQueryRoots() throws Exception {
|
||||
// Add device document.
|
||||
mDatabase.getMapper().startAddingDocuments(null);
|
||||
mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
|
||||
0, "Device", false, new MtpRoot[0], null, null));
|
||||
mDatabase.getMapper().stopAddingDocuments(null);
|
||||
addTestDevice();
|
||||
|
||||
// It the device does not have storages, it shows a device root.
|
||||
{
|
||||
@@ -930,6 +882,9 @@ public class MtpDatabaseTest extends AndroidTestCase {
|
||||
|
||||
// The new document should not be mapped with existing invalidated document.
|
||||
mDatabase.getMapper().clearMapping();
|
||||
addTestDevice();
|
||||
addTestStorage("1");
|
||||
|
||||
mDatabase.getMapper().startAddingDocuments("2");
|
||||
mDatabase.putNewDocument(
|
||||
0,
|
||||
|
||||
Reference in New Issue
Block a user