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

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

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

am: 8ce254a5

* commit '8ce254a5':
  Count error document to complete adding documents to the database.
parents 4c20b439 8ce254a5
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));
        }
    }