Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 53f5af3f authored by Daichi Hirono's avatar Daichi Hirono
Browse files

Keep metadata of documents as disconnected status after the device is

disconnected.

To restore Document IDs when the device is reconnected, we need to keep
the metadata in database so that we can use it as hint to remap document
ID with new MTP IDs.

BUG=26212981

Change-Id: Idcc93c41c09d082a709281022c56188dabc80515
parent df803ec6
Loading
Loading
Loading
Loading
+46 −21
Original line number Diff line number Diff line
@@ -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,17 +344,28 @@ 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.
            // 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();
            return changed;
@@ -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;
+79 −13
Original line number Diff line number Diff line
@@ -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,
@@ -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();
        }
    }
+8 −2
Original line number Diff line number Diff line
@@ -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," +
+14 −59
Original line number Diff line number Diff line
@@ -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,