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

Commit 47eb192b authored by Daichi Hirono's avatar Daichi Hirono
Browse files

Start using MtpDatabase in DocumentLoader.

BUG=25704514

Change-Id: I4d9247c148679ee7e40a1a03443e4c0299b1e44d
parent 9c5ac7bf
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