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

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

Merge "Count error document to complete adding documents to the database." into nyc-dev

parents 25fb0a59 678ed36b
Loading
Loading
Loading
Loading
+106 −134
Original line number Diff line number Diff line
@@ -81,11 +81,9 @@ class DocumentLoader implements AutoCloseable {
            // 3. startAddingChildDocuemnts.
            // 4. stopAddingChildDocuments - It removes the new document added at the step 2,
            //     because it is not updated between start/stopAddingChildDocuments.
            task = LoaderTask.create(mDatabase, mMtpManager, mDevice.operationsSupported, parent);
            task.fillDocuments(loadDocuments(
                    mMtpManager,
                    parent.mDeviceId,
                    task.getUnloadedObjectHandles(NUM_INITIAL_ENTRIES)));
            task = new LoaderTask(mMtpManager, mDatabase, mDevice.operationsSupported, parent);
            task.loadObjectHandles();
            task.loadObjectInfoList(NUM_INITIAL_ENTRIES);
        } else {
            // Once remove the existing task in order to add it to the head of the list.
            mTaskList.remove(task);
@@ -130,15 +128,11 @@ class DocumentLoader implements AutoCloseable {
                Preconditions.checkState(existingTask.getState() != LoaderTask.STATE_LOADING);
                mTaskList.remove(existingTask);
            }
            try {
                final LoaderTask newTask = LoaderTask.create(
                        mDatabase, mMtpManager, mDevice.operationsSupported, identifier);
            final LoaderTask newTask = new LoaderTask(
                    mMtpManager, mDatabase, mDevice.operationsSupported, identifier);
            newTask.loadObjectHandles();
            mTaskList.addFirst(newTask);
            return newTask;
            } catch (IOException exception) {
                Log.e(MtpDocumentsProvider.TAG, "Failed to create a task for mapping", exception);
                // Continue to release the background thread.
            }
        }

        mBackgroundThread = null;
@@ -169,24 +163,6 @@ class DocumentLoader implements AutoCloseable {
        mTaskList.clearTask(parentIdentifier);
    }

    /**
     * Helper method to loads multiple object info.
     */
    private static MtpObjectInfo[] loadDocuments(MtpManager manager, int deviceId, int[] handles)
            throws IOException {
        final ArrayList<MtpObjectInfo> objects = new ArrayList<>();
        for (int i = 0; i < handles.length; i++) {
            final MtpObjectInfo info = manager.getObjectInfo(deviceId, handles[i]);
            if (info == null) {
                Log.e(MtpDocumentsProvider.TAG,
                        "Failed to obtain object info handle=" + handles[i]);
                continue;
            }
            objects.add(info);
        }
        return objects.toArray(new MtpObjectInfo[objects.size()]);
    }

    /**
     * Background thread to fetch object info.
     */
@@ -203,12 +179,7 @@ class DocumentLoader implements AutoCloseable {
                if (task == null) {
                    return;
                }
                try {
                    final MtpObjectInfo[] objectInfos = loadDocuments(
                            mMtpManager,
                            task.mIdentifier.mDeviceId,
                            task.getUnloadedObjectHandles(NUM_LOADING_ENTRIES));
                    task.fillDocuments(objectInfos);
                task.loadObjectInfoList(NUM_LOADING_ENTRIES);
                final boolean shouldNotify =
                        task.mLastNotified.getTime() <
                        new Date().getTime() - NOTIFY_PERIOD_MS ||
@@ -216,9 +187,6 @@ class DocumentLoader implements AutoCloseable {
                if (shouldNotify) {
                    task.notify(mResolver);
                }
                } catch (IOException exception) {
                    task.setError(exception);
                }
            }
        }
    }
