| /* | |
| * 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); | |
| } | |
| } |