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

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

Merge "Start using MtpDatabase in DocumentLoader."

parents c09818e0 47eb192b
Loading
Loading
Loading
Loading
+79 −41
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@ package com.android.mtp;

import android.content.ContentResolver;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteException;
import android.mtp.MtpObjectInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -26,6 +26,7 @@ import android.os.Process;
import android.provider.DocumentsContract;
import android.util.Log;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
@@ -44,12 +45,14 @@ class DocumentLoader {

    private final MtpManager mMtpManager;
    private final ContentResolver mResolver;
    private final MtpDatabase mDatabase;
    private final TaskList mTaskList = new TaskList();
    private boolean mHasBackgroundThread = false;

    DocumentLoader(MtpManager mtpManager, ContentResolver resolver) {
    DocumentLoader(MtpManager mtpManager, ContentResolver resolver, MtpDatabase database) {
        mMtpManager = mtpManager;
        mResolver = resolver;
        mDatabase = database;
    }

    private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
@@ -65,13 +68,17 @@ class DocumentLoader {
            throws IOException {
        LoaderTask task = mTaskList.findTask(parent);
        if (task == null) {
            if (parent.mDocumentId == null) {
                throw new FileNotFoundException("Parent not found.");
            }

            int parentHandle = parent.mObjectHandle;
            // Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
            // getObjectHandles if we would like to obtain children under the root.
            if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
                parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
            }
            task = new LoaderTask(parent, mMtpManager.getObjectHandles(
            task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles(
                    parent.mDeviceId, parent.mStorageId, parentHandle));
            task.fillDocuments(loadDocuments(
                    mMtpManager,
@@ -83,11 +90,10 @@ class DocumentLoader {
        }

        mTaskList.addFirst(task);
        if (!task.completed() && !mHasBackgroundThread) {
        if (task.getState() == LoaderTask.STATE_LOADING && !mHasBackgroundThread) {
            mHasBackgroundThread = true;
            new BackgroundLoaderThread().start();
        }

        return task.createCursor(mResolver, columnNames);
    }

@@ -120,26 +126,20 @@ class DocumentLoader {
                    deviceId = task.mIdentifier.mDeviceId;
                    handles = task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES);
                }
                MtpObjectInfo[] objectInfos;

                try {
                    objectInfos = loadDocuments(mMtpManager, deviceId, handles);
                } catch (IOException exception) {
                    objectInfos = null;
                    Log.d(MtpDocumentsProvider.TAG, exception.getMessage());
                }
                synchronized (DocumentLoader.this) {
                    if (objectInfos != null) {
                    final MtpObjectInfo[] objectInfos =
                            loadDocuments(mMtpManager, deviceId, handles);
                    task.fillDocuments(objectInfos);
                    final boolean shouldNotify =
                            task.mLastNotified.getTime() <
                            new Date().getTime() - NOTIFY_PERIOD_MS ||
                                task.completed();
                            task.getState() != LoaderTask.STATE_LOADING;
                    if (shouldNotify) {
                        task.notify(mResolver);
                    }
                    } else {
                        mTaskList.remove(task);
                    }
                } catch (IOException exception) {
                    task.setError(exception);
                }
            }
        }
@@ -156,7 +156,7 @@ class DocumentLoader {

        LoaderTask findRunningTask() {
            for (int i = 0; i < size(); i++) {
                if (!get(i).completed())
                if (get(i).getState() == LoaderTask.STATE_LOADING)
                    return get(i);
            }
            return null;
@@ -165,7 +165,7 @@ class DocumentLoader {
        void clearCompletedTasks() {
            int i = 0;
            while (i < size()) {
                if (get(i).completed()) {
                if (get(i).getState() == LoaderTask.STATE_COMPLETED) {
                    remove(i);
                } else {
                    i++;
@@ -186,36 +186,51 @@ class DocumentLoader {
    }

    private static class LoaderTask {
        static final int STATE_LOADING = 0;
        static final int STATE_COMPLETED = 1;
        static final int STATE_ERROR = 2;

        final MtpDatabase mDatabase;
        final Identifier mIdentifier;
        final int[] mObjectHandles;
        final MtpObjectInfo[] mObjectInfos;
        Date mLastNotified;
        int mNumLoaded;
        Exception mError;

        LoaderTask(Identifier identifier, int[] objectHandles) {
        LoaderTask(MtpDatabase database, Identifier identifier, int[] objectHandles) {
            mDatabase = database;
            mIdentifier = identifier;
            mObjectHandles = objectHandles;
            mObjectInfos = new MtpObjectInfo[mObjectHandles.length];
            mNumLoaded = 0;
            mLastNotified = new Date();
        }

        Cursor createCursor(ContentResolver resolver, String[] columnNames) {
            final MatrixCursor cursor = new MatrixCursor(columnNames);
            final Identifier rootIdentifier = new Identifier(
                    mIdentifier.mDeviceId, mIdentifier.mStorageId);
            for (int i = 0; i < mNumLoaded; i++) {
                CursorHelper.addToCursor(mObjectInfos[i], rootIdentifier, cursor.newRow());
            }
        Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException {
            final Bundle extras = new Bundle();
            extras.putBoolean(DocumentsContract.EXTRA_LOADING, !completed());
            switch (getState()) {
                case STATE_LOADING:
                    extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
                    break;
                case STATE_ERROR:
                    throw new IOException(mError);
            }

            final Cursor cursor = mDatabase.queryChildDocuments(
                    columnNames, mIdentifier.mDocumentId, /* use old ID format */ true);
            cursor.setNotificationUri(resolver, createUri());
            cursor.respond(extras);

            return cursor;
        }

        boolean completed() {
            return mNumLoaded == mObjectInfos.length;
        int getState() {
            if (mError != null) {
                return STATE_ERROR;
            } else if (mNumLoaded == mObjectHandles.length) {
                return STATE_COMPLETED;
            } else {
                return STATE_LOADING;
            }
        }

        int[] getUnloadedObjectHandles(int count) {
@@ -230,9 +245,32 @@ class DocumentLoader {
            mLastNotified = new Date();
        }

        void fillDocuments(MtpObjectInfo[] objectInfos) {
            for (int i = 0; i < objectInfos.length; i++) {
                mObjectInfos[mNumLoaded++] = objectInfos[i];
        void fillDocuments(MtpObjectInfo[] objectInfoList) {
            if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
                return;
            }
            if (mNumLoaded == 0) {
                mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId);
            }
            try {
                mDatabase.putChildDocuments(
                        mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
                mNumLoaded += objectInfoList.length;
            } catch (SQLiteException exp) {
                mError = exp;
                mNumLoaded = 0;
            }
            if (getState() != STATE_LOADING) {
                mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
            }
        }

        void setError(Exception message) {
            final int lastState = getState();
            mError = message;
            mNumLoaded = 0;
            if (lastState == STATE_LOADING) {
                mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
            }
        }

+6 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ class Identifier {
    final int mDeviceId;
    final int mStorageId;
    final int mObjectHandle;
    final String mDocumentId;

    static Identifier createFromRootId(String rootId) {
        final String[] components = rootId.split("_");
@@ -45,9 +46,14 @@ class Identifier {
    }

    Identifier(int deviceId, int storageId, int objectHandle) {
        this(deviceId, storageId, objectHandle, null);
    }

    Identifier(int deviceId, int storageId, int objectHandle, String documentId) {
        mDeviceId = deviceId;
        mStorageId = storageId;
        mObjectHandle = objectHandle;
        mDocumentId = documentId;
    }

    // TODO: Make the ID persistent.
+33 −6
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;

@@ -79,8 +80,8 @@ class MtpDatabase {
    private final Map<String, Integer> mMappingMode = new HashMap<>();

    @VisibleForTesting
    MtpDatabase(Context context, boolean inMemory) {
        mDatabase = new MtpDatabaseInternal(context, inMemory);
    MtpDatabase(Context context, int flags) {
        mDatabase = new MtpDatabaseInternal(context, flags);
    }

    /**
@@ -111,7 +112,29 @@ class MtpDatabase {
     */
    @VisibleForTesting
    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
        return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
        return queryChildDocuments(columnNames, parentDocumentId, false);
    }

    @VisibleForTesting
    Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) {
        final String[] newColumnNames = new String[columnNames.length];

        // TODO: Temporary replace document ID with old format.
        for (int i = 0; i < columnNames.length; i++) {
            if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) {
                newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID +
                        " || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " +
                        DocumentsContract.Document.COLUMN_DOCUMENT_ID;
            } else {
                newColumnNames[i] = columnNames[i];
            }
        }

        return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId);
    }

    Identifier createIdentifier(String parentDocumentId) {
        return mDatabase.createIdentifier(parentDocumentId);
    }

    /**
@@ -193,9 +216,13 @@ 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,
                        valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID));
                final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
                // If it fails to insert/update documents, the document ID will be set with -1.
                // In this case we don't insert/update root extra information neither.
                if (documentId == null) {
                    continue;
                }
                values.put(Root.COLUMN_ROOT_ID, documentId);
                values.put(
                        Root.COLUMN_FLAGS,
                        Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
+3 −0
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@ class MtpDatabaseConstants {
    static final int DATABASE_VERSION = 1;
    static final String DATABASE_NAME = null;

    static final int FLAG_DATABASE_IN_MEMORY = 1;
    static final int FLAG_DATABASE_IN_FILE = 0;

    /**
     * Table representing documents including root documents.
     */
+92 −21
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.provider.DocumentsContract.Document;
@@ -35,8 +36,11 @@ import java.util.Objects;
 */
class MtpDatabaseInternal {
    private static class OpenHelper extends SQLiteOpenHelper {
        public OpenHelper(Context context, boolean inMemory) {
            super(context, inMemory ? null : DATABASE_NAME, null, DATABASE_VERSION);
        public OpenHelper(Context context, int flags) {
            super(context,
                  flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
                  null,
                  DATABASE_VERSION);
        }

        @Override
@@ -54,8 +58,8 @@ class MtpDatabaseInternal {

    private final SQLiteDatabase mDatabase;

    MtpDatabaseInternal(Context context, boolean inMemory) {
        final OpenHelper helper = new OpenHelper(context, inMemory);
    MtpDatabaseInternal(Context context, int flags) {
        final OpenHelper helper = new OpenHelper(context, flags);
        mDatabase = helper.getWritableDatabase();
    }

@@ -121,6 +125,64 @@ class MtpDatabaseInternal {
        deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
    }

    /**
     * Gets identifier from document ID.
     * @param documentId Document ID.
     * @return Identifier.
     */
    Identifier createIdentifier(String documentId) {
        // Currently documentId is old format.
        final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId);
        final String selection;
        final String[] args;
        if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
            selection = COLUMN_DEVICE_ID + "= ? AND " +
                    COLUMN_ROW_STATE + " IN (?, ?) AND " +
                    COLUMN_STORAGE_ID + "= ? AND " +
                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
            args = strings(
                    oldIdentifier.mDeviceId,
                    ROW_STATE_VALID,
                    ROW_STATE_INVALIDATED,
                    oldIdentifier.mStorageId);
        } else {
            selection = COLUMN_DEVICE_ID + "= ? AND " +
                    COLUMN_ROW_STATE + " IN (?, ?) AND " +
                    COLUMN_STORAGE_ID + "= ? AND " +
                    COLUMN_OBJECT_HANDLE + " = ?";
            args = strings(
                    oldIdentifier.mDeviceId,
                    ROW_STATE_VALID,
                    ROW_STATE_INVALIDATED,
                    oldIdentifier.mStorageId,
                    oldIdentifier.mObjectHandle);
        }

        final Cursor cursor = mDatabase.query(
                TABLE_DOCUMENTS,
                strings(Document.COLUMN_DOCUMENT_ID),
                selection,
                args,
                null,
                null,
                null,
                "1");
        try {
            if (cursor.getCount() == 0) {
                return oldIdentifier;
            } else {
                cursor.moveToNext();
                return new Identifier(
                        oldIdentifier.mDeviceId,
                        oldIdentifier.mStorageId,
                        oldIdentifier.mObjectHandle,
                        cursor.getString(0));
            }
        } finally {
            cursor.close();
        }
    }

    /**
     * Starts adding new documents.
     * The methods decides mapping mode depends on if all documents under the given parent have MTP
@@ -164,7 +226,7 @@ class MtpDatabaseInternal {
     * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
     * 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 valuesList Values for documents to be 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.
@@ -191,15 +253,22 @@ class MtpDatabaseInternal {
                        null,
                        null,
                        "1");
                try {
                    final long rowId;
                    if (candidateCursor.getCount() == 0) {
                        rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
                        if (rowId == -1) {
                            throw new SQLiteException("Failed to put a document into database.");
                        }
                        added = true;
                    } else if (!heuristic) {
                        candidateCursor.moveToNext();
                        final String documentId = candidateCursor.getString(0);
                        rowId = mDatabase.update(
                            TABLE_DOCUMENTS, values, SELECTION_DOCUMENT_ID, strings(documentId));
                                TABLE_DOCUMENTS,
                                values,
                                SELECTION_DOCUMENT_ID,
                                strings(documentId));
                    } else {
                        values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
                        rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
@@ -207,8 +276,10 @@ class MtpDatabaseInternal {
                    // Document ID is a primary integer key of the table. So the returned row
                    // IDs should be same with the document ID.
                    values.put(Document.COLUMN_DOCUMENT_ID, rowId);
                } finally {
                    candidateCursor.close();
                }
            }

            mDatabase.setTransactionSuccessful();
            return added;
Loading