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

Commit e983fd5f authored by Daichi Hirono's avatar Daichi Hirono Committed by Android (Google) Code Review
Browse files

Merge "Keep metadata of documents as disconnected status after the device is disconnected."

parents 9fb25b16 53f5af3f
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,