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 19d9caa6ab53aa784acaf241c0ded3c4ae2d342bf8Steve McKayimport static com.android.documentsui.base.Shared.DEBUG; 20c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 21c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.annotation.IntDef; 220bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tanimport android.app.Notification; 23e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tanimport android.app.NotificationChannel; 24c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.app.NotificationManager; 25bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport android.app.Service; 26c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.content.Intent; 2748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tanimport android.os.Handler; 28bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport android.os.IBinder; 29c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.os.PowerManager; 30e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tanimport android.os.UserManager; 31c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.support.annotation.VisibleForTesting; 32c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.util.Log; 33c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 34e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tanimport com.android.documentsui.R; 35e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tanimport com.android.documentsui.base.Features; 36e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan 37c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.lang.annotation.Retention; 38c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.lang.annotation.RetentionPolicy; 3948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tanimport java.util.ArrayList; 40bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport java.util.HashMap; 41c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.util.List; 42bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport java.util.Map; 43810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tanimport java.util.concurrent.ExecutorService; 44810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tanimport java.util.concurrent.Executors; 45810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tanimport java.util.concurrent.Future; 460bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tanimport java.util.concurrent.atomic.AtomicReference; 47bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 48bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport javax.annotation.concurrent.GuardedBy; 49bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 50bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKaypublic class FileOperationService extends Service implements Job.Listener { 51bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 52c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String TAG = "FileOperationService"; 53c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 544833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan // Extra used for OperationDialogFragment, Notifications and picking copy destination. 554833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan public static final String EXTRA_OPERATION_TYPE = "com.android.documentsui.OPERATION_TYPE"; 564833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan 574833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan // Extras used for OperationDialogFragment... 584833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan public static final String EXTRA_DIALOG_TYPE = "com.android.documentsui.DIALOG_TYPE"; 594833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST"; 604833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan 6199f1dc3da6defec0596864934bfe76adc96a1d62Steve McKay public static final String EXTRA_FAILED_URIS = "com.android.documentsui.FAILED_URIS"; 6299f1dc3da6defec0596864934bfe76adc96a1d62Steve McKay public static final String EXTRA_FAILED_DOCS = "com.android.documentsui.FAILED_DOCS"; 6399f1dc3da6defec0596864934bfe76adc96a1d62Steve McKay 644833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan // Extras used to start or cancel a file operation... 65c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID"; 66c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION"; 67c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL"; 68e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski 69c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @IntDef(flag = true, value = { 70c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay OPERATION_UNKNOWN, 71c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay OPERATION_COPY, 724f2254941c4e495e0653bd2773fc8a2ecc6daef3Tomasz Mikolajewski OPERATION_COMPRESS, 734f2254941c4e495e0653bd2773fc8a2ecc6daef3Tomasz Mikolajewski OPERATION_EXTRACT, 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; 814f2254941c4e495e0653bd2773fc8a2ecc6daef3Tomasz Mikolajewski public static final int OPERATION_EXTRACT = 2; 824f2254941c4e495e0653bd2773fc8a2ecc6daef3Tomasz Mikolajewski public static final int OPERATION_COMPRESS = 3; 834f2254941c4e495e0653bd2773fc8a2ecc6daef3Tomasz Mikolajewski public static final int OPERATION_MOVE = 4; 844f2254941c4e495e0653bd2773fc8a2ecc6daef3Tomasz Mikolajewski public static final int OPERATION_DELETE = 5; 85c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 86c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // TODO: Move it to a shared file when more operations are implemented. 87c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public static final int FAILURE_COPY = 1; 88c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 89e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan static final String NOTIFICATION_CHANNEL_ID = "channel_id"; 90e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan 91e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan private static final int POOL_SIZE = 2; // "pool size", not *max* "pool size". 92e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan 93e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan private static final int NOTIFICATION_ID_PROGRESS = 0; 94e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan private static final int NOTIFICATION_ID_FAILURE = 1; 95e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan private static final int NOTIFICATION_ID_WARNING = 2; 96e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan 97bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // The executor and job factory are visible for testing and non-final 98bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // so we'll have a way to inject test doubles from the test. It's 99bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // a sub-optimal arrangement. 100810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan @VisibleForTesting ExecutorService executor; 101810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan 10248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan // Use a separate thread pool to prioritize deletions. 103810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan @VisibleForTesting ExecutorService deletionExecutor; 104c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 10548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan // Use a handler to schedule monitor tasks. 10648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan @VisibleForTesting Handler handler; 10748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 1080bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Use a foreground manager to change foreground state of this service. 1090bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan @VisibleForTesting ForegroundManager foregroundManager; 1100bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 1110bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Use a notification manager to post and cancel notifications for jobs. 1120bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan @VisibleForTesting NotificationManager notificationManager; 1130bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 114e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan // Use a features to determine if notification channel is enabled. 115e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan @VisibleForTesting Features features; 116e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan 1170bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan @GuardedBy("mJobs") 1180bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan private final Map<String, JobRecord> mJobs = new HashMap<>(); 1190bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 1200bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // The job whose notification is used to keep the service in foreground mode. 1210bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan private final AtomicReference<Job> mForegroundJob = new AtomicReference<>(); 12200ee050745f8c3558e7ee93d56abeb0a4ad82472Steve McKay 123bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private PowerManager mPowerManager; 124bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private PowerManager.WakeLock mWakeLock; // the wake lock, if held. 125c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 12697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay private int mLastServiceId; 127c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 128c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @Override 129c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay public void onCreate() { 130bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // Allow tests to pre-set these with test doubles. 131bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (executor == null) { 132810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan executor = Executors.newFixedThreadPool(POOL_SIZE); 133810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan } 134810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan 135810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan if (deletionExecutor == null) { 136810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan deletionExecutor = Executors.newCachedThreadPool(); 137bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 138bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 13948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan if (handler == null) { 14048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan // Monitor tasks are small enough to schedule them on main thread. 14148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan handler = new Handler(); 14248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan } 14348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 1440bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (foregroundManager == null) { 1450bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan foregroundManager = createForegroundManager(this); 1460bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 1470bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 1480bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (notificationManager == null) { 1490bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan notificationManager = getSystemService(NotificationManager.class); 1500bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 1510bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 152e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan features = new Features.RuntimeFeatures(getResources(), UserManager.get(this)); 153e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan setUpNotificationChannel(); 154e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan 155c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay if (DEBUG) Log.d(TAG, "Created."); 156c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay mPowerManager = getSystemService(PowerManager.class); 157c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 158c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 159e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan private void setUpNotificationChannel() { 160e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan if (features.isNotificationChannelEnabled()) { 161e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan NotificationChannel channel = new NotificationChannel( 162e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan NOTIFICATION_CHANNEL_ID, 163e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan getString(R.string.app_label), 164e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan NotificationManager.IMPORTANCE_LOW); 165e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan notificationManager.createNotificationChannel(channel); 166e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan } 167e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan } 168e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan 169c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay @Override 17097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay public void onDestroy() { 17197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (DEBUG) Log.d(TAG, "Shutting down executor."); 17248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 17348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan List<Runnable> unfinishedCopies = executor.shutdownNow(); 17448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan List<Runnable> unfinishedDeletions = deletionExecutor.shutdownNow(); 17548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan List<Runnable> unfinished = 17648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan new ArrayList<>(unfinishedCopies.size() + unfinishedDeletions.size()); 17748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan unfinished.addAll(unfinishedCopies); 17848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan unfinished.addAll(unfinishedDeletions); 17997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (!unfinished.isEmpty()) { 18097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay Log.w(TAG, "Shutting down, but executor reports running jobs: " + unfinished); 18197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay } 18248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 18397b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay executor = null; 18448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan deletionExecutor = null; 18548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan handler = null; 186edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 18797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (DEBUG) Log.d(TAG, "Destroyed."); 18897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay } 18997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay 19097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay @Override 19197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay public int onStartCommand(Intent intent, int flags, int serviceId) { 192bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // TODO: Ensure we're not being called with retry or redeliver. 193bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // checkArgument(flags == 0); // retry and redeliver are not supported. 194c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 195c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay String jobId = intent.getStringExtra(EXTRA_JOB_ID); 1960af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(jobId != null); 197bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 198edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan if (DEBUG) Log.d(TAG, "onStartCommand: " + jobId + " with serviceId " + serviceId); 199edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 200c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay if (intent.hasExtra(EXTRA_CANCEL)) { 201c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay handleCancel(intent); 202bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } else { 2034833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan FileOperation operation = intent.getParcelableExtra(EXTRA_OPERATION); 2044833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan handleOperation(jobId, operation); 205c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 206c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 20797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay // Track the service supplied id so we can stop the service once we're out of work to do. 20897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay mLastServiceId = serviceId; 209c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 210edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan return START_NOT_STICKY; 211edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 212edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan 2134833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan private void handleOperation(String jobId, FileOperation operation) { 2140bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan synchronized (mJobs) { 215bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (mWakeLock == null) { 216e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 217c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 218c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 2190bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (mJobs.containsKey(jobId)) { 2204833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan Log.w(TAG, "Duplicate job id: " + jobId 2214833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan + ". Ignoring job request for operation: " + operation + "."); 2224833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan return; 2234833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan } 224c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 225e97112e02142fda6eaba18bd1fcaf000acb6e5dcGarfield Tan Job job = operation.createJob(this, this, jobId, features); 226bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 22797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (job == null) { 22897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay return; 22997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay } 23097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay 231ecc05c0286433c00bf9beb78066e64f1a80fc066Garfield, Tan assert (job != null); 232ecc05c0286433c00bf9beb78066e64f1a80fc066Garfield, Tan if (DEBUG) Log.d(TAG, "Scheduling job " + job.id + "."); 2334833477d7d42fa79ee42956bae4aebad77074e4bGarfield, Tan Future<?> future = getExecutorService(operation.getOpType()).submit(job); 2340bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan mJobs.put(jobId, new JobRecord(job, future)); 2358b98ab7dba92ec3c9bfe293e7ba24359abd6ddcbGarfield Tan 23618430f69bcf46df19f5752926bc943a93bda20beGarfield Tan // Acquire wake lock to keep CPU running until we finish all jobs. Acquire wake lock 2370bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // after we create a job and put it in mJobs to avoid potential leaking of wake lock 23818430f69bcf46df19f5752926bc943a93bda20beGarfield Tan // in case where job creation fails. 2398b98ab7dba92ec3c9bfe293e7ba24359abd6ddcbGarfield Tan mWakeLock.acquire(); 240ecc05c0286433c00bf9beb78066e64f1a80fc066Garfield, Tan } 241c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 242c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 243c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay /** 244c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * Cancels the operation corresponding to job id, identified in "EXTRA_JOB_ID". 245c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * 246c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay * @param intent The cancellation intent. 247c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay */ 248c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay private void handleCancel(Intent intent) { 2490af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(intent.hasExtra(EXTRA_CANCEL)); 2500af8afd3309538dec784ed0c9c35b252a8213123Steve McKay assert(intent.getStringExtra(EXTRA_JOB_ID) != null); 2510af8afd3309538dec784ed0c9c35b252a8213123Steve McKay 2520af8afd3309538dec784ed0c9c35b252a8213123Steve McKay String jobId = intent.getStringExtra(EXTRA_JOB_ID); 253c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 254bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "handleCancel: " + jobId); 255bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 2560bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan synchronized (mJobs) { 257bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey 258bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // cancellation requests from affecting unrelated copy jobs. However, if the current job ID 259bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // is null, the service most likely crashed and was revived by the incoming cancel intent. 260bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // In that case, always allow the cancellation to proceed. 2610bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan JobRecord record = mJobs.get(jobId); 262bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (record != null) { 263bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay record.job.cancel(); 264bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 265c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 266c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 267c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // Dismiss the progress notification here rather than in the copy loop. This preserves 268c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // interactivity for the user in case the copy loop is stalled. 269c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // Try to cancel it even if we don't have a job id...in case there is some sad 270c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay // orphan notification. 2710bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan notificationManager.cancel(jobId, NOTIFICATION_ID_PROGRESS); 272c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 273bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay // TODO: Guarantee the job is being finalized 274c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 275c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 276810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan private ExecutorService getExecutorService(@OpType int operationType) { 277810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan switch (operationType) { 278810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan case OPERATION_COPY: 2794f2254941c4e495e0653bd2773fc8a2ecc6daef3Tomasz Mikolajewski case OPERATION_COMPRESS: 2804f2254941c4e495e0653bd2773fc8a2ecc6daef3Tomasz Mikolajewski case OPERATION_EXTRACT: 281810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan case OPERATION_MOVE: 282810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan return executor; 283810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan case OPERATION_DELETE: 284810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan return deletionExecutor; 285810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan default: 286810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan throw new UnsupportedOperationException(); 287810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan } 288810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan } 289810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan 2900bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan @GuardedBy("mJobs") 291bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private void deleteJob(Job job) { 292bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "deleteJob: " + job.id); 293bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 29418430f69bcf46df19f5752926bc943a93bda20beGarfield Tan // Release wake lock before clearing jobs just in case we fail to clean them up. 2958b98ab7dba92ec3c9bfe293e7ba24359abd6ddcbGarfield Tan mWakeLock.release(); 2968b98ab7dba92ec3c9bfe293e7ba24359abd6ddcbGarfield Tan if (!mWakeLock.isHeld()) { 2978b98ab7dba92ec3c9bfe293e7ba24359abd6ddcbGarfield Tan mWakeLock = null; 2988b98ab7dba92ec3c9bfe293e7ba24359abd6ddcbGarfield Tan } 2998b98ab7dba92ec3c9bfe293e7ba24359abd6ddcbGarfield Tan 3000bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan JobRecord record = mJobs.remove(job.id); 30118430f69bcf46df19f5752926bc943a93bda20beGarfield Tan assert(record != null); 30218430f69bcf46df19f5752926bc943a93bda20beGarfield Tan record.job.cleanup(); 30318430f69bcf46df19f5752926bc943a93bda20beGarfield Tan 3040bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Delay the shutdown until we've cleaned up all notifications. shutdown() is now posted in 3050bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // onFinished(Job job) to main thread. 306bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 307bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 308bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay /** 309bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay * Most likely shuts down. Won't shut down if service has a pending 31097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay * message. Thread pool is deal with in onDestroy. 311bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay */ 312bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private void shutdown() { 31397b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay if (DEBUG) Log.d(TAG, "Shutting down. Last serviceId was " + mLastServiceId); 31418430f69bcf46df19f5752926bc943a93bda20beGarfield Tan assert(mWakeLock == null); 31597b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay 31697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay // Turns out, for us, stopSelfResult always returns false in tests, 31797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay // so we can't guard executor shutdown. For this reason we move 31897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay // executor shutdown to #onDestroy. 31997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay boolean gonnaStop = stopSelfResult(mLastServiceId); 320bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "Stopping service: " + gonnaStop); 321bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (!gonnaStop) { 322bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay Log.w(TAG, "Service should be stopping, but reports otherwise."); 323bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 324bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 325bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 326bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @VisibleForTesting 327bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay boolean holdsWakeLock() { 328bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay return mWakeLock != null && mWakeLock.isHeld(); 329bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 330bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 331bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @Override 332bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay public void onStart(Job job) { 333bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "onStart: " + job.id); 33448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 3350bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan Notification notification = job.getSetupNotification(); 3360bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // If there is no foreground job yet, set this job to foreground job. 3370bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (mForegroundJob.compareAndSet(null, job)) { 3380bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (DEBUG) Log.d(TAG, "Set foreground job to " + job.id); 3390bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan foregroundManager.startForeground(NOTIFICATION_ID_PROGRESS, notification); 3400bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 3410bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 34248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan // Show start up notification 3430bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (DEBUG) Log.d(TAG, "Posting notification for " + job.id); 3440bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan notificationManager.notify( 3450bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan job.id, NOTIFICATION_ID_PROGRESS, notification); 34648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 34748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan // Set up related monitor 3480bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan JobMonitor monitor = new JobMonitor(job, notificationManager, handler); 34948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan monitor.start(); 350c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 351c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 352bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @Override 353bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay public void onFinished(Job job) { 35448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan assert(job.isFinished()); 355bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay if (DEBUG) Log.d(TAG, "onFinished: " + job.id); 356bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 3570bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan synchronized (mJobs) { 3580bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Delete the job from mJobs first to avoid this job being selected as the foreground 3590bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // task again if we need to swap the foreground job. 3600bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan deleteJob(job); 3610bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 3620bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Update foreground state before cleaning up notification. If the finishing job is the 3630bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // foreground job, we would need to switch to another one or go to background before 3640bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // we can clean up notifications. 3650bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan updateForegroundState(job); 3660bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 3670bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Use the same thread of monitors to tackle notifications to avoid race conditions. 3680bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Otherwise we may fail to dismiss progress notification. 3690bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan handler.post(() -> cleanUpNotification(job)); 3700bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 3710bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Post the shutdown message to main thread after cleanUpNotification() to give it a 3720bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // chance to run. Otherwise this process may be torn down by Android before we've 3730bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // cleaned up the notifications of the last job. 3740bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (mJobs.isEmpty()) { 3750bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan handler.post(this::shutdown); 3760bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 3770bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 3780bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 3790bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 3800bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan @GuardedBy("mJobs") 3810bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan private void updateForegroundState(Job job) { 3820bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan Job candidate = mJobs.isEmpty() ? null : mJobs.values().iterator().next().job; 3830bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 3840bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // If foreground job is retiring and there is still work to do, we need to set it to a new 3850bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // job. 3860bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (mForegroundJob.compareAndSet(job, candidate)) { 3870bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (candidate == null) { 3880bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (DEBUG) Log.d(TAG, "Stop foreground"); 3890bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Remove the notification here just in case we're torn down before we have the 3900bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // chance to clean up notifications. 3910bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan foregroundManager.stopForeground(true); 3920bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } else { 3930bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (DEBUG) Log.d(TAG, "Switch foreground job to " + candidate.id); 3940bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 3950bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan Notification notification = (candidate.getState() == Job.STATE_STARTED) 3960bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan ? candidate.getSetupNotification() 3970bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan : candidate.getProgressNotification(); 3980bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan foregroundManager.startForeground(NOTIFICATION_ID_PROGRESS, notification); 3990bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan notificationManager.notify(candidate.id, NOTIFICATION_ID_PROGRESS, 4000bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan notification); 40148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan } 4020bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 4030bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 404dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski 4050bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan private void cleanUpNotification(Job job) { 4060bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 4070bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (DEBUG) Log.d(TAG, "Canceling notification for " + job.id); 4080bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan // Dismiss the ongoing copy notification when the copy is done. 4090bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan notificationManager.cancel(job.id, NOTIFICATION_ID_PROGRESS); 4100bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 4110bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (job.hasFailures()) { 4120bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (!job.failedUris.isEmpty()) { 4130bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan Log.e(TAG, "Job failed to resolve uris: " + job.failedUris + "."); 41448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan } 4150bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (!job.failedDocs.isEmpty()) { 4160bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan Log.e(TAG, "Job failed to process docs: " + job.failedDocs + "."); 4170bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 4180bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan notificationManager.notify( 4190bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan job.id, NOTIFICATION_ID_FAILURE, job.getFailureNotification()); 4200bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 421dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski 4220bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (job.hasWarnings()) { 4230bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan if (DEBUG) Log.d(TAG, "Job finished with warnings."); 4240bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan notificationManager.notify( 4250bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan job.id, NOTIFICATION_ID_WARNING, job.getWarningNotification()); 426bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 427c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 428c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 429bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private static final class JobRecord { 430bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay private final Job job; 431810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan private final Future<?> future; 432bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay 433810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan public JobRecord(Job job, Future<?> future) { 434bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay this.job = job; 435bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay this.future = future; 436bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay } 437c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 438c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay 43948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan /** 44048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan * A class used to periodically polls state of a job. 44148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan * 44248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan * <p>It's possible that jobs hang because underlying document providers stop responding. We 44348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan * still need to update notifications if jobs hang, so instead of jobs pushing their states, 44448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan * we poll states of jobs. 44548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan */ 44648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan private static final class JobMonitor implements Runnable { 44748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan private static final long PROGRESS_INTERVAL_MILLIS = 500L; 44848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 44948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan private final Job mJob; 45048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan private final NotificationManager mNotificationManager; 45148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan private final Handler mHandler; 45248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 45348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan private JobMonitor(Job job, NotificationManager notificationManager, Handler handler) { 45448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan mJob = job; 45548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan mNotificationManager = notificationManager; 45648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan mHandler = handler; 45748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan } 45848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 45948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan private void start() { 460edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan mHandler.post(this); 46148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan } 46248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 46348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan @Override 46448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan public void run() { 46548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan if (mJob.isFinished()) { 46648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan // Finish notification is already shown. Progress notification is removed. 46748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan // Just finish itself. 46848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan return; 46948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan } 47048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 471edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan // Only job in set up state has progress bar 472edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan if (mJob.getState() == Job.STATE_SET_UP) { 473edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan mNotificationManager.notify( 474edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan mJob.id, NOTIFICATION_ID_PROGRESS, mJob.getProgressNotification()); 475edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan } 47648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 47748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan mHandler.postDelayed(this, PROGRESS_INTERVAL_MILLIS); 47848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan } 47948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan } 48048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan 481bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay @Override 482bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay public IBinder onBind(Intent intent) { 483bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay return null; // Boilerplate. See super#onBind 484c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay } 4850bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 4860bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan private static ForegroundManager createForegroundManager(final Service service) { 4870bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan return new ForegroundManager() { 4880bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan @Override 4890bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan public void startForeground(int id, Notification notification) { 4900bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan service.startForeground(id, notification); 4910bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 4920bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 4930bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan @Override 4940bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan public void stopForeground(boolean removeNotification) { 4950bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan service.stopForeground(removeNotification); 4960bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 4970bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan }; 4980bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 4990bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan 5000bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan @VisibleForTesting 5010bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan interface ForegroundManager { 5020bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan void startForeground(int id, Notification notification); 5030bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan void stopForeground(boolean removeNotification); 5040bed4ce4cfc5b40fd2027681be1f498135e6486dGarfield Tan } 505c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay} 506