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

Commit 1566618f authored by Garfield Tan's avatar Garfield Tan
Browse files

Wait until the remote side has loaded everything before start copying.

Also added 2 test cases for it.

Test: Manual tests and automatic tests pass.

Bug: 34277120
Change-Id: Ia587909f1490104f2823d67c6bf1bf5d86d86d9d
(cherry picked from commit 99d7f709)
parent 5e1fd5e9
Loading
Loading
Loading
Loading
+88 −4
Original line number Diff line number Diff line
@@ -38,9 +38,12 @@ import android.content.ContentProviderClient;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.provider.DocumentsContract;
@@ -75,6 +78,8 @@ class CopyJob extends ResolvedResourcesJob {

    private static final String TAG = "CopyJob";

    private static final long LOADING_TIMEOUT = 60000; // 1 min

    final ArrayList<DocumentInfo> convertedFiles = new ArrayList<>();

    private long mStartTime = -1;
@@ -460,10 +465,9 @@ class CopyJob extends ResolvedResourcesJob {
        Cursor cursor = null;
        boolean success = true;
        // Iterate over srcs in the directory; copy to the destination directory.
        final Uri queryUri = buildChildDocumentsUri(srcDir.authority, srcDir.documentId);
        try {
            try {
                cursor = getClient(srcDir).query(queryUri, queryColumns, null, null, null);
                cursor = queryChildren(srcDir, queryColumns);
            } catch (RemoteException | RuntimeException e) {
                Metrics.logFileOperationFailure(
                        appContext, Metrics.SUBFILEOP_QUERY_CHILDREN, srcDir.derivedUri);
@@ -669,7 +673,6 @@ class CopyJob extends ResolvedResourcesJob {
    long calculateFileSizesRecursively(
            ContentProviderClient client, Uri uri) throws ResourceException {
        final String authority = uri.getAuthority();
        final Uri queryUri = buildChildDocumentsUri(authority, getDocumentId(uri));
        final String queryColumns[] = new String[] {
                Document.COLUMN_DOCUMENT_ID,
                Document.COLUMN_MIME_TYPE,
@@ -679,7 +682,7 @@ class CopyJob extends ResolvedResourcesJob {
        long result = 0;
        Cursor cursor = null;
        try {
            cursor = client.query(queryUri, queryColumns, null, null, null);
            cursor = queryChildren(client, uri, queryColumns);
            while (cursor.moveToNext() && !isCanceled()) {
                if (Document.MIME_TYPE_DIR.equals(
                        getCursorString(cursor, Document.COLUMN_MIME_TYPE))) {
@@ -703,6 +706,69 @@ class CopyJob extends ResolvedResourcesJob {
        return result;
    }

    /**
     * Queries children documents.
     *
     * SAF allows {@link DocumentsContract#EXTRA_LOADING} in {@link Cursor#getExtras()} to indicate
     * there are more data to be loaded. Wait until {@link DocumentsContract#EXTRA_LOADING} is
     * false and then return the cursor.
     *
     * @param srcDir the directory whose children are being loading
     * @param queryColumns columns of metadata to load
     * @return cursor of all children documents
     * @throws RemoteException when the remote throws or waiting for update times out
     */
    private Cursor queryChildren(DocumentInfo srcDir, String[] queryColumns)
            throws RemoteException {
        try (final ContentProviderClient client = getClient(srcDir)) {
            return queryChildren(client, srcDir.derivedUri, queryColumns);
        }
    }

    /**
     * Queries children documents.
     *
     * SAF allows {@link DocumentsContract#EXTRA_LOADING} in {@link Cursor#getExtras()} to indicate
     * there are more data to be loaded. Wait until {@link DocumentsContract#EXTRA_LOADING} is
     * false and then return the cursor.
     *
     * @param client the {@link ContentProviderClient} to use to query children
     * @param dirDocUri the document Uri of the directory whose children are being loaded
     * @param queryColumns columns of metadata to load
     * @return cursor of all children documents
     * @throws RemoteException when the remote throws or waiting for update times out
     */
    private Cursor queryChildren(ContentProviderClient client, Uri dirDocUri, String[] queryColumns)
            throws RemoteException {
        // TODO (b/34459983): Optimize this performance by processing partial result first while provider is loading
        // more data. Note we need to skip size calculation to achieve it.
        final Uri queryUri = buildChildDocumentsUri(dirDocUri.getAuthority(), getDocumentId(dirDocUri));
        Cursor cursor = client.query(
                queryUri, queryColumns, (String) null, null, null);
        while (cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING)) {
            cursor.registerContentObserver(new DirectoryChildrenObserver(queryUri));
            try {
                long start = System.currentTimeMillis();
                synchronized (queryUri) {
                    queryUri.wait(LOADING_TIMEOUT);
                }
                if (System.currentTimeMillis() - start > LOADING_TIMEOUT) {
                    // Timed out
                    throw new RemoteException("Timed out waiting on update for " + queryUri);
                }
            } catch (InterruptedException e) {
                // Should never happen
                throw new RuntimeException(e);
            }

            // Make another query
            cursor = client.query(
                    queryUri, queryColumns, (String) null, null, null);
        }

        return cursor;
    }

    /**
     * Returns true if {@code doc} is a descendant of {@code parentDoc}.
     * @throws ResourceException
@@ -733,4 +799,22 @@ class CopyJob extends ResolvedResourcesJob {
                .append("}")
                .toString();
    }

    private static class DirectoryChildrenObserver extends ContentObserver {

        private final Object mNotifier;

        private DirectoryChildrenObserver(Object notifier) {
            super(new Handler(Looper.getMainLooper()));
            assert(notifier != null);
            mNotifier = notifier;
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            synchronized (mNotifier) {
                mNotifier.notify();
            }
        }
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -304,4 +304,9 @@ public class DocumentsProviderHelper {
        return DocumentsContract.buildDocumentUri(mAuthority, documentId);
    }

    public void setLoadingDuration(long duration) throws RemoteException {
        final Bundle extra = new Bundle();
        extra.putLong(DocumentsContract.EXTRA_LOADING, duration);
        mClient.call("setLoadingDuration", null, extra);
    }
}
+38 −18
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.documentsui;

import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ProviderInfo;
@@ -25,10 +26,7 @@ import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.graphics.Point;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.*;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
@@ -91,6 +89,7 @@ public class StubProvider extends DocumentsProvider {
    private String mAuthority = DEFAULT_AUTHORITY;
    private SharedPreferences mPrefs;
    private Set<String> mSimulateReadErrorIds = new HashSet<>();
    private long mLoadingDuration = 0;

    @Override
    public void attachInfo(Context context, ProviderInfo info) {
@@ -134,6 +133,8 @@ public class StubProvider extends DocumentsProvider {
            mStorage.put(rootInfo.document.documentId, rootInfo.document);
            mRoots.put(rootId, rootInfo);
        }

        mLoadingDuration = 0;
    }

    /**
@@ -225,6 +226,21 @@ public class StubProvider extends DocumentsProvider {
    @Override
    public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
            throws FileNotFoundException {
        if (mLoadingDuration > 0) {
            final Uri notifyUri = DocumentsContract.buildDocumentUri(mAuthority, parentDocumentId);
            final ContentResolver resolver = getContext().getContentResolver();
            new Handler(Looper.getMainLooper()).postDelayed(
                    () -> resolver.notifyChange(notifyUri, null, false),
                    mLoadingDuration);
            mLoadingDuration = 0;

            MatrixCursor cursor = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
            Bundle bundle = new Bundle();
            bundle.putBoolean(DocumentsContract.EXTRA_LOADING, true);
            cursor.setExtras(bundle);
            cursor.setNotificationUri(resolver, notifyUri);
            return cursor;
        } else {
            final StubDocument parentDocument = mStorage.get(parentDocumentId);
            if (parentDocument == null || parentDocument.file.isFile()) {
                throw new FileNotFoundException();
@@ -242,6 +258,7 @@ public class StubProvider extends DocumentsProvider {
            }
            return result;
        }
    }

    @Override
    public Cursor queryRecentDocuments(String rootId, String[] projection)
@@ -489,6 +506,9 @@ public class StubProvider extends DocumentsProvider {
                return null;
            case "createDocumentWithFlags":
                return dispatchCreateDocumentWithFlags(extras);
            case "setLoadingDuration":
                mLoadingDuration = extras.getLong(DocumentsContract.EXTRA_LOADING);
                return null;
        }

        return null;
+5 −0
Original line number Diff line number Diff line
@@ -66,6 +66,11 @@ public class CopyJobTest extends AbstractCopyJobTest<CopyJob> {
        runCopyDirRecursivelyTest();
    }

    public void testCopyDirRecursively_loadingInFirstCursor() throws Exception {
        mDocs.setLoadingDuration(500);
        testCopyDirRecursively();
    }

    public void testNoCopyDirToSelf() throws Exception {
        runNoCopyDirToSelfTest();
    }
+5 −0
Original line number Diff line number Diff line
@@ -102,6 +102,11 @@ public class MoveJobTest extends AbstractCopyJobTest<MoveJob> {
        mDocs.assertChildCount(mSrcRoot, 0);
    }

    public void testMoveDirRecursively_loadingInFirstCursor() throws Exception {
        mDocs.setLoadingDuration(500);
        testMoveDirRecursively();
    }

    public void testNoMoveDirToSelf() throws Exception {
        runNoCopyDirToSelfTest();