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

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

Merge "[multi-part] Eliminate 1k selection limit"

parents a0aaed5d 05df8ec3
Loading
Loading
Loading
Loading
+34 −10
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.util.Log;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.services.FileOperation;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService.OpType;
import com.android.documentsui.services.FileOperations;
@@ -349,28 +350,35 @@ public final class DocumentClipper implements ClipboardManager.OnPrimaryClipChan
            return;
        }

        ClipDetails details = ClipDetails.createClipDetails(clipData);
        PersistableBundle bundle = clipData.getDescription().getExtras();
        @OpType int opType = getOpType(bundle);
        UrisSupplier uris = UrisSupplier.create(clipData);

        if (!canCopy(destination)) {
            callback.onOperationResult(
                    FileOperations.Callback.STATUS_REJECTED, details.getOpType(), 0);
                    FileOperations.Callback.STATUS_REJECTED, opType, 0);
            return;
        }

        if (details.getItemCount() == 0) {
        if (uris.getItemCount() == 0) {
            callback.onOperationResult(
                    FileOperations.Callback.STATUS_ACCEPTED, details.getOpType(), 0);
                    FileOperations.Callback.STATUS_ACCEPTED, opType, 0);
            return;
        }

        DocumentStack dstStack = new DocumentStack();
        dstStack.push(destination);
        dstStack.addAll(docStack);
        DocumentStack dstStack = new DocumentStack(docStack, destination);

        // Pass root here so that we can perform "download" root check when
        dstStack.root = docStack.root;
        String srcParentString = bundle.getString(SRC_PARENT_KEY);
        Uri srcParent = srcParentString == null ? null : Uri.parse(srcParentString);

        FileOperations.start(mContext, details, dstStack, callback);
        FileOperation operation = new FileOperation.Builder()
                .withOpType(opType)
                .withSrcParent(srcParent)
                .withDestination(dstStack)
                .withSrcs(uris)
                .build();

        FileOperations.start(mContext, operation, callback);
    }

    /**
@@ -399,8 +407,24 @@ public final class DocumentClipper implements ClipboardManager.OnPrimaryClipChan
        }

        ClipDescription description = data.getDescription();
        if (description == null) {
            return ClipStorage.NO_SELECTION_TAG;
        }

        BaseBundle bundle = description.getExtras();
        if (bundle == null) {
            return ClipStorage.NO_SELECTION_TAG;
        }

        return bundle.getLong(OP_JUMBO_SELECTION_TAG, ClipStorage.NO_SELECTION_TAG);
    }

    public static @OpType int getOpType(ClipData data) {
        PersistableBundle bundle = data.getDescription().getExtras();
        return getOpType(bundle);
    }

    private static @OpType int getOpType(PersistableBundle bundle) {
        return bundle.getInt(OP_TYPE_KEY);
    }
}
+2 −3
Original line number Diff line number Diff line
@@ -41,7 +41,6 @@ import android.provider.DocumentsContract;
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

import com.android.documentsui.MenuManager.DirectoryDetails;
import com.android.documentsui.RecentsProvider.RecentColumns;
@@ -156,7 +155,7 @@ public class DocumentsActivity extends BaseActivity {
            state.directoryCopy = intent.getBooleanExtra(
                    Shared.EXTRA_DIRECTORY_COPY, false);
            state.copyOperationSubType = intent.getIntExtra(
                    FileOperationService.EXTRA_OPERATION,
                    FileOperationService.EXTRA_OPERATION_TYPE,
                    FileOperationService.OPERATION_COPY);
        }
    }
@@ -386,7 +385,7 @@ public class DocumentsActivity extends BaseActivity {
            // Picking a copy destination is only used internally by us, so we
            // don't need to extend permissions to the caller.
            intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
            intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.copyOperationSubType);
            intent.putExtra(FileOperationService.EXTRA_OPERATION_TYPE, mState.copyOperationSubType);
        } else {
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+1 −1
Original line number Diff line number Diff line
@@ -139,7 +139,7 @@ public class FilesActivity extends BaseActivity {
        // Only show it manually for the first time (icicle is null).
        if (icicle == null && dialogType != DIALOG_TYPE_UNKNOWN) {
            final int opType = intent.getIntExtra(
                    FileOperationService.EXTRA_OPERATION,
                    FileOperationService.EXTRA_OPERATION_TYPE,
                    FileOperationService.OPERATION_COPY);
            final ArrayList<DocumentInfo> srcList =
                    intent.getParcelableArrayListExtra(FileOperationService.EXTRA_SRC_LIST);
+2 −2
Original line number Diff line number Diff line
@@ -60,7 +60,7 @@ public class OperationDialogFragment extends DialogFragment {
            @OpType int operationType) {
        final Bundle args = new Bundle();
        args.putInt(FileOperationService.EXTRA_DIALOG_TYPE, dialogType);
        args.putInt(FileOperationService.EXTRA_OPERATION, operationType);
        args.putInt(FileOperationService.EXTRA_OPERATION_TYPE, operationType);
        args.putParcelableArrayList(FileOperationService.EXTRA_SRC_LIST, failedSrcList);

        final FragmentTransaction ft = fm.beginTransaction();
@@ -78,7 +78,7 @@ public class OperationDialogFragment extends DialogFragment {
        final @DialogType int dialogType =
              getArguments().getInt(FileOperationService.EXTRA_DIALOG_TYPE);
        final @OpType int operationType =
              getArguments().getInt(FileOperationService.EXTRA_OPERATION);
              getArguments().getInt(FileOperationService.EXTRA_OPERATION_TYPE);
        final ArrayList<DocumentInfo> srcList = getArguments().getParcelableArrayList(
                FileOperationService.EXTRA_SRC_LIST);

+275 −0
Original line number Diff line number Diff line
@@ -18,11 +18,7 @@ package com.android.documentsui;

import static com.android.documentsui.DocumentClipper.OP_JUMBO_SELECTION_SIZE;
import static com.android.documentsui.DocumentClipper.OP_JUMBO_SELECTION_TAG;
import static com.android.documentsui.DocumentClipper.OP_TYPE_KEY;
import static com.android.documentsui.DocumentClipper.SRC_PARENT_KEY;

import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.content.ClipData;
import android.content.Context;
import android.net.Uri;
@@ -33,67 +29,33 @@ import android.support.annotation.VisibleForTesting;
import android.util.Log;

import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService.OpType;
import com.android.documentsui.services.FileOperation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

/**
 * ClipDetails is a parcelable project providing information of different type of file
 * management operations like cut, move and copy.
 * UrisSupplier provides doc uri list to {@link FileOperation}.
 *
 * Under the hood it provides cross-process synchronization support such that its consumer doesn't
 * <p>Under the hood it provides cross-process synchronization support such that its consumer doesn't
 * need to explicitly synchronize its access.
 */
