diff options
author | 2018-10-16 16:20:42 +0800 | |
---|---|---|
committer | 2018-10-16 20:26:55 +0800 | |
commit | 48200cdc95f31c65feb856260ac532e6a42510f5 (patch) | |
tree | c146eeb51d423fb9969d472ebfdbb2ce8175a03d | |
parent | d6a692ffc40fa4259e05ebfa906a21011fd804bf (diff) |
Roll back delete/undo feature to dialog style
Bug: 111863032
Test: atest DocumentsUITests
Change-Id: I03af16932548c10a9d69208ad7c25e9044863f28
20 files changed, 150 insertions, 564 deletions
diff --git a/res/layout/dialog_delete_confirmation.xml b/res/layout/dialog_delete_confirmation.xml index cd4d537ce..501736cec 100644 --- a/res/layout/dialog_delete_confirmation.xml +++ b/res/layout/dialog_delete_confirmation.xml @@ -21,6 +21,5 @@ android:paddingTop="24dp" android:paddingStart="24dp" android:paddingEnd="24dp" - android:textAppearance="@android:style/TextAppearance.Material.Subhead" - android:textColor="@color/dialog_title"> + android:textAppearance="@android:style/TextAppearance.Material.Subhead"> </TextView> diff --git a/src/com/android/documentsui/ActionModeAddons.java b/src/com/android/documentsui/ActionModeAddons.java index ef8ae1999..83eba78bb 100644 --- a/src/com/android/documentsui/ActionModeAddons.java +++ b/src/com/android/documentsui/ActionModeAddons.java @@ -21,4 +21,6 @@ package com.android.documentsui; public interface ActionModeAddons { void finishActionMode(); + + void finishOnConfirmed(int code); } diff --git a/src/com/android/documentsui/ActionModeController.java b/src/com/android/documentsui/ActionModeController.java index 8b74dbf5a..8ef20c4fb 100644 --- a/src/com/android/documentsui/ActionModeController.java +++ b/src/com/android/documentsui/ActionModeController.java @@ -29,6 +29,8 @@ import android.view.MenuItem; import android.view.View; import com.android.documentsui.MenuManager.SelectionDetails; +import com.android.documentsui.base.ConfirmationCallback; +import com.android.documentsui.base.ConfirmationCallback.Result; import com.android.documentsui.base.EventHandler; import com.android.documentsui.base.Menus; import com.android.documentsui.ui.MessageBuilder; @@ -184,6 +186,13 @@ public class ActionModeController extends SelectionObserver<String> } } + @Override + public void finishOnConfirmed(@Result int code) { + if (code == ConfirmationCallback.CONFIRM) { + finishActionMode(); + } + } + public ActionModeController reset( SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker) { assert(mActionMode == null); diff --git a/src/com/android/documentsui/Metrics.java b/src/com/android/documentsui/Metrics.java index 12df0bf1d..838ebf8b7 100644 --- a/src/com/android/documentsui/Metrics.java +++ b/src/com/android/documentsui/Metrics.java @@ -318,7 +318,6 @@ public final class Metrics { public static final int USER_ACTION_EXTRACT_TO = 28; public static final int USER_ACTION_VIEW_IN_APPLICATION = 29; public static final int USER_ACTION_INSPECTOR = 30; - public static final int USER_ACTION_UNDO_DELETE = 31; @IntDef(flag = false, value = { USER_ACTION_OTHER, @@ -350,8 +349,7 @@ public final class Metrics { USER_ACTION_COMPRESS, USER_ACTION_EXTRACT_TO, USER_ACTION_VIEW_IN_APPLICATION, - USER_ACTION_INSPECTOR, - USER_ACTION_UNDO_DELETE + USER_ACTION_INSPECTOR }) @Retention(RetentionPolicy.SOURCE) public @interface UserAction {} diff --git a/src/com/android/documentsui/Model.java b/src/com/android/documentsui/Model.java index f2de80228..afc85467b 100644 --- a/src/com/android/documentsui/Model.java +++ b/src/com/android/documentsui/Model.java @@ -43,7 +43,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -72,8 +71,6 @@ public class Model { private @Nullable Cursor mCursor; private int mCursorCount; private String mIds[] = new String[0]; - private Set<Selection<String>> mDocumentsToBeDeleted = new HashSet<>(); - private HashMap<Integer, ArrayList<String>> mDeletionFailedDocIds = new HashMap<>(); public Model(Features features) { mFeatures = features; @@ -110,8 +107,6 @@ public class Model { doc = null; mIsLoading = false; mFileNames.clear(); - mDocumentsToBeDeleted.clear(); - mDeletionFailedDocIds.clear(); notifyUpdateListeners(); } @@ -143,89 +138,9 @@ public class Model { notifyUpdateListeners(); } - public void markDocumentsToBeDeleted(Selection<String> selection) { - if (mDocumentsToBeDeleted.contains(selection)) { - return; - } - mDocumentsToBeDeleted.add(selection); - updateModelData(); - notifyUpdateListeners(); - } - - public void clearDocumentsToBeDeleted(Selection<String> selection) { - if (!mDocumentsToBeDeleted.contains(selection)) { - return; - } - mDocumentsToBeDeleted.remove(selection); - updateModelData(); - notifyUpdateListeners(); - } - - public void setDeletionFailedUris(Selection<String> selection, - ArrayList<Uri> deletionFailedUris) { - if (!mDocumentsToBeDeleted.contains(selection)) { - return; - } - - mDeletionFailedDocIds.put(selection.hashCode(), ModelId.build(deletionFailedUris)); - updateModelData(); - notifyUpdateListeners(); - } - - private void updateDocumentsToBeDeleted() { - for (Iterator<Selection<String>> i = mDocumentsToBeDeleted.iterator(); i.hasNext();) { - Selection<String> selection = i.next(); - int size = selection.size(); - ArrayList<String> failedDocIds = mDeletionFailedDocIds.get(selection.hashCode()); - for (String id : selection) { - // Check whether the id is in the current cursor or in the deletion failed list. - // If all ids are either not in the current cursor or in the deletion failed list, - // it means the deletion of this selection is done, and we can clear this selection. - if (!mPositions.containsKey(id) || - (failedDocIds != null && failedDocIds.contains(id))) { - size--; - } - if (size == 0) { - i.remove(); - mDeletionFailedDocIds.remove(selection.hashCode()); - break; - } - } - } - } - - private int getVisibleCount() { - int count = mPositions.size(); - for (Selection<String> selection : mDocumentsToBeDeleted) { - for (String id : selection) { - if (mPositions.containsKey(id)) { - count--; - } - } - } - return count; - } - - private boolean isDocumentToBeDeleted(String id) { - for (Selection<String> s : mDocumentsToBeDeleted) { - if (s.contains(id)) { - return true; - } - } - return false; - } - - private int getDocumentsToBeDeletedCount() { - int count = 0; - for (Selection<String> s : mDocumentsToBeDeleted) { - count += s.size(); - } - return count; - } - @VisibleForTesting public int getItemCount() { - return mCursorCount - getDocumentsToBeDeletedCount(); + return mCursorCount; } /** @@ -233,10 +148,9 @@ public class Model { * according to the current sort order. */ private void updateModelData() { + mIds = new String[mCursorCount]; mFileNames.clear(); mCursor.moveToPosition(-1); - mPositions.clear(); - String[] tmpIds = new String[mCursorCount]; for (int pos = 0; pos < mCursorCount; ++pos) { if (!mCursor.moveToNext()) { Log.e(TAG, "Fail to move cursor to next pos: " + pos); @@ -245,20 +159,14 @@ public class Model { // Generates a Model ID for a cursor entry that refers to a document. The Model ID is a // unique string that can be used to identify the document referred to by the cursor. // Prefix the ids with the authority to avoid collisions. - tmpIds[pos] = ModelId.build(mCursor); - mPositions.put(tmpIds[pos], pos); + mIds[pos] = ModelId.build(mCursor); mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME)); } - updateDocumentsToBeDeleted(); - - mIds = new String[getVisibleCount()]; - int index = 0; + // Populate the positions. + mPositions.clear(); for (int i = 0; i < mCursorCount; ++i) { - if (!isDocumentToBeDeleted(tmpIds[i])) { - mIds[index] = tmpIds[i]; - index++; - } + mPositions.put(mIds[i], i); } } diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java index b8b35a08e..69319d325 100644 --- a/src/com/android/documentsui/files/ActionHandler.java +++ b/src/com/android/documentsui/files/ActionHandler.java @@ -25,13 +25,10 @@ import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; -import android.os.Handler; -import android.os.Message; import android.provider.DocumentsContract; import android.text.TextUtils; import android.util.Log; import android.view.DragEvent; -import android.view.View; import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; import androidx.recyclerview.selection.MutableSelection; @@ -48,6 +45,7 @@ import com.android.documentsui.Metrics; import com.android.documentsui.Model; import com.android.documentsui.R; import com.android.documentsui.TimeoutTask; +import com.android.documentsui.base.ConfirmationCallback; import com.android.documentsui.base.DebugFlags; import com.android.documentsui.base.DocumentFilters; import com.android.documentsui.base.DocumentInfo; @@ -66,21 +64,16 @@ import com.android.documentsui.files.ActionHandler.Addons; import com.android.documentsui.inspector.InspectorActivity; import com.android.documentsui.queries.SearchViewManager; import com.android.documentsui.roots.ProvidersAccess; -import com.android.documentsui.services.DeleteJob; import com.android.documentsui.services.FileOperation; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperations; import com.android.documentsui.ui.DialogController; -import com.android.documentsui.ui.Snackbars; - -import com.google.android.material.snackbar.Snackbar; import androidx.annotation.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; -import java.util.function.Consumer; import javax.annotation.Nullable; @@ -100,8 +93,6 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa private final DragAndDropManager mDragAndDropManager; private final Model mModel; - private Snackbar mDeletionSnackbar; - ActionHandler( T activity, State state, @@ -302,7 +293,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa @Override public void deleteSelectedDocuments() { Metrics.logUserAction(mActivity, Metrics.USER_ACTION_DELETE); - final Selection<String> selection = getSelectedOrFocused(); + Selection selection = getSelectedOrFocused(); if (selection.isEmpty()) { return; @@ -310,82 +301,44 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa final @Nullable DocumentInfo srcParent = mState.stack.peek(); - UrisSupplier srcs; - try { - srcs = UrisSupplier.create( - selection, - mModel::getItemUri, - mClipStore); - } catch (Exception e) { - Log.e(TAG, "Failed to delete a file because we were unable to get item URIs.", e); - mDialogs.showFileOperationStatus( - FileOperations.Callback.STATUS_FAILED, - FileOperationService.OPERATION_DELETE, - selection.size()); - return; - } - mModel.markDocumentsToBeDeleted(selection); - Consumer<View> action = v -> { - Metrics.logUserAction(mActivity, Metrics.USER_ACTION_UNDO_DELETE); - mModel.clearDocumentsToBeDeleted(selection); - }; - Snackbar.Callback callback = new Snackbar.Callback() { - @Override - public void onDismissed(Snackbar snackbar, int event) { - super.onDismissed(snackbar, event); - if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) { - FileOperation operation = new FileOperation.Builder() - .withOpType(FileOperationService.OPERATION_DELETE) - .withDestination(mState.stack) - .withSrcs(srcs) - .withSrcParent(srcParent == null ? null : srcParent.derivedUri) - .build(); - operation.addMessageListener(new Handler.Callback() { - @Override - public boolean handleMessage(Message message) { - if (message.what == FileOperationService.MESSAGE_FINISH) { - operation.removeMessageListener(this); - - // If failure count equals selection size, - // it means all deletions failed. Just clear the selection. - final int failureCount = message.arg1; - if (failureCount == selection.size()) { - mModel.clearDocumentsToBeDeleted(selection); - return true; - } - - ArrayList<Uri> failureUris = message.getData() - .getParcelableArrayList(DeleteJob.KEY_FAILED_URIS); - if (failureUris != null) { - mModel.setDeletionFailedUris(selection, failureUris); - } - return true; - } - return false; - } - }); - FileOperations.start(mActivity, operation, null, FileOperations.createJobId()); - } - if (mDeletionSnackbar == snackbar) { - mDeletionSnackbar = null; - } - if (snackbar != null) { - snackbar.removeCallback(this); - } + // Model must be accessed in UI thread, since underlying cursor is not threadsafe. + List<DocumentInfo> docs = mModel.getDocuments(selection); + + ConfirmationCallback result = (@ConfirmationCallback.Result int code) -> { + // share the news with our caller, be it good or bad. + mActionModeAddons.finishOnConfirmed(code); + + if (code != ConfirmationCallback.CONFIRM) { + return; } - }; - mDeletionSnackbar = showDeletionSnackbar(mActivity, selection.size(), action, callback); - } - public Snackbar showDeletionSnackbar(Activity activity, int docCount, Consumer<View> action, - Snackbar.Callback callback) { - return Snackbars.showDelete(mActivity, docCount, action, callback); - } + UrisSupplier srcs; + try { + srcs = UrisSupplier.create( + selection, + mModel::getItemUri, + mClipStore); + } catch (Exception e) { + Log.e(TAG,"Failed to delete a file because we were unable to get item URIs.", e); + mDialogs.showFileOperationStatus( + FileOperations.Callback.STATUS_FAILED, + FileOperationService.OPERATION_DELETE, + selection.size()); + return; + } - public void dismissDeletionSnackBar() { - if (mDeletionSnackbar != null) { - mDeletionSnackbar.dismiss(); - } + FileOperation operation = new FileOperation.Builder() + .withOpType(FileOperationService.OPERATION_DELETE) + .withDestination(mState.stack) + .withSrcs(srcs) + .withSrcParent(srcParent == null ? null : srcParent.derivedUri) + .build(); + + FileOperations.start(mActivity, operation, mDialogs::showFileOperationStatus, + FileOperations.createJobId()); + }; + + mDialogs.confirmDelete(docs, result); } @Override @@ -442,7 +395,6 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa @Override public void loadDocumentsForCurrentStack() { - dismissDeletionSnackBar(); super.loadDocumentsForCurrentStack(); } diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java index c07deb6ed..5db68344c 100644 --- a/src/com/android/documentsui/files/FilesActivity.java +++ b/src/com/android/documentsui/files/FilesActivity.java @@ -262,12 +262,6 @@ public class FilesActivity extends BaseActivity implements ActionHandler.Addons } @Override - protected void onPause() { - super.onPause(); - mInjector.actions.dismissDeletionSnackBar(); - } - - @Override public String getDrawerTitle() { Intent intent = getIntent(); return (intent != null && intent.hasExtra(Intent.EXTRA_TITLE)) diff --git a/src/com/android/documentsui/services/DeleteJob.java b/src/com/android/documentsui/services/DeleteJob.java index c8b302b75..82c6e1301 100644 --- a/src/com/android/documentsui/services/DeleteJob.java +++ b/src/com/android/documentsui/services/DeleteJob.java @@ -17,7 +17,6 @@ package com.android.documentsui.services; import static com.android.documentsui.base.SharedMinimal.DEBUG; -import static com.android.documentsui.services.FileOperationService.MESSAGE_FINISH; import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE; import android.app.Notification; @@ -25,10 +24,6 @@ import android.app.Notification.Builder; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; -import android.os.Bundle; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; import android.util.Log; import com.android.documentsui.Metrics; @@ -39,25 +34,17 @@ import com.android.documentsui.base.Features; import com.android.documentsui.clipping.UrisSupplier; import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; import javax.annotation.Nullable; -public final class DeleteJob extends ResolvedResourcesJob { +final class DeleteJob extends ResolvedResourcesJob { private static final String TAG = "DeleteJob"; - public final static String KEY_FAILED_URIS = "deletion_failed_uris"; - private final Uri mParentUri; private volatile int mDocsProcessed = 0; - private final Messenger mMessenger; - - private final ArrayList<Uri> mDeletionFailedUris = new ArrayList<>(); - /** * Moves files to a destination identified by {@code destination}. * Performs most work by delegating to CopyJob, then deleting @@ -66,25 +53,9 @@ public final class DeleteJob extends ResolvedResourcesJob { * @see @link {@link Job} constructor for most param descriptions. */ DeleteJob(Context service, Listener listener, String id, DocumentStack stack, - UrisSupplier srcs, Messenger messenger, @Nullable Uri srcParent, Features features) { + UrisSupplier srcs, @Nullable Uri srcParent, Features features) { super(service, listener, id, OPERATION_DELETE, stack, srcs, features); mParentUri = srcParent; - mMessenger = messenger; - initDeletionFailedUrisList(); - } - - private void initDeletionFailedUrisList() { - Iterable<Uri> uris; - try { - uris = mResourceUris.getUris(appContext); - } catch (IOException e) { - Log.e(TAG, "Failed to read list of target resource Uris.", e); - failureCount = this.mResourceUris.getItemCount(); - return; - } - for (Uri uri : uris) { - mDeletionFailedUris.add(uri); - } } @Override @@ -143,7 +114,6 @@ public final class DeleteJob extends ResolvedResourcesJob { if (DEBUG) Log.d(TAG, "Deleting document @ " + doc.derivedUri); try { deleteDocument(doc, parentDoc); - mDeletionFailedUris.remove(doc.derivedUri); } catch (ResourceException e) { Metrics.logFileOperationFailure( appContext, Metrics.SUBFILEOP_DELETE_DOCUMENT, doc.derivedUri); @@ -161,29 +131,6 @@ public final class DeleteJob extends ResolvedResourcesJob { } @Override - void finish() { - super.finish(); - try { - Message message = Message.obtain(); - message.what = MESSAGE_FINISH; - // If the size of mDeletionFailedUris is 0, it means either 1). all deletions succeeded - // or 2). reading all uris from mResourceUris failed. For case 2). We also need to check - // the failureCount to get the correct count. - message.arg1 = mDeletionFailedUris.size() == 0 - ? (failureCount == mResourceUris.getItemCount() ? failureCount : 0) - : mDeletionFailedUris.size(); - if (message.arg1 > 0 && message.arg1 < mResourceUris.getItemCount()) { - Bundle b = new Bundle(); - b.putParcelableArrayList(KEY_FAILED_URIS, mDeletionFailedUris); - message.setData(b); - } - mMessenger.send(message); - } catch (RemoteException e) { - // Ignore. Most likely the frontend was killed. - } - } - - @Override public String toString() { return new StringBuilder() .append("DeleteJob") diff --git a/src/com/android/documentsui/services/FileOperation.java b/src/com/android/documentsui/services/FileOperation.java index 9ff16543d..29393a81e 100644 --- a/src/com/android/documentsui/services/FileOperation.java +++ b/src/com/android/documentsui/services/FileOperation.java @@ -261,7 +261,7 @@ public abstract class FileOperation implements Parcelable { getMessenger(), features); case OPERATION_DELETE: return new DeleteJob(service, listener, id, getDestination(), getSrc(), - getMessenger(), mSrcParent, features); + mSrcParent, features); default: throw new UnsupportedOperationException("Unsupported op type: " + getOpType()); } diff --git a/src/com/android/documentsui/ui/DialogController.java b/src/com/android/documentsui/ui/DialogController.java index c831052e2..6b46ea41e 100644 --- a/src/com/android/documentsui/ui/DialogController.java +++ b/src/com/android/documentsui/ui/DialogController.java @@ -25,6 +25,7 @@ import android.widget.TextView; import com.google.android.material.snackbar.Snackbar; import com.android.documentsui.R; +import com.android.documentsui.base.ConfirmationCallback; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Features; import com.android.documentsui.picker.OverwriteConfirmFragment; @@ -38,6 +39,8 @@ import java.util.List; public interface DialogController { + // Dialogs used in FilesActivity + void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback); void showFileOperationStatus(int status, int opType, int docCount); /** @@ -69,6 +72,42 @@ public interface DialogController { } @Override + public void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback) { + assert(!docs.isEmpty()); + + TextView message = + (TextView) mActivity.getLayoutInflater().inflate( + R.layout.dialog_delete_confirmation, null); + message.setText(mMessages.generateDeleteMessage(docs)); + + // For now, we implement this dialog NOT + // as a fragment (which can survive rotation and have its own state), + // but as a simple runtime dialog. So rotating a device with an + // active delete dialog...results in that dialog disappearing. + // We can do better, but don't have cycles for it now. + final AlertDialog alertDialog = new AlertDialog.Builder(mActivity) + .setView(message) + .setPositiveButton( + android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + callback.accept(ConfirmationCallback.CONFIRM); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .create(); + + alertDialog.setOnShowListener( + (DialogInterface) -> { + Button positive = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE); + positive.setFocusable(true); + positive.requestFocus(); + }); + alertDialog.show(); + } + + @Override public void showFileOperationStatus(@Status int status, @OpType int opType, int docCount) { if (status == FileOperations.Callback.STATUS_REJECTED) { showOperationUnsupported(); @@ -103,6 +142,9 @@ public interface DialogController { case FileOperationService.OPERATION_EXTRACT: Snackbars.showExtract(mActivity, docCount); break; + case FileOperationService.OPERATION_DELETE: + Snackbars.showDelete(mActivity, docCount); + break; default: throw new UnsupportedOperationException("Unsupported Operation: " + opType); } diff --git a/src/com/android/documentsui/ui/Snackbars.java b/src/com/android/documentsui/ui/Snackbars.java index eb6547da7..26b4307c8 100644 --- a/src/com/android/documentsui/ui/Snackbars.java +++ b/src/com/android/documentsui/ui/Snackbars.java @@ -18,8 +18,6 @@ package com.android.documentsui.ui; import androidx.annotation.StringRes; import android.app.Activity; -import android.content.res.TypedArray; -import android.graphics.Color; import android.view.Gravity; import android.view.View; import android.widget.TextView; @@ -32,7 +30,6 @@ import com.android.documentsui.base.Shared; import java.util.function.Consumer; public final class Snackbars { - public static final int DELETION_TIMEOUT = 10000; private Snackbars() {} @@ -62,14 +59,9 @@ public final class Snackbars { makeSnackbar(activity, message, Snackbar.LENGTH_SHORT).show(); } - public static final Snackbar showDelete(Activity activity, int docCount, Consumer<View> action, - Snackbar.Callback callback) { + public static final void showDelete(Activity activity, int docCount) { CharSequence message = Shared.getQuantityString(activity, R.plurals.deleting, docCount); - CharSequence actionText = activity.getResources().getText(R.string.undo); - Snackbar snackbar = makeSnackbarWithAction(activity, docCount, message, DELETION_TIMEOUT, - actionText, action, callback); - snackbar.show(); - return snackbar; + makeSnackbar(activity, message, Snackbar.LENGTH_SHORT).show(); } public static final void showOperationRejected(Activity activity) { diff --git a/tests/common/com/android/documentsui/TestActionModeAddons.java b/tests/common/com/android/documentsui/TestActionModeAddons.java index c928d6f23..5379bbb4c 100644 --- a/tests/common/com/android/documentsui/TestActionModeAddons.java +++ b/tests/common/com/android/documentsui/TestActionModeAddons.java @@ -15,12 +15,20 @@ */ package com.android.documentsui; +import com.android.documentsui.testing.TestConfirmationCallback; + public class TestActionModeAddons implements ActionModeAddons { public boolean finishActionModeCalled; + public final TestConfirmationCallback finishOnConfirmed = new TestConfirmationCallback(); @Override public void finishActionMode() { finishActionModeCalled = true; } + + @Override + public void finishOnConfirmed(int code) { + finishOnConfirmed.accept(code); + } } diff --git a/tests/common/com/android/documentsui/ui/TestDialogController.java b/tests/common/com/android/documentsui/ui/TestDialogController.java index d87cd0c80..3bd6db6fb 100644 --- a/tests/common/com/android/documentsui/ui/TestDialogController.java +++ b/tests/common/com/android/documentsui/ui/TestDialogController.java @@ -17,15 +17,19 @@ package com.android.documentsui.ui; import android.app.FragmentManager; +import com.android.documentsui.base.ConfirmationCallback; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.services.FileOperation; import com.android.documentsui.services.FileOperations; import junit.framework.Assert; +import java.util.List; + public class TestDialogController implements DialogController { + public int mNextConfirmationCode; private int mFileOpStatus; private boolean mNoApplicationFound; private boolean mDocumentsClipped; @@ -34,6 +38,13 @@ public class TestDialogController implements DialogController { private DocumentInfo mOverwriteTarget; public TestDialogController() { + // by default, always confirm + mNextConfirmationCode = ConfirmationCallback.CONFIRM; + } + + @Override + public void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback) { + callback.accept(mNextConfirmationCode); } @Override @@ -97,4 +108,12 @@ public class TestDialogController implements DialogController { public void assertOverwriteConfirmed(DocumentInfo expected) { Assert.assertEquals(expected, mOverwriteTarget); } + + public void confirmNext() { + mNextConfirmationCode = ConfirmationCallback.CONFIRM; + } + + public void rejectNext() { + mNextConfirmationCode = ConfirmationCallback.REJECT; + } } diff --git a/tests/functional/com/android/documentsui/FileDeleteUiTest.java b/tests/functional/com/android/documentsui/FileDeleteUiTest.java index b86e417ac..fbeaf3463 100644 --- a/tests/functional/com/android/documentsui/FileDeleteUiTest.java +++ b/tests/functional/com/android/documentsui/FileDeleteUiTest.java @@ -155,16 +155,16 @@ public class FileDeleteUiTest extends ActivityTest<FilesActivity> { exec.shutdown(); } - public void testDeleteAllDocument_AfterSnackbarDismissed() throws Exception { + public void testDeleteAllDocument() throws Exception { bots.roots.openRoot(ROOT_0_ID); bots.main.clickToolbarOverflowItem( context.getResources().getString(R.string.menu_select_all)); device.waitForIdle(); bots.main.clickToolbarItem(R.id.action_menu_delete); + bots.main.clickDialogOkButton(); device.waitForIdle(); - bots.directory.waitForDeleteSnackbarGone(); try { mCountDownLatch.await(WAIT_TIME_SECONDS, TimeUnit.SECONDS); } catch (Exception e) { @@ -179,18 +179,4 @@ public class FileDeleteUiTest extends ActivityTest<FilesActivity> { List<DocumentInfo> root1 = mDocsHelper.listChildren(rootDir0.documentId, 1000); assertTrue("Delete operation was not completed", root1.size() == 0); } - - public void testDeleteAllDocument_BeforeSnackbarDismissed() throws Exception { - bots.roots.openRoot(ROOT_0_ID); - bots.main.clickToolbarOverflowItem( - context.getResources().getString(R.string.menu_select_all)); - device.waitForIdle(); - - bots.main.clickToolbarItem(R.id.action_menu_delete); - device.waitForIdle(); - - bots.directory.waitForDeleteSnackbar(); - List<DocumentInfo> root1 = mDocsHelper.listChildren(rootDir0.documentId, 1000); - assertTrue("Documents are deleted", root1.size() == 1000); - } } diff --git a/tests/functional/com/android/documentsui/FileManagementUiTest.java b/tests/functional/com/android/documentsui/FileManagementUiTest.java index 0cdab9d80..5157ec331 100644 --- a/tests/functional/com/android/documentsui/FileManagementUiTest.java +++ b/tests/functional/com/android/documentsui/FileManagementUiTest.java @@ -81,7 +81,7 @@ public class FileManagementUiTest extends ActivityTest<FilesActivity> { device.waitForIdle(); bots.main.clickToolbarItem(R.id.action_menu_delete); - bots.directory.waitForDeleteSnackbarGone(); + bots.main.clickDialogOkButton(); device.waitForIdle(); bots.directory.assertDocumentsAbsent("file1.png"); @@ -120,16 +120,12 @@ public class FileManagementUiTest extends ActivityTest<FilesActivity> { bots.directory.waitForDocument("file1.png"); } - public void testDeleteDocument_Undo() throws Exception { + public void testDeleteDocument_Cancel() throws Exception { bots.directory.selectDocument("file1.png"); device.waitForIdle(); bots.main.clickToolbarItem(R.id.action_menu_delete); - bots.directory.waitForDeleteSnackbar(); - device.waitForIdle(); - - bots.directory.clickSnackbarAction(); - device.waitForIdle(); + bots.main.clickDialogCancelButton(); bots.directory.waitForDocument("file1.png"); } diff --git a/tests/functional/com/android/documentsui/FileUndoDeletionUiTest.java b/tests/functional/com/android/documentsui/FileUndoDeletionUiTest.java deleted file mode 100644 index 49b8110dc..000000000 --- a/tests/functional/com/android/documentsui/FileUndoDeletionUiTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.documentsui; - -import static com.android.documentsui.StubProvider.ROOT_0_ID; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.Bundle; -import android.os.RemoteException; -import android.provider.DocumentsContract; -import android.support.test.filters.LargeTest; -import android.support.test.uiautomator.UiObject; -import android.util.Log; - -import com.android.documentsui.base.DocumentInfo; -import com.android.documentsui.files.FilesActivity; -import com.android.documentsui.services.TestNotificationService; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** -* This class test the below points -* - Undo the deleted files -*/ -@LargeTest -public class FileUndoDeletionUiTest extends ActivityTest<FilesActivity> { - private static final String TAG = "FileUndoDeletionUiTest"; - - private static final int DUMMY_FILE_COUNT = 3; - - private String[] filenames = new String[DUMMY_FILE_COUNT]; - - public FileUndoDeletionUiTest() { - super(FilesActivity.class); - } - - @Override - public void setUp() throws Exception { - super.setUp(); - - // Set a flag to prevent many refreshes. - Bundle bundle = new Bundle(); - bundle.putBoolean(StubProvider.EXTRA_ENABLE_ROOT_NOTIFICATION, false); - mDocsHelper.configure(null, bundle); - - initTestFiles(); - } - - @Override - public void initTestFiles() { - for (int i = 0; i < DUMMY_FILE_COUNT; i++) { - filenames[i] = "file" + i + ".log"; - mDocsHelper.createDocument(rootDir0, "text/plain", filenames[i]); - } - } - - public void testDeleteUndoDocumentsUI() throws Exception { - bots.roots.openRoot(ROOT_0_ID); - bots.main.clickToolbarOverflowItem( - context.getResources().getString(R.string.menu_select_all)); - device.waitForIdle(); - - bots.main.clickToolbarItem(R.id.action_menu_delete); - device.waitForIdle(); - - bots.directory.waitForDeleteSnackbar(); - for (String filename : filenames) { - assertFalse("files are not deleted", bots.directory.hasDocuments(filename)); - } - - bots.directory.clickSnackbarAction(); - device.waitForIdle(); - - assertTrue("deleted files are not restored", bots.directory.hasDocuments(filenames)); - } - - public void testSelectionStateAfterDeleteUndo() throws Exception { - bots.roots.openRoot(ROOT_0_ID); - bots.directory.selectDocument("file1.log"); - device.waitForIdle(); - - bots.main.clickToolbarItem(R.id.action_menu_delete); - device.waitForIdle(); - - bots.directory.waitForDeleteSnackbar(); - - bots.directory.clickSnackbarAction(); - device.waitForIdle(); - - assertFalse("the file after deleting/undo is still selected", - bots.directory.isDocumentSelected("file1.log")); - } -} diff --git a/tests/unit/com/android/documentsui/ModelTest.java b/tests/unit/com/android/documentsui/ModelTest.java index 7d46f189d..8d3a33ba2 100644 --- a/tests/unit/com/android/documentsui/ModelTest.java +++ b/tests/unit/com/android/documentsui/ModelTest.java @@ -22,13 +22,10 @@ import static junit.framework.Assert.fail; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; -import android.net.Uri; import android.provider.DocumentsContract.Document; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; -import androidx.recyclerview.selection.MutableSelection; - import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.testing.TestEventListener; @@ -38,10 +35,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.Arrays; import java.util.BitSet; -import java.util.List; import java.util.Random; @RunWith(AndroidJUnit4.class) @@ -186,44 +180,4 @@ public class ModelTest { assertEquals(0, model.getItemCount()); } - - @Test - public void testDeletion_showFailedItem() { - // set all items to be deleted. - List<String> idsTobeDeleted = Arrays.asList(model.getModelIds()); - MutableSelection<String> selection = new MutableSelection<>(); - for (String id : idsTobeDeleted) { - selection.add(id); - } - - // set the first item as the deletion failed one. - String failedId = idsTobeDeleted.get(0); - ArrayList<Uri> failedUris = new ArrayList<>(); - failedUris.add(model.getItemUri(failedId)); - - // mark items to be deleted - model.markDocumentsToBeDeleted(selection); - String[] ids = model.getModelIds(); - assertEquals(0, ids.length); - - // simulate deletion but keep the failed item. - cursor.moveToFirst(); - MatrixCursor c = new MatrixCursor(COLUMNS); - MatrixCursor.RowBuilder row = c.newRow(); - row.add(RootCursorWrapper.COLUMN_AUTHORITY, AUTHORITY); - row.add(Document.COLUMN_DOCUMENT_ID, 0); - row.add(Document.COLUMN_FLAGS, Document.FLAG_SUPPORTS_DELETE); - row.add(Document.COLUMN_DISPLAY_NAME, NAMES[0]); - row.add(Document.COLUMN_SIZE, cursor.getColumnIndex(Document.COLUMN_SIZE)); - DirectoryResult r = new DirectoryResult(); - r.cursor = c; - model.update(r); - - // simulate receiving failed uris - model.setDeletionFailedUris(selection, failedUris); - ids = model.getModelIds(); - - assertEquals(1, ids.length); - assertEquals(failedId, ids[0]); - } } diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java index 805fade2c..24f8d7d85 100644 --- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java @@ -29,10 +29,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyObject; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.spy; import android.app.Activity; import android.app.PendingIntent; @@ -47,9 +43,6 @@ import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; import android.util.Pair; import android.view.DragEvent; -import android.view.View; - -import com.google.android.material.snackbar.Snackbar; import com.android.documentsui.AbstractActionHandler; import com.android.documentsui.ModelId; @@ -76,11 +69,8 @@ import androidx.core.util.Preconditions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import java.util.Arrays; -import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) @MediumTest @@ -110,8 +100,9 @@ public class ActionHandlerTest { ((TestActivityConfig) mEnv.injector.config).nextDocumentEnabled = true; mEnv.injector.dialogs = mDialogs; - ActionHandler<TestActivity> handler = createHandler(); - mHandler = spy(handler); + mHandler = createHandler(); + + mDialogs.confirmNext(); mEnv.selectDocument(TestEnv.FILE_GIF); } @@ -171,38 +162,31 @@ public class ActionHandlerTest { mEnv.selectionMgr.clearSelection(); mHandler.deleteSelectedDocuments(); + mDialogs.assertNoFileFailures(); mActivity.startService.assertNotCalled(); + mActionModeAddons.finishOnConfirmed.assertNeverCalled(); } @Test - public void testDeleteSelectedDocuments_Undo() { + public void testDeleteSelectedDocuments_Cancelable() { mEnv.populateStack(); - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - Consumer<View> callback = invocation.getArgument(2); - callback.accept(null); - return null; - } - }).when(mHandler).showDeletionSnackbar(anyObject(), anyInt(), anyObject(), anyObject()); + + mDialogs.rejectNext(); mHandler.deleteSelectedDocuments(); + mDialogs.assertNoFileFailures(); mActivity.startService.assertNotCalled(); + mActionModeAddons.finishOnConfirmed.assertRejected(); } // Recents root means when deleting the srcParent will be null. @Test public void testDeleteSelectedDocuments_RecentsRoot() { mEnv.state.stack.changeRoot(TestProvidersAccess.RECENTS); - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - Snackbar.Callback callback = invocation.getArgument(3); - callback.onDismissed(null, Snackbar.Callback.DISMISS_EVENT_MANUAL); - return null; - } - }).when(mHandler).showDeletionSnackbar(anyObject(), anyInt(), anyObject(), anyObject()); + mHandler.deleteSelectedDocuments(); + mDialogs.assertNoFileFailures(); mActivity.startService.assertCalled(); + mActionModeAddons.finishOnConfirmed.assertCalled(); } @Test diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java index 99947e886..5d4156558 100644 --- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java @@ -78,6 +78,8 @@ public class ActionHandlerTest { mLastAccessed ); + mEnv.dialogs.confirmNext(); + mEnv.selectionMgr.select("1"); AsyncTask.setDefaultExecutor(mEnv.mExecutor); diff --git a/tests/unit/com/android/documentsui/services/DeleteJobTest.java b/tests/unit/com/android/documentsui/services/DeleteJobTest.java index 50b3dad7a..0d8d39be8 100644 --- a/tests/unit/com/android/documentsui/services/DeleteJobTest.java +++ b/tests/unit/com/android/documentsui/services/DeleteJobTest.java @@ -21,16 +21,10 @@ import static com.android.documentsui.services.FileOperationService.OPERATION_DE import static com.google.common.collect.Lists.newArrayList; import android.net.Uri; -import android.os.Handler; -import android.os.Message; import android.provider.DocumentsContract; import android.support.test.filters.MediumTest; -import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; @MediumTest public class DeleteJobTest extends AbstractJobTest<DeleteJob> { @@ -62,90 +56,6 @@ public class DeleteJobTest extends AbstractJobTest<DeleteJob> { mDocs.assertChildCount(mSrcRoot, 0); } - public void testDeleteFile_SendDeletionFailedUris() throws Exception { - Uri invalidUri1 = Uri.parse("content://poodles/chuckleberry/ham"); - Uri validUri = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt"); - Uri invalidUri2 = Uri.parse("content://poodles/chuckleberry/ham2"); - mDocs.writeDocument(validUri, FRUITY_BYTES); - - Uri stack = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId); - FileOperation operation = createOperation(OPERATION_DELETE, - newArrayList(invalidUri1, validUri, invalidUri2), - DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId), stack); - - CountDownLatch latch = new CountDownLatch(1); - final ArrayList<Uri> deletionFailedUris = new ArrayList<>(); - operation.addMessageListener( - new Handler.Callback() { - @Override - public boolean handleMessage(Message message) { - if (message.what == FileOperationService.MESSAGE_FINISH) { - operation.removeMessageListener(this); - deletionFailedUris.addAll(message.getData() - .getParcelableArrayList(DeleteJob.KEY_FAILED_URIS)); - latch.countDown(); - return true; - } - return false; - } - } - ); - - createJob(operation).run(); - latch.await(10, TimeUnit.SECONDS); - - assertTrue("Not received failed uri:" + invalidUri1, - deletionFailedUris.contains(invalidUri1)); - assertTrue("Not received failed uri:" + invalidUri2, - deletionFailedUris.contains(invalidUri2)); - assertFalse("Received valid uri:" + validUri, - deletionFailedUris.contains(validUri)); - } - - public void testDeleteFile_SendDeletionCanceledUris() throws Exception { - Uri testUri1 = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt"); - Uri testUri2 = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt"); - Uri testUri3 = mDocs.createDocument(mSrcRoot, "text/plain", "test3.txt"); - mDocs.writeDocument(testUri1, FRUITY_BYTES); - mDocs.writeDocument(testUri2, FRUITY_BYTES); - mDocs.writeDocument(testUri3, FRUITY_BYTES); - - Uri stack = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId); - FileOperation operation = createOperation(OPERATION_DELETE, - newArrayList(testUri1, testUri2, testUri3), - DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId), stack); - - CountDownLatch latch = new CountDownLatch(1); - final AtomicInteger cancelCount = new AtomicInteger(); - operation.addMessageListener( - new Handler.Callback() { - @Override - public boolean handleMessage(Message message) { - if (message.what == FileOperationService.MESSAGE_FINISH) { - operation.removeMessageListener(this); - cancelCount.set(message.arg1); - latch.countDown(); - return true; - } - return false; - } - } - ); - - // Cancel the deletion job at onStart to ensure that none of the files will be deleted - TestJobListener listener = new TestJobListener() { - @Override - public void onStart(Job job) { - super.onStart(job); - job.cancel(); - } - }; - createJob(operation, listener).run(); - latch.await(10, TimeUnit.SECONDS); - - assertEquals(3, cancelCount.get()); - } - /** * Creates a job with a stack consisting to the default src directory. */ |