Merge "[multi-part] Eliminate 1k selection limit"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/ClipDetails.java b/packages/DocumentsUI/src/com/android/documentsui/ClipDetails.java
deleted file mode 100644
index 6cd0353..0000000
--- a/packages/DocumentsUI/src/com/android/documentsui/ClipDetails.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2016 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.DocumentClipper.OP_JUMBO_SELECTION_SIZE;
-import static com.android.documentsui.DocumentClipper.OP_JUMBO_SELECTION_TAG;
-import static com.android.documentsui.DocumentClipper.OP_TYPE_KEY;
-import static com.android.documentsui.DocumentClipper.SRC_PARENT_KEY;
-
-import android.annotation.CallSuper;
-import android.annotation.Nullable;
-import android.content.ClipData;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
-import com.android.documentsui.services.FileOperationService;
-import com.android.documentsui.services.FileOperationService.OpType;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.function.Function;
-
-/**
- * ClipDetails is a parcelable project providing information of different type of file
- * management operations like cut, move and copy.
- *
- * Under the hood it provides cross-process synchronization support such that its consumer doesn't
- * need to explicitly synchronize its access.
- */
-public abstract class ClipDetails implements Parcelable {
- private final @OpType int mOpType;
-
- // This field is used only for moving and deleting. Currently it's not the case,
- // but in the future those files may be from multiple different parents. In
- // such case, this needs to be replaced with pairs of parent and child.
- private final @Nullable Uri mSrcParent;
-
- private ClipDetails(ClipData clipData) {
- PersistableBundle bundle = clipData.getDescription().getExtras();
- mOpType = bundle.getInt(OP_TYPE_KEY);
-
- String srcParentString = bundle.getString(SRC_PARENT_KEY);
- mSrcParent = (srcParentString == null) ? null : Uri.parse(srcParentString);
-
- // Only copy doesn't need src parent
- assert(mOpType == FileOperationService.OPERATION_COPY || mSrcParent != null);
- }
-
- private ClipDetails(@OpType int opType, @Nullable Uri srcParent) {
- mOpType = opType;
- mSrcParent = srcParent;
-
- // Only copy doesn't need src parent
- assert(mOpType == FileOperationService.OPERATION_COPY || mSrcParent != null);
- }
-
- public @OpType int getOpType() {
- return mOpType;
- }
-
- public @Nullable Uri getSrcParent() {
- return mSrcParent;
- }
-
- public abstract int getItemCount();
-
- /**
- * Gets doc list from this clip detail. This may only be called once because it may read a file
- * to get the list.
- */
- public Iterable<Uri> getDocs(Context context) throws IOException {
- ClipStorage storage = DocumentsApplication.getClipStorage(context);
-
- return getDocs(storage);
- }
-
- @VisibleForTesting
- abstract Iterable<Uri> getDocs(ClipStorage storage) throws IOException;
-
- public void dispose(Context context) {
- ClipStorage storage = DocumentsApplication.getClipStorage(context);
- dispose(storage);
- }
-
- @VisibleForTesting
- void dispose(ClipStorage storage) {}
-
- private ClipDetails(Parcel in) {
- mOpType = in.readInt();
- mSrcParent = in.readParcelable(ClassLoader.getSystemClassLoader());
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @CallSuper
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mOpType);
- dest.writeParcelable(mSrcParent, 0);
- }
-
- private void appendTo(StringBuilder builder) {
- builder.append("opType=").append(mOpType);
- builder.append(", srcParent=").append(mSrcParent);
- }
-
- public static ClipDetails createClipDetails(ClipData clipData) {
- ClipDetails details;
- PersistableBundle bundle = clipData.getDescription().getExtras();
- if (bundle.containsKey(OP_JUMBO_SELECTION_TAG)) {
- details = new JumboClipDetails(clipData);
- } else {
- details = new StandardClipDetails(clipData);
- }
-
- return details;
- }
-
- public static ClipDetails createClipDetails(@OpType int opType, @Nullable Uri srcParent,
- Selection selection, Function<String, Uri> uriBuilder, Context context) {
- ClipStorage storage = DocumentsApplication.getClipStorage(context);
-
- List<Uri> uris = new ArrayList<>(selection.size());
- for (String id : selection) {
- uris.add(uriBuilder.apply(id));
- }
-
- return createClipDetails(opType, srcParent, uris, storage);
- }
-
- @VisibleForTesting
- static ClipDetails createClipDetails(@OpType int opType, @Nullable Uri srcParent,
- List<Uri> uris, ClipStorage storage) {
- ClipDetails details = (uris.size() > Shared.MAX_DOCS_IN_INTENT)
- ? new JumboClipDetails(opType, srcParent, uris, storage)
- : new StandardClipDetails(opType, srcParent, uris);
-
- return details;
- }
-
- private static class JumboClipDetails extends ClipDetails {
- private static final String TAG = "JumboClipDetails";
-
- private final long mSelectionTag;
- private final int mSelectionSize;
-
- private transient ClipStorage.Reader mReader;
-
- private JumboClipDetails(ClipData clipData) {
- super(clipData);
-
- PersistableBundle bundle = clipData.getDescription().getExtras();
- mSelectionTag = bundle.getLong(OP_JUMBO_SELECTION_TAG, ClipStorage.NO_SELECTION_TAG);
- assert(mSelectionTag != ClipStorage.NO_SELECTION_TAG);
-
- mSelectionSize = bundle.getInt(OP_JUMBO_SELECTION_SIZE);
- assert(mSelectionSize > Shared.MAX_DOCS_IN_INTENT);
- }
-
- private JumboClipDetails(@OpType int opType, @Nullable Uri srcParent, Collection<Uri> uris,
- ClipStorage storage) {
- super(opType, srcParent);
-
- mSelectionTag = storage.createTag();
- new ClipStorage.PersistTask(storage, uris, mSelectionTag).execute();
- mSelectionSize = uris.size();
- }
-
- @Override
- public int getItemCount() {
- return mSelectionSize;
- }
-
- @Override
- public Iterable<Uri> getDocs(ClipStorage storage) throws IOException {
- if (mReader != null) {
- throw new IllegalStateException(
- "JumboClipDetails#getDocs() can only be called once.");
- }
-
- mReader = storage.createReader(mSelectionTag);
-
- return mReader;
- }
-
- @Override
- void dispose(ClipStorage storage) {
- if (mReader != null) {
- try {
- mReader.close();
- } catch (IOException e) {
- Log.w(TAG, "Failed to close the reader.", e);
- }
- }
- try {
- storage.delete(mSelectionTag);
- } catch(IOException e) {
- Log.w(TAG, "Failed to delete clip with tag: " + mSelectionTag + ".", e);
- }
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("JumboClipDetails{");
- super.appendTo(builder);
- builder.append(", selectionTag=").append(mSelectionTag);
- builder.append(", selectionSize=").append(mSelectionSize);
- builder.append("}");
- return builder.toString();
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
-
- dest.writeLong(mSelectionTag);
- dest.writeInt(mSelectionSize);
- }
-
- private JumboClipDetails(Parcel in) {
- super(in);
-
- mSelectionTag = in.readLong();
- mSelectionSize = in.readInt();
- }
-
- public static final Parcelable.Creator<JumboClipDetails> CREATOR =
- new Parcelable.Creator<JumboClipDetails>() {
-
- @Override
- public JumboClipDetails createFromParcel(Parcel source) {
- return new JumboClipDetails(source);
- }
-
- @Override
- public JumboClipDetails[] newArray(int size) {
- return new JumboClipDetails[size];
- }
- };
- }
-
- @VisibleForTesting
- public static class StandardClipDetails extends ClipDetails {
- private final List<Uri> mDocs;
-
- private StandardClipDetails(ClipData clipData) {
- super(clipData);
- mDocs = listDocs(clipData);
- }
-
- @VisibleForTesting
- public StandardClipDetails(@OpType int opType, @Nullable Uri srcParent, List<Uri> docs) {
- super(opType, srcParent);
-
- mDocs = docs;
- }
-
- private List<Uri> listDocs(ClipData clipData) {
- ArrayList<Uri> docs = new ArrayList<>(clipData.getItemCount());
-
- for (int i = 0; i < clipData.getItemCount(); ++i) {
- Uri uri = clipData.getItemAt(i).getUri();
- assert(uri != null);
- docs.add(uri);
- }
-
- return docs;
- }
-
- @Override
- public int getItemCount() {
- return mDocs.size();
- }
-
- @Override
- public Iterable<Uri> getDocs(ClipStorage storage) {
- return mDocs;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("StandardClipDetails{");
- super.appendTo(builder);
- builder.append(", ").append("docs=").append(mDocs.toString());
- builder.append("}");
- return builder.toString();
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
-
- dest.writeTypedList(mDocs);
- }
-
- private StandardClipDetails(Parcel in) {
- super(in);
-
- mDocs = in.createTypedArrayList(Uri.CREATOR);
- }
-
- public static final Parcelable.Creator<StandardClipDetails> CREATOR =
- new Parcelable.Creator<StandardClipDetails>() {
-
- @Override
- public StandardClipDetails createFromParcel(Parcel source) {
- return new StandardClipDetails(source);
- }
-
- @Override
- public StandardClipDetails[] newArray(int size) {
- return new StandardClipDetails[size];
- }
- };
- }
-}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java
index 4c103c4..72413bd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java
@@ -32,6 +32,7 @@
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.services.FileOperation;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService.OpType;
import com.android.documentsui.services.FileOperations;
@@ -349,28 +350,35 @@
return;
}
- ClipDetails details = ClipDetails.createClipDetails(clipData);
+ PersistableBundle bundle = clipData.getDescription().getExtras();
+ @OpType int opType = getOpType(bundle);
+ UrisSupplier uris = UrisSupplier.create(clipData);
if (!canCopy(destination)) {
callback.onOperationResult(
- FileOperations.Callback.STATUS_REJECTED, details.getOpType(), 0);
+ FileOperations.Callback.STATUS_REJECTED, opType, 0);
return;
}
- if (details.getItemCount() == 0) {
+ if (uris.getItemCount() == 0) {
callback.onOperationResult(
- FileOperations.Callback.STATUS_ACCEPTED, details.getOpType(), 0);
+ FileOperations.Callback.STATUS_ACCEPTED, opType, 0);
return;
}
- DocumentStack dstStack = new DocumentStack();
- dstStack.push(destination);
- dstStack.addAll(docStack);
+ DocumentStack dstStack = new DocumentStack(docStack, destination);
- // Pass root here so that we can perform "download" root check when
- dstStack.root = docStack.root;
+ String srcParentString = bundle.getString(SRC_PARENT_KEY);
+ Uri srcParent = srcParentString == null ? null : Uri.parse(srcParentString);
- FileOperations.start(mContext, details, dstStack, callback);
+ FileOperation operation = new FileOperation.Builder()
+ .withOpType(opType)
+ .withSrcParent(srcParent)
+ .withDestination(dstStack)
+ .withSrcs(uris)
+ .build();
+
+ FileOperations.start(mContext, operation, callback);
}
/**
@@ -399,8 +407,24 @@
}
ClipDescription description = data.getDescription();
+ if (description == null) {
+ return ClipStorage.NO_SELECTION_TAG;
+ }
+
BaseBundle bundle = description.getExtras();
+ if (bundle == null) {
+ return ClipStorage.NO_SELECTION_TAG;
+ }
+
return bundle.getLong(OP_JUMBO_SELECTION_TAG, ClipStorage.NO_SELECTION_TAG);
}
+ public static @OpType int getOpType(ClipData data) {
+ PersistableBundle bundle = data.getDescription().getExtras();
+ return getOpType(bundle);
+ }
+
+ private static @OpType int getOpType(PersistableBundle bundle) {
+ return bundle.getInt(OP_TYPE_KEY);
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index b8559bc1..0a518cd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -41,7 +41,6 @@
import android.support.design.widget.Snackbar;
import android.util.Log;
import android.view.Menu;
-import android.view.MenuItem;
import com.android.documentsui.MenuManager.DirectoryDetails;
import com.android.documentsui.RecentsProvider.RecentColumns;
@@ -156,7 +155,7 @@
state.directoryCopy = intent.getBooleanExtra(
Shared.EXTRA_DIRECTORY_COPY, false);
state.copyOperationSubType = intent.getIntExtra(
- FileOperationService.EXTRA_OPERATION,
+ FileOperationService.EXTRA_OPERATION_TYPE,
FileOperationService.OPERATION_COPY);
}
}
@@ -386,7 +385,7 @@
// Picking a copy destination is only used internally by us, so we
// don't need to extend permissions to the caller.
intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
- intent.putExtra(FileOperationService.EXTRA_OPERATION, mState.copyOperationSubType);
+ intent.putExtra(FileOperationService.EXTRA_OPERATION_TYPE, mState.copyOperationSubType);
} else {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index f82bdf1..1edfffe 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -139,7 +139,7 @@
// Only show it manually for the first time (icicle is null).
if (icicle == null && dialogType != DIALOG_TYPE_UNKNOWN) {
final int opType = intent.getIntExtra(
- FileOperationService.EXTRA_OPERATION,
+ FileOperationService.EXTRA_OPERATION_TYPE,
FileOperationService.OPERATION_COPY);
final ArrayList<DocumentInfo> srcList =
intent.getParcelableArrayListExtra(FileOperationService.EXTRA_SRC_LIST);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/OperationDialogFragment.java b/packages/DocumentsUI/src/com/android/documentsui/OperationDialogFragment.java
index 9a3f7a8..140baad 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/OperationDialogFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/OperationDialogFragment.java
@@ -60,7 +60,7 @@
@OpType int operationType) {
final Bundle args = new Bundle();
args.putInt(FileOperationService.EXTRA_DIALOG_TYPE, dialogType);
- args.putInt(FileOperationService.EXTRA_OPERATION, operationType);
+ args.putInt(FileOperationService.EXTRA_OPERATION_TYPE, operationType);
args.putParcelableArrayList(FileOperationService.EXTRA_SRC_LIST, failedSrcList);
final FragmentTransaction ft = fm.beginTransaction();
@@ -78,7 +78,7 @@
final @DialogType int dialogType =
getArguments().getInt(FileOperationService.EXTRA_DIALOG_TYPE);
final @OpType int operationType =
- getArguments().getInt(FileOperationService.EXTRA_OPERATION);
+ getArguments().getInt(FileOperationService.EXTRA_OPERATION_TYPE);
final ArrayList<DocumentInfo> srcList = getArguments().getParcelableArrayList(
FileOperationService.EXTRA_SRC_LIST);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/UrisSupplier.java b/packages/DocumentsUI/src/com/android/documentsui/UrisSupplier.java
new file mode 100644
index 0000000..c5d30aa
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/UrisSupplier.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2016 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.DocumentClipper.OP_JUMBO_SELECTION_SIZE;
+import static com.android.documentsui.DocumentClipper.OP_JUMBO_SELECTION_TAG;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.services.FileOperation;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+
+/**
+ * UrisSupplier provides doc uri list to {@link FileOperation}.
+ *
+ * <p>Under the hood it provides cross-process synchronization support such that its consumer doesn't
+ * need to explicitly synchronize its access.
+ */
+public abstract class UrisSupplier implements Parcelable {
+
+ public abstract int getItemCount();
+
+ /**
+ * Gets doc list. This may only be called once because it may read a file
+ * to get the list.
+ *
+ * @param context We need context to obtain {@link ClipStorage}. It can't be sent in a parcel.
+ */
+ public Iterable<Uri> getDocs(Context context) throws IOException {
+ return getDocs(DocumentsApplication.getClipStorage(context));
+ }
+
+ @VisibleForTesting
+ abstract Iterable<Uri> getDocs(ClipStorage storage) throws IOException;
+
+ public void dispose(Context context) {
+ ClipStorage storage = DocumentsApplication.getClipStorage(context);
+ dispose(storage);
+ }
+
+ @VisibleForTesting
+ void dispose(ClipStorage storage) {}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static UrisSupplier create(ClipData clipData) {
+ UrisSupplier uris;
+ PersistableBundle bundle = clipData.getDescription().getExtras();
+ if (bundle.containsKey(OP_JUMBO_SELECTION_TAG)) {
+ uris = new JumboUrisSupplier(clipData);
+ } else {
+ uris = new StandardUrisSupplier(clipData);
+ }
+
+ return uris;
+ }
+
+ public static UrisSupplier create(
+ Selection selection, Function<String, Uri> uriBuilder, Context context) {
+ ClipStorage storage = DocumentsApplication.getClipStorage(context);
+
+ List<Uri> uris = new ArrayList<>(selection.size());
+ for (String id : selection) {
+ uris.add(uriBuilder.apply(id));
+ }
+
+ return create(uris, storage);
+ }
+
+ @VisibleForTesting
+ static UrisSupplier create(List<Uri> uris, ClipStorage storage) {
+ UrisSupplier urisSupplier = (uris.size() > Shared.MAX_DOCS_IN_INTENT)
+ ? new JumboUrisSupplier(uris, storage)
+ : new StandardUrisSupplier(uris);
+
+ return urisSupplier;
+ }
+
+ private static class JumboUrisSupplier extends UrisSupplier {
+ private static final String TAG = "JumboUrisSupplier";
+
+ private final long mSelectionTag;
+ private final int mSelectionSize;
+
+ private final transient AtomicReference<ClipStorage.Reader> mReader =
+ new AtomicReference<>();
+
+ private JumboUrisSupplier(ClipData clipData) {
+ PersistableBundle bundle = clipData.getDescription().getExtras();
+ mSelectionTag = bundle.getLong(OP_JUMBO_SELECTION_TAG, ClipStorage.NO_SELECTION_TAG);
+ assert(mSelectionTag != ClipStorage.NO_SELECTION_TAG);
+
+ mSelectionSize = bundle.getInt(OP_JUMBO_SELECTION_SIZE);
+ assert(mSelectionSize > Shared.MAX_DOCS_IN_INTENT);
+ }
+
+ private JumboUrisSupplier(Collection<Uri> uris, ClipStorage storage) {
+ mSelectionTag = storage.createTag();
+ new ClipStorage.PersistTask(storage, uris, mSelectionTag).execute();
+ mSelectionSize = uris.size();
+ }
+
+ @Override
+ public int getItemCount() {
+ return mSelectionSize;
+ }
+
+ @Override
+ Iterable<Uri> getDocs(ClipStorage storage) throws IOException {
+ ClipStorage.Reader reader = mReader.getAndSet(storage.createReader(mSelectionTag));
+ if (reader != null) {
+ reader.close();
+ mReader.get().close();
+ throw new IllegalStateException("This method can only be called once.");
+ }
+
+ return mReader.get();
+ }
+
+ @Override
+ void dispose(ClipStorage storage) {
+ try {
+ ClipStorage.Reader reader = mReader.get();
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to close the reader.", e);
+ }
+ try {
+ storage.delete(mSelectionTag);
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to delete clip with tag: " + mSelectionTag + ".", e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("JumboUrisSupplier{");
+ builder.append("selectionTag=").append(mSelectionTag);
+ builder.append(", selectionSize=").append(mSelectionSize);
+ builder.append("}");
+ return builder.toString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mSelectionTag);
+ dest.writeInt(mSelectionSize);
+ }
+
+ private JumboUrisSupplier(Parcel in) {
+ mSelectionTag = in.readLong();
+ mSelectionSize = in.readInt();
+ }
+
+ public static final Parcelable.Creator<JumboUrisSupplier> CREATOR =
+ new Parcelable.Creator<JumboUrisSupplier>() {
+
+ @Override
+ public JumboUrisSupplier createFromParcel(Parcel source) {
+ return new JumboUrisSupplier(source);
+ }
+
+ @Override
+ public JumboUrisSupplier[] newArray(int size) {
+ return new JumboUrisSupplier[size];
+ }
+ };
+ }
+
+ /**
+ * This class and its constructor is visible for testing to create test doubles of
+ * {@link UrisSupplier}.
+ */
+ @VisibleForTesting
+ public static class StandardUrisSupplier extends UrisSupplier {
+ private final List<Uri> mDocs;
+
+ private StandardUrisSupplier(ClipData clipData) {
+ mDocs = listDocs(clipData);
+ }
+
+ @VisibleForTesting
+ public StandardUrisSupplier(List<Uri> docs) {
+ mDocs = docs;
+ }
+
+ private List<Uri> listDocs(ClipData clipData) {
+ ArrayList<Uri> docs = new ArrayList<>(clipData.getItemCount());
+
+ for (int i = 0; i < clipData.getItemCount(); ++i) {
+ Uri uri = clipData.getItemAt(i).getUri();
+ assert(uri != null);
+ docs.add(uri);
+ }
+
+ return docs;
+ }
+
+ @Override
+ public int getItemCount() {
+ return mDocs.size();
+ }
+
+ @Override
+ Iterable<Uri> getDocs(ClipStorage storage) {
+ return mDocs;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("StandardUrisSupplier{");
+ builder.append("docs=").append(mDocs.toString());
+ builder.append("}");
+ return builder.toString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mDocs);
+ }
+
+ private StandardUrisSupplier(Parcel in) {
+ mDocs = in.createTypedArrayList(Uri.CREATOR);
+ }
+
+ public static final Parcelable.Creator<StandardUrisSupplier> CREATOR =
+ new Parcelable.Creator<StandardUrisSupplier>() {
+
+ @Override
+ public StandardUrisSupplier createFromParcel(Parcel source) {
+ return new StandardUrisSupplier(source);
+ }
+
+ @Override
+ public StandardUrisSupplier[] newArray(int size) {
+ return new StandardUrisSupplier[size];
+ }
+ };
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index f96341a..86c6c99 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -72,9 +72,9 @@
import android.widget.Toolbar;
import com.android.documentsui.BaseActivity;
-import com.android.documentsui.ClipDetails;
import com.android.documentsui.DirectoryLoader;
import com.android.documentsui.DirectoryResult;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.DocumentClipper;
import com.android.documentsui.DocumentsActivity;
import com.android.documentsui.DocumentsApplication;
@@ -97,6 +97,7 @@
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperation;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperationService.OpType;
import com.android.documentsui.services.FileOperations;
@@ -172,7 +173,7 @@
private @Nullable Selection mRestoredSelection = null;
// Here we save the clip details of moveTo/copyTo actions when picker shows up.
// This will be written to saved instance.
- private @Nullable ClipDetails mDetailsForCopy;
+ private @Nullable FileOperation mPendingOperation;
private boolean mSearchMode = false;
private @Nullable BandController mBandController;
@@ -269,7 +270,7 @@
mQuery = args.getString(Shared.EXTRA_QUERY);
mType = args.getInt(Shared.EXTRA_TYPE);
mSearchMode = args.getBoolean(Shared.EXTRA_SEARCH_MODE);
- mDetailsForCopy = args.getParcelable(FileOperationService.EXTRA_CLIP_DETAILS);
+ mPendingOperation = args.getParcelable(FileOperationService.EXTRA_OPERATION);
// Restore any selection we may have squirreled away in retained state.
@Nullable RetainedState retained = getBaseActivity().getRetainedState();
@@ -359,7 +360,7 @@
outState.putParcelable(Shared.EXTRA_DOC, mDocument);
outState.putString(Shared.EXTRA_QUERY, mQuery);
outState.putBoolean(Shared.EXTRA_SEARCH_MODE, mSearchMode);
- outState.putParcelable(FileOperationService.EXTRA_CLIP_DETAILS, mDetailsForCopy);
+ outState.putParcelable(FileOperationService.EXTRA_OPERATION, mPendingOperation);
}
@Override
@@ -400,21 +401,19 @@
private void handleCopyResult(int resultCode, Intent data) {
- ClipDetails details = mDetailsForCopy;
- mDetailsForCopy = null;
+ FileOperation operation = mPendingOperation;
+ mPendingOperation = null;
if (resultCode == Activity.RESULT_CANCELED || data == null) {
// User pressed the back button or otherwise cancelled the destination pick. Don't
// proceed with the copy.
- details.dispose(getContext());
+ operation.dispose(getContext());
return;
}
- FileOperations.start(
- getContext(),
- details,
- data.getParcelableExtra(Shared.EXTRA_STACK),
- mFileOpCallback);
+ operation.setDestination(data.getParcelableExtra(Shared.EXTRA_STACK));
+
+ FileOperations.start(getContext(), operation, mFileOpCallback);
}
protected boolean onDoubleTap(MotionInputEvent event) {
@@ -1010,14 +1009,19 @@
Log.w(TAG, "Action mode is null before deleting documents.");
}
- ClipDetails details = ClipDetails.createClipDetails(
- FileOperationService.OPERATION_DELETE,
- srcParent.derivedUri,
+ UrisSupplier srcs = UrisSupplier.create(
selected,
mModel::getItemUri,
getContext());
- FileOperations.start(getActivity(), details,
- getDisplayState().stack, mFileOpCallback);
+
+ FileOperation operation = new FileOperation.Builder()
+ .withOpType(FileOperationService.OPERATION_DELETE)
+ .withDestination(getDisplayState().stack)
+ .withSrcs(srcs)
+ .withSrcParent(srcParent.derivedUri)
+ .build();
+
+ FileOperations.start(getActivity(), operation, mFileOpCallback);
}
})
.setNegativeButton(android.R.string.cancel, null)
@@ -1041,9 +1045,15 @@
getActivity(),
DocumentsActivity.class);
+ UrisSupplier srcs =
+ UrisSupplier.create(selected, mModel::getItemUri, getContext());
+
Uri srcParent = getDisplayState().stack.peek().derivedUri;
- mDetailsForCopy = ClipDetails.createClipDetails(
- mode, srcParent, selected, mModel::getItemUri, getContext());
+ mPendingOperation = new FileOperation.Builder()
+ .withOpType(mode)
+ .withSrcParent(srcParent)
+ .withSrcs(srcs)
+ .build();
// Relay any config overrides bits present in the original intent.
Intent original = getActivity().getIntent();
@@ -1068,6 +1078,7 @@
// (like Downloads). This informs DocumentsActivity (the "picker")
// to restrict available roots to just those with support.
intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, hasDirectory(docs));
+ intent.putExtra(FileOperationService.EXTRA_OPERATION_TYPE, mode);
// This just identifies the type of request...we'll check it
// when we reveive a response.
@@ -1304,8 +1315,7 @@
ClipData clipData = event.getClipData();
assert (clipData != null);
- assert(ClipDetails.createClipDetails(clipData).getOpType()
- == FileOperationService.OPERATION_COPY);
+ assert(DocumentClipper.getOpType(clipData) == FileOperationService.OPERATION_COPY);
// Don't copy from the cwd into the cwd. Note: this currently doesn't work for
// multi-window drag, because localState isn't carried over from one process to
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
index 34bd696..c4f4dc1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
@@ -39,6 +39,24 @@
public RootInfo root;
+ public DocumentStack() {};
+
+ /**
+ * Makes a new copy, and pushes all docs to the new copy in the same order as they're passed
+ * as parameters, i.e. the last document will be at the top of the stack.
+ *
+ * @param src
+ * @param docs
+ */
+ public DocumentStack(DocumentStack src, DocumentInfo... docs) {
+ super(src);
+ for (DocumentInfo doc : docs) {
+ push(doc);
+ }
+
+ root = src.root;
+ }
+
public String getTitle() {
if (size() == 1 && root != null) {
return root.title;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index fac8667..390656c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -27,8 +27,9 @@
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;
import static com.android.documentsui.services.FileOperationService.EXTRA_DIALOG_TYPE;
-import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION;
+import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION_TYPE;
import static com.android.documentsui.services.FileOperationService.EXTRA_SRC_LIST;
+import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
import android.annotation.StringRes;
import android.app.Notification;
@@ -50,12 +51,13 @@
import android.util.Log;
import android.webkit.MimeTypeMap;
-import com.android.documentsui.ClipDetails;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
+import com.android.documentsui.services.FileOperationService.OpType;
import libcore.io.IoUtils;
@@ -85,17 +87,20 @@
/**
* @see @link {@link Job} constructor for most param descriptions.
- *
- * @param details clip details containing source file list
*/
- CopyJob(Context service, Context appContext, Listener listener,
- String id, DocumentStack destination, ClipDetails details) {
- super(service, appContext, listener, id, destination, details);
+ CopyJob(Context service, Listener listener, String id, DocumentStack destination,
+ UrisSupplier srcs) {
+ this(service, listener, id, OPERATION_COPY, destination, srcs);
+ }
- assert(details.getItemCount() > 0);
+ CopyJob(Context service, Listener listener, String id, @OpType int opType,
+ DocumentStack destination, UrisSupplier srcs) {
+ super(service, listener, id, opType, destination, srcs);
+
+ assert(srcs.getItemCount() > 0);
// delay the initialization of it to setUp() because it may be IO extensive.
- mSrcs = new ArrayList<>(details.getItemCount());
+ mSrcs = new ArrayList<>(srcs.getItemCount());
}
@Override
@@ -184,7 +189,7 @@
Notification getWarningNotification() {
final Intent navigateIntent = buildNavigateIntent(INTENT_TAG_WARNING);
navigateIntent.putExtra(EXTRA_DIALOG_TYPE, DIALOG_TYPE_CONVERTED);
- navigateIntent.putExtra(EXTRA_OPERATION, operationType);
+ navigateIntent.putExtra(EXTRA_OPERATION_TYPE, operationType);
navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, convertedFiles);
@@ -257,7 +262,7 @@
private void buildDocumentList() throws ResourceException {
try {
final ContentResolver resolver = appContext.getContentResolver();
- final Iterable<Uri> uris = details.getDocs(appContext);
+ final Iterable<Uri> uris = srcs.getDocs(appContext);
for (Uri uri : uris) {
DocumentInfo doc = DocumentInfo.fromUri(resolver, uri);
if (canCopy(doc, stack.root)) {
@@ -271,7 +276,7 @@
}
}
} catch(IOException e) {
- failedFileCount += details.getItemCount();
+ failedFileCount += srcs.getItemCount();
throw new ResourceException("Failed to open the list of docs to copy.", e);
}
}
@@ -659,7 +664,7 @@
.append("CopyJob")
.append("{")
.append("id=" + id)
- .append(", details=" + details)
+ .append(", docs=" + srcs)
.append(", destination=" + stack)
.append("}")
.toString();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
index f5bc85e..f6202c5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
@@ -17,6 +17,7 @@
package com.android.documentsui.services;
import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
import android.app.Notification;
import android.app.Notification.Builder;
@@ -25,7 +26,7 @@
import android.net.Uri;
import android.util.Log;
-import com.android.documentsui.ClipDetails;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
@@ -41,18 +42,18 @@
private volatile int mDocsProcessed = 0;
+ Uri mSrcParent;
/**
* Moves files to a destination identified by {@code destination}.
* Performs most work by delegating to CopyJob, then deleting
* a file after it has been copied.
*
* @see @link {@link Job} constructor for most param descriptions.
- *
- * @param details details that contains files to be deleted and their parent
*/
- DeleteJob(Context service, Context appContext, Listener listener,
- String id, DocumentStack stack, ClipDetails details) {
- super(service, appContext, listener, id, stack, details);
+ DeleteJob(Context service, Listener listener, String id, Uri srcParent, DocumentStack stack,
+ UrisSupplier srcs) {
+ super(service, listener, id, OPERATION_DELETE, stack, srcs);
+ mSrcParent = srcParent;
}
@Override
@@ -71,9 +72,9 @@
@Override
public Notification getProgressNotification() {
- mProgressBuilder.setProgress(details.getItemCount(), mDocsProcessed, false);
+ mProgressBuilder.setProgress(srcs.getItemCount(), mDocsProcessed, false);
String format = service.getString(R.string.delete_progress);
- mProgressBuilder.setSubText(String.format(format, mDocsProcessed, details.getItemCount()));
+ mProgressBuilder.setSubText(String.format(format, mDocsProcessed, srcs.getItemCount()));
mProgressBuilder.setContentText(null);
@@ -94,12 +95,12 @@
@Override
void start() {
try {
- final List<DocumentInfo> srcs = new ArrayList<>(details.getItemCount());
+ final List<DocumentInfo> srcs = new ArrayList<>(this.srcs.getItemCount());
- final Iterable<Uri> uris = details.getDocs(appContext);
+ final Iterable<Uri> uris = this.srcs.getDocs(appContext);
final ContentResolver resolver = appContext.getContentResolver();
- final DocumentInfo srcParent = DocumentInfo.fromUri(resolver, details.getSrcParent());
+ final DocumentInfo srcParent = DocumentInfo.fromUri(resolver, mSrcParent);
for (Uri uri : uris) {
DocumentInfo doc = DocumentInfo.fromUri(resolver, uri);
srcs.add(doc);
@@ -122,7 +123,7 @@
Metrics.logFileOperation(service, operationType, srcs, null);
} catch(IOException e) {
Log.e(TAG, "Failed to get list of docs or parent source.", e);
- failedFileCount += details.getItemCount();
+ failedFileCount += srcs.getItemCount();
}
}
@@ -132,7 +133,8 @@
.append("DeleteJob")
.append("{")
.append("id=" + id)
- .append(", details=" + details)
+ .append(", docs=" + srcs)
+ .append(", srcParent=" + mSrcParent)
.append(", location=" + stack)
.append("}")
.toString();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperation.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperation.java
new file mode 100644
index 0000000..ce63864
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperation.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2016 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.services;
+
+import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
+import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
+import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
+import static com.android.documentsui.services.FileOperationService.OPERATION_UNKNOWN;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.documentsui.UrisSupplier;
+import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.services.FileOperationService.OpType;
+
+/**
+ * FileOperation describes a file operation, such as move/copy/delete etc.
+ */
+public abstract class FileOperation implements Parcelable {
+ private final @OpType int mOpType;
+
+ private final UrisSupplier mSrcs;
+ private DocumentStack mDestination;
+
+ @VisibleForTesting
+ FileOperation(@OpType int opType, UrisSupplier srcs, DocumentStack destination) {
+ assert(opType != OPERATION_UNKNOWN);
+ assert(srcs.getItemCount() > 0);
+
+ mOpType = opType;
+ mSrcs = srcs;
+ mDestination = destination;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public @OpType int getOpType() {
+ return mOpType;
+ }
+
+ public UrisSupplier getSrc() {
+ return mSrcs;
+ }
+
+ public DocumentStack getDestination() {
+ return mDestination;
+ }
+
+ public void setDestination(DocumentStack destination) {
+ mDestination = destination;
+ }
+
+ public void dispose(Context context) {
+ mSrcs.dispose(context);
+ }
+
+ abstract Job createJob(Context service, Job.Listener listener, String id);
+
+ private void appendInfoTo(StringBuilder builder) {
+ builder.append("opType=").append(mOpType);
+ builder.append(", srcs=").append(mSrcs.toString());
+ builder.append(", destination=").append(mDestination.toString());
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ out.writeInt(mOpType);
+ out.writeParcelable(mSrcs, flag);
+ out.writeParcelable(mDestination, flag);
+ }
+
+ private FileOperation(Parcel in) {
+ mOpType = in.readInt();
+ mSrcs = in.readParcelable(FileOperation.class.getClassLoader());
+ mDestination = in.readParcelable(FileOperation.class.getClassLoader());
+ }
+
+ public static class CopyOperation extends FileOperation {
+ private CopyOperation(UrisSupplier srcs, DocumentStack destination) {
+ super(OPERATION_COPY, srcs, destination);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append("CopyOperation{");
+ super.appendInfoTo(builder);
+ builder.append("}");
+
+ return builder.toString();
+ }
+
+ CopyJob createJob(Context service, Job.Listener listener, String id) {
+ return new CopyJob(service, listener, id, getDestination(), getSrc());
+ }
+
+ private CopyOperation(Parcel in) {
+ super(in);
+ }
+
+ public static final Parcelable.Creator<CopyOperation> CREATOR =
+ new Parcelable.Creator<CopyOperation>() {
+
+ @Override
+ public CopyOperation createFromParcel(Parcel source) {
+ return new CopyOperation(source);
+ }
+
+ @Override
+ public CopyOperation[] newArray(int size) {
+ return new CopyOperation[size];
+ }
+ };
+ }
+
+ public static class MoveDeleteOperation extends FileOperation {
+ private final Uri mSrcParent;
+
+ private MoveDeleteOperation(
+ @OpType int opType, UrisSupplier srcs, Uri srcParent, DocumentStack destination) {
+ super(opType, srcs, destination);
+
+ assert(srcParent != null);
+ mSrcParent = srcParent;
+ }
+
+ @Override
+ Job createJob(Context service, Job.Listener listener, String id) {
+ switch(getOpType()) {
+ case OPERATION_MOVE:
+ return new MoveJob(
+ service, listener, id, mSrcParent, getDestination(), getSrc());
+ case OPERATION_DELETE:
+ return new DeleteJob(
+ service, listener, id, mSrcParent, getDestination(), getSrc());
+ default:
+ throw new UnsupportedOperationException("Unsupported op type: " + getOpType());
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append("MoveDeleteOperation{");
+ super.appendInfoTo(builder);
+ builder.append(", srcParent=").append(mSrcParent.toString());
+ builder.append("}");
+
+ return builder.toString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flag) {
+ super.writeToParcel(out, flag);
+ out.writeParcelable(mSrcParent, flag);
+ }
+
+ private MoveDeleteOperation(Parcel in) {
+ super(in);
+ mSrcParent = in.readParcelable(null);
+ }
+
+ public static final Parcelable.Creator<MoveDeleteOperation> CREATOR =
+ new Parcelable.Creator<MoveDeleteOperation>() {
+
+
+ @Override
+ public MoveDeleteOperation createFromParcel(Parcel source) {
+ return new MoveDeleteOperation(source);
+ }
+
+ @Override
+ public MoveDeleteOperation[] newArray(int size) {
+ return new MoveDeleteOperation[size];
+ }
+ };
+ }
+
+ public static class Builder {
+ private @OpType int mOpType;
+ private Uri mSrcParent;
+ private UrisSupplier mSrcs;
+ private DocumentStack mDestination;
+
+ public Builder withOpType(@OpType int opType) {
+ mOpType = opType;
+ return this;
+ }
+
+ public Builder withSrcParent(Uri srcParent) {
+ mSrcParent = srcParent;
+ return this;
+ }
+
+ public Builder withSrcs(UrisSupplier srcs) {
+ mSrcs = srcs;
+ return this;
+ }
+
+ public Builder withDestination(DocumentStack destination) {
+ mDestination = destination;
+ return this;
+ }
+
+ public FileOperation build() {
+ switch (mOpType) {
+ case OPERATION_COPY:
+ return new CopyOperation(mSrcs, mDestination);
+ case OPERATION_MOVE:
+ case OPERATION_DELETE:
+ return new MoveDeleteOperation(mOpType, mSrcs, mSrcParent, mDestination);
+ default:
+ throw new UnsupportedOperationException("Unsupported op type: " + mOpType);
+ }
+ }
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
index fec0050..b61c1c9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
@@ -25,15 +25,9 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
-import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
-import com.android.documentsui.ClipDetails;
-import com.android.documentsui.Shared;
-import com.android.documentsui.model.DocumentStack;
-import com.android.documentsui.services.Job.Factory;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -55,13 +49,17 @@
public static final String TAG = "FileOperationService";
+ // Extra used for OperationDialogFragment, Notifications and picking copy destination.
+ public static final String EXTRA_OPERATION_TYPE = "com.android.documentsui.OPERATION_TYPE";
+
+ // Extras used for OperationDialogFragment...
+ public static final String EXTRA_DIALOG_TYPE = "com.android.documentsui.DIALOG_TYPE";
+ public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
+
+ // Extras used to start or cancel a file operation...
public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID";
public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION";
public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
- public static final String EXTRA_CLIP_DETAILS = "com.android.documentsui.SRC_CLIP_DETAIL";
- public static final String EXTRA_DIALOG_TYPE = "com.android.documentsui.DIALOG_TYPE";
-
- public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
@IntDef(flag = true, value = {
OPERATION_UNKNOWN,
@@ -86,7 +84,6 @@
// Use a separate thread pool to prioritize deletions.
@VisibleForTesting ExecutorService deletionExecutor;
- @VisibleForTesting Factory jobFactory;
// Use a handler to schedule monitor tasks.
@VisibleForTesting Handler handler;
@@ -111,10 +108,6 @@
deletionExecutor = Executors.newCachedThreadPool();
}
- if (jobFactory == null) {
- jobFactory = Job.Factory.instance;
- }
-
if (handler == null) {
// Monitor tasks are small enough to schedule them on main thread.
handler = new Handler();
@@ -159,9 +152,8 @@
if (intent.hasExtra(EXTRA_CANCEL)) {
handleCancel(intent);
} else {
- ClipDetails details = intent.getParcelableExtra(EXTRA_CLIP_DETAILS);
- assert(details.getOpType() != OPERATION_UNKNOWN);
- handleOperation(intent, jobId, details);
+ FileOperation operation = intent.getParcelableExtra(EXTRA_OPERATION);
+ handleOperation(jobId, operation);
}
// Track the service supplied id so we can stop the service once we're out of work to do.
@@ -170,15 +162,19 @@
return START_NOT_STICKY;
}
- private void handleOperation(Intent intent, String jobId, ClipDetails details) {
+ private void handleOperation(String jobId, FileOperation operation) {
synchronized (mRunning) {
if (mWakeLock == null) {
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
}
- DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
+ if (mRunning.containsKey(jobId)) {
+ Log.w(TAG, "Duplicate job id: " + jobId
+ + ". Ignoring job request for operation: " + operation + ".");
+ return;
+ }
- Job job = createJob(jobId, details, stack);
+ Job job = operation.createJob(this, this, jobId);
if (job == null) {
return;
@@ -188,7 +184,7 @@
assert (job != null);
if (DEBUG) Log.d(TAG, "Scheduling job " + job.id + ".");
- Future<?> future = getExecutorService(details.getOpType()).submit(job);
+ Future<?> future = getExecutorService(operation.getOpType()).submit(job);
mRunning.put(jobId, new JobRecord(job, future));
}
}
@@ -226,37 +222,6 @@
// TODO: Guarantee the job is being finalized
}
- /**
- * Creates a new job. Returns null if a job with {@code id} already exists.
- * @return
- */
- @GuardedBy("mRunning")
- private @Nullable Job createJob(
- String id, ClipDetails details, DocumentStack stack) {
-
- assert(details.getItemCount() > 0);
-
- if (mRunning.containsKey(id)) {
- Log.w(TAG, "Duplicate job id: " + id
- + ". Ignoring job request for details: " + details + ", stack: " + stack + ".");
- return null;
- }
-
- switch (details.getOpType()) {
- case OPERATION_COPY:
- return jobFactory.createCopy(
- this, getApplicationContext(), this, id, stack, details);
- case OPERATION_MOVE:
- return jobFactory.createMove(
- this, getApplicationContext(), this, id, stack, details);
- case OPERATION_DELETE:
- return jobFactory.createDelete(
- this, getApplicationContext(), this, id, stack, details);
- default:
- throw new UnsupportedOperationException();
- }
- }
-
private ExecutorService getExecutorService(@OpType int operationType) {
switch (operationType) {
case OPERATION_COPY:
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
index 034c0d7..01956a1 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
@@ -19,21 +19,17 @@
import static android.os.SystemClock.elapsedRealtime;
import static com.android.documentsui.Shared.DEBUG;
-import static com.android.documentsui.Shared.EXTRA_STACK;
import static com.android.documentsui.services.FileOperationService.EXTRA_CANCEL;
import static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID;
-import static com.android.documentsui.services.FileOperationService.EXTRA_CLIP_DETAILS;
+import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION;
import android.annotation.IntDef;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
-import android.os.Parcelable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
-import com.android.documentsui.ClipDetails;
-import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.services.FileOperationService.OpType;
import java.lang.annotation.Retention;
@@ -57,16 +53,15 @@
/**
* Tries to start the activity. Returns the job id.
*/
- public static String start(Context context, ClipDetails details,
- DocumentStack stack, Callback callback) {
+ public static String start(Context context, FileOperation operation, Callback callback) {
if (DEBUG) Log.d(TAG, "Handling generic 'start' call.");
String jobId = createJobId();
- Intent intent = createBaseIntent(context, jobId, details, stack);
+ Intent intent = createBaseIntent(context, jobId, operation);
- callback.onOperationResult(
- Callback.STATUS_ACCEPTED, details.getOpType(), details.getItemCount());
+ callback.onOperationResult(Callback.STATUS_ACCEPTED, operation.getOpType(),
+ operation.getSrc().getItemCount());
context.startService(intent);
@@ -89,17 +84,14 @@
*
* @param jobId A unique jobid for this job.
* Use {@link #createJobId} if you don't have one handy.
- * @param details the clip details that contains source files and their parent
* @return Id of the job.
*/
public static Intent createBaseIntent(
- Context context, String jobId, ClipDetails details,
- DocumentStack localeStack) {
+ Context context, String jobId, FileOperation operation) {
Intent intent = new Intent(context, FileOperationService.class);
intent.putExtra(EXTRA_JOB_ID, jobId);
- intent.putExtra(EXTRA_CLIP_DETAILS, details);
- intent.putExtra(EXTRA_STACK, (Parcelable) localeStack);
+ intent.putExtra(EXTRA_OPERATION, operation);
return intent;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
index 0b4735f..29e0210 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
@@ -20,11 +20,8 @@
import static com.android.documentsui.services.FileOperationService.EXTRA_CANCEL;
import static com.android.documentsui.services.FileOperationService.EXTRA_DIALOG_TYPE;
import static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID;
-import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION;
+import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION_TYPE;
import static com.android.documentsui.services.FileOperationService.EXTRA_SRC_LIST;
-import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
-import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
-import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
import static com.android.documentsui.services.FileOperationService.OPERATION_UNKNOWN;
import android.annotation.DrawableRes;
@@ -43,7 +40,7 @@
import android.provider.DocumentsContract;
import android.util.Log;
-import com.android.documentsui.ClipDetails;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.FilesActivity;
import com.android.documentsui.Metrics;
import com.android.documentsui.OperationDialogFragment;
@@ -91,7 +88,7 @@
final @OpType int operationType;
final String id;
final DocumentStack stack;
- final ClipDetails details;
+ final UrisSupplier srcs;
int failedFileCount = 0;
final ArrayList<DocumentInfo> failedFiles = new ArrayList<>();
@@ -104,28 +101,26 @@
* A simple progressable job, much like an AsyncTask, but with support
* for providing various related notification, progress and navigation information.
* @param service The service context in which this job is running.
- * @param appContext The context of the invoking application. This is usually
- * just {@code getApplicationContext()}.
* @param listener
* @param id Arbitrary string ID
* @param stack The documents stack context relating to this request. This is the
* destination in the Files app where the user will be take when the
* navigation intent is invoked (presumably from notification).
- * @param details details that contains {@link FileOperationService.OpType}
+ * @param srcs the list of docs to operate on
*/
- Job(Context service, Context appContext, Listener listener,
- String id, DocumentStack stack, ClipDetails details) {
+ Job(Context service, Listener listener, String id,
+ @OpType int opType, DocumentStack stack, UrisSupplier srcs) {
- assert(details.getOpType() != OPERATION_UNKNOWN);
+ assert(opType != OPERATION_UNKNOWN);
this.service = service;
- this.appContext = appContext;
+ this.appContext = service.getApplicationContext();
this.listener = listener;
- this.operationType = details.getOpType();
+ this.operationType = opType;
this.id = id;
this.stack = stack;
- this.details = details;
+ this.srcs = srcs;
mProgressBuilder = createProgressBuilder();
}
@@ -156,7 +151,7 @@
// NOTE: If this details is a JumboClipDetails, and it's still referred in primary clip
// at this point, user won't be able to paste it to anywhere else because the underlying
- details.dispose(appContext);
+ srcs.dispose(appContext);
}
}
@@ -255,7 +250,7 @@
Notification getFailureNotification(@PluralsRes int titleId, @DrawableRes int icon) {
final Intent navigateIntent = buildNavigateIntent(INTENT_TAG_FAILURE);
navigateIntent.putExtra(EXTRA_DIALOG_TYPE, OperationDialogFragment.DIALOG_TYPE_FAILURE);
- navigateIntent.putExtra(EXTRA_OPERATION, operationType);
+ navigateIntent.putExtra(EXTRA_OPERATION_TYPE, operationType);
navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, failedFiles);
final Notification.Builder errorBuilder = new Notification.Builder(service)
@@ -330,40 +325,6 @@
}
/**
- * Factory class that facilitates our testing FileOperationService.
- */
- static class Factory {
-
- static final Factory instance = new Factory();
-
- Job createCopy(Context service, Context appContext, Listener listener,
- String id, DocumentStack stack, ClipDetails details) {
- assert(details.getOpType() == OPERATION_COPY);
- assert(details.getItemCount() > 0);
- assert(stack.peek().isCreateSupported());
- return new CopyJob(service, appContext, listener, id, stack, details);
- }
-
- Job createMove(Context service, Context appContext, Listener listener,
- String id, DocumentStack stack, ClipDetails details) {
- assert(details.getOpType() == OPERATION_MOVE);
- assert(details.getItemCount() > 0);
- assert(stack.peek().isCreateSupported());
- return new MoveJob(service, appContext, listener, id, stack, details);
- }
-
- Job createDelete(Context service, Context appContext, Listener listener,
- String id, DocumentStack stack, ClipDetails details) {
- assert(details.getOpType() == OPERATION_DELETE);
- assert(details.getItemCount() > 0);
- // stack is empty if we delete docs from recent.
- // we can't currently delete from archives.
- assert(stack.isEmpty() || stack.peek().isDirectory());
- return new DeleteJob(service, appContext, listener, id, stack, details);
- }
- }
-
- /**
* Listener interface employed by the service that owns us as well as tests.
*/
interface Listener {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
index 75c4dc0..5e9d5cc 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
@@ -17,17 +17,19 @@
package com.android.documentsui.services;
import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
import android.app.Notification;
import android.app.Notification.Builder;
import android.content.ContentResolver;
import android.content.Context;
+import android.net.Uri;
import android.os.RemoteException;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.util.Log;
-import com.android.documentsui.ClipDetails;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
@@ -39,6 +41,7 @@
private static final String TAG = "MoveJob";
+ Uri mSrcParentUri;
DocumentInfo mSrcParent;
/**
@@ -47,12 +50,11 @@
* a file after it has been copied.
*
* @see @link {@link Job} constructor for most param descriptions.
- *
- * @param details {@link ClipDetails} that contains list of files to be moved and their parent
*/
- MoveJob(Context service, Context appContext, Listener listener,
- String id, DocumentStack destination, ClipDetails details) {
- super(service, appContext, listener, id, destination, details);
+ MoveJob(Context service, Listener listener,
+ String id, Uri srcParent, DocumentStack destination, UrisSupplier srcs) {
+ super(service, listener, id, OPERATION_MOVE, destination, srcs);
+ mSrcParentUri = srcParent;
}
@Override
@@ -81,16 +83,21 @@
}
@Override
- public void start() {
+ public boolean setUp() {
final ContentResolver resolver = appContext.getContentResolver();
try {
- mSrcParent = DocumentInfo.fromUri(resolver, details.getSrcParent());
+ mSrcParent = DocumentInfo.fromUri(resolver, mSrcParentUri);
} catch(FileNotFoundException e) {
Log.e(TAG, "Failed to create srcParent.", e);
- failedFileCount += details.getItemCount();
- return;
+ failedFileCount += srcs.getItemCount();
+ return false;
}
+ return super.setUp();
+ }
+
+ @Override
+ public void start() {
super.start();
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/ClipDetailsTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UrisSupplierTest.java
similarity index 64%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/ClipDetailsTest.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/UrisSupplierTest.java
index b0647b8..719f0e2 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/ClipDetailsTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UrisSupplierTest.java
@@ -25,8 +25,6 @@
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
-import com.android.documentsui.services.FileOperationService;
-import com.android.documentsui.services.FileOperationService.OpType;
import com.android.documentsui.testing.TestScheduledExecutorService;
import org.junit.AfterClass;
@@ -42,12 +40,9 @@
@RunWith(AndroidJUnit4.class)
@MediumTest
-public class ClipDetailsTest {
+public class UrisSupplierTest {
private static final String AUTHORITY = "foo";
- private static final @OpType int OP_TYPE = FileOperationService.OPERATION_COPY;
- private static final Uri SRC_PARENT =
- DocumentsContract.buildDocumentUri(AUTHORITY, Integer.toString(0));
private static final List<Uri> SHORT_URI_LIST = createList(3);
private static final List<Uri> LONG_URI_LIST = createList(Shared.MAX_DOCS_IN_INTENT + 5);
@@ -71,72 +66,58 @@
}
@Test
- public void testOpTypeEquals_shortList() {
- ClipDetails details = createDetailsWithShortList();
-
- assertEquals(OP_TYPE, details.getOpType());
- }
-
- @Test
- public void testOpTypeEquals_longList() {
- ClipDetails details = createDetailsWithLongList();
-
- assertEquals(OP_TYPE, details.getOpType());
- }
-
- @Test
public void testItemCountEquals_shortList() {
- ClipDetails details = createDetailsWithShortList();
+ UrisSupplier uris = createWithShortList();
- assertEquals(SHORT_URI_LIST.size(), details.getItemCount());
+ assertEquals(SHORT_URI_LIST.size(), uris.getItemCount());
}
@Test
public void testItemCountEquals_longList() {
- ClipDetails details = createDetailsWithLongList();
+ UrisSupplier uris = createWithLongList();
- assertEquals(LONG_URI_LIST.size(), details.getItemCount());
+ assertEquals(LONG_URI_LIST.size(), uris.getItemCount());
}
@Test
public void testGetDocsEquals_shortList() throws Exception {
- ClipDetails details = createDetailsWithShortList();
+ UrisSupplier uris = createWithShortList();
- assertIterableEquals(SHORT_URI_LIST, details.getDocs(mStorage));
+ assertIterableEquals(SHORT_URI_LIST, uris.getDocs(mStorage));
}
@Test
public void testGetDocsEquals_longList() throws Exception {
- ClipDetails details = createDetailsWithLongList();
+ UrisSupplier uris = createWithLongList();
- assertIterableEquals(LONG_URI_LIST, details.getDocs(mStorage));
+ assertIterableEquals(LONG_URI_LIST, uris.getDocs(mStorage));
}
@Test
public void testDispose_shortList() throws Exception {
- ClipDetails details = createDetailsWithShortList();
+ UrisSupplier uris = createWithShortList();
- details.dispose(mStorage);
+ uris.dispose(mStorage);
}
@Test
public void testDispose_longList() throws Exception {
- ClipDetails details = createDetailsWithLongList();
+ UrisSupplier uris = createWithLongList();
- details.dispose(mStorage);
+ uris.dispose(mStorage);
}
- private ClipDetails createDetailsWithShortList() {
- return ClipDetails.createClipDetails(OP_TYPE, SRC_PARENT, SHORT_URI_LIST, mStorage);
+ private UrisSupplier createWithShortList() {
+ return UrisSupplier.create(SHORT_URI_LIST, mStorage);
}
- private ClipDetails createDetailsWithLongList() {
- ClipDetails details =
- ClipDetails.createClipDetails(OP_TYPE, SRC_PARENT, LONG_URI_LIST, mStorage);
+ private UrisSupplier createWithLongList() {
+ UrisSupplier uris =
+ UrisSupplier.create(LONG_URI_LIST, mStorage);
mExecutor.runAll();
- return details;
+ return uris;
}
private void assertIterableEquals(Iterable<Uri> expected, Iterable<Uri> value) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
index cd05939..2560f2c 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
@@ -16,8 +16,6 @@
package com.android.documentsui.services;
-import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
-
import static com.google.common.collect.Lists.newArrayList;
import android.net.Uri;
@@ -25,12 +23,19 @@
import android.test.suitebuilder.annotation.MediumTest;
import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.services.FileOperationService.OpType;
import java.util.List;
@MediumTest
public abstract class AbstractCopyJobTest<T extends CopyJob> extends AbstractJobTest<T> {
+ private final @OpType int mOpType;
+
+ AbstractCopyJobTest(@OpType int opType) {
+ mOpType = opType;
+ }
+
public void runCopyFilesTest() throws Exception {
Uri testFile1 = mDocs.createDocument(mSrcRoot, "text/plain", "test1.txt");
mDocs.writeDocument(testFile1, HAM_BYTES);
@@ -111,7 +116,7 @@
public void runNoCopyDirToSelfTest() throws Exception {
Uri testDir = mDocs.createFolder(mSrcRoot, "someDir");
- createJob(OPERATION_COPY,
+ createJob(mOpType,
newArrayList(testDir),
DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId),
testDir).run();
@@ -127,7 +132,7 @@
Uri testDir = mDocs.createFolder(mSrcRoot, "someDir");
Uri destDir = mDocs.createFolder(testDir, "theDescendent");
- createJob(OPERATION_COPY,
+ createJob(mOpType,
newArrayList(testDir),
DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId),
destDir).run();
@@ -163,6 +168,6 @@
final T createJob(List<Uri> srcs) throws Exception {
Uri srcParent = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId);
Uri destination = DocumentsContract.buildDocumentUri(AUTHORITY, mDestRoot.documentId);
- return createJob(OPERATION_COPY, srcs, srcParent, destination);
+ return createJob(mOpType, srcs, srcParent, destination);
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java
index c3cbe3f..053942b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java
@@ -27,14 +27,14 @@
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.MediumTest;
-import com.android.documentsui.ClipDetails;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.DocumentsProviderHelper;
import com.android.documentsui.StubProvider;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.services.FileOperationService.OpType;
-import com.android.documentsui.testing.ClipDetailsFactory;
+import com.android.documentsui.testing.DocsProviders;
import java.util.List;
@@ -91,10 +91,13 @@
stack.push(DocumentInfo.fromUri(mResolver, destination));
stack.root = mSrcRoot;
- ClipDetails details = ClipDetailsFactory.createClipDetails(opType, srcParent, srcs);
- return createJob(details, stack);
+ UrisSupplier urisSupplier = DocsProviders.createDocsProvider(srcs);
+ FileOperation operation = new FileOperation.Builder()
+ .withOpType(opType)
+ .withSrcs(urisSupplier)
+ .withDestination(stack)
+ .withSrcParent(srcParent)
+ .build();
+ return (T) operation.createJob(mContext, mJobListener, FileOperations.createJobId());
}
-
- abstract T createJob(ClipDetails details, DocumentStack destination)
- throws Exception;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
index eac06ca..64211c2 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
@@ -16,18 +16,21 @@
package com.android.documentsui.services;
+import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
+
import static com.google.common.collect.Lists.newArrayList;
import android.net.Uri;
import android.provider.DocumentsContract.Document;
import android.test.suitebuilder.annotation.MediumTest;
-import com.android.documentsui.ClipDetails;
-import com.android.documentsui.model.DocumentStack;
-
@MediumTest
public class CopyJobTest extends AbstractCopyJobTest<CopyJob> {
+ public CopyJobTest() {
+ super(OPERATION_COPY);
+ }
+
public void testCopyFiles() throws Exception {
runCopyFilesTest();
}
@@ -74,11 +77,4 @@
public void testCopyFileWithReadErrors() throws Exception {
runCopyFileWithReadErrorsTest();
}
-
- @Override
- CopyJob createJob(ClipDetails details, DocumentStack stack)
- throws Exception {
- return new CopyJob(
- mContext, mContext, mJobListener, FileOperations.createJobId(), stack, details);
- }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java
index 050c7ea..9dbe7ce 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java
@@ -24,9 +24,6 @@
import android.provider.DocumentsContract;
import android.test.suitebuilder.annotation.MediumTest;
-import com.android.documentsui.ClipDetails;
-import com.android.documentsui.model.DocumentStack;
-
import java.util.List;
@MediumTest
@@ -53,12 +50,4 @@
Uri stack = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId);
return createJob(OPERATION_DELETE, srcs, srcParent, stack);
}
-
- // TODO: Remove inheritance, as stack is not used for deleting, nor srcParent.
- @Override
- DeleteJob createJob(ClipDetails details, DocumentStack stack)
- throws Exception {
- return new DeleteJob(
- mContext, mContext, mJobListener, FileOperations.createJobId(), stack, details);
- }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/FileOperationServiceTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/FileOperationServiceTest.java
index e16d5ae..b49d15d 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/FileOperationServiceTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/FileOperationServiceTest.java
@@ -18,6 +18,7 @@
import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
+import static com.android.documentsui.services.FileOperationService.OpType;
import static com.android.documentsui.services.FileOperations.createBaseIntent;
import static com.android.documentsui.services.FileOperations.createJobId;
@@ -26,14 +27,15 @@
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.test.ServiceTestCase;
import android.test.suitebuilder.annotation.MediumTest;
-import com.android.documentsui.ClipDetails;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
-import com.android.documentsui.services.Job.Listener;
-import com.android.documentsui.testing.ClipDetailsFactory;
+import com.android.documentsui.testing.DocsProviders;
import com.android.documentsui.testing.TestHandler;
import com.android.documentsui.testing.TestScheduledExecutorService;
@@ -50,11 +52,13 @@
private static final DocumentInfo GAMMA_DOC = createDoc("gamma");
private static final DocumentInfo DELTA_DOC = createDoc("delta");
+ private final List<TestJob> mCopyJobs = new ArrayList<>();
+ private final List<TestJob> mDeleteJobs = new ArrayList<>();
+
private FileOperationService mService;
private TestScheduledExecutorService mExecutor;
private TestScheduledExecutorService mDeletionExecutor;
private TestHandler mHandler;
- private TestJobFactory mJobFactory;
public FileOperationServiceTest() {
super(FileOperationService.class);
@@ -68,7 +72,9 @@
mExecutor = new TestScheduledExecutorService();
mDeletionExecutor = new TestScheduledExecutorService();
mHandler = new TestHandler();
- mJobFactory = new TestJobFactory();
+
+ mCopyJobs.clear();
+ mDeleteJobs.clear();
// Install test doubles.
mService = getService();
@@ -81,9 +87,13 @@
assertNull(mService.handler);
mService.handler = mHandler;
+ }
- assertNull(mService.jobFactory);
- mService.jobFactory = mJobFactory;
+ @Override
+ protected void tearDown() {
+ // There are lots of progress notifications generated in this test case.
+ // Dismiss all of them here.
+ mHandler.dispatchAllMessages();
}
public void testRunsCopyJobs() throws Exception {
@@ -91,7 +101,7 @@
startService(createCopyIntent(newArrayList(GAMMA_DOC), DELTA_DOC));
mExecutor.runAll();
- mJobFactory.assertAllCopyJobsStarted();
+ assertAllCopyJobsStarted();
}
public void testRunsCopyJobs_AfterExceptionInJobCreation() throws Exception {
@@ -102,20 +112,20 @@
}
startService(createCopyIntent(newArrayList(GAMMA_DOC), DELTA_DOC));
- mJobFactory.assertJobsCreated(1);
+ assertJobsCreated(1);
mExecutor.runAll();
- mJobFactory.assertAllCopyJobsStarted();
+ assertAllCopyJobsStarted();
}
public void testRunsCopyJobs_AfterFailure() throws Exception {
startService(createCopyIntent(newArrayList(ALPHA_DOC), BETA_DOC));
startService(createCopyIntent(newArrayList(GAMMA_DOC), DELTA_DOC));
- mJobFactory.copyJobs.get(0).fail(ALPHA_DOC);
+ mCopyJobs.get(0).fail(ALPHA_DOC);
mExecutor.runAll();
- mJobFactory.assertAllCopyJobsStarted();
+ assertAllCopyJobsStarted();
}
public void testRunsCopyJobs_notRunsDeleteJobs() throws Exception {
@@ -123,14 +133,14 @@
startService(createDeleteIntent(newArrayList(GAMMA_DOC)));
mExecutor.runAll();
- mJobFactory.assertNoDeleteJobsStarted();
+ assertNoDeleteJobsStarted();
}
public void testRunsDeleteJobs() throws Exception {
startService(createDeleteIntent(newArrayList(ALPHA_DOC)));
mDeletionExecutor.runAll();
- mJobFactory.assertAllDeleteJobsStarted();
+ assertAllDeleteJobsStarted();
}
public void testRunsDeleteJobs_NotRunsCopyJobs() throws Exception {
@@ -138,7 +148,7 @@
startService(createDeleteIntent(newArrayList(GAMMA_DOC)));
mDeletionExecutor.runAll();
- mJobFactory.assertNoCopyJobsStarted();
+ assertNoCopyJobsStarted();
}
public void testUpdatesNotification() throws Exception {
@@ -148,7 +158,7 @@
// Assert monitoring continues until job is done
assertTrue(mHandler.hasScheduledMessage());
// Two notifications -- one for setup; one for progress
- assertEquals(2, mJobFactory.copyJobs.get(0).getNumOfNotifications());
+ assertEquals(2, mCopyJobs.get(0).getNumOfNotifications());
}
public void testStopsUpdatingNotificationAfterFinished() throws Exception {
@@ -160,7 +170,7 @@
assertFalse(mHandler.hasScheduledMessage());
// Assert no more notification is generated after finish.
- assertEquals(2, mJobFactory.copyJobs.get(0).getNumOfNotifications());
+ assertEquals(2, mCopyJobs.get(0).getNumOfNotifications());
}
@@ -202,7 +212,7 @@
startService(createCopyIntent(newArrayList(ALPHA_DOC), BETA_DOC));
startService(createCopyIntent(newArrayList(GAMMA_DOC), DELTA_DOC));
- mJobFactory.copyJobs.get(0).fail(ALPHA_DOC);
+ mCopyJobs.get(0).fail(ALPHA_DOC);
mExecutor.runAll();
shutdownService();
@@ -214,8 +224,8 @@
startService(createCopyIntent(newArrayList(ALPHA_DOC), BETA_DOC));
startService(createCopyIntent(newArrayList(GAMMA_DOC), DELTA_DOC));
- mJobFactory.copyJobs.get(0).fail(ALPHA_DOC);
- mJobFactory.copyJobs.get(1).fail(GAMMA_DOC);
+ mCopyJobs.get(0).fail(ALPHA_DOC);
+ mCopyJobs.get(1).fail(GAMMA_DOC);
mExecutor.runAll();
shutdownService();
@@ -233,10 +243,10 @@
uris.add(file.derivedUri);
}
- ClipDetails details =
- ClipDetailsFactory.createClipDetails(OPERATION_COPY, SRC_PARENT, uris);
+ UrisSupplier urisSupplier = DocsProviders.createDocsProvider(uris);
+ TestFileOperation operation = new TestFileOperation(OPERATION_COPY, urisSupplier, stack);
- return createBaseIntent(getContext(), createJobId(), details, stack);
+ return createBaseIntent(getContext(), createJobId(), operation);
}
private Intent createDeleteIntent(ArrayList<DocumentInfo> files) {
@@ -247,10 +257,10 @@
uris.add(file.derivedUri);
}
- ClipDetails details =
- ClipDetailsFactory.createClipDetails(OPERATION_DELETE, SRC_PARENT, uris);
+ UrisSupplier urisSupplier = DocsProviders.createDocsProvider(uris);
+ TestFileOperation operation = new TestFileOperation(OPERATION_DELETE, urisSupplier, stack);
- return createBaseIntent(getContext(), createJobId(), details, stack);
+ return createBaseIntent(getContext(), createJobId(), operation);
}
private static DocumentInfo createDoc(String name) {
@@ -264,6 +274,33 @@
return createDoc(uri);
}
+ void assertAllCopyJobsStarted() {
+ for (TestJob job : mCopyJobs) {
+ job.assertStarted();
+ }
+ }
+
+ void assertAllDeleteJobsStarted() {
+ for (TestJob job : mDeleteJobs) {
+ job.assertStarted();
+ }
+ }
+
+ void assertNoCopyJobsStarted() {
+ for (TestJob job : mCopyJobs) {
+ job.assertNotStarted();
+ }
+ }
+
+ void assertNoDeleteJobsStarted() {
+ for (TestJob job : mDeleteJobs) {
+ job.assertNotStarted();
+ }
+ }
+
+ void assertJobsCreated(int expected) {
+ assertEquals(expected, mCopyJobs.size() + mDeleteJobs.size());
+ }
private static DocumentInfo createDoc(Uri destination) {
DocumentInfo destDoc = new DocumentInfo();
destDoc.derivedUri = destination;
@@ -275,72 +312,56 @@
mDeletionExecutor.assertShutdown();
}
- private final class TestJobFactory extends Job.Factory {
+ private final class TestFileOperation extends FileOperation {
- private final List<TestJob> copyJobs = new ArrayList<>();
- private final List<TestJob> deleteJobs = new ArrayList<>();
-
- private Runnable mJobRunnable = () -> {
+ private final Runnable mJobRunnable = () -> {
// The following statement is executed concurrently to Job.start() in real situation.
// Call it in TestJob.start() to mimic this behavior.
mHandler.dispatchNextMessage();
};
+ private final @OpType int mOpType;
+ private final UrisSupplier mSrcs;
+ private final DocumentStack mDestination;
- void assertAllCopyJobsStarted() {
- for (TestJob job : copyJobs) {
- job.assertStarted();
- }
- }
-
- void assertAllDeleteJobsStarted() {
- for (TestJob job : deleteJobs) {
- job.assertStarted();
- }
- }
-
- void assertNoCopyJobsStarted() {
- for (TestJob job : copyJobs) {
- job.assertNotStarted();
- }
- }
-
- void assertNoDeleteJobsStarted() {
- for (TestJob job : deleteJobs) {
- job.assertNotStarted();
- }
- }
-
- void assertJobsCreated(int expected) {
- assertEquals(expected, copyJobs.size() + deleteJobs.size());
+ private TestFileOperation(
+ @OpType int opType, UrisSupplier srcs, DocumentStack destination) {
+ super(opType, srcs, destination);
+ mOpType = opType;
+ mSrcs = srcs;
+ mDestination = destination;
}
@Override
- Job createCopy(Context service, Context appContext, Listener listener, String id,
- DocumentStack stack, ClipDetails details) {
+ public Job createJob(Context service, Job.Listener listener, String id) {
+ TestJob job =
+ new TestJob(service, listener, id, mOpType, mDestination, mSrcs, mJobRunnable);
- if (details.getItemCount() == 0) {
- throw new RuntimeException("Empty srcs not supported!");
+ if (mOpType == OPERATION_COPY) {
+ mCopyJobs.add(job);
}
- TestJob job = new TestJob(
- service, appContext, listener, id, stack, details, mJobRunnable);
- copyJobs.add(job);
+ if (mOpType == OPERATION_DELETE) {
+ mDeleteJobs.add(job);
+ }
+
return job;
}
- @Override
- Job createDelete(Context service, Context appContext, Listener listener, String id,
- DocumentStack stack, ClipDetails details) {
+ /**
+ * CREATOR is required for Parcelables, but we never pass this class via parcel.
+ */
+ public Parcelable.Creator<TestFileOperation> CREATOR =
+ new Parcelable.Creator<TestFileOperation>() {
- if (details.getItemCount() == 0) {
- throw new RuntimeException("Empty srcs not supported!");
+ @Override
+ public TestFileOperation createFromParcel(Parcel source) {
+ throw new UnsupportedOperationException("Can't create from a parcel.");
}
- TestJob job = new TestJob(
- service, appContext, listener, id, stack, details, mJobRunnable);
- deleteJobs.add(job);
-
- return job;
- }
+ @Override
+ public TestFileOperation[] newArray(int size) {
+ throw new UnsupportedOperationException("Can't create a new array.");
+ }
+ };
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
index fd5c92a..56d96cc 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
@@ -16,18 +16,21 @@
package com.android.documentsui.services;
+import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
+
import static com.google.common.collect.Lists.newArrayList;
import android.net.Uri;
import android.provider.DocumentsContract.Document;
import android.test.suitebuilder.annotation.MediumTest;
-import com.android.documentsui.ClipDetails;
-import com.android.documentsui.model.DocumentStack;
-
@MediumTest
public class MoveJobTest extends AbstractCopyJobTest<MoveJob> {
+ public MoveJobTest() {
+ super(OPERATION_MOVE);
+ }
+
public void testMoveFiles() throws Exception {
runCopyFilesTest();
@@ -105,11 +108,4 @@
}
// TODO: Add test cases for moving when multi-parented.
-
- @Override
- MoveJob createJob(ClipDetails details, DocumentStack stack)
- throws Exception {
- return new MoveJob(
- mContext, mContext, mJobListener, FileOperations.createJobId(), stack, details);
- }
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
index a7e1d66..0c273c0 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/TestJob.java
@@ -23,10 +23,11 @@
import android.app.Notification.Builder;
import android.content.Context;
-import com.android.documentsui.ClipDetails;
+import com.android.documentsui.UrisSupplier;
import com.android.documentsui.R;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
+import com.android.documentsui.services.FileOperationService.OpType;
import java.text.NumberFormat;
@@ -38,9 +39,9 @@
private int mNumOfNotifications = 0;
TestJob(
- Context service, Context appContext, Listener listener,
- String id, DocumentStack stack, ClipDetails details, Runnable startRunnable) {
- super(service, appContext, listener, id, stack, details);
+ Context service, Listener listener, String id,
+ @OpType int opType, DocumentStack stack, UrisSupplier srcs, Runnable startRunnable) {
+ super(service, listener, id, opType, stack, srcs);
mStartRunnable = startRunnable;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/ClipDetailsFactory.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/DocsProviders.java
similarity index 66%
rename from packages/DocumentsUI/tests/src/com/android/documentsui/testing/ClipDetailsFactory.java
rename to packages/DocumentsUI/tests/src/com/android/documentsui/testing/DocsProviders.java
index d833528..d438892 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/ClipDetailsFactory.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/DocsProviders.java
@@ -18,15 +18,14 @@
import android.net.Uri;
-import com.android.documentsui.ClipDetails;
-import com.android.documentsui.services.FileOperationService.OpType;
+import com.android.documentsui.UrisSupplier;
import java.util.List;
-public final class ClipDetailsFactory {
- private ClipDetailsFactory() {}
+public final class DocsProviders {
+ private DocsProviders() {}
- public static ClipDetails createClipDetails(@OpType int opType, Uri srcParent, List<Uri> docs) {
- return new ClipDetails.StandardClipDetails(opType, srcParent, docs);
+ public static UrisSupplier createDocsProvider(List<Uri> docs) {
+ return new UrisSupplier.StandardUrisSupplier(docs);
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestHandler.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestHandler.java
index c18ef1f..143ec71 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestHandler.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestHandler.java
@@ -41,6 +41,12 @@
mTimer.fastForwardToNextTask();
}
+ public void dispatchAllMessages() {
+ while (hasScheduledMessage()) {
+ dispatchNextMessage();
+ }
+ }
+
@Override
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
msg.setTarget(this);