public abstract class ClipDetails implements Parcelable {
    private final @OpType int mOpType;

    // This field is used only for moving and deleting. Currently it's not the case,
    // but in the future those files may be from multiple different parents. In
    // such case, this needs to be replaced with pairs of parent and child.
    private final @Nullable Uri mSrcParent;

    private ClipDetails(ClipData clipData) {
        PersistableBundle bundle = clipData.getDescription().getExtras();
        mOpType = bundle.getInt(OP_TYPE_KEY);

        String srcParentString = bundle.getString(SRC_PARENT_KEY);
        mSrcParent = (srcParentString == null) ? null : Uri.parse(srcParentString);

        // Only copy doesn't need src parent
        assert(mOpType == FileOperationService.OPERATION_COPY || mSrcParent != null);
    }

    private ClipDetails(@OpType int opType, @Nullable Uri srcParent) {
        mOpType = opType;
        mSrcParent = srcParent;

        // Only copy doesn't need src parent
        assert(mOpType == FileOperationService.OPERATION_COPY || mSrcParent != null);
    }

    public @OpType int getOpType() {
        return mOpType;
    }

    public @Nullable Uri getSrcParent() {
        return mSrcParent;
    }
public abstract class UrisSupplier implements Parcelable {

    public abstract int getItemCount();

    /**
     * Gets doc list from this clip detail. This may only be called once because it may read a file
     * Gets doc list. This may only be called once because it may read a file
     * to get the list.
     *
     * @param context We need context to obtain {@link ClipStorage}. It can't be sent in a parcel.
     */
    public Iterable<Uri> getDocs(Context context) throws IOException {
        ClipStorage storage = DocumentsApplication.getClipStorage(context);

        return getDocs(storage);
        return getDocs(DocumentsApplication.getClipStorage(context));
    }

