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 android.os.SystemClock.elapsedRealtime;
20 import static com.android.documentsui.base.SharedMinimal.DEBUG;
21 import static com.android.documentsui.services.FileOperationService.EXTRA_CANCEL;
22 import static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID;
23 import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION;
24 
25 import androidx.annotation.IntDef;
26 import android.app.Activity;
27 import android.content.Context;
28 import android.content.Intent;
29 import androidx.annotation.VisibleForTesting;
30 import android.util.Log;
31 
32 import com.android.documentsui.services.FileOperationService.OpType;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 
37 import javax.annotation.Nullable;
38 
39 /**
40  * Helper functions for starting various file operations.
41  */
42 public final class FileOperations {
43 
44     private static final String TAG = "FileOperations";
45 
46     private static final IdBuilder idBuilder = new IdBuilder();
47 
FileOperations()48     private FileOperations() {}
49 
createJobId()50     public static String createJobId() {
51         return idBuilder.getNext();
52     }
53 
54     /**
55      * Tries to start the activity. Returns the job id.
56      * @param jobId Optional job id. If null, then it will be auto-generated.
57      */
start(Context context, FileOperation operation, Callback callback, @Nullable String jobId)58     public static String start(Context context, FileOperation operation, Callback callback,
59             @Nullable String jobId) {
60 
61         if (DEBUG) {
62             Log.d(TAG, "Handling generic 'start' call.");
63         }
64 
65         String newJobId = jobId != null ? jobId : createJobId();
66         Intent intent = createBaseIntent(context, newJobId, operation);
67         if (callback != null) {
68             callback.onOperationResult(Callback.STATUS_ACCEPTED, operation.getOpType(),
69                     operation.getSrc().getItemCount());
70         }
71 
72         context.startService(intent);
73 
74         return newJobId;
75     }
76 
77     @VisibleForTesting
cancel(Activity activity, String jobId)78     public static void cancel(Activity activity, String jobId) {
79         if (DEBUG) {
80             Log.d(TAG, "Attempting to canceling operation: " + jobId);
81         }
82 
83         Intent intent = new Intent(activity, FileOperationService.class);
84         intent.putExtra(EXTRA_CANCEL, true);
85         intent.putExtra(EXTRA_JOB_ID, jobId);
86 
87         activity.startService(intent);
88     }
89 
90     /**
91      * Starts the service for an operation.
92      *
93      * @param jobId A unique jobid for this job.
94      *     Use {@link #createJobId} if you don't have one handy.
95      * @return Id of the job.
96      */
createBaseIntent( Context context, String jobId, FileOperation operation)97     public static Intent createBaseIntent(
98             Context context, String jobId, FileOperation operation) {
99 
100         Intent intent = new Intent(context, FileOperationService.class);
101         intent.putExtra(EXTRA_JOB_ID, jobId);
102         intent.putExtra(EXTRA_OPERATION, operation);
103 
104         return intent;
105     }
106 
107     private static final class IdBuilder {
108 
109         // Remember last job time so we can guard against collisions.
110         private long mLastJobTime;
111 
112         // If we detect a collision, use subId to make distinct.
113         private int mSubId;
114 
getNext()115         public synchronized String getNext() {
116             long time = elapsedRealtime();
117             if (time == mLastJobTime) {
118                 mSubId++;
119             } else {
120                 mSubId = 0;
121             }
122             mLastJobTime = time;
123             return String.valueOf(mLastJobTime) + "-" + String.valueOf(mSubId);
124         }
125     }
126 
127     /**
128      * A functional callback called when the file operation starts or fails to start.
129      */
130     @FunctionalInterface
131     public interface Callback {
132         @Retention(RetentionPolicy.SOURCE)
133         @IntDef({STATUS_ACCEPTED, STATUS_REJECTED})
134         @interface Status {}
135         static final int STATUS_ACCEPTED = 0;
136         static final int STATUS_REJECTED = 1;
137         static final int STATUS_FAILED = 2;
138 
139         /**
140          * Performs operation when the file operation starts or fails to start.
141          *
142          * @param status {@link Status} of this operation.
143          * @param opType file operation type {@link OpType}.
144          * @param docCount number of documents operated.
145          */
onOperationResult(@tatus int status, @OpType int opType, int docCount)146         void onOperationResult(@Status int status, @OpType int opType, int docCount);
147     }
148 }
149