summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/com/android/documentsui/ActionModeAddons.java2
-rw-r--r--src/com/android/documentsui/ActionModeController.java9
-rw-r--r--src/com/android/documentsui/Metrics.java4
-rw-r--r--src/com/android/documentsui/Model.java73
-rw-r--r--src/com/android/documentsui/files/ActionHandler.java99
-rw-r--r--src/com/android/documentsui/files/FilesActivity.java6
-rw-r--r--src/com/android/documentsui/services/FileOperations.java7
-rw-r--r--src/com/android/documentsui/ui/DialogController.java46
-rw-r--r--src/com/android/documentsui/ui/Snackbars.java22
-rw-r--r--tests/common/com/android/documentsui/TestActionModeAddons.java8
-rw-r--r--tests/common/com/android/documentsui/bots/DirectoryListBot.java8
-rw-r--r--tests/common/com/android/documentsui/ui/TestDialogController.java18
-rw-r--r--tests/functional/com/android/documentsui/FileDeleteUiTest.java18
-rw-r--r--tests/functional/com/android/documentsui/FileUndoDeletionUiTest.java99
-rw-r--r--tests/unit/com/android/documentsui/files/ActionHandlerTest.java41
-rw-r--r--tests/unit/com/android/documentsui/picker/ActionHandlerTest.java2
16 files changed, 308 insertions, 154 deletions
diff --git a/src/com/android/documentsui/ActionModeAddons.java b/src/com/android/documentsui/ActionModeAddons.java
index 83eba78bb..ef8ae1999 100644
--- a/src/com/android/documentsui/ActionModeAddons.java
+++ b/src/com/android/documentsui/ActionModeAddons.java
@@ -21,6 +21,4 @@ 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 1c371fe30..4e0baef45 100644
--- a/src/com/android/documentsui/ActionModeController.java
+++ b/src/com/android/documentsui/ActionModeController.java
@@ -29,8 +29,6 @@ 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.selection.Selection;
@@ -185,13 +183,6 @@ public class ActionModeController extends SelectionObserver
}
}
- @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 838ebf8b7..12df0bf1d 100644
--- a/src/com/android/documentsui/Metrics.java
+++ b/src/com/android/documentsui/Metrics.java
@@ -318,6 +318,7 @@ 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,
@@ -349,7 +350,8 @@ public final class Metrics {
USER_ACTION_COMPRESS,
USER_ACTION_EXTRACT_TO,
USER_ACTION_VIEW_IN_APPLICATION,
- USER_ACTION_INSPECTOR
+ USER_ACTION_INSPECTOR,
+ USER_ACTION_UNDO_DELETE
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserAction {}
diff --git a/src/com/android/documentsui/Model.java b/src/com/android/documentsui/Model.java
index 96dfc5dec..f02c324a1 100644
--- a/src/com/android/documentsui/Model.java
+++ b/src/com/android/documentsui/Model.java
@@ -32,7 +32,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import android.util.Log;
-import com.android.documentsui.DirectoryResult;
import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.EventListener;
@@ -45,6 +44,7 @@ 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;
@@ -73,6 +73,7 @@ public class Model {
private @Nullable Cursor mCursor;
private int mCursorCount;
private String mIds[] = new String[0];
+ private Set<Selection> mDocumentsToBeDeleted = new HashSet<>();
public Model(Features features) {
mFeatures = features;
@@ -109,13 +110,13 @@ public class Model {
doc = null;
mIsLoading = false;
mFileNames.clear();
+ mDocumentsToBeDeleted.clear();
notifyUpdateListeners();
}
@VisibleForTesting
protected void update(DirectoryResult result) {
assert(result != null);
-
if (DEBUG) Log.i(TAG, "Updating model with new result set.");
if (result.exception != null) {
@@ -141,9 +142,56 @@ public class Model {
notifyUpdateListeners();
}
+ public void markDocumentsToBeDeleted(Selection selection) {
+ if (mDocumentsToBeDeleted.contains(selection)) {
+ return;
+ }
+ mDocumentsToBeDeleted.add(selection);
+ updateModelData();
+ notifyUpdateListeners();
+ }
+
+ public void restoreDocumentsToBeDeleted(Selection selection) {
+ if (!mDocumentsToBeDeleted.contains(selection)) {
+ return;
+ }
+ mDocumentsToBeDeleted.remove(selection);
+ updateModelData();
+ notifyUpdateListeners();
+ }
+
+ private boolean isDocumentToBeDeleted(String id) {
+ for (Selection s : mDocumentsToBeDeleted) {
+ if (s.contains(id)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void updateDocumentsToBeDeleted() {
+ for (Iterator<Selection> i = mDocumentsToBeDeleted.iterator(); i.hasNext();) {
+ Selection selection = i.next();
+ for (String id : selection) {
+ if (!mPositions.containsKey(id)) {
+ i.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ private int getDocumentsToBeDeletedCount() {
+ int count = 0;
+ for (Selection s : mDocumentsToBeDeleted) {
+ count += s.size();
+ }
+ return count;
+ }
+
@VisibleForTesting
public int getItemCount() {
- return mCursorCount;
+ return mCursorCount - getDocumentsToBeDeletedCount();
}
/**
@@ -151,9 +199,10 @@ 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);
@@ -164,18 +213,24 @@ public class Model {
// If the cursor is a merged cursor over multiple authorities, then prefix the ids
// with the authority to avoid collisions.
if (mCursor instanceof MergeCursor) {
- mIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY)
+ tmpIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY)
+ "|" + getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
} else {
- mIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
+ tmpIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
}
+ mPositions.put(tmpIds[pos], pos);
mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME));
}
- // Populate the positions.
- mPositions.clear();
+ updateDocumentsToBeDeleted();
+
+ mIds = new String[mCursorCount - getDocumentsToBeDeletedCount()];
+ int index = 0;
for (int i = 0; i < mCursorCount; ++i) {
- mPositions.put(mIds[i], i);
+ if (!isDocumentToBeDeleted(tmpIds[i])) {
+ mIds[index] = tmpIds[i];
+ index++;
+ }
}
}
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index 21926561f..19c812ffd 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -25,11 +25,11 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
-import android.os.Build;
import android.provider.DocumentsContract;
import android.text.TextUtils;
import android.util.Log;
import android.view.DragEvent;
+import android.view.View;
import com.android.documentsui.AbstractActionHandler;
import com.android.documentsui.ActionModeAddons;
@@ -42,8 +42,6 @@ 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.ConfirmationCallback.Result;
import com.android.documentsui.base.DebugFlags;
import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentInfo;
@@ -69,11 +67,15 @@ 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 androidx.annotation.VisibleForTesting;
+import android.support.design.widget.Snackbar;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import javax.annotation.Nullable;
@@ -93,6 +95,8 @@ 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,
@@ -301,44 +305,57 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
final @Nullable DocumentInfo srcParent = mState.stack.peek();
- // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
- List<DocumentInfo> docs = mModel.getDocuments(selection);
-
- ConfirmationCallback result = (@Result int code) -> {
- // share the news with our caller, be it good or bad.
- mActionModeAddons.finishOnConfirmed(code);
-
- if (code != ConfirmationCallback.CONFIRM) {
- return;
- }
-
- 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;
+ 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.restoreDocumentsToBeDeleted(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();
+
+ FileOperations.start(mActivity, operation, null,
+ FileOperations.createJobId());
+ }
+ if (mDeletionSnackbar == snackbar) {
+ mDeletionSnackbar = null;
+ }
}
-
- 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());
};
+ mDeletionSnackbar = showDeletionSnackbar(mActivity, selection.size(), action, callback);
+ }
- mDialogs.confirmDelete(docs, result);
+ public Snackbar showDeletionSnackbar(Activity activity, int docCount, Consumer<View> action,
+ Snackbar.Callback callback) {
+ return Snackbars.showDelete(mActivity, docCount, action, callback);
+ }
+
+ public void dismissDeletionSnackBar() {
+ if (mDeletionSnackbar != null) {
+ mDeletionSnackbar.dismiss();
+ }
}
@Override
@@ -394,6 +411,12 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
}
@Override
+ public void loadDocumentsForCurrentStack() {
+ dismissDeletionSnackBar();
+ super.loadDocumentsForCurrentStack();
+ }
+
+ @Override
public void initLocation(Intent intent) {
assert(intent != null);
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 3f1f47495..f27f0174f 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -262,6 +262,12 @@ 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/FileOperations.java b/src/com/android/documentsui/services/FileOperations.java
index edb6d3757..5c9c62b37 100644
--- a/src/com/android/documentsui/services/FileOperations.java
+++ b/src/com/android/documentsui/services/FileOperations.java
@@ -62,9 +62,10 @@ public final class FileOperations {
String newJobId = jobId != null ? jobId : createJobId();
Intent intent = createBaseIntent(context, newJobId, operation);
-
- callback.onOperationResult(Callback.STATUS_ACCEPTED, operation.getOpType(),
- operation.getSrc().getItemCount());
+ if (callback != null) {
+ callback.onOperationResult(Callback.STATUS_ACCEPTED, operation.getOpType(),
+ operation.getSrc().getItemCount());
+ }
context.startService(intent);
diff --git a/src/com/android/documentsui/ui/DialogController.java b/src/com/android/documentsui/ui/DialogController.java
index 0de98683f..24a0b5155 100644
--- a/src/com/android/documentsui/ui/DialogController.java
+++ b/src/com/android/documentsui/ui/DialogController.java
@@ -24,7 +24,6 @@ import android.widget.Button;
import android.widget.TextView;
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;
@@ -35,12 +34,9 @@ import com.android.documentsui.services.FileOperations.Callback.Status;
import com.android.documentsui.services.FileOperations;
import java.util.List;
-import javax.annotation.Nullable;
public interface DialogController {
- // Dialogs used in FilesActivity
- void confirmDelete(List<DocumentInfo> docs, ConfirmationCallback callback);
void showFileOperationStatus(int status, int opType, int docCount);
/**
@@ -72,44 +68,7 @@ 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) {
+ public void showFileOperationStatus(@Status int status, @OpType int opType, int docCount) {
if (status == FileOperations.Callback.STATUS_REJECTED) {
showOperationUnsupported();
return;
@@ -143,9 +102,6 @@ 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 005e3ef32..c587e0053 100644
--- a/src/com/android/documentsui/ui/Snackbars.java
+++ b/src/com/android/documentsui/ui/Snackbars.java
@@ -26,7 +26,11 @@ import android.widget.TextView;
import com.android.documentsui.R;
import com.android.documentsui.base.Shared;
+import java.util.function.Consumer;
+
public final class Snackbars {
+ public static final int DELETION_TIMEOUT = 10000;
+
private Snackbars() {}
public static final void showDocumentsClipped(Activity activity, int docCount) {
@@ -55,9 +59,14 @@ public final class Snackbars {
makeSnackbar(activity, message, Snackbar.LENGTH_SHORT).show();
}
- public static final void showDelete(Activity activity, int docCount) {
+ public static final Snackbar showDelete(Activity activity, int docCount, Consumer<View> action,
+ Snackbar.Callback callback) {
CharSequence message = Shared.getQuantityString(activity, R.plurals.deleting, docCount);
- makeSnackbar(activity, message, Snackbar.LENGTH_SHORT).show();
+ CharSequence actionText = activity.getResources().getText(R.string.undo);
+ Snackbar snackbar = makeSnackbarWithAction(activity, docCount, message, DELETION_TIMEOUT,
+ actionText, action, callback);
+ snackbar.show();
+ return snackbar;
}
public static final void showOperationRejected(Activity activity) {
@@ -73,7 +82,6 @@ public final class Snackbars {
}
public static final void showInspectorError(Activity activity) {
-
//Document Inspector uses a different view from other files app activities.
final View view = activity.findViewById(R.id.fragment_container);
Snackbar.make(view, R.string.inspector_load_error, Snackbar.LENGTH_INDEFINITE).show();
@@ -90,6 +98,14 @@ public final class Snackbars {
snackbar.show();
}
+ public static final Snackbar makeSnackbarWithAction(Activity activity, int docCount,
+ CharSequence message, int duration, CharSequence actionText,
+ Consumer<View> action, final Snackbar.Callback callback) {
+ return makeSnackbar(activity, message, duration)
+ .setAction(actionText, action::accept)
+ .addCallback(callback);
+ }
+
public static final Snackbar makeSnackbar(Activity activity, @StringRes int messageId,
int duration) {
return Snackbars.makeSnackbar(
diff --git a/tests/common/com/android/documentsui/TestActionModeAddons.java b/tests/common/com/android/documentsui/TestActionModeAddons.java
index 5379bbb4c..c928d6f23 100644
--- a/tests/common/com/android/documentsui/TestActionModeAddons.java
+++ b/tests/common/com/android/documentsui/TestActionModeAddons.java
@@ -15,20 +15,12 @@
*/
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/bots/DirectoryListBot.java b/tests/common/com/android/documentsui/bots/DirectoryListBot.java
index aaf5c8c79..dd8688360 100644
--- a/tests/common/com/android/documentsui/bots/DirectoryListBot.java
+++ b/tests/common/com/android/documentsui/bots/DirectoryListBot.java
@@ -56,7 +56,7 @@ public class DirectoryListBot extends Bots.BaseBot {
private static final String DIR_LIST_ID = "com.android.documentsui:id/dir_list";
private static final BySelector SNACK_DELETE =
- By.desc(Pattern.compile("^Deleting [0-9]+ file.+"));
+ By.text(Pattern.compile("^Deleting [0-9]+ item.+"));
private UiAutomation mAutomation;
public DirectoryListBot(
@@ -222,6 +222,12 @@ public class DirectoryListBot extends Bots.BaseBot {
return mDevice.wait(Until.findObject(By.text(message)), mTimeout);
}
+ public void clickSnackbarAction() throws UiObjectNotFoundException {
+ UiObject snackbarAction =
+ findObject("com.android.documentsui:id/snackbar_action");
+ snackbarAction.click();
+ }
+
public void waitForDeleteSnackbar() {
mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout);
}
diff --git a/tests/common/com/android/documentsui/ui/TestDialogController.java b/tests/common/com/android/documentsui/ui/TestDialogController.java
index b67ce46dd..d87cd0c80 100644
--- a/tests/common/com/android/documentsui/ui/TestDialogController.java
+++ b/tests/common/com/android/documentsui/ui/TestDialogController.java
@@ -17,18 +17,15 @@ 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;
@@ -37,13 +34,6 @@ 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
@@ -107,12 +97,4 @@ 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 fbeaf3463..b86e417ac 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() throws Exception {
+ public void testDeleteAllDocument_AfterSnackbarDismissed() 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,4 +179,18 @@ 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/FileUndoDeletionUiTest.java b/tests/functional/com/android/documentsui/FileUndoDeletionUiTest.java
new file mode 100644
index 000000000..0da36ea55
--- /dev/null
+++ b/tests/functional/com/android/documentsui/FileUndoDeletionUiTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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));
+ }
+}
diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
index 1916924ed..00ca81bf0 100644
--- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
@@ -29,6 +29,10 @@ 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;
@@ -38,11 +42,13 @@ import android.net.Uri;
import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Path;
+import android.support.design.widget.Snackbar;
import android.support.test.InstrumentationRegistry;
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.android.documentsui.AbstractActionHandler;
import com.android.documentsui.R;
@@ -68,8 +74,11 @@ 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
@@ -99,9 +108,8 @@ public class ActionHandlerTest {
((TestActivityConfig) mEnv.injector.config).nextDocumentEnabled = true;
mEnv.injector.dialogs = mDialogs;
- mHandler = createHandler();
-
- mDialogs.confirmNext();
+ ActionHandler<TestActivity> handler = createHandler();
+ mHandler = spy(handler);
mEnv.selectDocument(TestEnv.FILE_GIF);
}
@@ -161,31 +169,38 @@ public class ActionHandlerTest {
mEnv.selectionMgr.clearSelection();
mHandler.deleteSelectedDocuments();
- mDialogs.assertNoFileFailures();
mActivity.startService.assertNotCalled();
- mActionModeAddons.finishOnConfirmed.assertNeverCalled();
}
@Test
- public void testDeleteSelectedDocuments_Cancelable() {
+ public void testDeleteSelectedDocuments_Undo() {
mEnv.populateStack();
-
- mDialogs.rejectNext();
+ 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());
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 5d4156558..99947e886 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -78,8 +78,6 @@ public class ActionHandlerTest {
mLastAccessed
);
- mEnv.dialogs.confirmNext();
-
mEnv.selectionMgr.select("1");
AsyncTask.setDefaultExecutor(mEnv.mExecutor);