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