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

Commit 43850cec authored by Garfield Tan's avatar Garfield Tan Committed by Android (Google) Code Review
Browse files

Merge "Separate paste code from DirectoryFragment." into nyc-andromeda-dev

parents 3d0f6c41 4d00914f
Loading
Loading
Loading
Loading
+127 −15
Original line number Diff line number Diff line
@@ -18,21 +18,21 @@ package com.android.documentsui;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.PersistableBundle;
import android.provider.DocumentsContract;
import android.support.annotation.Nullable;
import android.util.Log;

import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService.OpType;

import libcore.io.IoUtils;
import com.android.documentsui.services.FileOperations;

import java.util.ArrayList;
import java.util.Arrays;
@@ -53,7 +53,7 @@ public final class DocumentClipper {
    private Context mContext;
    private ClipboardManager mClipboard;

    public DocumentClipper(Context context) {
    DocumentClipper(Context context) {
        mContext = context;
        mClipboard = context.getSystemService(ClipboardManager.class);
    }
@@ -114,7 +114,13 @@ public final class DocumentClipper {
        for (int i = 0; i < count; ++i) {
            ClipData.Item item = clipData.getItemAt(i);
            Uri itemUri = item.getUri();
            srcDocs.add(createDocument(itemUri));
            DocumentInfo docInfo = createDocument(itemUri);
            if (docInfo != null) {
                srcDocs.add(docInfo);
            } else {
                // This uri either doesn't exist, or is invalid.
                Log.w(TAG, "Can't create document info from uri: " + itemUri);
            }
        }

        return srcDocs;
@@ -196,23 +202,129 @@ public final class DocumentClipper {
        DocumentInfo doc = null;
        if (uri != null && DocumentsContract.isDocumentUri(mContext, uri)) {
            ContentResolver resolver = mContext.getContentResolver();
            ContentProviderClient client = null;
            Cursor cursor = null;
            try {
                client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, uri.getAuthority());
                cursor = client.query(uri, null, null, null, null);
                cursor.moveToPosition(0);
                doc = DocumentInfo.fromCursor(cursor, uri.getAuthority());
                doc = DocumentInfo.fromUri(resolver, uri);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            } finally {
                IoUtils.closeQuietly(cursor);
                ContentProviderClient.releaseQuietly(client);
            }
        }
        return doc;
    }

    /**
     * Copies documents from clipboard. It's the same as {@link #copyFromClipData} with clipData
     * returned from {@link ClipboardManager#getPrimaryClip()}.
     *
     * @param destination destination document.
     * @param docStack the document stack to the destination folder,
     * @param callback callback to notify when operation finishes.
     */
    public void copyFromClipboard(DocumentInfo destination, DocumentStack docStack,
            FileOperations.Callback callback) {
        copyFromClipData(destination, docStack, mClipboard.getPrimaryClip(), callback);
    }

    /**
     * Copies documents from given clip data.
     *
     * @param destination destination document
     * @param docStack the document stack to the destination folder
     * @param clipData the clipData to copy from, or null to copy from clipboard
     * @param callback callback to notify when operation finishes
     */
    public void copyFromClipData(final DocumentInfo destination, DocumentStack docStack,
            @Nullable final ClipData clipData, final FileOperations.Callback callback) {
        if (clipData == null) {
            Log.i(TAG, "Received null clipData. Ignoring.");
            return;
        }

        new AsyncTask<Void, Void, ClipDetails>() {

            @Override
            protected ClipDetails doInBackground(Void... params) {
                return getClipDetails(clipData);
            }

            @Override
            protected void onPostExecute(ClipDetails clipDetails) {
                if (clipDetails == null) {
                    Log.w(TAG,  "Received null clipDetails. Ignoring.");
                    return;
                }

                List<DocumentInfo> docs = clipDetails.docs;
                @OpType int type = clipDetails.opType;
                DocumentInfo srcParent = clipDetails.parent;
                moveDocuments(docs, destination, docStack, type, srcParent, callback);
            }
        }.execute();
    }

    /**
     * Moves {@code docs} from {@code srcParent} to {@code destination}.
     * operationType can be copy or cut
     * srcParent Must be non-null for move operations.
     */
    private void moveDocuments(
            List<DocumentInfo> docs,
            DocumentInfo destination,
            DocumentStack docStack,
            @OpType int operationType,
            DocumentInfo srcParent,
            FileOperations.Callback callback) {

        RootInfo destRoot = docStack.root;
        if (!canCopy(docs, destRoot, destination)) {
            callback.onOperationResult(FileOperations.Callback.STATUS_REJECTED, operationType, 0);
            return;
        }

        if (docs.isEmpty()) {
            callback.onOperationResult(FileOperations.Callback.STATUS_ACCEPTED, operationType, 0);
            return;
        }

        DocumentStack dstStack = new DocumentStack();
        dstStack.push(destination);
        dstStack.addAll(docStack);
        switch (operationType) {
            case FileOperationService.OPERATION_MOVE:
                FileOperations.move(mContext, docs, srcParent, dstStack, callback);
                break;
            case FileOperationService.OPERATION_COPY:
                FileOperations.copy(mContext, docs, dstStack, callback);
                break;
            default:
                throw new UnsupportedOperationException("Unsupported operation: " + operationType);
        }
    }

    /**
     * Returns true if the list of files can be copied to destination. Note that this
     * is a policy check only. Currently the method does not attempt to verify
     * available space or any other environmental aspects possibly resulting in
     * failure to copy.
     *
     * @return true if the list of files can be copied to destination.
     */
    private boolean canCopy(List<DocumentInfo> files, RootInfo root, DocumentInfo dest) {
        if (dest == null || !dest.isDirectory() || !dest.isCreateSupported()) {
            return false;
        }

        // Can't copy folders to downloads, because we don't show folders there.
        if (root.isDownloads()) {
            for (DocumentInfo docs : files) {
                if (docs.isDirectory()) {
                    return false;
                }
            }
        }

        return true;
    }

    public static class ClipDetails {
        public final @OpType int opType;
        public final List<DocumentInfo> docs;
+8 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ public class DocumentsApplication extends Application {

    private ThumbnailCache mThumbnailCache;

    private DocumentClipper mClipper;

    public static RootsCache getRootsCache(Context context) {
        return ((DocumentsApplication) context.getApplicationContext()).mRoots;
    }
@@ -55,6 +57,10 @@ public class DocumentsApplication extends Application {
        return client;
    }

    public static DocumentClipper getDocumentClipper(Context context) {
        return ((DocumentsApplication) context.getApplicationContext()).mClipper;
    }

    @Override
    public void onCreate() {
        super.onCreate();
@@ -67,6 +73,8 @@ public class DocumentsApplication extends Application {

        mThumbnailCache = new ThumbnailCache(memoryClassBytes / 4);

        mClipper = new DocumentClipper(this);

        final IntentFilter packageFilter = new IntentFilter();
        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+1 −1
Original line number Diff line number Diff line
@@ -76,7 +76,7 @@ public class FilesActivity extends BaseActivity {
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        mClipper = new DocumentClipper(this);
        mClipper = DocumentsApplication.getDocumentClipper(this);

        RootsFragment.show(getFragmentManager(), null);

+17 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.documentsui;

import android.annotation.StringRes;
import android.app.Activity;
import android.support.design.widget.Snackbar;
import android.view.View;
@@ -23,7 +24,22 @@ import android.view.View;
public final class Snackbars {
    private Snackbars() {}

    public static final Snackbar makeSnackbar(Activity activity, int messageId, int duration) {
    public static final void showMove(Activity activity, int docCount) {
        CharSequence message = Shared.getQuantityString(activity, R.plurals.move_begin, docCount);
        makeSnackbar(activity, message, Snackbar.LENGTH_SHORT).show();
    }

    public static final void showCopy(Activity activity, int docCount) {
        CharSequence message = Shared.getQuantityString(activity, R.plurals.copy_begin, docCount);
        makeSnackbar(activity, message, Snackbar.LENGTH_SHORT).show();
    }

    public static final void showPasteFailed(Activity activity) {
        makeSnackbar(activity, R.string.clipboard_files_cannot_paste, Snackbar.LENGTH_SHORT).show();
    }

    public static final Snackbar makeSnackbar(Activity activity, @StringRes int messageId,
            int duration) {
        return Snackbars.makeSnackbar(
                activity, activity.getResources().getText(messageId), duration);
    }
+36 −120
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import static com.android.documentsui.model.DocumentInfo.getCursorString;
import com.google.common.collect.Lists;

import android.annotation.IntDef;
import android.annotation.StringRes;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
@@ -53,6 +52,7 @@ import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.design.widget.Snackbar;
import android.support.v13.view.DragStartHelper;
import android.support.v7.widget.GridLayoutManager;
@@ -181,6 +181,32 @@ public class DirectoryFragment extends Fragment

    private DirectoryDragListener mOnDragListener;

    /**
     * A callback to show snackbar at the beginning of moving and copying.
     */
    private final FileOperations.Callback mFileOpCallback = (status, opType, docCount) -> {
        if (status == FileOperations.Callback.STATUS_REJECTED) {
            Snackbars.showPasteFailed(getActivity());
            return;
        }

        if (docCount == 0) {
            // Nothing has been pasted, so there is no need to show a snackbar.
            return;
        }

        switch (opType) {
            case FileOperationService.OPERATION_MOVE:
                Snackbars.showMove(getActivity(), docCount);
                break;
            case FileOperationService.OPERATION_COPY:
                Snackbars.showCopy(getActivity(), docCount);
                break;
            default:
                throw new UnsupportedOperationException("Unsupported Operation: " + opType);
        }
    };

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@@ -283,7 +309,7 @@ public class DirectoryFragment extends Fragment
        mFocusManager = new FocusManager(context, mRecView, mModel);

        mTuner = FragmentTuner.pick(getContext(), state);
        mClipper = new DocumentClipper(context);
        mClipper = DocumentsApplication.getDocumentClipper(getContext());

        final ActivityManager am = (ActivityManager) context.getSystemService(
                Context.ACTIVITY_SERVICE);
@@ -343,11 +369,12 @@ public class DirectoryFragment extends Fragment
                FileOperationService.OPERATION_COPY);

        FileOperations.start(
                getActivity(),
                getContext(),
                getDisplayState().selectedDocumentsForCopy,
                getDisplayState().stack.peek(),
                (DocumentStack) data.getParcelableExtra(Shared.EXTRA_STACK),
                operationType);
                operationType,
                mFileOpCallback);
    }

    protected boolean onDoubleTap(MotionEvent e) {
@@ -1035,94 +1062,6 @@ public class DirectoryFragment extends Fragment
        return commonType[0] + "/" + commonType[1];
    }

    private void copyFromClipboard(final DocumentInfo destination) {
        new AsyncTask<Void, Void, ClipDetails>() {

            @Override
            protected ClipDetails doInBackground(Void... params) {
                return mClipper.getClipDetails();
            }

            @Override
            protected void onPostExecute(ClipDetails clipDetails) {
                if (clipDetails == null) {
                    Log.w(TAG, "Received null clipDetails from primary clipboard. Ignoring.");
                    return;
                }
                List<DocumentInfo> docs = clipDetails.docs;
                @OpType int type = clipDetails.opType;
                DocumentInfo srcParent = clipDetails.parent;
                moveDocuments(docs, destination, type, srcParent);
            }
        }.execute();
    }

    private void copyFromClipData(final ClipData clipData, final DocumentInfo destination) {
        assert(clipData != null);

        new AsyncTask<Void, Void, ClipDetails>() {

            @Override
            protected ClipDetails doInBackground(Void... params) {
                return mClipper.getClipDetails(clipData);
            }

            @Override
            protected void onPostExecute(ClipDetails clipDetails) {
                if (clipDetails == null) {
                    Log.w(TAG,  "Received null clipDetails. Ignoring.");
                    return;
                }

                List<DocumentInfo> docs = clipDetails.docs;
                @OpType int type = clipDetails.opType;
                DocumentInfo srcParent = clipDetails.parent;
                moveDocuments(docs, destination, type, srcParent);
            }
        }.execute();
    }

    /**
     * Moves {@code docs} from {@code srcParent} to {@code destination}.
     * operationType can be copy or cut
     * srcParent Must be non-null for move operations.
     */
    private void moveDocuments(final List<DocumentInfo> docs, final DocumentInfo destination,
            final @OpType int operationType, final DocumentInfo srcParent) {
        BaseActivity activity = (BaseActivity) getActivity();
        if (!canCopy(docs, activity.getCurrentRoot(), destination)) {
            Snackbars.makeSnackbar(
                    getActivity(),
                    R.string.clipboard_files_cannot_paste,
                    Snackbar.LENGTH_SHORT)
                    .show();
            return;
        }

        if (docs.isEmpty()) {
            return;
        }

        final DocumentStack curStack = getDisplayState().stack;
        DocumentStack dstStack = new DocumentStack();
        if (destination != null) {
            dstStack.push(destination);
            dstStack.addAll(curStack);
        } else {
            dstStack = curStack;
        }
        switch (operationType) {
            case FileOperationService.OPERATION_MOVE:
                FileOperations.move(getActivity(), docs, srcParent, dstStack);
                break;
            case FileOperationService.OPERATION_COPY:
                FileOperations.copy(getActivity(), docs, dstStack);
                break;
            default:
                throw new UnsupportedOperationException("Unsupported operation: " + operationType);
        }
    }

    public void copySelectedToClipboard() {
        Metrics.logUserAction(getContext(), Metrics.USER_ACTION_COPY_CLIPBOARD);

@@ -1147,6 +1086,7 @@ public class DirectoryFragment extends Fragment

    public void cutSelectedToClipboard() {
        Metrics.logUserAction(getContext(), Metrics.USER_ACTION_CUT_CLIPBOARD);

        Selection selection = mSelectionManager.getSelection(new Selection());
        if (selection.isEmpty()) {
            return;
@@ -1171,36 +1111,12 @@ public class DirectoryFragment extends Fragment
    public void pasteFromClipboard() {
        Metrics.logUserAction(getContext(), Metrics.USER_ACTION_PASTE_CLIPBOARD);

        DocumentInfo destination = ((BaseActivity) getActivity()).getCurrentDirectory();
        copyFromClipboard(destination);
        BaseActivity activity = (BaseActivity) getActivity();
        DocumentInfo destination = activity.getCurrentDirectory();
        mClipper.copyFromClipboard(destination, activity.getDisplayState().stack, mFileOpCallback);
        getActivity().invalidateOptionsMenu();
    }

    /**
     * Returns true if the list of files can be copied to destination. Note that this
     * is a policy check only. Currently the method does not attempt to verify
     * available space or any other environmental aspects possibly resulting in
     * failure to copy.
     *
     * @return true if the list of files can be copied to destination.
     */
    private boolean canCopy(List<DocumentInfo> files, RootInfo root, DocumentInfo dest) {
        if (dest == null || !dest.isDirectory() || !dest.isCreateSupported()) {
            return false;
        }

        // Can't copy folders to downloads, because we don't show folders there.
        if (root.isDownloads()) {
            for (DocumentInfo docs : files) {
                if (docs.isDirectory()) {
                    return false;
                }
            }
        }

        return true;
    }

    public void selectAllFiles() {
        Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SELECT_ALL);

@@ -1316,7 +1232,7 @@ public class DirectoryFragment extends Fragment
                src == null ? Metrics.USER_ACTION_DRAG_N_DROP_MULTI_WINDOW
                        : Metrics.USER_ACTION_DRAG_N_DROP);

        copyFromClipData(clipData, dst);
        mClipper.copyFromClipData(dst, getDisplayState().stack, clipData, mFileOpCallback);
        return true;
    }

Loading