    @VisibleForTesting
@@ -107,41 +69,24 @@ public abstract class ClipDetails implements Parcelable {
    @VisibleForTesting
    void dispose(ClipStorage storage) {}

    private ClipDetails(Parcel in) {
        mOpType = in.readInt();
        mSrcParent = in.readParcelable(ClassLoader.getSystemClassLoader());
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @CallSuper
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mOpType);
        dest.writeParcelable(mSrcParent, 0);
    }

    private void appendTo(StringBuilder builder) {
        builder.append("opType=").append(mOpType);
        builder.append(", srcParent=").append(mSrcParent);
    }

    public static ClipDetails createClipDetails(ClipData clipData) {
        ClipDetails details;
    public static UrisSupplier create(ClipData clipData) {
        UrisSupplier uris;
        PersistableBundle bundle = clipData.getDescription().getExtras();
        if (bundle.containsKey(OP_JUMBO_SELECTION_TAG)) {
            details = new JumboClipDetails(clipData);
            uris = new JumboUrisSupplier(clipData);
        } else {
            details = new StandardClipDetails(clipData);
            uris = new StandardUrisSupplier(clipData);
        }

        return details;
        return uris;
    }

    public static ClipDetails createClipDetails(@OpType int opType, @Nullable Uri srcParent,
    public static UrisSupplier create(
            Selection selection, Function<String, Uri> uriBuilder, Context context) {
        ClipStorage storage = DocumentsApplication.getClipStorage(context);

@@ -150,30 +95,28 @@ public abstract class ClipDetails implements Parcelable {
            uris.add(uriBuilder.apply(id));
        }

        return createClipDetails(opType, srcParent, uris, storage);
        return create(uris, storage);
    }

    @VisibleForTesting
    static ClipDetails createClipDetails(@OpType int opType, @Nullable Uri srcParent,
            List<Uri> uris, ClipStorage storage) {
        ClipDetails details = (uris.size() > Shared.MAX_DOCS_IN_INTENT)
                ? new JumboClipDetails(opType, srcParent, uris, storage)
                : new StandardClipDetails(opType, srcParent, uris);
    static UrisSupplier create(List<Uri> uris, ClipStorage storage) {
        UrisSupplier urisSupplier = (uris.size() > Shared.MAX_DOCS_IN_INTENT)
                ? new JumboUrisSupplier(uris, storage)
                : new StandardUrisSupplier(uris);

        return details;
        return urisSupplier;
    }

    private static class JumboClipDetails extends ClipDetails {
        private static final String TAG = "JumboClipDetails";
    private static class JumboUrisSupplier extends UrisSupplier {
        private static final String TAG = "JumboUrisSupplier";

        private final long mSelectionTag;
        private final int mSelectionSize;

        private transient ClipStorage.Reader mReader;

        private JumboClipDetails(ClipData clipData) {
            super(clipData);
        private final transient AtomicReference<ClipStorage.Reader> mReader =
                new AtomicReference<>();

        private JumboUrisSupplier(ClipData clipData) {
            PersistableBundle bundle = clipData.getDescription().getExtras();
            mSelectionTag = bundle.getLong(OP_JUMBO_SELECTION_TAG, ClipStorage.NO_SELECTION_TAG);
            assert(mSelectionTag != ClipStorage.NO_SELECTION_TAG);
@@ -182,10 +125,7 @@ public abstract class ClipDetails implements Parcelable {
            assert(mSelectionSize > Shared.MAX_DOCS_IN_INTENT);
        }

        private JumboClipDetails(@OpType int opType, @Nullable Uri srcParent, Collection<Uri> uris,
                ClipStorage storage) {
            super(opType, srcParent);

        private JumboUrisSupplier(Collection<Uri> uris, ClipStorage storage) {
            mSelectionTag = storage.createTag();
            new ClipStorage.PersistTask(storage, uris, mSelectionTag).execute();
            mSelectionSize = uris.size();
@@ -197,26 +137,27 @@ public abstract class ClipDetails implements Parcelable {
        }

        @Override
        public Iterable<Uri> getDocs(ClipStorage storage) throws IOException {
            if (mReader != null) {
                throw new IllegalStateException(
                        "JumboClipDetails#getDocs() can only be called once.");
        Iterable<Uri> getDocs(ClipStorage storage) throws IOException {
            ClipStorage.Reader reader = mReader.getAndSet(storage.createReader(mSelectionTag));
            if (reader != null) {
                reader.close();
                mReader.get().close();
                throw new IllegalStateException("This method can only be called once.");
            }

            mReader = storage.createReader(mSelectionTag);

            return mReader;
            return mReader.get();
        }

        @Override
        void dispose(ClipStorage storage) {
            if (mReader != null) {
            try {
                    mReader.close();
                ClipStorage.Reader reader = mReader.get();
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                Log.w(TAG, "Failed to close the reader.", e);
            }
            }
            try {
                storage.delete(mSelectionTag);
            } catch(IOException e) {
@@ -227,9 +168,8 @@ public abstract class ClipDetails implements Parcelable {
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("JumboClipDetails{");
            super.appendTo(builder);
            builder.append(", selectionTag=").append(mSelectionTag);
            builder.append("JumboUrisSupplier{");
            builder.append("selectionTag=").append(mSelectionTag);
            builder.append(", selectionSize=").append(mSelectionSize);
            builder.append("}");
            return builder.toString();
@@ -237,47 +177,44 @@ public abstract class ClipDetails implements Parcelable {

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);

            dest.writeLong(mSelectionTag);
            dest.writeInt(mSelectionSize);
        }

        private JumboClipDetails(Parcel in) {
            super(in);

        private JumboUrisSupplier(Parcel in) {
            mSelectionTag = in.readLong();
            mSelectionSize = in.readInt();
        }

        public static final Parcelable.Creator<JumboClipDetails> CREATOR =
                new Parcelable.Creator<JumboClipDetails>() {
        public static final Parcelable.Creator<JumboUrisSupplier> CREATOR =
                new Parcelable.Creator<JumboUrisSupplier>() {

                    @Override
                    public JumboClipDetails createFromParcel(Parcel source) {
                        return new JumboClipDetails(source);
                    public JumboUrisSupplier createFromParcel(Parcel source) {
                        return new JumboUrisSupplier(source);
                    }

                    @Override
                    public JumboClipDetails[] newArray(int size) {
                        return new JumboClipDetails[size];
                    public JumboUrisSupplier[] newArray(int size) {
                        return new JumboUrisSupplier[size];
                    }
                };
    }

    /**
     * This class and its constructor is visible for testing to create test doubles of
     * {@link UrisSupplier}.
     */
    @VisibleForTesting
    public static class StandardClipDetails extends ClipDetails {
    public static class StandardUrisSupplier extends UrisSupplier {
        private final List<Uri> mDocs;

        private StandardClipDetails(ClipData clipData) {
            super(clipData);
        private StandardUrisSupplier(ClipData clipData) {
            mDocs = listDocs(clipData);
        }

        @VisibleForTesting
        public StandardClipDetails(@OpType int opType, @Nullable Uri srcParent, List<Uri> docs) {
            super(opType, srcParent);

        public StandardUrisSupplier(List<Uri> docs) {
            mDocs = docs;
        }

@@ -299,44 +236,39 @@ public abstract class ClipDetails implements Parcelable {
        }

        @Override
        public Iterable<Uri> getDocs(ClipStorage storage) {
        Iterable<Uri> getDocs(ClipStorage storage) {
            return mDocs;
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append("StandardClipDetails{");
            super.appendTo(builder);
            builder.append(", ").append("docs=").append(mDocs.toString());
            builder.append("StandardUrisSupplier{");
            builder.append("docs=").append(mDocs.toString());
            builder.append("}");
            return builder.toString();
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            super.writeToParcel(dest, flags);

            dest.writeTypedList(mDocs);
        }

        private StandardClipDetails(Parcel in) {
            super(in);

        private StandardUrisSupplier(Parcel in) {
            mDocs = in.createTypedArrayList(Uri.CREATOR);
        }

        public static final Parcelable.Creator<StandardClipDetails> CREATOR =
                new Parcelable.Creator<StandardClipDetails>() {
        public static final Parcelable.Creator<StandardUrisSupplier> CREATOR =
                new Parcelable.Creator<StandardUrisSupplier>() {

                    @Override
                    public StandardClipDetails createFromParcel(Parcel source) {
                        return new StandardClipDetails(source);
                    public StandardUrisSupplier createFromParcel(Parcel source) {
                        return new StandardUrisSupplier(source);
                    }

                    @Override
                    public StandardClipDetails[] newArray(int size) {
                        return new StandardClipDetails[size];
                    public StandardUrisSupplier[] newArray(int size) {
                        return new StandardUrisSupplier[size];
                    }
                };
    }
Loading