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

Commit c86d5ae5 authored by Daichi Hirono's avatar Daichi Hirono Committed by android-build-merger
Browse files

Use device key to map device documents.

am: ebd24051

* commit 'ebd24051':
  Use device key to map device documents.
parents 9a5daca3 ebd24051
Loading
Loading
Loading
Loading
+92 −76
Original line number Diff line number Diff line
@@ -24,13 +24,13 @@ import android.database.sqlite.SQLiteDatabase;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.util.ArraySet;
import android.util.Log;

import com.android.internal.util.Preconditions;

import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static com.android.mtp.MtpDatabaseConstants.*;
import static com.android.mtp.MtpDatabase.strings;
@@ -44,11 +44,9 @@ class Mapper {
    private final MtpDatabase mDatabase;

    /**
     * Mapping mode for a parent. The key is document ID of parent, or null for root documents.
     * Methods operate the state needs to be synchronized.
     * TODO: Replace this with unboxing int map.
     * IDs which currently Mapper operates mapping for.
     */
    private final Map<String, Integer> mMappingMode = new HashMap<>();
    private final Set<String> mInMappingIds = new ArraySet<>();

    Mapper(MtpDatabase database) {
        mDatabase = database;
@@ -75,7 +73,7 @@ class Mapper {
                    extraValuesList,
                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
                    EMPTY_ARGS,
                    Document.COLUMN_DISPLAY_NAME);
                    strings(COLUMN_DEVICE_ID, COLUMN_MAPPING_KEY));
            database.setTransactionSuccessful();
            return changed;
        } finally {
@@ -96,18 +94,6 @@ class Mapper {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        database.beginTransaction();
        try {
            final String mapColumn;
            Preconditions.checkState(mMappingMode.containsKey(parentDocumentId));
            switch (mMappingMode.get(parentDocumentId)) {
                case MAP_BY_MTP_IDENTIFIER:
                    mapColumn = COLUMN_STORAGE_ID;
                    break;
                case MAP_BY_NAME:
                    mapColumn = Document.COLUMN_DISPLAY_NAME;
                    break;
                default:
                    throw new Error("Unexpected map mode.");
            }
            final ContentValues[] valuesList = new ContentValues[roots.length];
            final ContentValues[] extraValuesList = new ContentValues[roots.length];
            for (int i = 0; i < roots.length; i++) {
@@ -122,7 +108,7 @@ class Mapper {
                    extraValuesList,
                    COLUMN_PARENT_DOCUMENT_ID + " = ?",
                    strings(parentDocumentId),
                    mapColumn);
                    strings(COLUMN_STORAGE_ID, Document.COLUMN_DISPLAY_NAME));

            database.setTransactionSuccessful();
            return changed;
@@ -141,23 +127,10 @@ class Mapper {
     */
    synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents)
            throws FileNotFoundException {
        final String mapColumn;
        Preconditions.checkState(mMappingMode.containsKey(parentId));
        switch (mMappingMode.get(parentId)) {
            case MAP_BY_MTP_IDENTIFIER:
                mapColumn = COLUMN_OBJECT_HANDLE;
                break;
            case MAP_BY_NAME:
                mapColumn = Document.COLUMN_DISPLAY_NAME;
                break;
            default:
                throw new Error("Unexpected map mode.");
        }
        final ContentValues[] valuesList = new ContentValues[documents.length];
        for (int i = 0; i < documents.length; i++) {
            valuesList[i] = new ContentValues();
            MtpDatabase.getObjectDocumentValues(
                    valuesList[i], deviceId, parentId, documents[i]);
            MtpDatabase.getObjectDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
        }
        putDocuments(
                parentId,
@@ -165,14 +138,14 @@ class Mapper {
                null,
                COLUMN_PARENT_DOCUMENT_ID + " = ?",
                strings(parentId),
                mapColumn);
                strings(COLUMN_OBJECT_HANDLE, Document.COLUMN_DISPLAY_NAME));
    }

    void clearMapping() {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        database.beginTransaction();
        try {
            mMappingMode.clear();
            mInMappingIds.clear();
            // Disconnect all device rows.
            try {
                startAddingDocuments(null);
@@ -211,7 +184,7 @@ class Mapper {
        database.beginTransaction();
        try {
            getParentOrHaltMapping(parentDocumentId);
            Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
            Preconditions.checkState(!mInMappingIds.contains(parentDocumentId));

            // Set all valid documents as invalidated.
            final ContentValues values = new ContentValues();
@@ -222,15 +195,8 @@ class Mapper {
                    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_DEVICE_ID + " IS NULL",
                    args) > 0;
            database.setTransactionSuccessful();
            mMappingMode.put(
                    parentDocumentId, useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER);
            mInMappingIds.add(parentDocumentId);
        } finally {
            database.endTransaction();
        }
@@ -249,7 +215,7 @@ class Mapper {
     * @param rootExtraValuesList Values for root extra to be stored in the database.
     * @param selection SQL where closure to select rows that shares the same parent.
     * @param args Argument for selection SQL.
     * @return Whether it adds at least one new row that is not mapped with existing document ID.
     * @return Whether the database content is changed.
     * @throws FileNotFoundException When parentId is not registered in the database.
     */
    private boolean putDocuments(
@@ -258,13 +224,15 @@ class Mapper {
            @Nullable ContentValues[] rootExtraValuesList,
            String selection,
            String[] args,
            String mappingKey) throws FileNotFoundException {
            String[] mappingKeys) throws FileNotFoundException {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        boolean added = false;
        boolean changed = false;
        database.beginTransaction();
        try {
            getParentOrHaltMapping(parentId);
            Preconditions.checkState(mMappingMode.containsKey(parentId));
            Preconditions.checkState(mInMappingIds.contains(parentId));
            final ContentValues oldRowSnapshot = new ContentValues();
            final ContentValues newRowSnapshot = new ContentValues();
            for (int i = 0; i < valuesList.length; i++) {
                final ContentValues values = valuesList[i];
                final ContentValues rootExtraValues;
@@ -273,29 +241,18 @@ class Mapper {
                } else {
                    rootExtraValues = null;
                }
                final Cursor candidateCursor = database.query(
                        TABLE_DOCUMENTS,
                        strings(Document.COLUMN_DOCUMENT_ID),
                        selection + " AND " +
                        COLUMN_ROW_STATE + " IN (?, ?) AND " +
                        mappingKey + "=?",
                        DatabaseUtils.appendSelectionArgs(
                                args,
                                strings(ROW_STATE_INVALIDATED,
                                        ROW_STATE_DISCONNECTED,
                                        values.getAsString(mappingKey))),
                        null,
                        null,
                        null,
                        "1");
                try {
                try (final Cursor candidateCursor =
                        queryCandidate(selection, args, mappingKeys, values)) {
                    final long rowId;
                    if (candidateCursor.getCount() == 0) {
                    if (candidateCursor == null) {
                        rowId = database.insert(TABLE_DOCUMENTS, null, values);
                        added = true;
                        changed = true;
                    } else {
                        candidateCursor.moveToNext();
                        rowId = candidateCursor.getLong(0);
                        if (!changed) {
                            mDatabase.writeRowSnapshot(String.valueOf(rowId), oldRowSnapshot);
                        }
                        database.update(
                                TABLE_DOCUMENTS,
                                values,
@@ -309,13 +266,20 @@ class Mapper {
                        rootExtraValues.put(Root.COLUMN_ROOT_ID, rowId);
                        database.replace(TABLE_ROOT_EXTRA, null, rootExtraValues);
                    }
                } finally {
                    candidateCursor.close();

                    if (!changed) {
                        mDatabase.writeRowSnapshot(String.valueOf(rowId), newRowSnapshot);
                        // Put row state as string because SQLite returns snapshot values as string.
                        oldRowSnapshot.put(COLUMN_ROW_STATE, String.valueOf(ROW_STATE_VALID));
                        if (!oldRowSnapshot.equals(newRowSnapshot)) {
                            changed = true;
                        }
                    }
                }
            }

            database.setTransactionSuccessful();
            return added;
            return changed;
        } finally {
            database.endTransaction();
        }
@@ -345,8 +309,8 @@ class Mapper {
        database.beginTransaction();
        try {
            final Identifier parentIdentifier = getParentOrHaltMapping(parentId);
            Preconditions.checkState(mMappingMode.containsKey(parentId));
            mMappingMode.remove(parentId);
            Preconditions.checkState(mInMappingIds.contains(parentId));
            mInMappingIds.remove(parentId);

            boolean changed = false;
            // Delete/disconnect all invalidated rows that cannot be mapped.
@@ -374,6 +338,58 @@ class Mapper {
        }
    }

    /**
     * Queries candidate for each mappingKey, and returns the first cursor that includes a
     * candidate.
     *
     * @param selection Pre-selection for candidate.
     * @param args Arguments for selection.
     * @param mappingKeys List of mapping key columns.
     * @param values Values of document that Mapper tries to map.
     * @return Cursor for mapping candidate or null when Mapper does not find any candidate.
     */
    private @Nullable Cursor queryCandidate(
            String selection, String[] args, String[] mappingKeys, ContentValues values) {
        for (final String mappingKey : mappingKeys) {
            final Cursor candidateCursor = queryCandidate(selection, args, mappingKey, values);
            if (candidateCursor.getCount() == 0) {
                candidateCursor.close();
                continue;
            }
            return candidateCursor;
        }
        return null;
    }

    /**
     * Looks for mapping candidate with given mappingKey.
     *
     * @param selection Pre-selection for candidate.
     * @param args Arguments for selection.
     * @param mappingKey Column name of mapping key.
     * @param values Values of document that Mapper tries to map.
     * @return Cursor for mapping candidate.
     */
    private Cursor queryCandidate(
            String selection, String[] args, String mappingKey, ContentValues values) {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        return database.query(
                TABLE_DOCUMENTS,
                strings(Document.COLUMN_DOCUMENT_ID),
                selection + " AND " +
                COLUMN_ROW_STATE + " IN (?, ?) AND " +
                mappingKey + " = ?",
                DatabaseUtils.appendSelectionArgs(
                        args,
                        strings(ROW_STATE_INVALIDATED,
                                ROW_STATE_DISCONNECTED,
                                values.getAsString(mappingKey))),
                null,
                null,
                null,
                "1");
    }

    /**
     * Returns the parent identifier from parent document ID if the parent ID is found in the
     * database. Otherwise it halts mapping and throws FileNotFoundException.
@@ -395,7 +411,7 @@ class Mapper {
            }
            return identifier;
        } catch (FileNotFoundException error) {
            mMappingMode.remove(parentId);
            mInMappingIds.remove(parentId);
            throw error;
        }
    }
+20 −0
Original line number Diff line number Diff line
@@ -562,6 +562,25 @@ class MtpDatabase {
        }
    }

    void writeRowSnapshot(String documentId, ContentValues values) throws FileNotFoundException {
        try (final Cursor cursor = mDatabase.query(
                JOIN_ROOTS,
                strings("*"),
                SELECTION_DOCUMENT_ID,
                strings(documentId),
                null,
                null,
                null,
                "1")) {
            if (cursor.getCount() == 0) {
                throw new FileNotFoundException();
            }
            cursor.moveToNext();
            values.clear();
            DatabaseUtils.cursorRowToContentValues(cursor, values);
        }
    }

    private static class OpenHelper extends SQLiteOpenHelper {
        public OpenHelper(Context context, int flags) {
            super(context,
@@ -600,6 +619,7 @@ class MtpDatabase {
        values.putNull(COLUMN_PARENT_DOCUMENT_ID);
        values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
        values.put(COLUMN_DOCUMENT_TYPE, DOCUMENT_TYPE_DEVICE);
        values.put(COLUMN_MAPPING_KEY, device.deviceKey);
        values.put(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR);
        values.put(Document.COLUMN_DISPLAY_NAME, device.name);
        values.putNull(Document.COLUMN_SUMMARY);
+4 −12
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 = 3;
    static final int DATABASE_VERSION = 4;
    static final String DATABASE_NAME = "database";

    static final int FLAG_DATABASE_IN_MEMORY = 1;
@@ -62,6 +62,7 @@ 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";

    /**
     * The state represents that the row has a valid object handle.
@@ -83,16 +84,6 @@ class MtpDatabaseConstants {
     */
    static final int ROW_STATE_DISCONNECTED = 2;

    /**
     * Mapping mode that uses MTP identifier to find corresponding rows.
     */
    static final int MAP_BY_MTP_IDENTIFIER = 0;

    /**
     * Mapping mode that uses name to find corresponding rows.
     */
    static final int MAP_BY_NAME = 1;

    @IntDef(value = { DOCUMENT_TYPE_DEVICE, DOCUMENT_TYPE_STORAGE, DOCUMENT_TYPE_OBJECT })
    @Retention(RetentionPolicy.SOURCE)
    public @interface DocumentType {}
@@ -125,6 +116,7 @@ class MtpDatabaseConstants {
            COLUMN_PARENT_DOCUMENT_ID + " INTEGER," +
            COLUMN_ROW_STATE + " INTEGER NOT NULL," +
            COLUMN_DOCUMENT_TYPE + " INTEGER NOT NULL," +
            COLUMN_MAPPING_KEY + " STRING," +
            Document.COLUMN_MIME_TYPE + " TEXT NOT NULL," +
            Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
            Document.COLUMN_SUMMARY + " TEXT," +
@@ -174,7 +166,7 @@ class MtpDatabaseConstants {

    private static String createJoinFromClosure(
            String table1, String table2, String column1, String column2) {
        return table1 + " INNER JOIN " + table2 +
        return table1 + " LEFT JOIN " + table2 +
                " ON " + table1 + "." + column1 + " = " + table2 + "." + column2;
    }
}
+5 −2
Original line number Diff line number Diff line
@@ -21,17 +21,20 @@ import android.annotation.Nullable;
class MtpDeviceRecord {
    public final int deviceId;
    public final String name;
    public final @Nullable String deviceKey;
    public final boolean opened;
    public final MtpRoot[] roots;
    public final @Nullable int[] operationsSupported;
    public final @Nullable int[] eventsSupported;

    MtpDeviceRecord(int deviceId, String name, boolean opened, MtpRoot[] roots,
                    @Nullable int[] operationsSupported, @Nullable int[] eventsSupported) {
    MtpDeviceRecord(int deviceId, String name, @Nullable String deviceKey, boolean opened,
                    MtpRoot[] roots, @Nullable int[] operationsSupported,
                    @Nullable int[] eventsSupported) {
        this.deviceId = deviceId;
        this.name = name;
        this.opened = opened;
        this.roots = roots;
        this.deviceKey = deviceKey;
        this.operationsSupported = operationsSupported;
        this.eventsSupported = eventsSupported;
    }
+3 −0
Original line number Diff line number Diff line
@@ -374,6 +374,9 @@ public class MtpDocumentsProvider extends DocumentsProvider {
     * Resumes root scanner to handle the update of device list.
     */
    void resumeRootScanner() {
        if (DEBUG) {
            Log.d(MtpDocumentsProvider.TAG, "resumeRootScanner");
        }
        mRootScanner.resume();
    }

Loading