FileOperationService.java revision dd2b31c758e8d694867af58a425180689d25ead1
1edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project/* 2edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * Copyright (C) 2015 The Android Open Source Project 3edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * 4edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 5edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * you may not use this file except in compliance with the License. 6edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * You may obtain a copy of the License at 7edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * 8edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 9edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * 10edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 11edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 12edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * See the License for the specific language governing permissions and 14edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * limitations under the License. 15edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project */ 16edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 17edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectpackage com.android.documentsui.services; 18d8fb7b586f3cfac42694208547b58438d7f3b3edMathias Agopian 19edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport static com.android.documentsui.Shared.DEBUG; 20edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport static com.android.internal.util.Preconditions.checkArgument; 21518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopianimport static com.android.internal.util.Preconditions.checkNotNull; 22518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopianimport static com.android.internal.util.Preconditions.checkState; 23edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 24edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.annotation.IntDef; 25edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.app.NotificationManager; 26edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.app.Service; 27edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.content.Intent; 28edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.os.IBinder; 29edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.os.PowerManager; 30edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.support.annotation.Nullable; 31edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.support.annotation.VisibleForTesting; 32edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.util.Log; 33edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 34e03de9379410fd9947189f0f14e3ec457df2ebfeRomain Guyimport com.android.documentsui.Shared; 3524035338ed6329e4d85fb00cf99a91e2cdd55ba5Mathias Agopianimport com.android.documentsui.model.DocumentInfo; 369429e9c8ad8ae41104c693235a9376b3086da2e9Mathias Agopianimport com.android.documentsui.model.DocumentStack; 371cadb25da1ed875bdd078270e642966724a0c39aMathias Agopianimport com.android.documentsui.services.Job.Factory; 38edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 39864f839e969ba3417d82ab3ff7906b2f69afa900David Liimport java.lang.annotation.Retention; 400469dd6d55fa331bfd7de9431da98b6340d82271Siva Velusamyimport java.lang.annotation.RetentionPolicy; 41518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopianimport java.util.HashMap; 42518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopianimport java.util.List; 43edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport java.util.Map; 44518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopianimport java.util.concurrent.ScheduledExecutorService; 45518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopianimport java.util.concurrent.ScheduledFuture; 46edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport java.util.concurrent.ScheduledThreadPoolExecutor; 47edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport java.util.concurrent.TimeUnit; 48edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 49edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport javax.annotation.concurrent.GuardedBy; 50edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 51ada798b7ca7cabc255aa159964b64975e7fdb2dfMathias Agopianpublic class FileOperationService extends Service implements Job.Listener { 52ada798b7ca7cabc255aa159964b64975e7fdb2dfMathias Agopian 53518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian private static final int DEFAULT_DELAY = 0; 54518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian private static final int MAX_DELAY = 10 * 1000; // ten seconds 55edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project private static final int POOL_SIZE = 2; // "pool size", not *max* "pool size". 56edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project private static final int NOTIFICATION_ID_PROGRESS = 0; 57edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project private static final int NOTIFICATION_ID_FAILURE = 1; 58a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich private static final int NOTIFICATION_ID_WARNING = 2; 59a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich 60a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public static final String TAG = "FileOperationService"; 61a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich 62a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID"; 63a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public static final String EXTRA_DELAY = "com.android.documentsui.DELAY"; 64518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION"; 65518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL"; 66518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST"; 67518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian public static final String EXTRA_DIALOG_TYPE = "com.android.documentsui.DIALOG_TYPE"; 68518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian 69518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian // This extra is used only for moving and deleting. Currently it's not the case, 70a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich // but in the future those files may be from multiple different parents. In 71a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich // such case, this needs to be replaced with pairs of parent and child. 72a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public static final String EXTRA_SRC_PARENT = "com.android.documentsui.SRC_PARENT"; 73a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich 74a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public static final int OPERATION_UNKNOWN = -1; 75a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public static final int OPERATION_COPY = 1; 76a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public static final int OPERATION_MOVE = 2; 77a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public static final int OPERATION_DELETE = 3; 78a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich 79518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian @IntDef(flag = true, value = { 80a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich OPERATION_UNKNOWN, 81a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich OPERATION_COPY, 82a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich OPERATION_MOVE, 83518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian OPERATION_DELETE 84518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian }) 85499c6f02e696622a532a504be9706896aea5a304David Li @Retention(RetentionPolicy.SOURCE) 862f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li public @interface OpType {} 8793a826f78f6313db791e6fc880439189897651b3Siva Velusamy 8893a826f78f6313db791e6fc880439189897651b3Siva Velusamy // TODO: Move it to a shared file when more operations are implemented. 8993a826f78f6313db791e6fc880439189897651b3Siva Velusamy public static final int FAILURE_COPY = 1; 902f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li 912f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li // The executor and job factory are visible for testing and non-final 922f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li // so we'll have a way to inject test doubles from the test. It's 932f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li // a sub-optimal arrangement. 940469dd6d55fa331bfd7de9431da98b6340d82271Siva Velusamy @VisibleForTesting ScheduledExecutorService executor; 952f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li @VisibleForTesting Factory jobFactory; 960469dd6d55fa331bfd7de9431da98b6340d82271Siva Velusamy 9793a826f78f6313db791e6fc880439189897651b3Siva Velusamy private PowerManager mPowerManager; 9893a826f78f6313db791e6fc880439189897651b3Siva Velusamy private PowerManager.WakeLock mWakeLock; // the wake lock, if held. 9993a826f78f6313db791e6fc880439189897651b3Siva Velusamy private NotificationManager mNotificationManager; 100ccfa5c3364a88b0acdbe555b210bd2bc9feb6285Mathias Agopian 10193a826f78f6313db791e6fc880439189897651b3Siva Velusamy @GuardedBy("mRunning") 102499c6f02e696622a532a504be9706896aea5a304David Li private Map<String, JobRecord> mRunning = new HashMap<>(); 1032f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li 1042f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li private int mLastServiceId; 105499c6f02e696622a532a504be9706896aea5a304David Li 1060469dd6d55fa331bfd7de9431da98b6340d82271Siva Velusamy @Override 1070469dd6d55fa331bfd7de9431da98b6340d82271Siva Velusamy public void onCreate() { 10885f33a7168c5563aa7765b91d5d045fe62bcfcd8David Li // Allow tests to pre-set these with test doubles. 109a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich if (executor == null) { 110a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich executor = new ScheduledThreadPoolExecutor(POOL_SIZE); 111518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian } 112518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian 113a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich if (jobFactory == null) { 114a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich jobFactory = Job.Factory.instance; 115ccfa5c3364a88b0acdbe555b210bd2bc9feb6285Mathias Agopian } 1162f5a6557ef6a7b9fd33077cfd8a037904d41e3bdDavid Li 1170469dd6d55fa331bfd7de9431da98b6340d82271Siva Velusamy if (DEBUG) Log.d(TAG, "Created."); 118a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich mPowerManager = getSystemService(PowerManager.class); 119a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich mNotificationManager = getSystemService(NotificationManager.class); 120a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich } 121a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich 122a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich @Override 123a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich public void onDestroy() { 124a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich if (DEBUG) Log.d(TAG, "Shutting down executor."); 125a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich List<Runnable> unfinished = executor.shutdownNow(); 126a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich if (!unfinished.isEmpty()) { 127a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich Log.w(TAG, "Shutting down, but executor reports running jobs: " + unfinished); 128a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich } 129518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian executor = null; 130a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich if (DEBUG) Log.d(TAG, "Destroyed."); 131a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich } 132a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich 133a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich @Override 134518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian public int onStartCommand(Intent intent, int flags, int serviceId) { 135a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich // TODO: Ensure we're not being called with retry or redeliver. 136a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich // checkArgument(flags == 0); // retry and redeliver are not supported. 137a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich 138a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich String jobId = intent.getStringExtra(EXTRA_JOB_ID); 139a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich @OpType int operationType = intent.getIntExtra(EXTRA_OPERATION, OPERATION_UNKNOWN); 140edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project checkArgument(jobId != null); 141edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 1426f0871222f04dfeb479d37fe9753d491e3150e42Mathias Agopian if (intent.hasExtra(EXTRA_CANCEL)) { 143518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian handleCancel(intent); 144e6f43ddce78d6846af12550ff9193c5c6fe5844bSteve Block } else { 145d274eae545ded690846416d6bfe987d8405eeabaMathias Agopian checkArgument(operationType != OPERATION_UNKNOWN); 146ecfe091af3e3e5d7165fe64a5f9c84c4576a6c06Mathias Agopian handleOperation(intent, serviceId, jobId, operationType); 147ecfe091af3e3e5d7165fe64a5f9c84c4576a6c06Mathias Agopian } 148ecfe091af3e3e5d7165fe64a5f9c84c4576a6c06Mathias Agopian 149ecfe091af3e3e5d7165fe64a5f9c84c4576a6c06Mathias Agopian return START_NOT_STICKY; 150ecfe091af3e3e5d7165fe64a5f9c84c4576a6c06Mathias Agopian } 151ecfe091af3e3e5d7165fe64a5f9c84c4576a6c06Mathias Agopian 152ecfe091af3e3e5d7165fe64a5f9c84c4576a6c06Mathias Agopian private void handleOperation(Intent intent, int serviceId, String jobId, int operationType) { 153d274eae545ded690846416d6bfe987d8405eeabaMathias Agopian if (DEBUG) Log.d(TAG, "onStartCommand: " + jobId + " with serviceId " + serviceId); 1546f0871222f04dfeb479d37fe9753d491e3150e42Mathias Agopian 15505c53113e0c73c7cab61edf53524c61c20a547c2Mathias Agopian // Track the service supplied id so we can stop the service once we're out of work to do. 15605c53113e0c73c7cab61edf53524c61c20a547c2Mathias Agopian mLastServiceId = serviceId; 157edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 158edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project Job job = null; 159edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project synchronized (mRunning) { 160edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project if (mWakeLock == null) { 161edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 162a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich } 163a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich 164a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich List<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST); 165a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich DocumentInfo srcParent = intent.getParcelableExtra(EXTRA_SRC_PARENT); 166edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK); 167edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 168618fa10949c42eb83fa5fe105fe542bcff833ddaMathias Agopian job = createJob(operationType, jobId, srcs, srcParent, stack); 169edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 170618fa10949c42eb83fa5fe105fe542bcff833ddaMathias Agopian if (job == null) { 17105c53113e0c73c7cab61edf53524c61c20a547c2Mathias Agopian return; 172a2dd6cf59962e3a21a47df29b2f243e904839ba7Jack Palevich } 173edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 174edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project mWakeLock.acquire(); 175edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project } 176edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project 177edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project checkState(job != null); 178518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian int delay = intent.getIntExtra(EXTRA_DELAY, DEFAULT_DELAY); 179edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project checkArgument(delay <= MAX_DELAY); 1805b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian if (DEBUG) Log.d( 1813ede7c133af1fc9713d7f2aedd785ce6bad780e8Eric Hassold TAG, "Scheduling job " + job.id + " to run in " + delay + " milliseconds."); 1825b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian ScheduledFuture<?> future = executor.schedule(job, delay, TimeUnit.MILLISECONDS); 1835b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian mRunning.put(jobId, new JobRecord(job, future)); 1845b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian } 1855b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian 1863ede7c133af1fc9713d7f2aedd785ce6bad780e8Eric Hassold /** 1873ede7c133af1fc9713d7f2aedd785ce6bad780e8Eric Hassold * Cancels the operation corresponding to job id, identified in "EXTRA_JOB_ID". 1883ede7c133af1fc9713d7f2aedd785ce6bad780e8Eric Hassold * 1893ede7c133af1fc9713d7f2aedd785ce6bad780e8Eric Hassold * @param intent The cancellation intent. 1907773c435bc5da8217433e1b242d3a6712a17b5f7Mathias Agopian */ 1915b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian private void handleCancel(Intent intent) { 1923ede7c133af1fc9713d7f2aedd785ce6bad780e8Eric Hassold checkArgument(intent.hasExtra(EXTRA_CANCEL)); 1935b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian String jobId = checkNotNull(intent.getStringExtra(EXTRA_JOB_ID)); 1945b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian 195edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project if (DEBUG) Log.d(TAG, "handleCancel: " + jobId); 196ada798b7ca7cabc255aa159964b64975e7fdb2dfMathias Agopian 197edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project synchronized (mRunning) { 198edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey 199edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project // cancellation requests from affecting unrelated copy jobs. However, if the current job ID 200edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project // is null, the service most likely crashed and was revived by the incoming cancel intent. 201edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project // In that case, always allow the cancellation to proceed. 202edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project JobRecord record = mRunning.get(jobId); 203518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian if (record != null) { 204518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian record.job.cancel(); 205076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian 206076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian // If the job hasn't been started, cancel it and explicitly clean up. 207518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian // If it *has* been started, we wait for it to recognize this, then 208076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian // allow it stop working in an orderly fashion. 209076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian if (record.future.getDelay(TimeUnit.MILLISECONDS) > 0) { 2105b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian record.future.cancel(false); 211076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian onFinished(record.job); 212f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian } 213f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian } 214f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian } 215f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian 216f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian // Dismiss the progress notification here rather than in the copy loop. This preserves 217f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian // interactivity for the user in case the copy loop is stalled. 218f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian // Try to cancel it even if we don't have a job id...in case there is some sad 219f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian // orphan notification. 220f0480de37492597a5c5cf1e6f8346f1467e3a552Mathias Agopian mNotificationManager.cancel(jobId, NOTIFICATION_ID_PROGRESS); 221076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian 222edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project // TODO: Guarantee the job is being finalized 2235b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian } 2245b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian 2255b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian /** 2265b287a6ea8dfac7ab3e03ae1e98f9e2214cbae09Mathias Agopian * Creates a new job. Returns null if a job with {@code id} already exists. 227076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian * @return 228ada798b7ca7cabc255aa159964b64975e7fdb2dfMathias Agopian */ 229076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian @GuardedBy("mRunning") 230076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian private @Nullable Job createJob( 231923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian @OpType int operationType, String id, List<DocumentInfo> srcs, DocumentInfo srcParent, 232923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian DocumentStack stack) { 23348d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian 23448d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian if (mRunning.containsKey(id)) { 23548d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian Log.w(TAG, "Duplicate job id: " + id 23648d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian + ". Ignoring job request for srcs: " + srcs + ", stack: " + stack + "."); 23748d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian return null; 23848d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian } 23948d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian 24048d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian Job job = null; 24148d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian switch (operationType) { 24248d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian case OPERATION_COPY: 24348d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian job = jobFactory.createCopy(this, getApplicationContext(), this, id, stack, srcs); 24448d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian break; 24548d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian case OPERATION_MOVE: 24648d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian job = jobFactory.createMove(this, getApplicationContext(), this, id, stack, srcs, 24748d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian srcParent); 24848d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian break; 24948d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian case OPERATION_DELETE: 25048d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian job = jobFactory.createDelete(this, getApplicationContext(), this, id, stack, srcs, 25148d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian srcParent); 25248d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian break; 253923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian default: 254a69e0ed4a38ded9778d37da453899d527c4396b9Mathias Agopian throw new UnsupportedOperationException(); 255923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian } 256923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian 257518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian return checkNotNull(job); 258edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project } 259923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian 260923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian @GuardedBy("mRunning") 261edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project private void deleteJob(Job job) { 262076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian if (DEBUG) Log.d(TAG, "deleteJob: " + job.id); 263de58697644a52a614ad9498aa087e95d4a223673Mathias Agopian 264923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian JobRecord record = mRunning.remove(job.id); 265518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian checkArgument(record != null); 266ada798b7ca7cabc255aa159964b64975e7fdb2dfMathias Agopian record.job.cleanup(); 267ada798b7ca7cabc255aa159964b64975e7fdb2dfMathias Agopian 268edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project if (mRunning.isEmpty()) { 2697773c435bc5da8217433e1b242d3a6712a17b5f7Mathias Agopian shutdown(); 2707773c435bc5da8217433e1b242d3a6712a17b5f7Mathias Agopian } 2717773c435bc5da8217433e1b242d3a6712a17b5f7Mathias Agopian } 2727773c435bc5da8217433e1b242d3a6712a17b5f7Mathias Agopian 273ada798b7ca7cabc255aa159964b64975e7fdb2dfMathias Agopian /** 274edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * Most likely shuts down. Won't shut down if service has a pending 275edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * message. Thread pool is deal with in onDestroy. 276ada798b7ca7cabc255aa159964b64975e7fdb2dfMathias Agopian */ 277edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project private void shutdown() { 278edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project if (DEBUG) Log.d(TAG, "Shutting down. Last serviceId was " + mLastServiceId); 279518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian mWakeLock.release(); 280518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian mWakeLock = null; 281518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian 282923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian // Turns out, for us, stopSelfResult always returns false in tests, 283518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian // so we can't guard executor shutdown. For this reason we move 284923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian // executor shutdown to #onDestroy. 285518ec112f468eb67bf681b3eec896d7bfb4ff98dMathias Agopian boolean gonnaStop = stopSelfResult(mLastServiceId); 286923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian if (DEBUG) Log.d(TAG, "Stopping service: " + gonnaStop); 287923c661a86c9e0737b3f16ceffd77e71e023ca54Mathias Agopian if (!gonnaStop) { 288076b1cc3a9b90aa5b381a1ed268ca0b548444c9bMathias Agopian Log.w(TAG, "Service should be stopping, but reports otherwise."); 2891cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 290e6f43ddce78d6846af12550ff9193c5c6fe5844bSteve Block } 2911cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 2921cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian @VisibleForTesting 29348d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian boolean holdsWakeLock() { 29448d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian return mWakeLock != null && mWakeLock.isHeld(); 29548d438d05f14c2f4bd83ae89f520368cd49122dfMathias Agopian } 2961cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 2971cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian @Override 2981cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian public void onStart(Job job) { 2991cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian if (DEBUG) Log.d(TAG, "onStart: " + job.id); 3001cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian mNotificationManager.notify(job.id, NOTIFICATION_ID_PROGRESS, job.getSetupNotification()); 3011cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3021cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3031cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian @Override 3041cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian public void onFinished(Job job) { 3051cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian if (DEBUG) Log.d(TAG, "onFinished: " + job.id); 3061cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3071cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian // Dismiss the ongoing copy notification when the copy is done. 3081cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian mNotificationManager.cancel(job.id, NOTIFICATION_ID_PROGRESS); 3091cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3101cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian if (job.hasFailures()) { 3111cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian Log.e(TAG, "Job failed on files: " + job.failedFiles.size() + "."); 3121cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian mNotificationManager.notify( 3131cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian job.id, NOTIFICATION_ID_FAILURE, job.getFailureNotification()); 3141cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3151cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3161cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian if (job.hasWarnings()) { 3171cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian if (DEBUG) Log.d(TAG, "Job finished with warnings."); 3181cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian mNotificationManager.notify( 3191cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian job.id, NOTIFICATION_ID_WARNING, job.getWarningNotification()); 3201cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3211cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3221cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian synchronized (mRunning) { 3231cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian deleteJob(job); 3241cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3251cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3261cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3271cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian @Override 3281cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian public void onProgress(CopyJob job) { 3291cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian if (DEBUG) Log.d(TAG, "onProgress: " + job.id); 3301cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian mNotificationManager.notify( 3311cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian job.id, NOTIFICATION_ID_PROGRESS, job.getProgressNotification()); 3321cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3331cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3341cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian private static final class JobRecord { 3351cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian private final Job job; 3361cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian private final ScheduledFuture<?> future; 3371cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3381cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian public JobRecord(Job job, ScheduledFuture<?> future) { 3391cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian this.job = job; 3401cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian this.future = future; 3411cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3421cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3431cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian 3441cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian @Override 3451cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian public IBinder onBind(Intent intent) { 3461cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian return null; // Boilerplate. See super#onBind 3471cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian } 3481cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian} 3491cadb25da1ed875bdd078270e642966724a0c39aMathias Agopian