summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author shawnlin <shawnlin@google.com> 2018-08-20 16:20:43 +0800
committer shawnlin <shawnlin@google.com> 2018-08-31 22:13:50 +0800
commit65bbe7934b8c1811b3bb65416ea4a37cf62d6d58 (patch)
treec686a2ab4f4f2e3a3cd51dbe6c69c8259bf922d8
parentbda5932d34493ad4070f1ab6cc7b83764cec0c50 (diff)
Fixed files flickering in & out during a deletion.
Clear selection when all ids in it are either no longer in the current cursor or in the failure list. Change-Id: Idc1de86f8173626765236e0bf6bd40d421cef51b Fixes: 112741407 Test: manul, add some files, delete files, observe the deletion process Test: atest DocumentsUITests
-rw-r--r--src/com/android/documentsui/Model.java65
-rw-r--r--src/com/android/documentsui/ModelId.java68
-rw-r--r--src/com/android/documentsui/files/ActionHandler.java35
-rw-r--r--src/com/android/documentsui/services/DeleteJob.java39
-rw-r--r--src/com/android/documentsui/services/FileOperation.java2
-rw-r--r--tests/common/com/android/documentsui/testing/TestEnv.java6
-rw-r--r--tests/unit/com/android/documentsui/ModelTest.java46
-rw-r--r--tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java5
-rw-r--r--tests/unit/com/android/documentsui/files/ActionHandlerTest.java4
-rw-r--r--tests/unit/com/android/documentsui/services/AbstractJobTest.java20
-rw-r--r--tests/unit/com/android/documentsui/services/DeleteJobTest.java42
11 files changed, 300 insertions, 32 deletions
diff --git a/src/com/android/documentsui/Model.java b/src/com/android/documentsui/Model.java
index 7141ab90e..f2de80228 100644
--- a/src/com/android/documentsui/Model.java
+++ b/src/com/android/documentsui/Model.java
@@ -23,7 +23,6 @@ import static com.android.documentsui.base.SharedMinimal.VERBOSE;
import androidx.annotation.IntDef;
import android.app.AuthenticationRequiredException;
import android.database.Cursor;
-import android.database.MergeCursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.DocumentsContract;
@@ -38,7 +37,6 @@ import com.android.documentsui.base.DocumentFilters;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.EventListener;
import com.android.documentsui.base.Features;
-import com.android.documentsui.roots.RootCursorWrapper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -75,6 +73,7 @@ public class Model {
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;
@@ -112,6 +111,7 @@ public class Model {
mIsLoading = false;
mFileNames.clear();
mDocumentsToBeDeleted.clear();
+ mDeletionFailedDocIds.clear();
notifyUpdateListeners();
}
@@ -152,7 +152,7 @@ public class Model {
notifyUpdateListeners();
}
- public void restoreDocumentsToBeDeleted(Selection<String> selection) {
+ public void clearDocumentsToBeDeleted(Selection<String> selection) {
if (!mDocumentsToBeDeleted.contains(selection)) {
return;
}
@@ -161,27 +161,60 @@ public class Model {
notifyUpdateListeners();
}
- private boolean isDocumentToBeDeleted(String id) {
- for (Selection<String> s : mDocumentsToBeDeleted) {
- if (s.contains(id)) {
- return true;
- }
+ public void setDeletionFailedUris(Selection<String> selection,
+ ArrayList<Uri> deletionFailedUris) {
+ if (!mDocumentsToBeDeleted.contains(selection)) {
+ return;
}
- return false;
+
+ 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) {
- if (!mPositions.containsKey(id)) {
+ // 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) {
@@ -211,21 +244,15 @@ 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.
- // If the cursor is a merged cursor over multiple authorities, then prefix the ids
- // with the authority to avoid collisions.
- if (mCursor instanceof MergeCursor) {
- tmpIds[pos] = getCursorString(mCursor, RootCursorWrapper.COLUMN_AUTHORITY)
- + "|" + getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
- } else {
- tmpIds[pos] = getCursorString(mCursor, Document.COLUMN_DOCUMENT_ID);
- }
+ // Prefix the ids with the authority to avoid collisions.
+ tmpIds[pos] = ModelId.build(mCursor);
mPositions.put(tmpIds[pos], pos);
mFileNames.add(getCursorString(mCursor, Document.COLUMN_DISPLAY_NAME));
}
updateDocumentsToBeDeleted();
- mIds = new String[mCursorCount - getDocumentsToBeDeletedCount()];
+ mIds = new String[getVisibleCount()];
int index = 0;
for (int i = 0; i < mCursorCount; ++i) {
if (!isDocumentToBeDeleted(tmpIds[i])) {
diff --git a/src/com/android/documentsui/ModelId.java b/src/com/android/documentsui/ModelId.java
new file mode 100644
index 000000000..e178c6318
--- /dev/null
+++ b/src/com/android/documentsui/ModelId.java
@@ -0,0 +1,68 @@
+package com.android.documentsui;
+
+import static com.android.documentsui.base.DocumentInfo.getCursorString;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.util.Log;
+
+import com.android.documentsui.base.DocumentInfo;
+import com.android.documentsui.roots.RootCursorWrapper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ModelId {
+ private final static String TAG = "ModelId";
+
+ public static final String build(Uri uri) {
+ String documentId;
+ try {
+ documentId = DocumentsContract.getDocumentId(uri);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Failed to get document id.", e);
+ return null;
+ }
+ String authority;
+ authority = uri.getAuthority();
+ return ModelId.build(authority, documentId);
+ }
+
+ public static final String build(DocumentInfo docInfo) {
+ if (docInfo == null) {
+ return null;
+ }
+ return ModelId.build(docInfo.authority, docInfo.documentId);
+ }
+
+ public static final String build(Cursor cursor) {
+ if (cursor == null) {
+ return null;
+ }
+ return ModelId.build(getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY),
+ getCursorString(cursor, DocumentsContract.Document.COLUMN_DOCUMENT_ID));
+ }
+
+ public static final ArrayList<String> build(ArrayList<Uri> uris) {
+ if (uris == null || uris.isEmpty()) {
+ return null;
+ }
+ ArrayList<String> ids = new ArrayList<>();
+ String id;
+ for (Uri uri : uris) {
+ id = ModelId.build(uri);
+ if (id != null) {
+ ids.add(id);
+ }
+ }
+ return ids;
+ }
+
+ public static final String build(String authority, String docId) {
+ if (authority == null || authority.isEmpty() || docId == null || docId.isEmpty()) {
+ return null;
+ }
+ return authority + "|" + docId;
+ }
+}
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index 347bdc845..c1c280c53 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -25,6 +25,8 @@ 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;
@@ -64,6 +66,7 @@ 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;
@@ -298,7 +301,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
@Override
public void deleteSelectedDocuments() {
Metrics.logUserAction(mActivity, Metrics.USER_ACTION_DELETE);
- Selection<String> selection = getSelectedOrFocused();
+ final Selection<String> selection = getSelectedOrFocused();
if (selection.isEmpty()) {
return;
@@ -323,7 +326,7 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
mModel.markDocumentsToBeDeleted(selection);
Consumer<View> action = v -> {
Metrics.logUserAction(mActivity, Metrics.USER_ACTION_UNDO_DELETE);
- mModel.restoreDocumentsToBeDeleted(selection);
+ mModel.clearDocumentsToBeDeleted(selection);
};
Snackbar.Callback callback = new Snackbar.Callback() {
@Override
@@ -336,9 +339,31 @@ public class ActionHandler<T extends Activity & Addons> extends AbstractActionHa
.withSrcs(srcs)
.withSrcParent(srcParent == null ? null : srcParent.derivedUri)
.build();
-
- FileOperations.start(mActivity, operation, null,
- FileOperations.createJobId());
+ 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;
diff --git a/src/com/android/documentsui/services/DeleteJob.java b/src/com/android/documentsui/services/DeleteJob.java
index 041eba721..04f1955b8 100644
--- a/src/com/android/documentsui/services/DeleteJob.java
+++ b/src/com/android/documentsui/services/DeleteJob.java
@@ -17,6 +17,7 @@
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;
@@ -24,6 +25,10 @@ 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;
@@ -34,16 +39,22 @@ import com.android.documentsui.base.Features;
import com.android.documentsui.clipping.UrisSupplier;
import java.io.FileNotFoundException;
+import java.util.ArrayList;
import javax.annotation.Nullable;
-final class DeleteJob extends ResolvedResourcesJob {
+public 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;
+
/**
* Moves files to a destination identified by {@code destination}.
* Performs most work by delegating to CopyJob, then deleting
@@ -52,9 +63,10 @@ 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, @Nullable Uri srcParent, Features features) {
+ UrisSupplier srcs, Messenger messenger, @Nullable Uri srcParent, Features features) {
super(service, listener, id, OPERATION_DELETE, stack, srcs, features);
mParentUri = srcParent;
+ mMessenger = messenger;
}
@Override
@@ -130,6 +142,29 @@ final class DeleteJob extends ResolvedResourcesJob {
}
@Override
+ void finish() {
+ super.finish();
+ try {
+ Message message = Message.obtain();
+ message.what = MESSAGE_FINISH;
+ message.arg1 = failureCount;
+ if (failureCount > 0 && failureCount < mResourceUris.getItemCount()) {
+ Bundle b = new Bundle();
+ ArrayList<Uri> uris = new ArrayList<>();
+ uris.addAll(failedUris);
+ for (DocumentInfo documentInfo : failedDocs) {
+ uris.add(documentInfo.derivedUri);
+ }
+ b.putParcelableArrayList(KEY_FAILED_URIS, uris);
+ 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 29393a81e..9ff16543d 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(),
- mSrcParent, features);
+ getMessenger(), mSrcParent, features);
default:
throw new UnsupportedOperationException("Unsupported op type: " + getOpType());
}
diff --git a/tests/common/com/android/documentsui/testing/TestEnv.java b/tests/common/com/android/documentsui/testing/TestEnv.java
index 93cc032a1..d30453649 100644
--- a/tests/common/com/android/documentsui/testing/TestEnv.java
+++ b/tests/common/com/android/documentsui/testing/TestEnv.java
@@ -23,6 +23,7 @@ import android.test.mock.MockContentResolver;
import com.android.documentsui.DocsSelectionHelper;
import com.android.documentsui.FocusManager;
import com.android.documentsui.Injector;
+import com.android.documentsui.ModelId;
import com.android.documentsui.SelectionHelpers;
import com.android.documentsui.archives.ArchivesProvider;
import com.android.documentsui.base.DocumentInfo;
@@ -172,7 +173,8 @@ public class TestEnv {
}
public void populateStack() {
- DocumentInfo rootDoc = model.getDocument("1");
+ DocumentInfo rootDoc = model.getDocument(
+ ModelId.build(TestProvidersAccess.HOME.authority, "1"));
// These are test setup sanity checks, not test assertions.
assert rootDoc != null;
@@ -193,7 +195,7 @@ public class TestEnv {
public void selectDocument(DocumentInfo info) {
List<String> ids = new ArrayList<>(1);
- ids.add(info.documentId);
+ ids.add(ModelId.build(info.authority, info.documentId));
selectionMgr.setItemsSelected(ids, true);
}
diff --git a/tests/unit/com/android/documentsui/ModelTest.java b/tests/unit/com/android/documentsui/ModelTest.java
index 8d3a33ba2..7d46f189d 100644
--- a/tests/unit/com/android/documentsui/ModelTest.java
+++ b/tests/unit/com/android/documentsui/ModelTest.java
@@ -22,10 +22,13 @@ 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;
@@ -35,7 +38,10 @@ 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)
@@ -180,4 +186,44 @@ 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/dirlist/DirectoryAddonsAdapterTest.java b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
index 0c26623f2..06912e13d 100644
--- a/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java
@@ -27,6 +27,7 @@ import android.view.ViewGroup;
import com.android.documentsui.ActionHandler;
import com.android.documentsui.Model;
+import com.android.documentsui.ModelId;
import com.android.documentsui.base.Features;
import com.android.documentsui.base.State;
import com.android.documentsui.testing.TestActionHandler;
@@ -71,10 +72,10 @@ public class DirectoryAddonsAdapterTest extends AndroidTestCase {
mEnv.model.createFile("b"); // id will be "2"
mEnv.model.update();
- assertEquals(0, mAdapter.getPosition("1"));
+ assertEquals(0, mAdapter.getPosition(ModelId.build(AUTHORITY, "1")));
// adapter inserts a view between item 0 and 1 to force layout
// break between folders and files. This is reflected by an offset position.
- assertEquals(2, mAdapter.getPosition("2"));
+ assertEquals(2, mAdapter.getPosition(ModelId.build(AUTHORITY, "2")));
}
// Tests that the item count is correct for a directory containing only subdirs.
diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
index 00ca81bf0..4dfc64223 100644
--- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
@@ -51,6 +51,7 @@ import android.view.DragEvent;
import android.view.View;
import com.android.documentsui.AbstractActionHandler;
+import com.android.documentsui.ModelId;
import com.android.documentsui.R;
import com.android.documentsui.TestActionModeAddons;
import com.android.documentsui.archives.ArchivesProvider;
@@ -525,7 +526,8 @@ public class ActionHandlerTest {
public void testRefresh() throws Exception {
refreshAnswer = false;
mEnv.populateStack();
- mHandler.refreshDocument(mEnv.model.getDocument("1"), (boolean answer) -> {
+ mHandler.refreshDocument(mEnv.model.getDocument(
+ ModelId.build(TestProvidersAccess.HOME.authority, "1")), (boolean answer) -> {
refreshAnswer = answer;
});
diff --git a/tests/unit/com/android/documentsui/services/AbstractJobTest.java b/tests/unit/com/android/documentsui/services/AbstractJobTest.java
index 7ca913dda..ce850dd9c 100644
--- a/tests/unit/com/android/documentsui/services/AbstractJobTest.java
+++ b/tests/unit/com/android/documentsui/services/AbstractJobTest.java
@@ -94,6 +94,26 @@ public abstract class AbstractJobTest<T extends Job> extends AndroidTestCase {
mDestRoot = mDocs.getRoot(ROOT_1_ID);
}
+ FileOperation createOperation(@OpType int opType, List<Uri> srcs, Uri srcParent,
+ Uri destination) throws Exception {
+ DocumentStack stack =
+ new DocumentStack(mSrcRoot, DocumentInfo.fromUri(mResolver, destination));
+
+ UrisSupplier urisSupplier = DocsProviders.createDocsProvider(srcs);
+ FileOperation operation = new FileOperation.Builder()
+ .withOpType(opType)
+ .withSrcs(urisSupplier)
+ .withDestination(stack)
+ .withSrcParent(srcParent)
+ .build();
+ return operation;
+ }
+
+ final T createJob(FileOperation operation) {
+ return (T) operation.createJob(
+ mContext, mJobListener, FileOperations.createJobId(), mFeatures);
+ }
+
final T createJob(@OpType int opType, List<Uri> srcs, Uri srcParent, Uri destination)
throws Exception {
DocumentStack stack =
diff --git a/tests/unit/com/android/documentsui/services/DeleteJobTest.java b/tests/unit/com/android/documentsui/services/DeleteJobTest.java
index 0d8d39be8..e02b94aea 100644
--- a/tests/unit/com/android/documentsui/services/DeleteJobTest.java
+++ b/tests/unit/com/android/documentsui/services/DeleteJobTest.java
@@ -21,10 +21,15 @@ 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;
@MediumTest
public class DeleteJobTest extends AbstractJobTest<DeleteJob> {
@@ -56,6 +61,43 @@ 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);
+
+ for (Uri uri : deletionFailedUris) {
+ assertTrue("Not receiving failed uri:" + uri, deletionFailedUris.contains(uri));
+ }
+ }
+
/**
* Creates a job with a stack consisting to the default src directory.
*/