@@ -271,43 +239,66 @@ class DocumentLoader implements AutoCloseable {
     * Each task is responsible for fetching child documents for the given parent document.
     */
    private static class LoaderTask {
        static final int STATE_LOADING = 0;
        static final int STATE_COMPLETED = 1;
        static final int STATE_ERROR = 2;
        static final int STATE_START = 0;
        static final int STATE_LOADING = 1;
        static final int STATE_COMPLETED = 2;
        static final int STATE_ERROR = 3;

        final MtpManager mManager;
        final MtpDatabase mDatabase;
        final int[] mOperationsSupported;
        final Identifier mIdentifier;
        final int[] mObjectHandles;
        int[] mObjectHandles;
        int mState;
        Date mLastNotified;
        int mNumLoaded;
        Exception mError;
        int mPosition;
        IOException mError;

        LoaderTask(MtpDatabase database, int[] operationsSupported, Identifier identifier,
                int[] objectHandles) {
        LoaderTask(MtpManager manager, MtpDatabase database, int[] operationsSupported,
                Identifier identifier) {
            Preconditions.checkNotNull(operationsSupported);
            Preconditions.checkNotNull(objectHandles);
            mManager = manager;
            mDatabase = database;
            mOperationsSupported = operationsSupported;
            mIdentifier = identifier;
            mObjectHandles = objectHandles;
            mNumLoaded = 0;
            mObjectHandles = null;
            mState = STATE_START;
            mPosition = 0;
            mLastNotified = new Date();
        }

        synchronized void loadObjectHandles() {
            assert mState == STATE_START;
            int parentHandle = mIdentifier.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 (mIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
                parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
            }
            try {
                mObjectHandles = mManager.getObjectHandles(
                        mIdentifier.mDeviceId, mIdentifier.mStorageId, parentHandle);
                mState = STATE_LOADING;
            } catch (IOException error) {
                mError = error;
                mState = STATE_ERROR;
            }
        }

        /**
         * Returns a cursor that traverses the child document of the parent document handled by the
         * task.
         * The returned task may have a EXTRA_LOADING flag.
         */
        Cursor createCursor(ContentResolver resolver, String[] columnNames) throws IOException {
        synchronized Cursor createCursor(ContentResolver resolver, String[] columnNames)
                throws IOException {
            final Bundle extras = new Bundle();
            switch (getState()) {
                case STATE_LOADING:
                    extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
                    break;
                case STATE_ERROR:
                    throw new IOException(mError);
                    throw mError;
            }

            final Cursor cursor =
@@ -318,100 +309,81 @@ class DocumentLoader implements AutoCloseable {
            return cursor;
        }

        /**
         * Returns a state of the task.
         */
        int getState() {
            if (mError != null) {
                return STATE_ERROR;
            } else if (mNumLoaded == mObjectHandles.length) {
                return STATE_COMPLETED;
            } else {
                return STATE_LOADING;
            }
        }

        /**
         * Obtains object handles that have not been loaded yet.
         */
        int[] getUnloadedObjectHandles(int count) {
            return Arrays.copyOfRange(
                    mObjectHandles,
                    mNumLoaded,
                    Math.min(mNumLoaded + count, mObjectHandles.length));
        }

        /**
         * Notifies a change of child list of the document.
         */
        void notify(ContentResolver resolver) {
            resolver.notifyChange(createUri(), null, false);
            mLastNotified = new Date();
        }

        /**
         * Stores object information into database.
         */
        void fillDocuments(MtpObjectInfo[] objectInfoList) {
            if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
        void loadObjectInfoList(int count) {
            synchronized (this) {
                if (mState != STATE_LOADING) {
                    return;
                }
                if (mPosition == 0) {
                    try{
                if (mNumLoaded == 0) {
                        mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
                    } catch (FileNotFoundException error) {
                        mError = error;
                        mState = STATE_ERROR;
                        return;
                    }
                mDatabase.getMapper().putChildDocuments(
                        mIdentifier.mDeviceId, mIdentifier.mDocumentId, mOperationsSupported,
                        objectInfoList);
                mNumLoaded += objectInfoList.length;
                if (getState() != STATE_LOADING) {
                    mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
                }
            } catch (FileNotFoundException exception) {
                setErrorInternal(exception);
            }
            final ArrayList<MtpObjectInfo> infoList = new ArrayList<>();
            for (int chunkEnd = mPosition + count;
                    mPosition < mObjectHandles.length && mPosition < chunkEnd;
                    mPosition++) {
                try {
                    infoList.add(mManager.getObjectInfo(
                            mIdentifier.mDeviceId, mObjectHandles[mPosition]));
                } catch (IOException error) {
                    Log.e(MtpDocumentsProvider.TAG, "Failed to load object info", error);
                }

        /**
         * Marks the loading task as error.
         */
        void setError(Exception error) {
            final int lastState = getState();
            setErrorInternal(error);
            if (lastState == STATE_LOADING) {
            }
            synchronized (this) {
                try {
                    mDatabase.getMapper().putChildDocuments(
                            mIdentifier.mDeviceId,
                            mIdentifier.mDocumentId,
                            mOperationsSupported,
                            infoList.toArray(new MtpObjectInfo[infoList.size()]));
                } catch (FileNotFoundException error) {
                    // Looks like the parent document information is removed.
                    // Adding documents has already cancelled in Mapper so we don't need to invoke
                    // stopAddingDocuments.
                    mError = error;
                    mState = STATE_ERROR;
                    return;
                }
                if (mPosition >= mObjectHandles.length) {
                    try{
                        mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
                } catch (FileNotFoundException exception) {
                    setErrorInternal(exception);
                        mState = STATE_COMPLETED;
                    } catch (FileNotFoundException error) {
                        mError = error;
                        mState = STATE_ERROR;
                        return;
                    }
                }
            }

        private void setErrorInternal(Exception error) {
            Log.e(MtpDocumentsProvider.TAG, "Error in DocumentLoader thread", error);
            mError = error;
            mNumLoaded = 0;
        }

        private Uri createUri() {
            return DocumentsContract.buildChildDocumentsUri(
                    MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
        /**
         * Returns a state of the task.
         */
        int getState() {
            return mState;
        }

        /**
         * Creates a LoaderTask that loads children of the given document.
         * Notifies a change of child list of the document.
         */
        static LoaderTask create(MtpDatabase database, MtpManager manager,
                int[] operationsSupported, Identifier parent)
                throws IOException {
            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 (parent.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE) {
                parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
        void notify(ContentResolver resolver) {
            resolver.notifyChange(createUri(), null, false);
            mLastNotified = new Date();
        }
            return new LoaderTask(database, operationsSupported, parent, manager.getObjectHandles(
                    parent.mDeviceId, parent.mStorageId, parentHandle));

        private Uri createUri() {
            return DocumentsContract.buildChildDocumentsUri(
                    MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
        }
    }
}
+6 −3
Original line number Diff line number Diff line
@@ -130,11 +130,14 @@ class MtpManager {
        return devices.toArray(new MtpDeviceRecord[devices.size()]);
    }

    MtpObjectInfo getObjectInfo(int deviceId, int objectHandle)
            throws IOException {
    MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            return device.getObjectInfo(objectHandle);
            final MtpObjectInfo info = device.getObjectInfo(objectHandle);
            if (info == null) {
                throw new IOException("Failed to get object info: " + objectHandle);
            }
            return info;
        }
    }

+51 −7
Original line number Diff line number Diff line
@@ -55,13 +55,6 @@ public class DocumentLoaderTest extends AndroidTestCase {

        mManager = new BlockableTestMtpManager(getContext());
        mResolver = new TestContentResolver();
        mLoader = new DocumentLoader(
                new MtpDeviceRecord(
                        0, "Device", "Key", true, new MtpRoot[0],
                        TestUtil.OPERATIONS_SUPPORTED, new int[0]),
                mManager,
                mResolver,
                mDatabase);
    }

    @Override
@@ -71,6 +64,8 @@ public class DocumentLoaderTest extends AndroidTestCase {
    }

    public void testBasic() throws Exception {
        setUpLoader();

        final Uri uri = DocumentsContract.buildChildDocumentsUri(
                MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId);
        setUpDocument(mManager, 40);
@@ -107,6 +102,55 @@ public class DocumentLoaderTest extends AndroidTestCase {
        assertEquals(2, mResolver.getChangeCount(uri));
    }

    public void testError_GetObjectHandles() throws Exception {
        mManager = new BlockableTestMtpManager(getContext()) {
            @Override
            int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle)
                    throws IOException {
                throw new IOException();
            }
        };
        setUpLoader();
        mManager.setObjectHandles(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, null);
        try {
            try (final Cursor cursor = mLoader.queryChildDocuments(
                    MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {}
            fail();
        } catch (IOException exception) {
            // Expect exception.
        }
    }

    public void testError_GetObjectInfo() throws Exception {
        mManager = new BlockableTestMtpManager(getContext()) {
            @Override
            MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
                if (objectHandle == DocumentLoader.NUM_INITIAL_ENTRIES) {
                    throw new IOException();
                } else {
                    return super.getObjectInfo(deviceId, objectHandle);
                }
            }
        };
        setUpLoader();
        setUpDocument(mManager, DocumentLoader.NUM_INITIAL_ENTRIES);
        try (final Cursor cursor = mLoader.queryChildDocuments(
                MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION, mParentIdentifier)) {
            // Even if MtpManager returns an error for a document, loading must complete.
            assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
        }
    }

    private void setUpLoader() {
        mLoader = new DocumentLoader(
                new MtpDeviceRecord(
                        0, "Device", "Key", true, new MtpRoot[0],
                        TestUtil.OPERATIONS_SUPPORTED, new int[0]),
                mManager,
                mResolver,
                mDatabase);
    }

    private void setUpDocument(TestMtpManager manager, int count) {
        int[] childDocuments = new int[count];
        for (int i = 0; i < childDocuments.length; i++) {
+4 −8
Original line number Diff line number Diff line
@@ -419,20 +419,16 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
        try {
            mProvider.queryChildDocuments("1", null, null);
            fail();
        } catch (Throwable error) {
            assertTrue(error instanceof FileNotFoundException);
        }
        } catch (FileNotFoundException error) {}
    }

    public void testQueryChildDocuments_documentError() throws Exception {
        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
        mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
        try {
            mProvider.queryChildDocuments("1", null, null);
            fail();
        } catch (Throwable error) {
            assertTrue(error instanceof FileNotFoundException);
        try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
            assertEquals(0, cursor.getCount());
            assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
        }
    }