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.Shared.DEBUG; 21 import static com.android.documentsui.Shared.EXTRA_STACK; 22 import static com.android.documentsui.Shared.asArrayList; 23 import static com.android.documentsui.Shared.getQuantityString; 24 import static com.android.documentsui.services.FileOperationService.EXTRA_CANCEL; 25 import static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID; 26 import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION; 27 import static com.android.documentsui.services.FileOperationService.EXTRA_SRC_LIST; 28 import static com.android.documentsui.services.FileOperationService.EXTRA_SRC_PARENT; 29 import static com.android.documentsui.services.FileOperationService.OPERATION_COPY; 30 import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE; 31 import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE; 32 33 import android.app.Activity; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.res.Resources; 37 import android.os.Parcelable; 38 import android.support.annotation.VisibleForTesting; 39 import android.support.design.widget.Snackbar; 40 import android.util.Log; 41 42 import com.android.documentsui.R; 43 import com.android.documentsui.Snackbars; 44 import com.android.documentsui.model.DocumentInfo; 45 import com.android.documentsui.model.DocumentStack; 46 import com.android.documentsui.services.FileOperationService.OpType; 47 48 import java.util.List; 49 50 /** 51 * Helper functions for starting various file operations. 52 */ 53 public final class FileOperations { 54 55 private static final String TAG = "FileOperations"; 56 57 private static final IdBuilder idBuilder = new IdBuilder(); 58 59 private FileOperations() {} 60 61 public static String createJobId() { 62 return idBuilder.getNext(); 63 } 64 65 /** 66 * Tries to start the activity. Returns the job id. 67 */ 68 public static String start( 69 Activity activity, List<DocumentInfo> srcDocs, 70 DocumentStack stack, int operationType) { 71 72 if (DEBUG) Log.d(TAG, "Handling generic 'start' call."); 73 74 switch (operationType) { 75 case OPERATION_COPY: 76 return FileOperations.copy(activity, srcDocs, stack); 77 case OPERATION_MOVE: 78 throw new IllegalArgumentException("Moving requires providing the source parent."); 79 case OPERATION_DELETE: 80 throw new UnsupportedOperationException("Delete isn't currently supported."); 81 default: 82 throw new UnsupportedOperationException("Unknown operation: " + operationType); 83 } 84 } 85 86 /** 87 * Tries to start the activity. Returns the job id. 88 */ 89 public static String start( 90 Activity activity, List<DocumentInfo> srcDocs, DocumentInfo srcParent, 91 DocumentStack stack, int operationType) { 92 93 if (DEBUG) Log.d(TAG, "Handling generic 'start' call."); 94 95 switch (operationType) { 96 case OPERATION_COPY: 97 return FileOperations.copy(activity, srcDocs, stack); 98 case OPERATION_MOVE: 99 return FileOperations.move(activity, srcDocs, srcParent, stack); 100 case OPERATION_DELETE: 101 throw new UnsupportedOperationException("Delete isn't currently supported."); 102 default: 103 throw new UnsupportedOperationException("Unknown operation: " + operationType); 104 } 105 } 106 107 @VisibleForTesting 108 public static void cancel(Activity activity, String jobId) { 109 if (DEBUG) Log.d(TAG, "Attempting to canceling operation: " + jobId); 110 111 Intent intent = new Intent(activity, FileOperationService.class); 112 intent.putExtra(EXTRA_CANCEL, true); 113 intent.putExtra(EXTRA_JOB_ID, jobId); 114 115 activity.startService(intent); 116 } 117 118 @VisibleForTesting 119 public static String copy( 120 Activity activity, List<DocumentInfo> srcDocs, DocumentStack destination) { 121 String jobId = createJobId(); 122 if (DEBUG) Log.d(TAG, "Initiating 'copy' operation id: " + jobId); 123 124 Intent intent = createBaseIntent(OPERATION_COPY, activity, jobId, srcDocs, destination); 125 126 createSharedSnackBar(activity, R.plurals.copy_begin, srcDocs.size()) 127 .show(); 128 129 activity.startService(intent); 130 131 return jobId; 132 } 133 134 /** 135 * Starts the service for a move operation. 136 * 137 * @param jobId A unique jobid for this job. 138 * Use {@link #createJobId} if you don't have one handy. 139 * @param srcDocs A list of src files to copy. 140 * @param srcParent Parent of all the source documents. 141 * @param destination The move destination stack. 142 */ 143 public static String move( 144 Activity activity, List<DocumentInfo> srcDocs, DocumentInfo srcParent, 145 DocumentStack destination) { 146 String jobId = createJobId(); 147 if (DEBUG) Log.d(TAG, "Initiating 'move' operation id: " + jobId); 148 149 Intent intent = createBaseIntent(OPERATION_MOVE, activity, jobId, srcDocs, srcParent, 150 destination); 151 152 createSharedSnackBar(activity, R.plurals.move_begin, srcDocs.size()) 153 .show(); 154 155 activity.startService(intent); 156 157 return jobId; 158 } 159 160 /** 161 * Starts the service for a delete operation. 162 * 163 * @param jobId A unique jobid for this job. 164 * Use {@link #createJobId} if you don't have one handy. 165 * @param srcDocs A list of src files to delete. 166 * @param srcParent Parent of all the source documents. 167 * @return Id of the job. 168 */ 169 public static String delete( 170 Activity activity, List<DocumentInfo> srcDocs, DocumentInfo srcParent, 171 DocumentStack location) { 172 String jobId = createJobId(); 173 if (DEBUG) Log.d(TAG, "Initiating 'delete' operation id " + jobId + "."); 174 175 Intent intent = createBaseIntent(OPERATION_DELETE, activity, jobId, srcDocs, srcParent, 176 location); 177 activity.startService(intent); 178 179 return jobId; 180 } 181 182 /** 183 * Starts the service for an operation. 184 * 185 * @param jobId A unique jobid for this job. 186 * Use {@link #createJobId} if you don't have one handy. 187 * @param srcDocs A list of src files for an operation. 188 * @return Id of the job. 189 */ 190 public static Intent createBaseIntent( 191 @OpType int operationType, Context context, String jobId, List<DocumentInfo> srcDocs, 192 DocumentStack localeStack) { 193 194 Intent intent = new Intent(context, FileOperationService.class); 195 intent.putExtra(EXTRA_JOB_ID, jobId); 196 intent.putParcelableArrayListExtra(EXTRA_SRC_LIST, asArrayList(srcDocs)); 197 intent.putExtra(EXTRA_STACK, (Parcelable) localeStack); 198 intent.putExtra(EXTRA_OPERATION, operationType); 199 200 return intent; 201 } 202 203 /** 204 * Starts the service for an operation. 205 * 206 * @param jobId A unique jobid for this job. 207 * Use {@link #createJobId} if you don't have one handy. 208 * @param srcDocs A list of src files to copy. 209 * @param srcParent Parent of all the source documents. 210 * @return Id of the job. 211 */ 212 public static Intent createBaseIntent( 213 @OpType int operationType, Context context, String jobId, 214 List<DocumentInfo> srcDocs, DocumentInfo srcParent, DocumentStack localeStack) { 215 216 Intent intent = new Intent(context, FileOperationService.class); 217 intent.putExtra(EXTRA_JOB_ID, jobId); 218 intent.putParcelableArrayListExtra(EXTRA_SRC_LIST, asArrayList(srcDocs)); 219 intent.putExtra(EXTRA_SRC_PARENT, srcParent); 220 intent.putExtra(EXTRA_STACK, (Parcelable) localeStack); 221 intent.putExtra(EXTRA_OPERATION, operationType); 222 223 return intent; 224 } 225 226 private static Snackbar createSharedSnackBar(Activity activity, int contentId, int fileCount) { 227 Resources res = activity.getResources(); 228 return Snackbars.makeSnackbar( 229 activity, 230 getQuantityString(activity, contentId, fileCount), 231 Snackbar.LENGTH_SHORT); 232 } 233 234 private static final class IdBuilder { 235 236 // Remember last job time so we can guard against collisions. 237 private long mLastJobTime; 238 239 // If we detect a collision, use subId to make distinct. 240 private int mSubId; 241 242 public synchronized String getNext() { 243 long time = elapsedRealtime(); 244 if (time == mLastJobTime) { 245 mSubId++; 246 } else { 247 mSubId = 0; 248 } 249 mLastJobTime = time; 250 return String.valueOf(mLastJobTime) + "-" + String.valueOf(mSubId); 251 } 252 } 253 } 254