Loading packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java +34 −10 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.util.Log; 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; Loading Loading @@ -349,28 +350,35 @@ public final class DocumentClipper implements ClipboardManager.OnPrimaryClipChan 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); } /** Loading Loading @@ -399,8 +407,24 @@ public final class DocumentClipper implements ClipboardManager.OnPrimaryClipChan } 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); } } packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +2 −3 Original line number Diff line number Diff line Loading @@ -41,7 +41,6 @@ import android.provider.DocumentsContract; 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; Loading Loading @@ -156,7 +155,7 @@ public class DocumentsActivity extends BaseActivity { state.directoryCopy = intent.getBooleanExtra( Shared.EXTRA_DIRECTORY_COPY, false); state.copyOperationSubType = intent.getIntExtra( FileOperationService.EXTRA_OPERATION, FileOperationService.EXTRA_OPERATION_TYPE, FileOperationService.OPERATION_COPY); } } Loading Loading @@ -386,7 +385,7 @@ public class DocumentsActivity extends BaseActivity { // 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 Loading packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +1 −1 Original line number Diff line number Diff line Loading @@ -139,7 +139,7 @@ public class FilesActivity extends BaseActivity { // 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); Loading packages/DocumentsUI/src/com/android/documentsui/OperationDialogFragment.java +2 −2 Original line number Diff line number Diff line Loading @@ -60,7 +60,7 @@ public class OperationDialogFragment extends DialogFragment { @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(); Loading @@ -78,7 +78,7 @@ public class OperationDialogFragment extends DialogFragment { 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); Loading packages/DocumentsUI/src/com/android/documentsui/ClipDetails.java→packages/DocumentsUI/src/com/android/documentsui/UrisSupplier.java +275 −0 Original line number Diff line number Diff line Loading @@ -18,11 +18,7 @@ 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; Loading @@ -33,67 +29,33 @@ 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 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; /** * ClipDetails is a parcelable project providing information of different type of file * management operations like cut, move and copy. * UrisSupplier provides doc uri list to {@link FileOperation}. * * Under the hood it provides cross-process synchronization support such that its consumer doesn't * <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 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 class UrisSupplier implements Parcelable { public abstract int getItemCount(); /** * Gets doc list from this clip detail. This may only be called once because it may read a file * 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 { ClipStorage storage = DocumentsApplication.getClipStorage(context); return getDocs(storage); return getDocs(DocumentsApplication.getClipStorage(context)); } @VisibleForTesting Loading @@ -107,41 +69,24 @@ public abstract class ClipDetails implements Parcelable { @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; public static UrisSupplier create(ClipData clipData) { UrisSupplier uris; PersistableBundle bundle = clipData.getDescription().getExtras(); if (bundle.containsKey(OP_JUMBO_SELECTION_TAG)) { details = new JumboClipDetails(clipData); uris = new JumboUrisSupplier(clipData); } else { details = new StandardClipDetails(clipData); uris = new StandardUrisSupplier(clipData); } return details; return uris; } public static ClipDetails createClipDetails(@OpType int opType, @Nullable Uri srcParent, public static UrisSupplier create( Selection selection, Function<String, Uri> uriBuilder, Context context) { ClipStorage storage = DocumentsApplication.getClipStorage(context); Loading @@ -150,30 +95,28 @@ public abstract class ClipDetails implements Parcelable { uris.add(uriBuilder.apply(id)); } return createClipDetails(opType, srcParent, uris, storage); return create(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); 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 details; return urisSupplier; } private static class JumboClipDetails extends ClipDetails { private static final String TAG = "JumboClipDetails"; private static class JumboUrisSupplier extends UrisSupplier { private static final String TAG = "JumboUrisSupplier"; private final long mSelectionTag; private final int mSelectionSize; private transient ClipStorage.Reader mReader; private JumboClipDetails(ClipData clipData) { super(clipData); 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); Loading @@ -182,10 +125,7 @@ public abstract class ClipDetails implements Parcelable { assert(mSelectionSize > Shared.MAX_DOCS_IN_INTENT); } private JumboClipDetails(@OpType int opType, @Nullable Uri srcParent, Collection<Uri> uris, ClipStorage storage) { super(opType, srcParent); private JumboUrisSupplier(Collection<Uri> uris, ClipStorage storage) { mSelectionTag = storage.createTag(); new ClipStorage.PersistTask(storage, uris, mSelectionTag).execute(); mSelectionSize = uris.size(); Loading @@ -197,26 +137,27 @@ public abstract class ClipDetails implements Parcelable { } @Override public Iterable<Uri> getDocs(ClipStorage storage) throws IOException { if (mReader != null) { throw new IllegalStateException( "JumboClipDetails#getDocs() can only be called once."); 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."); } mReader = storage.createReader(mSelectionTag); return mReader; return mReader.get(); } @Override void dispose(ClipStorage storage) { if (mReader != null) { try { mReader.close(); 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) { Loading @@ -227,9 +168,8 @@ public abstract class ClipDetails implements Parcelable { @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("JumboClipDetails{"); super.appendTo(builder); builder.append(", selectionTag=").append(mSelectionTag); builder.append("JumboUrisSupplier{"); builder.append("selectionTag=").append(mSelectionTag); builder.append(", selectionSize=").append(mSelectionSize); builder.append("}"); return builder.toString(); Loading @@ -237,47 +177,44 @@ public abstract class ClipDetails implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeLong(mSelectionTag); dest.writeInt(mSelectionSize); } private JumboClipDetails(Parcel in) { super(in); private JumboUrisSupplier(Parcel in) { mSelectionTag = in.readLong(); mSelectionSize = in.readInt(); } public static final Parcelable.Creator<JumboClipDetails> CREATOR = new Parcelable.Creator<JumboClipDetails>() { public static final Parcelable.Creator<JumboUrisSupplier> CREATOR = new Parcelable.Creator<JumboUrisSupplier>() { @Override public JumboClipDetails createFromParcel(Parcel source) { return new JumboClipDetails(source); public JumboUrisSupplier createFromParcel(Parcel source) { return new JumboUrisSupplier(source); } @Override public JumboClipDetails[] newArray(int size) { return new JumboClipDetails[size]; 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 StandardClipDetails extends ClipDetails { public static class StandardUrisSupplier extends UrisSupplier { private final List<Uri> mDocs; private StandardClipDetails(ClipData clipData) { super(clipData); private StandardUrisSupplier(ClipData clipData) { mDocs = listDocs(clipData); } @VisibleForTesting public StandardClipDetails(@OpType int opType, @Nullable Uri srcParent, List<Uri> docs) { super(opType, srcParent); public StandardUrisSupplier(List<Uri> docs) { mDocs = docs; } Loading @@ -299,44 +236,39 @@ public abstract class ClipDetails implements Parcelable { } @Override public Iterable<Uri> getDocs(ClipStorage storage) { 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("StandardUrisSupplier{"); builder.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); private StandardUrisSupplier(Parcel in) { mDocs = in.createTypedArrayList(Uri.CREATOR); } public static final Parcelable.Creator<StandardClipDetails> CREATOR = new Parcelable.Creator<StandardClipDetails>() { public static final Parcelable.Creator<StandardUrisSupplier> CREATOR = new Parcelable.Creator<StandardUrisSupplier>() { @Override public StandardClipDetails createFromParcel(Parcel source) { return new StandardClipDetails(source); public StandardUrisSupplier createFromParcel(Parcel source) { return new StandardUrisSupplier(source); } @Override public StandardClipDetails[] newArray(int size) { return new StandardClipDetails[size]; public StandardUrisSupplier[] newArray(int size) { return new StandardUrisSupplier[size]; } }; } Loading Loading
packages/DocumentsUI/src/com/android/documentsui/DocumentClipper.java +34 −10 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import android.util.Log; 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; Loading Loading @@ -349,28 +350,35 @@ public final class DocumentClipper implements ClipboardManager.OnPrimaryClipChan 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); } /** Loading Loading @@ -399,8 +407,24 @@ public final class DocumentClipper implements ClipboardManager.OnPrimaryClipChan } 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); } }
packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +2 −3 Original line number Diff line number Diff line Loading @@ -41,7 +41,6 @@ import android.provider.DocumentsContract; 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; Loading Loading @@ -156,7 +155,7 @@ public class DocumentsActivity extends BaseActivity { state.directoryCopy = intent.getBooleanExtra( Shared.EXTRA_DIRECTORY_COPY, false); state.copyOperationSubType = intent.getIntExtra( FileOperationService.EXTRA_OPERATION, FileOperationService.EXTRA_OPERATION_TYPE, FileOperationService.OPERATION_COPY); } } Loading Loading @@ -386,7 +385,7 @@ public class DocumentsActivity extends BaseActivity { // 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 Loading
packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java +1 −1 Original line number Diff line number Diff line Loading @@ -139,7 +139,7 @@ public class FilesActivity extends BaseActivity { // 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); Loading
packages/DocumentsUI/src/com/android/documentsui/OperationDialogFragment.java +2 −2 Original line number Diff line number Diff line Loading @@ -60,7 +60,7 @@ public class OperationDialogFragment extends DialogFragment { @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(); Loading @@ -78,7 +78,7 @@ public class OperationDialogFragment extends DialogFragment { 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); Loading
packages/DocumentsUI/src/com/android/documentsui/ClipDetails.java→packages/DocumentsUI/src/com/android/documentsui/UrisSupplier.java +275 −0 Original line number Diff line number Diff line Loading @@ -18,11 +18,7 @@ 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; Loading @@ -33,67 +29,33 @@ 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 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; /** * ClipDetails is a parcelable project providing information of different type of file * management operations like cut, move and copy. * UrisSupplier provides doc uri list to {@link FileOperation}. * * Under the hood it provides cross-process synchronization support such that its consumer doesn't * <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 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 class UrisSupplier implements Parcelable { public abstract int getItemCount(); /** * Gets doc list from this clip detail. This may only be called once because it may read a file * 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 { ClipStorage storage = DocumentsApplication.getClipStorage(context); return getDocs(storage); return getDocs(DocumentsApplication.getClipStorage(context)); } @VisibleForTesting Loading @@ -107,41 +69,24 @@ public abstract class ClipDetails implements Parcelable { @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; public static UrisSupplier create(ClipData clipData) { UrisSupplier uris; PersistableBundle bundle = clipData.getDescription().getExtras(); if (bundle.containsKey(OP_JUMBO_SELECTION_TAG)) { details = new JumboClipDetails(clipData); uris = new JumboUrisSupplier(clipData); } else { details = new StandardClipDetails(clipData); uris = new StandardUrisSupplier(clipData); } return details; return uris; } public static ClipDetails createClipDetails(@OpType int opType, @Nullable Uri srcParent, public static UrisSupplier create( Selection selection, Function<String, Uri> uriBuilder, Context context) { ClipStorage storage = DocumentsApplication.getClipStorage(context); Loading @@ -150,30 +95,28 @@ public abstract class ClipDetails implements Parcelable { uris.add(uriBuilder.apply(id)); } return createClipDetails(opType, srcParent, uris, storage); return create(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); 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 details; return urisSupplier; } private static class JumboClipDetails extends ClipDetails { private static final String TAG = "JumboClipDetails"; private static class JumboUrisSupplier extends UrisSupplier { private static final String TAG = "JumboUrisSupplier"; private final long mSelectionTag; private final int mSelectionSize; private transient ClipStorage.Reader mReader; private JumboClipDetails(ClipData clipData) { super(clipData); 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); Loading @@ -182,10 +125,7 @@ public abstract class ClipDetails implements Parcelable { assert(mSelectionSize > Shared.MAX_DOCS_IN_INTENT); } private JumboClipDetails(@OpType int opType, @Nullable Uri srcParent, Collection<Uri> uris, ClipStorage storage) { super(opType, srcParent); private JumboUrisSupplier(Collection<Uri> uris, ClipStorage storage) { mSelectionTag = storage.createTag(); new ClipStorage.PersistTask(storage, uris, mSelectionTag).execute(); mSelectionSize = uris.size(); Loading @@ -197,26 +137,27 @@ public abstract class ClipDetails implements Parcelable { } @Override public Iterable<Uri> getDocs(ClipStorage storage) throws IOException { if (mReader != null) { throw new IllegalStateException( "JumboClipDetails#getDocs() can only be called once."); 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."); } mReader = storage.createReader(mSelectionTag); return mReader; return mReader.get(); } @Override void dispose(ClipStorage storage) { if (mReader != null) { try { mReader.close(); 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) { Loading @@ -227,9 +168,8 @@ public abstract class ClipDetails implements Parcelable { @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("JumboClipDetails{"); super.appendTo(builder); builder.append(", selectionTag=").append(mSelectionTag); builder.append("JumboUrisSupplier{"); builder.append("selectionTag=").append(mSelectionTag); builder.append(", selectionSize=").append(mSelectionSize); builder.append("}"); return builder.toString(); Loading @@ -237,47 +177,44 @@ public abstract class ClipDetails implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeLong(mSelectionTag); dest.writeInt(mSelectionSize); } private JumboClipDetails(Parcel in) { super(in); private JumboUrisSupplier(Parcel in) { mSelectionTag = in.readLong(); mSelectionSize = in.readInt(); } public static final Parcelable.Creator<JumboClipDetails> CREATOR = new Parcelable.Creator<JumboClipDetails>() { public static final Parcelable.Creator<JumboUrisSupplier> CREATOR = new Parcelable.Creator<JumboUrisSupplier>() { @Override public JumboClipDetails createFromParcel(Parcel source) { return new JumboClipDetails(source); public JumboUrisSupplier createFromParcel(Parcel source) { return new JumboUrisSupplier(source); } @Override public JumboClipDetails[] newArray(int size) { return new JumboClipDetails[size]; 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 StandardClipDetails extends ClipDetails { public static class StandardUrisSupplier extends UrisSupplier { private final List<Uri> mDocs; private StandardClipDetails(ClipData clipData) { super(clipData); private StandardUrisSupplier(ClipData clipData) { mDocs = listDocs(clipData); } @VisibleForTesting public StandardClipDetails(@OpType int opType, @Nullable Uri srcParent, List<Uri> docs) { super(opType, srcParent); public StandardUrisSupplier(List<Uri> docs) { mDocs = docs; } Loading @@ -299,44 +236,39 @@ public abstract class ClipDetails implements Parcelable { } @Override public Iterable<Uri> getDocs(ClipStorage storage) { 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("StandardUrisSupplier{"); builder.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); private StandardUrisSupplier(Parcel in) { mDocs = in.createTypedArrayList(Uri.CREATOR); } public static final Parcelable.Creator<StandardClipDetails> CREATOR = new Parcelable.Creator<StandardClipDetails>() { public static final Parcelable.Creator<StandardUrisSupplier> CREATOR = new Parcelable.Creator<StandardUrisSupplier>() { @Override public StandardClipDetails createFromParcel(Parcel source) { return new StandardClipDetails(source); public StandardUrisSupplier createFromParcel(Parcel source) { return new StandardUrisSupplier(source); } @Override public StandardClipDetails[] newArray(int size) { return new StandardClipDetails[size]; public StandardUrisSupplier[] newArray(int size) { return new StandardUrisSupplier[size]; } }; } Loading