1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.documentsui.services;
18 
19 import static com.android.documentsui.services.FileOperationService.OPERATION_COMPRESS;
20 import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
21 import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
22 import static com.android.documentsui.services.FileOperationService.OPERATION_EXTRACT;
23 import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
24 import static com.android.documentsui.services.FileOperationService.OPERATION_UNKNOWN;
25 import static androidx.core.util.Preconditions.checkArgument;
26 
27 import android.content.Context;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.Messenger;
33 import android.os.Parcel;
34 import android.os.Parcelable;
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.documentsui.base.DocumentStack;
38 import com.android.documentsui.base.Features;
39 import com.android.documentsui.clipping.UrisSupplier;
40 import com.android.documentsui.services.FileOperationService.OpType;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 import javax.annotation.Nullable;
46 
47 /**
48  * FileOperation describes a file operation, such as move/copy/delete etc.
49  */
50 public abstract class FileOperation implements Parcelable {
51     private final @OpType int mOpType;
52 
53     private final UrisSupplier mSrcs;
54     private final List<Handler.Callback> mMessageListeners = new ArrayList<>();
55     private DocumentStack mDestination;
56     private Messenger mMessenger = new Messenger(
57             new Handler(Looper.getMainLooper(), this::onMessage));
58 
59     @VisibleForTesting
FileOperation(@pType int opType, UrisSupplier srcs, DocumentStack destination)60     FileOperation(@OpType int opType, UrisSupplier srcs, DocumentStack destination) {
61         checkArgument(opType != OPERATION_UNKNOWN);
62         checkArgument(srcs.getItemCount() > 0);
63 
64         mOpType = opType;
65         mSrcs = srcs;
66         mDestination = destination;
67     }
68 
69     @Override
describeContents()70     public int describeContents() {
71         return 0;
72     }
73 
getOpType()74     public @OpType int getOpType() {
75         return mOpType;
76     }
77 
getSrc()78     public UrisSupplier getSrc() {
79         return mSrcs;
80     }
81 
getDestination()82     public DocumentStack getDestination() {
83         return mDestination;
84     }
85 
getMessenger()86     public Messenger getMessenger() {
87         return mMessenger;
88     }
89 
setDestination(DocumentStack destination)90     public void setDestination(DocumentStack destination) {
91         mDestination = destination;
92     }
93 
dispose()94     public void dispose() {
95         mSrcs.dispose();
96     }
97 
createJob(Context service, Job.Listener listener, String id, Features features)98     abstract Job createJob(Context service, Job.Listener listener, String id, Features features);
99 
appendInfoTo(StringBuilder builder)100     private void appendInfoTo(StringBuilder builder) {
101         builder.append("opType=").append(mOpType);
102         builder.append(", srcs=").append(mSrcs.toString());
103         builder.append(", destination=").append(mDestination.toString());
104     }
105 
106     @Override
writeToParcel(Parcel out, int flag)107     public void writeToParcel(Parcel out, int flag) {
108         out.writeInt(mOpType);
109         out.writeParcelable(mSrcs, flag);
110         out.writeParcelable(mDestination, flag);
111         out.writeParcelable(mMessenger, flag);
112     }
113 
FileOperation(Parcel in)114     private FileOperation(Parcel in) {
115         mOpType = in.readInt();
116         mSrcs = in.readParcelable(FileOperation.class.getClassLoader());
117         mDestination = in.readParcelable(FileOperation.class.getClassLoader());
118         mMessenger = in.readParcelable(FileOperation.class.getClassLoader());
119     }
120 
121     public static class CopyOperation extends FileOperation {
CopyOperation(UrisSupplier srcs, DocumentStack destination)122         private CopyOperation(UrisSupplier srcs, DocumentStack destination) {
123             super(OPERATION_COPY, srcs, destination);
124         }
125 
126         @Override
toString()127         public String toString() {
128             StringBuilder builder = new StringBuilder();
129 
130             builder.append("CopyOperation{");
131             super.appendInfoTo(builder);
132             builder.append("}");
133 
134             return builder.toString();
135         }
136 
137         @Override
createJob(Context service, Job.Listener listener, String id, Features features)138         CopyJob createJob(Context service, Job.Listener listener, String id, Features features) {
139             return new CopyJob(
140                     service, listener, id, getDestination(), getSrc(), getMessenger(), features);
141         }
142 
CopyOperation(Parcel in)143         private CopyOperation(Parcel in) {
144             super(in);
145         }
146 
147         public static final Parcelable.Creator<CopyOperation> CREATOR =
148                 new Parcelable.Creator<CopyOperation>() {
149 
150                     @Override
151                     public CopyOperation createFromParcel(Parcel source) {
152                         return new CopyOperation(source);
153                     }
154 
155                     @Override
156                     public CopyOperation[] newArray(int size) {
157                         return new CopyOperation[size];
158                     }
159                 };
160     }
161 
162     public static class CompressOperation extends FileOperation {
CompressOperation(UrisSupplier srcs, DocumentStack destination)163         private CompressOperation(UrisSupplier srcs, DocumentStack destination) {
164             super(OPERATION_COMPRESS, srcs, destination);
165         }
166 
167         @Override
toString()168         public String toString() {
169             StringBuilder builder = new StringBuilder();
170 
171             builder.append("CompressOperation{");
172             super.appendInfoTo(builder);
173             builder.append("}");
174 
175             return builder.toString();
176         }
177 
178         @Override
createJob(Context service, Job.Listener listener, String id, Features features)179         CopyJob createJob(Context service, Job.Listener listener, String id, Features features) {
180             return new CompressJob(service, listener, id, getDestination(), getSrc(),
181                     getMessenger(), features);
182         }
183 
CompressOperation(Parcel in)184         private CompressOperation(Parcel in) {
185             super(in);
186         }
187 
188         public static final Parcelable.Creator<CompressOperation> CREATOR =
189                 new Parcelable.Creator<CompressOperation>() {
190 
191                     @Override
192                     public CompressOperation createFromParcel(Parcel source) {
193                         return new CompressOperation(source);
194                     }
195 
196                     @Override
197                     public CompressOperation[] newArray(int size) {
198                         return new CompressOperation[size];
199                     }
200                 };
201     }
202 
203     public static class ExtractOperation extends FileOperation {
ExtractOperation(UrisSupplier srcs, DocumentStack destination)204         private ExtractOperation(UrisSupplier srcs, DocumentStack destination) {
205             super(OPERATION_EXTRACT, srcs, destination);
206         }
207 
208         @Override
toString()209         public String toString() {
210             StringBuilder builder = new StringBuilder();
211 
212             builder.append("ExtractOperation{");
213             super.appendInfoTo(builder);
214             builder.append("}");
215 
216             return builder.toString();
217         }
218 
219         // TODO: Replace CopyJob with ExtractJob.
220         @Override
createJob(Context service, Job.Listener listener, String id, Features features)221         CopyJob createJob(Context service, Job.Listener listener, String id, Features features) {
222             return new CopyJob(
223                     service, listener, id, getDestination(), getSrc(), getMessenger(), features);
224         }
225 
ExtractOperation(Parcel in)226         private ExtractOperation(Parcel in) {
227             super(in);
228         }
229 
230         public static final Parcelable.Creator<ExtractOperation> CREATOR =
231                 new Parcelable.Creator<ExtractOperation>() {
232 
233                     @Override
234                     public ExtractOperation createFromParcel(Parcel source) {
235                         return new ExtractOperation(source);
236                     }
237 
238                     @Override
239                     public ExtractOperation[] newArray(int size) {
240                         return new ExtractOperation[size];
241                     }
242                 };
243     }
244 
245     public static class MoveDeleteOperation extends FileOperation {
246         private final @Nullable Uri mSrcParent;
247 
MoveDeleteOperation(@pType int opType, UrisSupplier srcs, DocumentStack destination, @Nullable Uri srcParent)248         private MoveDeleteOperation(@OpType int opType, UrisSupplier srcs,
249                 DocumentStack destination, @Nullable Uri srcParent) {
250             super(opType, srcs, destination);
251 
252             mSrcParent = srcParent;
253         }
254 
255         @Override
createJob(Context service, Job.Listener listener, String id, Features features)256         Job createJob(Context service, Job.Listener listener, String id, Features features) {
257             switch(getOpType()) {
258                 case OPERATION_MOVE:
259                     return new MoveJob(
260                             service, listener, id, getDestination(), getSrc(), mSrcParent,
261                             getMessenger(), features);
262                 case OPERATION_DELETE:
263                     return new DeleteJob(service, listener, id, getDestination(), getSrc(),
264                             mSrcParent, features);
265                 default:
266                     throw new UnsupportedOperationException("Unsupported op type: " + getOpType());
267             }
268         }
269 
270         @Override
toString()271         public String toString() {
272             StringBuilder builder = new StringBuilder();
273 
274             builder.append("MoveDeleteOperation{");
275             super.appendInfoTo(builder);
276             builder.append(", srcParent=").append(mSrcParent.toString());
277             builder.append("}");
278 
279             return builder.toString();
280         }
281 
282         @Override
writeToParcel(Parcel out, int flag)283         public void writeToParcel(Parcel out, int flag) {
284             super.writeToParcel(out, flag);
285             out.writeParcelable(mSrcParent, flag);
286         }
287 
MoveDeleteOperation(Parcel in)288         private MoveDeleteOperation(Parcel in) {
289             super(in);
290             mSrcParent = in.readParcelable(null);
291         }
292 
293         public static final Parcelable.Creator<MoveDeleteOperation> CREATOR =
294                 new Parcelable.Creator<MoveDeleteOperation>() {
295 
296 
297             @Override
298             public MoveDeleteOperation createFromParcel(Parcel source) {
299                 return new MoveDeleteOperation(source);
300             }
301 
302             @Override
303             public MoveDeleteOperation[] newArray(int size) {
304                 return new MoveDeleteOperation[size];
305             }
306         };
307     }
308 
309     public static class Builder {
310         private @OpType int mOpType;
311         private Uri mSrcParent;
312         private UrisSupplier mSrcs;
313         private DocumentStack mDestination;
314 
withOpType(@pType int opType)315         public Builder withOpType(@OpType int opType) {
316             mOpType = opType;
317             return this;
318         }
319 
withSrcParent(@ullable Uri srcParent)320         public Builder withSrcParent(@Nullable Uri srcParent) {
321             mSrcParent = srcParent;
322             return this;
323         }
324 
withSrcs(UrisSupplier srcs)325         public Builder withSrcs(UrisSupplier srcs) {
326             mSrcs = srcs;
327             return this;
328         }
329 
withDestination(DocumentStack destination)330         public Builder withDestination(DocumentStack destination) {
331             mDestination = destination;
332             return this;
333         }
334 
build()335         public FileOperation build() {
336             switch (mOpType) {
337                 case OPERATION_COPY:
338                     return new CopyOperation(mSrcs, mDestination);
339                 case OPERATION_COMPRESS:
340                     return new CompressOperation(mSrcs, mDestination);
341                 case OPERATION_EXTRACT:
342                     return new ExtractOperation(mSrcs, mDestination);
343                 case OPERATION_MOVE:
344                 case OPERATION_DELETE:
345                     return new MoveDeleteOperation(mOpType, mSrcs, mDestination, mSrcParent);
346                 default:
347                     throw new UnsupportedOperationException("Unsupported op type: " + mOpType);
348             }
349         }
350     }
351 
onMessage(Message message)352     boolean onMessage(Message message) {
353         for (Handler.Callback listener : mMessageListeners) {
354             if (listener.handleMessage(message)) {
355               return true;
356             }
357         }
358         return false;
359     }
360 
361     /**
362      * Registers a listener for messages from the service job.
363      *
364      * Callbacks must return true if the message is handled, and false if not.
365      * Once handled, consecutive callbacks will not be called.
366      */
addMessageListener(Handler.Callback handler)367     public void addMessageListener(Handler.Callback handler) {
368         mMessageListeners.add(handler);
369     }
370 
removeMessageListener(Handler.Callback handler)371     public void removeMessageListener(Handler.Callback handler) {
372         mMessageListeners.remove(handler);
373     }
374 }
375