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