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

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

Merge "Start to use MtpDatabase in RootScanner."

parents bc195a6b dc473446
Loading
Loading
Loading
Loading
+107 −38
Original line number Diff line number Diff line
@@ -71,6 +71,11 @@ import java.util.Map;
@VisibleForTesting
class MtpDatabase {
    private final MtpDatabaseInternal mDatabase;

    /**
     * Mapping mode for roots/documents where we start adding child documents.
     * Methods operate the state needs to be synchronized.
     */
    private final Map<String, Integer> mMappingMode = new HashMap<>();

    @VisibleForTesting
@@ -78,23 +83,49 @@ class MtpDatabase {
        mDatabase = new MtpDatabaseInternal(context);
    }

    /**
     * Closes the database.
     */
    @VisibleForTesting
    void close() {
        mDatabase.close();
    }

    /**
     * {@link MtpDatabaseInternal#queryRoots}
     */
    Cursor queryRoots(String[] columnNames) {
        return mDatabase.queryRoots(columnNames);
    }

    /**
     * {@link MtpDatabaseInternal#queryRootDocuments}
     */
    @VisibleForTesting
    Cursor queryRootDocuments(String[] columnNames) {
        return mDatabase.queryRootDocuments(columnNames);
    }

    /**
     * {@link MtpDatabaseInternal#queryChildDocuments}
     */
    @VisibleForTesting
    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
        return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
    }

    @VisibleForTesting
    void startAddingRootDocuments(int deviceId) {
    /**
     * {@link MtpDatabaseInternal#removeDeviceRows}
     */
    void removeDeviceRows(int deviceId) {
        mDatabase.removeDeviceRows(deviceId);
    }

    /**
     * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents.
     * @param deviceId Device ID.
     */
    synchronized void startAddingRootDocuments(int deviceId) {
        final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
        if (mMappingMode.containsKey(mappingStateKey)) {
            throw new Error("Mapping for the root has already started.");
@@ -105,8 +136,12 @@ class MtpDatabase {
                        SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
    }

    /**
     * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for child of specific documents.
     * @param parentDocumentId Document ID for parent document.
     */
    @VisibleForTesting
    void startAddingChildDocuments(String parentDocumentId) {
    synchronized void startAddingChildDocuments(String parentDocumentId) {
        final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
        if (mMappingMode.containsKey(mappingStateKey)) {
            throw new Error("Mapping for the root has already started.");
@@ -116,20 +151,18 @@ class MtpDatabase {
                mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
    }

    @VisibleForTesting
    void putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
    /**
     * Puts root information to database.
     * @param deviceId Device ID
     * @param resources Resources required to localize root name.
     * @param roots List of root information.
     * @return If roots are added or removed from the database.
     */
    synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
        mDatabase.beginTransaction();
        try {
            final ContentValues[] valuesList = new ContentValues[roots.length];
            for (int i = 0; i < roots.length; i++) {
                if (roots[i].mDeviceId != deviceId) {
                    throw new IllegalArgumentException();
                }
                valuesList[i] = new ContentValues();
                getRootDocumentValues(valuesList[i], resources, roots[i]);
            }
            boolean heuristic;
            String mapColumn;
            final boolean heuristic;
            final String mapColumn;
            switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) {
                case MAP_BY_MTP_IDENTIFIER:
                    heuristic = false;
@@ -142,7 +175,15 @@ class MtpDatabase {
                default:
                    throw new Error("Unexpected map mode.");
            }
            final long[] documentIds = mDatabase.putDocuments(
            final ContentValues[] valuesList = new ContentValues[roots.length];
            for (int i = 0; i < roots.length; i++) {
                if (roots[i].mDeviceId != deviceId) {
                    throw new IllegalArgumentException();
                }
                valuesList[i] = new ContentValues();
                getRootDocumentValues(valuesList[i], resources, roots[i]);
            }
            final boolean changed = mDatabase.putDocuments(
                    valuesList,
                    SELECTION_ROOT_DOCUMENTS,
                    Integer.toString(deviceId),
@@ -152,33 +193,38 @@ class MtpDatabase {
            int i = 0;
            for (final MtpRoot root : roots) {
                // Use the same value for the root ID and the corresponding document ID.
                values.put(Root.COLUMN_ROOT_ID, documentIds[i++]);
                values.put(
                        Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
                        Root.COLUMN_ROOT_ID,
                        valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID));
                values.put(
                        Root.COLUMN_FLAGS,
                        Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
                values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
                values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
                values.put(Root.COLUMN_MIME_TYPES, "");
                mDatabase.putRootExtra(values);
            }
            mDatabase.setTransactionSuccessful();
            return changed;
        } finally {
            mDatabase.endTransaction();
        }
    }

    /**
     * Puts document information to database.
     * @param deviceId Device ID
     * @param parentId Parent document ID.
     * @param documents List of document information.
     */
    @VisibleForTesting
    void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
        final ContentValues[] valuesList = new ContentValues[documents.length];
        for (int i = 0; i < documents.length; i++) {
            valuesList[i] = new ContentValues();
            getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
        }
        boolean heuristic;
        String mapColumn;
    synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
        final boolean heuristic;
        final String mapColumn;
        switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) {
            case MAP_BY_MTP_IDENTIFIER:
                heuristic = false;
                mapColumn = COLUMN_STORAGE_ID;
                mapColumn = COLUMN_OBJECT_HANDLE;
                break;
            case MAP_BY_NAME:
                heuristic = true;
@@ -187,40 +233,55 @@ class MtpDatabase {
            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();
            getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
        }
        mDatabase.putDocuments(
                valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
    }

    /**
     * Clears mapping between MTP identifier and document/root ID.
     */
    @VisibleForTesting
    void clearMapping() {
    synchronized void clearMapping() {
        mDatabase.clearMapping();
        mMappingMode.clear();
    }

    @VisibleForTesting
    void stopAddingRootDocuments(int deviceId) {
    /**
     * Stops adding root documents.
     * @param deviceId Device ID.
     * @return True if new rows are added/removed.
     */
    synchronized boolean stopAddingRootDocuments(int deviceId) {
        final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId);
        switch (mMappingMode.get(mappingModeKey)) {
            case MAP_BY_MTP_IDENTIFIER:
                mDatabase.stopAddingDocuments(
                mMappingMode.remove(mappingModeKey);
                return mDatabase.stopAddingDocuments(
                        SELECTION_ROOT_DOCUMENTS,
                        Integer.toString(deviceId),
                        COLUMN_STORAGE_ID);
                break;
            case MAP_BY_NAME:
                mDatabase.stopAddingDocuments(
                mMappingMode.remove(mappingModeKey);
                return mDatabase.stopAddingDocuments(
                        SELECTION_ROOT_DOCUMENTS,
                        Integer.toString(deviceId),
                        Document.COLUMN_DISPLAY_NAME);
                break;
            default:
                throw new Error("Unexpected mapping state.");
        }
        mMappingMode.remove(mappingModeKey);
    }

    /**
     * Stops adding documents under the parent.
     * @param parentId Document ID of the parent.
     */
    @VisibleForTesting
    void stopAddingChildDocuments(String parentId) {
    synchronized void stopAddingChildDocuments(String parentId) {
        final String mappingModeKey = getChildDocumentsMappingStateKey(parentId);
        switch (mMappingMode.get(mappingModeKey)) {
            case MAP_BY_MTP_IDENTIFIER:
@@ -303,11 +364,19 @@ class MtpDatabase {
        values.put(Document.COLUMN_SIZE, info.getCompressedSize());
    }

    private String getRootDocumentsMappingStateKey(int deviceId) {
    /**
     * @param deviceId Device ID.
     * @return Key for {@link #mMappingMode}.
     */
    private static String getRootDocumentsMappingStateKey(int deviceId) {
        return "RootDocuments/" + deviceId;
    }

    private String getChildDocumentsMappingStateKey(String parentDocumentId) {
    /**
     * @param parentDocumentId Document ID for the parent document.
     * @return Key for {@link #mMappingMode}.
     */
    private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
        return "ChildDocuments/" + parentDocumentId;
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -126,7 +126,11 @@ class MtpDatabaseConstants {
                            Root.COLUMN_TITLE + "," +
                    TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
                            Root.COLUMN_SUMMARY + "," +
                    // Temporary replace COLUMN_DOCUMENT_ID with old format.
                    TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
                    Root.COLUMN_DOCUMENT_ID + "_," +
                    TABLE_DOCUMENTS + "." + COLUMN_DEVICE_ID + "|| '_' ||" +
                    TABLE_DOCUMENTS + "." + COLUMN_STORAGE_ID + "||'_0' AS " +
                    Root.COLUMN_DOCUMENT_ID + "," +
                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
                    TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +
+68 −15
Original line number Diff line number Diff line
@@ -59,6 +59,15 @@ class MtpDatabaseInternal {
        mDatabase = helper.getWritableDatabase();
    }

    void close() {
        mDatabase.close();
    }

    /**
     * Queries roots information.
     * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
     * @return Database cursor.
     */
    Cursor queryRoots(String[] columnNames) {
        return mDatabase.query(
                VIEW_ROOTS,
@@ -70,6 +79,12 @@ class MtpDatabaseInternal {
                null);
    }

    /**
     * Queries root documents information.
     * @param columnNames Column names defined in
     *     {@link android.provider.DocumentsContract.Document}.
     * @return Database cursor.
     */
    Cursor queryRootDocuments(String[] columnNames) {
        return mDatabase.query(
                TABLE_DOCUMENTS,
@@ -81,6 +96,12 @@ class MtpDatabaseInternal {
                null);
    }

    /**
     * Queries documents information.
     * @param columnNames Column names defined in
     *     {@link android.provider.DocumentsContract.Document}.
     * @return Database cursor.
     */
    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
        return mDatabase.query(
                TABLE_DOCUMENTS,
@@ -92,6 +113,14 @@ class MtpDatabaseInternal {
                null);
    }

    /**
     * Remove all rows belong to a device.
     * @param deviceId Device ID.
     */
    void removeDeviceRows(int deviceId) {
        deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
    }

    /**
     * Starts adding new documents.
     * The methods decides mapping mode depends on if all documents under the given parent have MTP
@@ -133,24 +162,23 @@ class MtpDatabaseInternal {
     * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
     * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
     * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
     * rows.
     * rows. If the methods adds rows to database, it updates valueList with correct document ID.
     *
     * @param valuesList Values that are stored in the database.
     * @param selection SQL where closure to select rows that shares the same parent.
     * @param arg Argument for selection SQL.
     * @param heuristic Whether the mapping mode is heuristic.
     * @return List of Document ID inserted to the table.
     * @return Whether the method adds new rows.
     */
    long[] putDocuments(
    boolean putDocuments(
            ContentValues[] valuesList,
            String selection,
            String arg,
            boolean heuristic,
            String mappingKey) {
        boolean added = false;
        mDatabase.beginTransaction();
        try {
            final long[] documentIds = new long[valuesList.length];
            int i = 0;
            for (final ContentValues values : valuesList) {
                final Cursor candidateCursor = mDatabase.query(
                        TABLE_DOCUMENTS,
@@ -166,6 +194,7 @@ class MtpDatabaseInternal {
                final long rowId;
                if (candidateCursor.getCount() == 0) {
                    rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
                    added = true;
                } else if (!heuristic) {
                    candidateCursor.moveToNext();
                    final String documentId = candidateCursor.getString(0);
@@ -177,17 +206,21 @@ class MtpDatabaseInternal {
                }
                // Document ID is a primary integer key of the table. So the returned row
                // IDs should be same with the document ID.
                documentIds[i++] = rowId;
                values.put(Document.COLUMN_DOCUMENT_ID, rowId);
                candidateCursor.close();
            }

            mDatabase.setTransactionSuccessful();
            return documentIds;
            return added;
        } finally {
            mDatabase.endTransaction();
        }
    }

    /**
     * Puts extra information for root documents.
     * @param values Values containing extra information.
     */
    void putRootExtra(ContentValues values) {
        mDatabase.replace(TABLE_ROOT_EXTRA, null, values);
    }
@@ -199,8 +232,9 @@ class MtpDatabaseInternal {
     * @param selection Query to select rows for resolving.
     * @param arg Argument for selection SQL.
     * @param groupKey Column name used to find corresponding rows.
     * @return Whether the methods adds or removed visible rows.
     */
    void stopAddingDocuments(String selection, String arg, String groupKey) {
    boolean stopAddingDocuments(String selection, String arg, String groupKey) {
        mDatabase.beginTransaction();
        try {
            // Get 1-to-1 mapping of invalidated document and pending document.
@@ -265,22 +299,29 @@ class MtpDatabaseInternal {
            }
            mergingCursor.close();

            boolean changed = false;

            // Delete all invalidated rows that cannot be mapped.
            deleteDocumentsAndRoots(
            if (deleteDocumentsAndRoots(
                    COLUMN_ROW_STATE + " = ? AND " + selection,
                    strings(ROW_STATE_INVALIDATED, arg));
                    strings(ROW_STATE_INVALIDATED, arg))) {
                changed = true;
            }

            // The database cannot find old document ID for the pending rows.
            // Turn the all pending rows into valid state, which means the rows become to be
            // valid with new document ID.
            values.clear();
            values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
            mDatabase.update(
            if (mDatabase.update(
                    TABLE_DOCUMENTS,
                    values,
                    COLUMN_ROW_STATE + " = ? AND " + selection,
                    strings(ROW_STATE_PENDING, arg));
                    strings(ROW_STATE_PENDING, arg)) != 0) {
                changed = true;
            }
            mDatabase.setTransactionSuccessful();
            return changed;
        } finally {
            mDatabase.endTransaction();
        }
@@ -307,14 +348,23 @@ class MtpDatabaseInternal {
        }
    }

    /**
     * {@link android.database.sqlite.SQLiteDatabase#beginTransaction()}
     */
    void beginTransaction() {
        mDatabase.beginTransaction();
    }

    /**
     * {@link android.database.sqlite.SQLiteDatabase#setTransactionSuccessful()}
     */
    void setTransactionSuccessful() {
        mDatabase.setTransactionSuccessful();
    }

    /**
     * {@link android.database.sqlite.SQLiteDatabase#endTransaction()}
     */
    void endTransaction() {
        mDatabase.endTransaction();
    }
@@ -323,11 +373,13 @@ class MtpDatabaseInternal {
     * Deletes a document, and its root information if the document is a root document.
     * @param selection Query to select documents.
     * @param args Arguments for selection.
     * @return Whether the method deletes rows.
     */
    private void deleteDocumentsAndRoots(String selection, String[] args) {
    private boolean deleteDocumentsAndRoots(String selection, String[] args) {
        mDatabase.beginTransaction();
        try {
            mDatabase.delete(
            int deleted = 0;
            deleted += mDatabase.delete(
                    TABLE_ROOT_EXTRA,
                    Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
                            false,
@@ -339,8 +391,9 @@ class MtpDatabaseInternal {
                            null,
                            null) + ")",
                    args);
            mDatabase.delete(TABLE_DOCUMENTS, selection, args);
            deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
            mDatabase.setTransactionSuccessful();
            return deleted != 0;
        } finally {
            mDatabase.endTransaction();
        }
+18 −17
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
    private Map<Integer, DeviceToolkit> mDeviceToolkits;
    private RootScanner mRootScanner;
    private Resources mResources;
    private MtpDatabase mDatabase;

    /**
     * Provides singleton instance to MtpDocumentsService.
@@ -77,17 +78,23 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        mMtpManager = new MtpManager(getContext());
        mResolver = getContext().getContentResolver();
        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
        mRootScanner = new RootScanner(mResolver, mMtpManager);
        mDatabase = new MtpDatabase(getContext());
        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
        return true;
    }

    @VisibleForTesting
    void onCreateForTesting(Resources resources, MtpManager mtpManager, ContentResolver resolver) {
    void onCreateForTesting(
            Resources resources,
            MtpManager mtpManager,
            ContentResolver resolver,
            MtpDatabase database) {
        mResources = resources;
        mMtpManager = mtpManager;
        mResolver = resolver;
        mDeviceToolkits = new HashMap<Integer, DeviceToolkit>();
        mRootScanner = new RootScanner(mResolver, mMtpManager);
        mDatabase = database;
        mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase);
    }

    @Override
@@ -95,17 +102,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        if (projection == null) {
            projection = MtpDocumentsProvider.DEFAULT_ROOT_PROJECTION;
        }
        final MatrixCursor cursor = new MatrixCursor(projection);
        final MtpRoot[] roots = mRootScanner.getRoots();
        for (final MtpRoot root : roots) {
            final Identifier rootIdentifier = new Identifier(root.mDeviceId, root.mStorageId);
            final MatrixCursor.RowBuilder builder = cursor.newRow();
            builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId());
            builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
            builder.add(Root.COLUMN_TITLE, root.getRootName(mResources));
            builder.add(Root.COLUMN_DOCUMENT_ID, rootIdentifier.toDocumentId());
            builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace);
        }
        final Cursor cursor = mDatabase.queryRoots(projection);
        cursor.setNotificationUri(
                mResolver, DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY));
        return cursor;
@@ -266,14 +263,16 @@ public class MtpDocumentsProvider extends DocumentsProvider {
        // TODO: Flush the device before closing (if not closed externally).
        getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
        mDeviceToolkits.remove(deviceId);
        mDatabase.removeDeviceRows(deviceId);
        mMtpManager.closeDevice(deviceId);
        mRootScanner.scanNow();
        mRootScanner.notifyChange();
    }

    void closeAllDevices() {
    void close() throws InterruptedException {
        boolean closed = false;
        for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
            try {
                mDatabase.removeDeviceRows(deviceId);
                mMtpManager.closeDevice(deviceId);
                getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
                closed = true;
@@ -282,8 +281,10 @@ public class MtpDocumentsProvider extends DocumentsProvider {
            }
        }
        if (closed) {
            mRootScanner.scanNow();
            mRootScanner.notifyChange();
        }
        mRootScanner.close();
        mDatabase.close();
    }

    boolean hasOpenedDevices() {
+5 −1
Original line number Diff line number Diff line
@@ -79,9 +79,13 @@ public class MtpDocumentsService extends Service {
    @Override
    public void onDestroy() {
        final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
        provider.closeAllDevices();
        unregisterReceiver(mReceiver);
        mReceiver = null;
        try {
            provider.close();
        } catch (InterruptedException e) {
            Log.e(MtpDocumentsProvider.TAG, e.getMessage());
        }
        super.onDestroy();
    }

Loading