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

Commit 64722614 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

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

parents 03fc78b3 99d7f709
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();