diff options
author | 2017-04-19 15:19:49 -0700 | |
---|---|---|
committer | 2017-04-19 17:27:29 -0700 | |
commit | c1a32aea59f47b06f3157a7f87ef5b9bf45f7627 (patch) | |
tree | fdb01e4bbd51ffbe2ade3bd886d0125983bab617 | |
parent | 7a4fa239abefd05d03d2e62c651d7b08f682d8b4 (diff) |
Disable Move/Move to/Cut to clipboard for read-only files.
Bug: 37357780
Change-Id: I26f4e604c080b79f54f5ee9f9a07535d577e4a0a
9 files changed, 82 insertions, 3 deletions
diff --git a/src/com/android/documentsui/DragAndDropManager.java b/src/com/android/documentsui/DragAndDropManager.java index e783df99a..c6aa3e2d5 100644 --- a/src/com/android/documentsui/DragAndDropManager.java +++ b/src/com/android/documentsui/DragAndDropManager.java @@ -167,6 +167,10 @@ public interface DragAndDropManager { // type of file operations. private boolean mIsCtrlPressed; + // Boolean flag for current drag and drop operation. Returns true if the files can only + // be copied (ie. Read-Only files) + private boolean mMustBeCopied; + // Drag events info. These are used to derive state and update drag shadow when user changes // Ctrl key state. private View mView; @@ -231,6 +235,9 @@ public interface DragAndDropManager { List<Uri> uris = new ArrayList<>(srcs.size()); for (DocumentInfo doc : srcs) { uris.add(doc.derivedUri); + if (!doc.isRemoveSupported() && !doc.isDeleteSupported()) { + mMustBeCopied = true; + } } mClipData = mClipper.getClipDataForDocuments( uris, FileOperationService.OPERATION_UNKNOWN, parent); @@ -447,9 +454,14 @@ public interface DragAndDropManager { mClipData = null; mDestDoc = null; mDestRoot = null; + mMustBeCopied = false; } private @OpType int calculateOpType(ClipData clipData, RootInfo destRoot) { + if (mMustBeCopied) { + return FileOperationService.OPERATION_COPY; + } + final String srcRootUri = clipData.getDescription().getExtras().getString(SRC_ROOT_KEY); final String destRootUri = destRoot.getUri().toString(); diff --git a/src/com/android/documentsui/base/DocumentFilters.java b/src/com/android/documentsui/base/DocumentFilters.java index de8085b04..a095b558e 100644 --- a/src/com/android/documentsui/base/DocumentFilters.java +++ b/src/com/android/documentsui/base/DocumentFilters.java @@ -34,6 +34,7 @@ public final class DocumentFilters { public static final Predicate<Cursor> ANY = (Cursor c) -> { return true; }; public static final Predicate<Cursor> VIRTUAL = DocumentFilters::isVirtual; + public static final Predicate<Cursor> NOT_MOVABLE = DocumentFilters::isNotMovable; private static final Predicate<Cursor> O_SHARABLE = DocumentFilters::isSharableInO; private static final Predicate<Cursor> PREO_SHARABLE = DocumentFilters::isSharablePreO; @@ -70,4 +71,13 @@ public final class DocumentFilters { int flags = getCursorInt(c, Document.COLUMN_FLAGS); return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0; } + + /** + * Filter that passes (returns true) for files that can not be moved. + */ + private static final boolean isNotMovable(Cursor c) { + int flags = getCursorInt(c, Document.COLUMN_FLAGS); + return (flags & Document.FLAG_SUPPORTS_REMOVE) == 0 + && (flags & Document.FLAG_SUPPORTS_DELETE) == 0; + } } diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java index cf4b6a2d7..aef9aca4a 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -75,6 +75,7 @@ import com.android.documentsui.Metrics; import com.android.documentsui.Model; import com.android.documentsui.R; import com.android.documentsui.ThumbnailCache; +import com.android.documentsui.base.DocumentFilters; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.EventHandler; @@ -657,6 +658,10 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On return true; case R.id.menu_move_to: + if (mModel.hasDocuments(selection, DocumentFilters.NOT_MOVABLE)) { + mInjector.dialogs.showOperationUnsupported(); + return true; + } // Exit selection mode first, so we avoid deselecting deleted documents. mActionModeController.finishActionMode(); transferDocuments(selection, null, FileOperationService.OPERATION_MOVE); diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java index 635a19984..e77de2927 100644 --- a/src/com/android/documentsui/files/ActionHandler.java +++ b/src/com/android/documentsui/files/ActionHandler.java @@ -236,6 +236,12 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa if (selection.isEmpty()) { return; } + + if (mModel.hasDocuments(selection, DocumentFilters.NOT_MOVABLE)) { + mDialogs.showOperationUnsupported(); + return; + } + mSelectionMgr.clearSelection(); mClipper.clipDocumentsForCut(mModel::getItemUri, selection, mState.stack.peek()); diff --git a/src/com/android/documentsui/ui/DialogController.java b/src/com/android/documentsui/ui/DialogController.java index 4c2d88684..0de98683f 100644 --- a/src/com/android/documentsui/ui/DialogController.java +++ b/src/com/android/documentsui/ui/DialogController.java @@ -50,6 +50,7 @@ public interface DialogController { void showProgressDialog(String jobId, FileOperation operation); void showNoApplicationFound(); + void showOperationUnsupported(); void showViewInArchivesUnsupported(); void showDocumentsClipped(int size); @@ -110,7 +111,7 @@ public interface DialogController { public void showFileOperationStatus(@Status int status, @OpType int opType, int docCount) { if (status == FileOperations.Callback.STATUS_REJECTED) { - Snackbars.showOperationRejected(mActivity); + showOperationUnsupported(); return; } if (status == FileOperations.Callback.STATUS_FAILED) { @@ -182,6 +183,11 @@ public interface DialogController { } @Override + public void showOperationUnsupported() { + Snackbars.showOperationRejected(mActivity); + } + + @Override public void showViewInArchivesUnsupported() { Snackbars.makeSnackbar(mActivity, R.string.toast_view_in_archives_unsupported, Snackbar.LENGTH_SHORT).show(); diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java index 92bf2ab78..902c19fd2 100644 --- a/tests/common/com/android/documentsui/testing/TestEnv.java +++ b/tests/common/com/android/documentsui/testing/TestEnv.java @@ -55,6 +55,7 @@ public class TestEnv { public static DocumentInfo FILE_ARCHIVE; public static DocumentInfo FILE_IN_ARCHIVE; public static DocumentInfo FILE_VIRTUAL; + public static DocumentInfo FILE_READ_ONLY; public final TestScheduledExecutorService mExecutor; public final State state = new State(); @@ -138,8 +139,9 @@ public class TestEnv { "UbuntuFlappyBird.iso", Document.FLAG_SUPPORTS_DELETE | Document.FLAG_PARTIAL); + FILE_READ_ONLY = model.createFile("topsecretsystemfile.bin", 0); FILE_ARCHIVE = model.createFile("whatsinthere.zip"); - FILE_IN_ARCHIVE = archiveModel.createFile("whatsinthere.png"); + FILE_IN_ARCHIVE = archiveModel.createFile("whatsinthere.png", 0); FILE_VIRTUAL = model.createDocument( "virtualdoc.vnd", "application/vnd.google-apps.document", diff --git a/tests/common/com/android/documentsui/ui/TestDialogController.java b/tests/common/com/android/documentsui/ui/TestDialogController.java index 350755be8..b67ce46dd 100644 --- a/tests/common/com/android/documentsui/ui/TestDialogController.java +++ b/tests/common/com/android/documentsui/ui/TestDialogController.java @@ -33,6 +33,7 @@ public class TestDialogController implements DialogController { private boolean mNoApplicationFound; private boolean mDocumentsClipped; private boolean mViewInArchivesUnsupported; + private boolean mShowOperationUnsupported; private DocumentInfo mOverwriteTarget; public TestDialogController() { @@ -61,6 +62,11 @@ public class TestDialogController implements DialogController { } @Override + public void showOperationUnsupported() { + mShowOperationUnsupported = true; + } + + @Override public void showViewInArchivesUnsupported() { mViewInArchivesUnsupported = true; } @@ -87,6 +93,9 @@ public class TestDialogController implements DialogController { Assert.assertFalse(mNoApplicationFound); } + public void assertShowOperationUnsupported() { + Assert.assertTrue(mShowOperationUnsupported); + } public void assertViewInArchivesShownUnsupported() { Assert.assertTrue(mViewInArchivesUnsupported); } diff --git a/tests/unit/com/android/documentsui/DragAndDropManagerTests.java b/tests/unit/com/android/documentsui/DragAndDropManagerTests.java index 2e9c2cb04..fafbccb4f 100644 --- a/tests/unit/com/android/documentsui/DragAndDropManagerTests.java +++ b/tests/unit/com/android/documentsui/DragAndDropManagerTests.java @@ -37,7 +37,6 @@ import com.android.documentsui.DragAndDropManager.RuntimeDragAndDropManager; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.RootInfo; import com.android.documentsui.services.FileOperationService; -import com.android.documentsui.services.FileOperationService.OpType; import com.android.documentsui.services.FileOperations; import com.android.documentsui.testing.ClipDatas; import com.android.documentsui.testing.KeyEvents; @@ -723,6 +722,26 @@ public class DragAndDropManagerTests { mClipper.opType.assertLastArgument(FileOperationService.OPERATION_MOVE); } + @Test + public void testDrop_Copies_SameRoot_ReadOnlyFile_DropOnDocument() { + mManager.startDrag( + mStartDragView, + TestEnv.FOLDER_0, + Arrays.asList(TestEnv.FILE_READ_ONLY), + TestProvidersAccess.DOWNLOADS, + Arrays.asList(TestEnv.FOLDER_0.derivedUri, TestEnv.FILE_READ_ONLY.derivedUri), + mIconHelper); + + mManager.updateState(mUpdateShadowView, TestProvidersAccess.DOWNLOADS, TestEnv.FOLDER_2); + + final DocumentStack stack = new DocumentStack( + TestProvidersAccess.DOWNLOADS, TestEnv.FOLDER_1, TestEnv.FOLDER_2); + assertTrue(mManager.drop(mClipData, mManager, stack, mCallback)); + + mClipper.copy.assertLastArgument(Pair.create(stack, mClipData)); + mClipper.opType.assertLastArgument(FileOperationService.OPERATION_COPY); + } + private void assertStateUpdated(@State int expected) { mShadowBuilder.state.assertLastArgument(expected); mShadowUpdateListener.assertCalled(); diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java index c183cfe5f..ef9945825 100644 --- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java @@ -154,6 +154,16 @@ public class ActionHandlerTest { mActionModeAddons.finishOnConfirmed.assertRejected(); } + @Test + public void testCutSelectedDocuments_ContainsNonMovableItem() { + mEnv.selectDocument(TestEnv.FILE_READ_ONLY); + + mHandler.cutToClipboard(); + mDialogs.assertDocumentsClippedNotShown(); + mDialogs.assertShowOperationUnsupported(); + mActivity.startService.assertNotCalled(); + } + // Recents root means when deleting the srcParent will be null. @Test public void testDeleteSelectedDocuments_RecentsRoot() { |