FileOperationService.java revision edce554c3eaff20a9bf349c1bc18c0b49e812c74
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;
2548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tanimport android.os.Handler;
26bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport android.os.IBinder;
27c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.os.PowerManager;
2897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKayimport android.support.annotation.Nullable;
29c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.support.annotation.VisibleForTesting;
30c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.util.Log;
31c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
32edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tanimport com.android.documentsui.ClipDetails;
33c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.android.documentsui.Shared;
34c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.android.documentsui.model.DocumentStack;
35bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport com.android.documentsui.services.Job.Factory;
36c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
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;
46bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
47bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKayimport javax.annotation.concurrent.GuardedBy;
48bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
49bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKaypublic class FileOperationService extends Service implements Job.Listener {
50bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
516b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski    private static final int POOL_SIZE = 2;  // "pool size", not *max* "pool size".
526b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski    private static final int NOTIFICATION_ID_PROGRESS = 0;
536b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski    private static final int NOTIFICATION_ID_FAILURE = 1;
54dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski    private static final int NOTIFICATION_ID_WARNING = 2;
55c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
56c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String TAG = "FileOperationService";
57c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
58c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID";
59c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION";
60c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
61edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan    public static final String EXTRA_CLIP_DETAILS = "com.android.documentsui.SRC_CLIP_DETAIL";
62dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski    public static final String EXTRA_DIALOG_TYPE = "com.android.documentsui.DIALOG_TYPE";
63c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
64edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan    public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
65e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski
66c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @IntDef(flag = true, value = {
67c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            OPERATION_UNKNOWN,
68c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            OPERATION_COPY,
69c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            OPERATION_MOVE,
70c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            OPERATION_DELETE
71c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    })
72c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Retention(RetentionPolicy.SOURCE)
73c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public @interface OpType {}
74f171934ad7a9d901e81a33bbd0ec74aa34a48787Steve McKay    public static final int OPERATION_UNKNOWN = -1;
75f171934ad7a9d901e81a33bbd0ec74aa34a48787Steve McKay    public static final int OPERATION_COPY = 1;
76f171934ad7a9d901e81a33bbd0ec74aa34a48787Steve McKay    public static final int OPERATION_MOVE = 2;
77f171934ad7a9d901e81a33bbd0ec74aa34a48787Steve McKay    public static final int OPERATION_DELETE = 3;
78c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
79c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    // TODO: Move it to a shared file when more operations are implemented.
80c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final int FAILURE_COPY = 1;
81c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
82bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    // The executor and job factory are visible for testing and non-final
83bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    // so we'll have a way to inject test doubles from the test. It's
84bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    // a sub-optimal arrangement.
85810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan    @VisibleForTesting ExecutorService executor;
86810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan
8748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan    // Use a separate thread pool to prioritize deletions.
88810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan    @VisibleForTesting ExecutorService deletionExecutor;
89bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    @VisibleForTesting Factory jobFactory;
90c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
9148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan    // Use a handler to schedule monitor tasks.
9248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan    @VisibleForTesting Handler handler;
9348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
94bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    private PowerManager mPowerManager;
95bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    private PowerManager.WakeLock mWakeLock;  // the wake lock, if held.
96c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    private NotificationManager mNotificationManager;
97c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
98bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    @GuardedBy("mRunning")
99bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    private Map<String, JobRecord> mRunning = new HashMap<>();
100c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
10197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay    private int mLastServiceId;
102c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
103c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Override
104c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public void onCreate() {
105bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        // Allow tests to pre-set these with test doubles.
106bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (executor == null) {
107810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan            executor = Executors.newFixedThreadPool(POOL_SIZE);
108810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan        }
109810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan
110810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan        if (deletionExecutor == null) {
111810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan            deletionExecutor = Executors.newCachedThreadPool();
112bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        }
113bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
114bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (jobFactory == null) {
115bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            jobFactory = Job.Factory.instance;
116bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        }
117c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
11848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        if (handler == null) {
11948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            // Monitor tasks are small enough to schedule them on main thread.
12048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            handler = new Handler();
12148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        }
12248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
123c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (DEBUG) Log.d(TAG, "Created.");
124c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        mPowerManager = getSystemService(PowerManager.class);
125c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        mNotificationManager = getSystemService(NotificationManager.class);
126c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
127c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
128c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Override
12997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay    public void onDestroy() {
13097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        if (DEBUG) Log.d(TAG, "Shutting down executor.");
13148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
13248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        List<Runnable> unfinishedCopies = executor.shutdownNow();
13348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        List<Runnable> unfinishedDeletions = deletionExecutor.shutdownNow();
13448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        List<Runnable> unfinished =
13548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                new ArrayList<>(unfinishedCopies.size() + unfinishedDeletions.size());
13648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        unfinished.addAll(unfinishedCopies);
13748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        unfinished.addAll(unfinishedDeletions);
13897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        if (!unfinished.isEmpty()) {
13997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay            Log.w(TAG, "Shutting down, but executor reports running jobs: " + unfinished);
14097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        }
14148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
14297b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        executor = null;
14348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        deletionExecutor = null;
14448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        handler = null;
145edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan
14697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        if (DEBUG) Log.d(TAG, "Destroyed.");
14797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay    }
14897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay
14997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay    @Override
15097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay    public int onStartCommand(Intent intent, int flags, int serviceId) {
151bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        // TODO: Ensure we're not being called with retry or redeliver.
152bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        // checkArgument(flags == 0);  // retry and redeliver are not supported.
153c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
154c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        String jobId = intent.getStringExtra(EXTRA_JOB_ID);
1550af8afd3309538dec784ed0c9c35b252a8213123Steve McKay        assert(jobId != null);
156bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
157edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan        if (DEBUG) Log.d(TAG, "onStartCommand: " + jobId + " with serviceId " + serviceId);
158edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan
159c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (intent.hasExtra(EXTRA_CANCEL)) {
160c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            handleCancel(intent);
161bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        } else {
162edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            ClipDetails details = intent.getParcelableExtra(EXTRA_CLIP_DETAILS);
163edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            assert(details.getOpType() != OPERATION_UNKNOWN);
164edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            handleOperation(intent, jobId, details);
165c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        }
166c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
16797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        // Track the service supplied id so we can stop the service once we're out of work to do.
16897b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        mLastServiceId = serviceId;
169c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
170edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan        return START_NOT_STICKY;
171edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan    }
172edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan
173edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan    private void handleOperation(Intent intent, String jobId, ClipDetails details) {
174bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        synchronized (mRunning) {
175bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            if (mWakeLock == null) {
176e0094419cd5f4e1cb55bcabc30c7e066d065827cTomasz Mikolajewski                mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
177c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            }
178c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
179bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
180c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
181edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            Job job = createJob(jobId, details, stack);
182bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
18397b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay            if (job == null) {
18497b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay                return;
18597b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay            }
18697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay
187bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            mWakeLock.acquire();
188bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
189ecc05c0286433c00bf9beb78066e64f1a80fc066Garfield, Tan            assert (job != null);
190ecc05c0286433c00bf9beb78066e64f1a80fc066Garfield, Tan            if (DEBUG) Log.d(TAG, "Scheduling job " + job.id + ".");
191edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            Future<?> future = getExecutorService(details.getOpType()).submit(job);
192ecc05c0286433c00bf9beb78066e64f1a80fc066Garfield, Tan            mRunning.put(jobId, new JobRecord(job, future));
193ecc05c0286433c00bf9beb78066e64f1a80fc066Garfield, Tan        }
194c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
195c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
196c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    /**
197c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * Cancels the operation corresponding to job id, identified in "EXTRA_JOB_ID".
198c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     *
199c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * @param intent The cancellation intent.
200c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     */
201c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    private void handleCancel(Intent intent) {
2020af8afd3309538dec784ed0c9c35b252a8213123Steve McKay        assert(intent.hasExtra(EXTRA_CANCEL));
2030af8afd3309538dec784ed0c9c35b252a8213123Steve McKay        assert(intent.getStringExtra(EXTRA_JOB_ID) != null);
2040af8afd3309538dec784ed0c9c35b252a8213123Steve McKay
2050af8afd3309538dec784ed0c9c35b252a8213123Steve McKay        String jobId = intent.getStringExtra(EXTRA_JOB_ID);
206c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
207bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (DEBUG) Log.d(TAG, "handleCancel: " + jobId);
208bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
209bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        synchronized (mRunning) {
210bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey
211bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            // cancellation requests from affecting unrelated copy jobs.  However, if the current job ID
212bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            // is null, the service most likely crashed and was revived by the incoming cancel intent.
213bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            // In that case, always allow the cancellation to proceed.
214bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            JobRecord record = mRunning.get(jobId);
215bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            if (record != null) {
216bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay                record.job.cancel();
217bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            }
218c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        }
219c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
220c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // Dismiss the progress notification here rather than in the copy loop. This preserves
221c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // interactivity for the user in case the copy loop is stalled.
222c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // Try to cancel it even if we don't have a job id...in case there is some sad
223c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // orphan notification.
2246b6c16e6a20d37beb06c1820ec63c763ff53aa67Tomasz Mikolajewski        mNotificationManager.cancel(jobId, NOTIFICATION_ID_PROGRESS);
225c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
226bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        // TODO: Guarantee the job is being finalized
227c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
228c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
22997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay    /**
23097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay     * Creates a new job. Returns null if a job with {@code id} already exists.
23197b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay     * @return
23297b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay     */
233bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    @GuardedBy("mRunning")
23497b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay    private @Nullable Job createJob(
235edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            String id, ClipDetails details, DocumentStack stack) {
236c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
237edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan        assert(details.getItemCount() > 0);
2383d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay
23997b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        if (mRunning.containsKey(id)) {
24097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay            Log.w(TAG, "Duplicate job id: " + id
241edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan                    + ". Ignoring job request for details: " + details + ", stack: " + stack + ".");
24297b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay            return null;
24397b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        }
244c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
245edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan        switch (details.getOpType()) {
246c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            case OPERATION_COPY:
2473d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay                return jobFactory.createCopy(
248edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan                        this, getApplicationContext(), this, id, stack, details);
249c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            case OPERATION_MOVE:
2503d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay                return jobFactory.createMove(
251edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan                        this, getApplicationContext(), this, id, stack, details);
252c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            case OPERATION_DELETE:
2533d68b2b3af9e248937258636329c72bcf78c0ae5Steve McKay                return jobFactory.createDelete(
254edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan                        this, getApplicationContext(), this, id, stack, details);
255c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            default:
256c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                throw new UnsupportedOperationException();
257c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        }
258bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    }
259bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
260810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan    private ExecutorService getExecutorService(@OpType int operationType) {
261810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan        switch (operationType) {
262810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan            case OPERATION_COPY:
263810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan            case OPERATION_MOVE:
264810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan                return executor;
265810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan            case OPERATION_DELETE:
266810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan                return deletionExecutor;
267810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan            default:
268810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan                throw new UnsupportedOperationException();
269810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan        }
270810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan    }
271810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan
272bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    @GuardedBy("mRunning")
273bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    private void deleteJob(Job job) {
274bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (DEBUG) Log.d(TAG, "deleteJob: " + job.id);
275bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
276bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        JobRecord record = mRunning.remove(job.id);
2770af8afd3309538dec784ed0c9c35b252a8213123Steve McKay        assert(record != null);
278bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        record.job.cleanup();
279bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
280bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (mRunning.isEmpty()) {
281bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            shutdown();
282bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        }
283bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    }
284bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
285bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    /**
286bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay     * Most likely shuts down. Won't shut down if service has a pending
28797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay     * message. Thread pool is deal with in onDestroy.
288bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay     */
289bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    private void shutdown() {
29097b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        if (DEBUG) Log.d(TAG, "Shutting down. Last serviceId was " + mLastServiceId);
291bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        mWakeLock.release();
292bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        mWakeLock = null;
29397b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay
29497b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        // Turns out, for us, stopSelfResult always returns false in tests,
29597b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        // so we can't guard executor shutdown. For this reason we move
29697b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        // executor shutdown to #onDestroy.
29797b4be4638f1697895e5d1f3fdf8bf28a3bb9c13Steve McKay        boolean gonnaStop = stopSelfResult(mLastServiceId);
298bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (DEBUG) Log.d(TAG, "Stopping service: " + gonnaStop);
299bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (!gonnaStop) {
300bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            Log.w(TAG, "Service should be stopping, but reports otherwise.");
301bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        }
302bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    }
303bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
304bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    @VisibleForTesting
305bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    boolean holdsWakeLock() {
306bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        return mWakeLock != null && mWakeLock.isHeld();
307bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    }
308bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
309bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    @Override
310bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    public void onStart(Job job) {
311bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (DEBUG) Log.d(TAG, "onStart: " + job.id);
31248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
31348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        // Show start up notification
31448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        mNotificationManager.notify(
31548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                job.id, NOTIFICATION_ID_PROGRESS, job.getSetupNotification());
31648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
31748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        // Set up related monitor
31848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        JobMonitor monitor = new JobMonitor(job, mNotificationManager, handler);
31948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        monitor.start();
320c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
321c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
322bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    @Override
323bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    public void onFinished(Job job) {
32448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        assert(job.isFinished());
325bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        if (DEBUG) Log.d(TAG, "onFinished: " + job.id);
326bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
32748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        // Use the same thread of monitors to tackle notifications to avoid race conditions.
32848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        // Otherwise we may fail to dismiss progress notification.
32948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        handler.post(() -> {
33048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            // Dismiss the ongoing copy notification when the copy is done.
33148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            mNotificationManager.cancel(job.id, NOTIFICATION_ID_PROGRESS);
332bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
33348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            if (job.hasFailures()) {
334edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan                Log.e(TAG, "Job failed on files: " + job.failedFileCount + ".");
33548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                mNotificationManager.notify(
33648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                        job.id, NOTIFICATION_ID_FAILURE, job.getFailureNotification());
33748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            }
338dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski
33948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            if (job.hasWarnings()) {
34048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                if (DEBUG) Log.d(TAG, "Job finished with warnings.");
34148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                mNotificationManager.notify(
34248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                        job.id, NOTIFICATION_ID_WARNING, job.getWarningNotification());
34348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            }
34448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        });
345dd2b31c758e8d694867af58a425180689d25ead1Tomasz Mikolajewski
346bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        synchronized (mRunning) {
347bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            deleteJob(job);
348bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        }
349c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
350c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
351bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    private static final class JobRecord {
352bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        private final Job job;
353810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan        private final Future<?> future;
354bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay
355810fb82f4461544ce34a5702668cf0e3123582c2Garfield, Tan        public JobRecord(Job job, Future<?> future) {
356bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            this.job = job;
357bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay            this.future = future;
358bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        }
359c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
360c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
36148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan    /**
36248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan     * A class used to periodically polls state of a job.
36348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan     *
36448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan     * <p>It's possible that jobs hang because underlying document providers stop responding. We
36548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan     * still need to update notifications if jobs hang, so instead of jobs pushing their states,
36648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan     * we poll states of jobs.
36748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan     */
36848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan    private static final class JobMonitor implements Runnable {
36948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        private static final long PROGRESS_INTERVAL_MILLIS = 500L;
37048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
37148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        private final Job mJob;
37248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        private final NotificationManager mNotificationManager;
37348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        private final Handler mHandler;
37448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
37548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        private JobMonitor(Job job, NotificationManager notificationManager, Handler handler) {
37648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            mJob = job;
37748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            mNotificationManager = notificationManager;
37848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            mHandler = handler;
37948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        }
38048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
38148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        private void start() {
382edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            mHandler.post(this);
38348ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        }
38448ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
38548ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        @Override
38648ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        public void run() {
38748ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            if (mJob.isFinished()) {
38848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                // Finish notification is already shown. Progress notification is removed.
38948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                // Just finish itself.
39048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan                return;
39148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            }
39248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
393edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            // Only job in set up state has progress bar
394edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            if (mJob.getState() == Job.STATE_SET_UP) {
395edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan                mNotificationManager.notify(
396edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan                        mJob.id, NOTIFICATION_ID_PROGRESS, mJob.getProgressNotification());
397edce554c3eaff20a9bf349c1bc18c0b49e812c74Garfield, Tan            }
39848ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
39948ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan            mHandler.postDelayed(this, PROGRESS_INTERVAL_MILLIS);
40048ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan        }
40148ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan    }
40248ef36fc843a46077f2a503db13eaba3afe5b548Garfield, Tan
403bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    @Override
404bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay    public IBinder onBind(Intent intent) {
405bbeba5265c5baa42c6db93fc8030c6055747da4dSteve McKay        return null;  // Boilerplate. See super#onBind
406c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
407c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay}
408