| /* |
| * 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 androidx.core.util.Preconditions.checkArgument; |
| |
| import static com.android.documentsui.services.FileOperationService.OPERATION_COMPRESS; |
| 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_EXTRACT; |
| 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.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.documentsui.base.DocumentStack; |
| import com.android.documentsui.base.Features; |
| import com.android.documentsui.clipping.UrisSupplier; |
| import com.android.documentsui.services.FileOperationService.OpType; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * FileOperation describes a file operation, such as move/copy/delete etc. File operation currently |
| * supports and assumes on current user only. |
| */ |
| public abstract class FileOperation implements Parcelable { |
| private final @OpType int mOpType; |
| |
| private final UrisSupplier mSrcs; |
| private final List<Handler.Callback> mMessageListeners = new ArrayList<>(); |
| private DocumentStack mDestination; |
| private Messenger mMessenger = new Messenger( |
| new Handler(Looper.getMainLooper(), this::onMessage)); |
| |
| @VisibleForTesting |
| FileOperation(@OpType int opType, UrisSupplier srcs, DocumentStack destination) { |
| checkArgument(opType != OPERATION_UNKNOWN); |
| checkArgument(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 Messenger getMessenger() { |
| return mMessenger; |
| } |
| |
| public void setDestination(DocumentStack destination) { |
| mDestination = destination; |
| } |
| |
| public void dispose() { |
| mSrcs.dispose(); |
| } |
| |
| abstract Job createJob(Context service, Job.Listener listener, String id, Features features); |
| |
| 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); |
| out.writeParcelable(mMessenger, flag); |
| } |
| |
| private FileOperation(Parcel in) { |
| mOpType = in.readInt(); |
| mSrcs = in.readParcelable(FileOperation.class.getClassLoader()); |
| mDestination = in.readParcelable(FileOperation.class.getClassLoader()); |
| mMessenger = 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(); |
| } |
| |
| @Override |
| CopyJob createJob(Context service, Job.Listener listener, String id, Features features) { |
| return new CopyJob( |
| service, listener, id, getDestination(), getSrc(), getMessenger(), features); |
| } |
| |
| 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 CompressOperation extends FileOperation { |
| private CompressOperation(UrisSupplier srcs, DocumentStack destination) { |
| super(OPERATION_COMPRESS, srcs, destination); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| |
| builder.append("CompressOperation{"); |
| super.appendInfoTo(builder); |
| builder.append("}"); |
| |
| return builder.toString(); |
| } |
| |
| @Override |
| CopyJob createJob(Context service, Job.Listener listener, String id, Features features) { |
| return new CompressJob(service, listener, id, getDestination(), getSrc(), |
| getMessenger(), features); |
| } |
| |
| private CompressOperation(Parcel in) { |
| super(in); |
| } |
| |
| public static final Parcelable.Creator<CompressOperation> CREATOR = |
| new Parcelable.Creator<CompressOperation>() { |
| |
| @Override |
| public CompressOperation createFromParcel(Parcel source) { |
| return new CompressOperation(source); |
| } |
| |
| @Override |
| public CompressOperation[] newArray(int size) { |
| return new CompressOperation[size]; |
| } |
| }; |
| } |
| |
| public static class ExtractOperation extends FileOperation { |
| private ExtractOperation(UrisSupplier srcs, DocumentStack destination) { |
| super(OPERATION_EXTRACT, srcs, destination); |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| |
| builder.append("ExtractOperation{"); |
| super.appendInfoTo(builder); |
| builder.append("}"); |
| |
| return builder.toString(); |
| } |
| |
| // TODO: Replace CopyJob with ExtractJob. |
| @Override |
| CopyJob createJob(Context service, Job.Listener listener, String id, Features features) { |
| return new CopyJob( |
| service, listener, id, getDestination(), getSrc(), getMessenger(), features); |
| } |
| |
| private ExtractOperation(Parcel in) { |
| super(in); |
| } |
| |
| public static final Parcelable.Creator<ExtractOperation> CREATOR = |
| new Parcelable.Creator<ExtractOperation>() { |
| |
| @Override |
| public ExtractOperation createFromParcel(Parcel source) { |
| return new ExtractOperation(source); |
| } |
| |
| @Override |
| public ExtractOperation[] newArray(int size) { |
| return new ExtractOperation[size]; |
| } |
| }; |
| } |
| |
| public static class MoveDeleteOperation extends FileOperation { |
| private final @Nullable Uri mSrcParent; |
| |
| private MoveDeleteOperation(@OpType int opType, UrisSupplier srcs, |
| DocumentStack destination, @Nullable Uri srcParent) { |
| super(opType, srcs, destination); |
| |
| mSrcParent = srcParent; |
| } |
| |
| @Override |
| Job createJob(Context service, Job.Listener listener, String id, Features features) { |
| switch(getOpType()) { |
| case OPERATION_MOVE: |
| return new MoveJob( |
| service, listener, id, getDestination(), getSrc(), mSrcParent, |
| getMessenger(), features); |
| case OPERATION_DELETE: |
| return new DeleteJob(service, listener, id, getDestination(), getSrc(), |
| mSrcParent, features); |
| 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(@Nullable 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_COMPRESS: |
| return new CompressOperation(mSrcs, mDestination); |
| case OPERATION_EXTRACT: |
| return new ExtractOperation(mSrcs, mDestination); |
| case OPERATION_MOVE: |
| case OPERATION_DELETE: |
| return new MoveDeleteOperation(mOpType, mSrcs, mDestination, mSrcParent); |
| default: |
| throw new UnsupportedOperationException("Unsupported op type: " + mOpType); |
| } |
| } |
| } |
| |
| boolean onMessage(Message message) { |
| for (Handler.Callback listener : mMessageListeners) { |
| if (listener.handleMessage(message)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Registers a listener for messages from the service job. |
| * |
| * Callbacks must return true if the message is handled, and false if not. |
| * Once handled, consecutive callbacks will not be called. |
| */ |
| public void addMessageListener(Handler.Callback handler) { |
| mMessageListeners.add(handler); |
| } |
| |
| public void removeMessageListener(Handler.Callback handler) { |
| mMessageListeners.remove(handler); |
| } |
| } |