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