FileOperationService.java revision 3d68b2b3af9e248937258636329c72bcf78c0ae5
1c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay/* 2c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * Copyright (C) 2015 The Android Open Source Project 3c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * 4c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * Licensed under the Apache License, Version 2.0 (the "License"); 5c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * you may not use this file except in compliance with the License. 6c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * You may obtain a copy of the License at 7c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * 8c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * http://www.apache.org/licenses/LICENSE-2.0 9c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * 10c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * Unless required by applicable law or agreed to in writing, software 11c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * distributed under the License is distributed on an "AS IS" BASIS, 12c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * See the License for the specific language governing permissions and 14c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * limitations under the License. 15c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay */ 16c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 17c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKaypackage com.android.documentsui.services; 18c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 19c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport static com.android.documentsui.Shared.DEBUG; 20c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 21c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.annotation.IntDef; 22c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.app.NotificationManager; 23bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport android.app.Service; 24c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.content.Intent; 25bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport android.os.IBinder; 26c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.os.PowerManager; 2797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKayimport android.support.annotation.Nullable; 28c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.support.annotation.VisibleForTesting; 29c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.util.Log; 30c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 31c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.android.documentsui.Shared; 32c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.android.documentsui.model.DocumentInfo; 33c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.android.documentsui.model.DocumentStack; 34bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport com.android.documentsui.services.Job.Factory; 35c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 36c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.lang.annotation.Retention; 37c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.lang.annotation.RetentionPolicy; 38bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport java.util.HashMap; 39c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.util.List; 40bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport java.util.Map; 41bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport java.util.concurrent.ScheduledExecutorService; 42bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport java.util.concurrent.ScheduledFuture; 43bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport java.util.concurrent.ScheduledThreadPoolExecutor; 44bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport java.util.concurrent.TimeUnit; 45bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 46bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport javax.annotation.concurrent.GuardedBy; 47bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 48bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKaypublic class FileOperationService extends Service implements Job.Listener { 49bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 50bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private static final int DEFAULT_DELAY = 0; 51bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private static final int MAX_DELAY = 10 * 1000; // ten seconds 526b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski private static final int POOL_SIZE = 2; // "pool size", not *max* "pool size". 536b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski private static final int NOTIFICATION_ID_PROGRESS = 0; 546b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski private static final int NOTIFICATION_ID_FAILURE = 1; 55dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski private static final int NOTIFICATION_ID_WARNING = 2; 56c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 57c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String TAG = "FileOperationService"; 58c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 59c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID"; 60bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay public static final String EXTRA_DELAY = "com.android.documentsui.DELAY"; 61c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION"; 62c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL"; 63c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST"; 64dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski public static final String EXTRA_DIALOG_TYPE = "com.android.documentsui.DIALOG_TYPE"; 65c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 66e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski // This extra is used only for moving and deleting. Currently it's not the case, 67e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski // but in the future those files may be from multiple different parents. In 68e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski // such case, this needs to be replaced with pairs of parent and child. 69e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski public static final String EXTRA_SRC_PARENT = "com.android.documentsui.SRC_PARENT"; 70e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski 71c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @IntDef(flag = true, value = { 72c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay OPERATION_UNKNOWN, 73c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay OPERATION_COPY, 74c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay OPERATION_MOVE, 75c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay OPERATION_DELETE 76c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay }) 77c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @Retention(RetentionPolicy.SOURCE) 78c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public @interface OpType {} 79f171934ad7a9d901e81a33bbd0ec74aa34a48787Steve McKay public static final int OPERATION_UNKNOWN = -1; 80f171934ad7a9d901e81a33bbd0ec74aa34a48787Steve McKay public static final int OPERATION_COPY = 1; 81f171934ad7a9d901e81a33bbd0ec74aa34a48787Steve McKay public static final int OPERATION_MOVE = 2; 82f171934ad7a9d901e81a33bbd0ec74aa34a48787Steve McKay public static final int OPERATION_DELETE = 3; 83c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 84c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // TODO: Move it to a shared file when more operations are implemented. 85c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final int FAILURE_COPY = 1; 86c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 87bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // The executor and job factory are visible for testing and non-final 88bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // so we'll have a way to inject test doubles from the test. It's 89bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // a sub-optimal arrangement. 90bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @VisibleForTesting ScheduledExecutorService executor; 91bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @VisibleForTesting Factory jobFactory; 92c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 93bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private PowerManager mPowerManager; 94bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private PowerManager.WakeLock mWakeLock; // the wake lock, if held. 95c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay private NotificationManager mNotificationManager; 96c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 97bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @GuardedBy("mRunning") 98bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private Map<String, JobRecord> mRunning = new HashMap<>(); 99c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 10097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay private int mLastServiceId; 101c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 102c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @Override 103c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public void onCreate() { 104bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // Allow tests to pre-set these with test doubles. 105bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (executor == null) { 106bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay executor = new ScheduledThreadPoolExecutor(POOL_SIZE); 107bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 108bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 109bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (jobFactory == null) { 110bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay jobFactory = Job.Factory.instance; 111bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 112c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 113c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay if (DEBUG) Log.d(TAG, "Created."); 114c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay mPowerManager = getSystemService(PowerManager.class); 115c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay mNotificationManager = getSystemService(NotificationManager.class); 116c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 117c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 118c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @Override 11997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay public void onDestroy() { 12097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (DEBUG) Log.d(TAG, "Shutting down executor."); 12197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay List<Runnable> unfinished = executor.shutdownNow(); 12297b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (!unfinished.isEmpty()) { 12397b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay Log.w(TAG, "Shutting down, but executor reports running jobs: " + unfinished); 12497b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay } 12597b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay executor = null; 12697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (DEBUG) Log.d(TAG, "Destroyed."); 12797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay } 12897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay 12997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay @Override 13097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay public int onStartCommand(Intent intent, int flags, int serviceId) { 131bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // TODO: Ensure we're not being called with retry or redeliver. 132bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // checkArgument(flags == 0); // retry and redeliver are not supported. 133c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 134c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay String jobId = intent.getStringExtra(EXTRA_JOB_ID); 135c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @OpType int operationType = intent.getIntExtra(EXTRA_OPERATION, OPERATION_UNKNOWN); 1360af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(jobId != null); 137bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 138c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay if (intent.hasExtra(EXTRA_CANCEL)) { 139c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay handleCancel(intent); 140bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } else { 1410af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(operationType != OPERATION_UNKNOWN); 14297b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay handleOperation(intent, serviceId, jobId, operationType); 143c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 144c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 145bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay return START_NOT_STICKY; 146bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 147c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 14897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay private void handleOperation(Intent intent, int serviceId, String jobId, int operationType) { 14997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (DEBUG) Log.d(TAG, "onStartCommand: " + jobId + " with serviceId " + serviceId); 150c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 15197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay // Track the service supplied id so we can stop the service once we're out of work to do. 15297b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay mLastServiceId = serviceId; 153c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 154bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay Job job = null; 155bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay synchronized (mRunning) { 156bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (mWakeLock == null) { 157e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 158c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 159c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 160bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay List<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST); 161e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski DocumentInfo srcParent = intent.getParcelableExtra(EXTRA_SRC_PARENT); 162bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK); 163c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 164e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski job = createJob(operationType, jobId, srcs, srcParent, stack); 165bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 16697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (job == null) { 16797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay return; 16897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay } 16997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay 170bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay mWakeLock.acquire(); 171c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 172bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 1730af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(job != null); 174bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay int delay = intent.getIntExtra(EXTRA_DELAY, DEFAULT_DELAY); 1750af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(delay <= MAX_DELAY); 17697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (DEBUG) Log.d( 17797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay TAG, "Scheduling job " + job.id + " to run in " + delay + " milliseconds."); 178bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay ScheduledFuture<?> future = executor.schedule(job, delay, TimeUnit.MILLISECONDS); 179bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay mRunning.put(jobId, new JobRecord(job, future)); 180c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 181c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 182c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay /** 183c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * Cancels the operation corresponding to job id, identified in "EXTRA_JOB_ID". 184c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * 185c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * @param intent The cancellation intent. 186c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay */ 187c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay private void handleCancel(Intent intent) { 1880af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(intent.hasExtra(EXTRA_CANCEL)); 1890af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(intent.getStringExtra(EXTRA_JOB_ID) != null); 1900af8afd3309538dec784ed0c9c35b252a8213123Steve McKay 1910af8afd3309538dec784ed0c9c35b252a8213123Steve McKay String jobId = intent.getStringExtra(EXTRA_JOB_ID); 192c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 193bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "handleCancel: " + jobId); 194bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 195bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay synchronized (mRunning) { 196bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey 197bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // cancellation requests from affecting unrelated copy jobs. However, if the current job ID 198bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // is null, the service most likely crashed and was revived by the incoming cancel intent. 199bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // In that case, always allow the cancellation to proceed. 200bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay JobRecord record = mRunning.get(jobId); 201bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (record != null) { 202bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay record.job.cancel(); 203bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 204bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // If the job hasn't been started, cancel it and explicitly clean up. 205bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // If it *has* been started, we wait for it to recognize this, then 206bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // allow it stop working in an orderly fashion. 207bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (record.future.getDelay(TimeUnit.MILLISECONDS) > 0) { 208bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay record.future.cancel(false); 209bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay onFinished(record.job); 210bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 211bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 212c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 213c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 214c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // Dismiss the progress notification here rather than in the copy loop. This preserves 215c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // interactivity for the user in case the copy loop is stalled. 216c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // Try to cancel it even if we don't have a job id...in case there is some sad 217c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // orphan notification. 2186b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski mNotificationManager.cancel(jobId, NOTIFICATION_ID_PROGRESS); 219c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 220bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // TODO: Guarantee the job is being finalized 221c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 222c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 22397b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay /** 22497b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay * Creates a new job. Returns null if a job with {@code id} already exists. 22597b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay * @return 22697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay */ 227bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @GuardedBy("mRunning") 22897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay private @Nullable Job createJob( 229e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski @OpType int operationType, String id, List<DocumentInfo> srcs, DocumentInfo srcParent, 230e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski DocumentStack stack) { 231c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 2323d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay if (srcs.isEmpty()) { 2333d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay Log.w(TAG, "Ignoring job request with empty srcs list. Id: " + id); 2343d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay return null; 2353d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay } 2363d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay 23797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (mRunning.containsKey(id)) { 23897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay Log.w(TAG, "Duplicate job id: " + id 23997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay + ". Ignoring job request for srcs: " + srcs + ", stack: " + stack + "."); 24097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay return null; 24197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay } 242c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 243c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay switch (operationType) { 244c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay case OPERATION_COPY: 2453d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay return jobFactory.createCopy( 2463d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay this, getApplicationContext(), this, id, stack, srcs); 247c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay case OPERATION_MOVE: 2483d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay return jobFactory.createMove( 2493d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay this, getApplicationContext(), this, id, stack, srcs, 250e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski srcParent); 251c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay case OPERATION_DELETE: 2523d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay return jobFactory.createDelete( 2533d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay this, getApplicationContext(), this, id, stack, srcs, 254e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski srcParent); 255c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay default: 256c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay throw new UnsupportedOperationException(); 257c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 258bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 259bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 260bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @GuardedBy("mRunning") 261bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private void deleteJob(Job job) { 262bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "deleteJob: " + job.id); 263bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 264bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay JobRecord record = mRunning.remove(job.id); 2650af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(record != null); 266bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay record.job.cleanup(); 267bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 268bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (mRunning.isEmpty()) { 269bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay shutdown(); 270bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 271bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 272bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 273bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay /** 274bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay * Most likely shuts down. Won't shut down if service has a pending 27597b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay * message. Thread pool is deal with in onDestroy. 276bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay */ 277bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private void shutdown() { 27897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (DEBUG) Log.d(TAG, "Shutting down. Last serviceId was " + mLastServiceId); 279bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay mWakeLock.release(); 280bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay mWakeLock = null; 28197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay 28297b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay // Turns out, for us, stopSelfResult always returns false in tests, 28397b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay // so we can't guard executor shutdown. For this reason we move 28497b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay // executor shutdown to #onDestroy. 28597b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay boolean gonnaStop = stopSelfResult(mLastServiceId); 286bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "Stopping service: " + gonnaStop); 287bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (!gonnaStop) { 288bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay Log.w(TAG, "Service should be stopping, but reports otherwise."); 289bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 290bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 291bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 292bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @VisibleForTesting 293bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay boolean holdsWakeLock() { 294bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay return mWakeLock != null && mWakeLock.isHeld(); 295bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 296bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 297bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @Override 298bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay public void onStart(Job job) { 299bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "onStart: " + job.id); 3006b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski mNotificationManager.notify(job.id, NOTIFICATION_ID_PROGRESS, job.getSetupNotification()); 301c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 302c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 303bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @Override 304bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay public void onFinished(Job job) { 305bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "onFinished: " + job.id); 306bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 307bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // Dismiss the ongoing copy notification when the copy is done. 3086b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski mNotificationManager.cancel(job.id, NOTIFICATION_ID_PROGRESS); 309bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 310dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski if (job.hasFailures()) { 311dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski Log.e(TAG, "Job failed on files: " + job.failedFiles.size() + "."); 312dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski mNotificationManager.notify( 313dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski job.id, NOTIFICATION_ID_FAILURE, job.getFailureNotification()); 314dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski } 315dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski 316dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski if (job.hasWarnings()) { 317dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski if (DEBUG) Log.d(TAG, "Job finished with warnings."); 318dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski mNotificationManager.notify( 319dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski job.id, NOTIFICATION_ID_WARNING, job.getWarningNotification()); 320dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski } 321dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski 322bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay synchronized (mRunning) { 323bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay deleteJob(job); 324bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 325c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 326c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 327c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @Override 328c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public void onProgress(CopyJob job) { 329bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "onProgress: " + job.id); 3306b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski mNotificationManager.notify( 3316b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski job.id, NOTIFICATION_ID_PROGRESS, job.getProgressNotification()); 332c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 333c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 334bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private static final class JobRecord { 335bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private final Job job; 336bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private final ScheduledFuture<?> future; 337bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 338bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay public JobRecord(Job job, ScheduledFuture<?> future) { 339bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay this.job = job; 340bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay this.future = future; 341bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 342c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 343c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 344bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @Override 345bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay public IBinder onBind(Intent intent) { 346bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay return null; // Boilerplate. See super#onBind 347c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 348c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay} 349