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