FileOperationService.java revision c83baa0574ee9e34c0e06bda1ff08928d880ee36
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 android.os.SystemClock.elapsedRealtime;
20c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport static com.android.documentsui.Shared.DEBUG;
21c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport static com.android.internal.util.Preconditions.checkArgument;
22c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport static com.android.internal.util.Preconditions.checkNotNull;
23c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport static com.android.internal.util.Preconditions.checkState;
24c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
25c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.annotation.IntDef;
26c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.app.IntentService;
27c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.app.NotificationManager;
28c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.content.Intent;
29c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.os.PowerManager;
30c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.support.annotation.Nullable;
31c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.support.annotation.VisibleForTesting;
32c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport android.util.Log;
33c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
34c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.android.documentsui.Shared;
35c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.android.documentsui.model.DocumentInfo;
36c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.android.documentsui.model.DocumentStack;
37c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
38c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport com.google.common.base.Objects;
39c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
40c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.lang.annotation.Retention;
41c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.lang.annotation.RetentionPolicy;
42c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.util.ArrayList;
43c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKayimport java.util.List;
44c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
45c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKaypublic class FileOperationService extends IntentService implements Job.Listener {
46c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String TAG = "FileOperationService";
47c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
48c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String EXTRA_JOB_ID = "com.android.documentsui.JOB_ID";
49c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String EXTRA_OPERATION = "com.android.documentsui.OPERATION";
50c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String EXTRA_CANCEL = "com.android.documentsui.CANCEL";
51c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
52c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE";
53c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
54c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final int OPERATION_UNKNOWN = -1;
55c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final int OPERATION_COPY = 1;
56c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final int OPERATION_MOVE = 2;
57c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final int OPERATION_DELETE = 3;
58c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
59c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @IntDef(flag = true, value = {
60c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            OPERATION_UNKNOWN,
61c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            OPERATION_COPY,
62c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            OPERATION_MOVE,
63c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            OPERATION_DELETE
64c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    })
65c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Retention(RetentionPolicy.SOURCE)
66c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public @interface OpType {}
67c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
68c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    // TODO: Move it to a shared file when more operations are implemented.
69c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static final int FAILURE_COPY = 1;
70c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
71c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    private PowerManager mPowerManager;
72c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
73c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    private NotificationManager mNotificationManager;
74c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
75c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    // TODO: Rework service to support multiple concurrent jobs.
76c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    private volatile Job mJob;
77c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
78c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    // For testing only.
79c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Nullable private TestOnlyListener mJobFinishedListener;
80c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
81c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public FileOperationService() {
82c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        super("FileOperationService");
83c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
84c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
85c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Override
86c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public void onCreate() {
87c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        super.onCreate();
88c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
89c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (DEBUG) Log.d(TAG, "Created.");
90c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        mPowerManager = getSystemService(PowerManager.class);
91c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        mNotificationManager = getSystemService(NotificationManager.class);
92c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
93c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
94c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Override
95c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public int onStartCommand(Intent intent, int flags, int startId) {
96c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (DEBUG) Log.d(TAG, "onStartCommand: " + intent);
97c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (intent.hasExtra(EXTRA_CANCEL)) {
98c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            handleCancel(intent);
99c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            return START_REDELIVER_INTENT;
100c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        } else {
101c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            return super.onStartCommand(intent, flags, startId);
102c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        }
103c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
104c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
105c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Override
106c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    protected void onHandleIntent(Intent intent) {
107c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (DEBUG) Log.d(TAG, "onHandleIntent: " + intent);
108c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
109c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        String jobId = intent.getStringExtra(EXTRA_JOB_ID);
110c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        @OpType int operationType = intent.getIntExtra(EXTRA_OPERATION, OPERATION_UNKNOWN);
111c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        checkArgument(jobId != null);
112c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (intent.hasExtra(EXTRA_CANCEL)) {
113c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            handleCancel(intent);
114c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            return;
115c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        }
116c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
117c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        checkArgument(operationType != OPERATION_UNKNOWN);
118c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
119c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        PowerManager.WakeLock wakeLock = mPowerManager.newWakeLock(
120c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                PowerManager.PARTIAL_WAKE_LOCK, TAG);
121c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
122c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        ArrayList<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
123c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
124c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
125c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        Job job = createJob(operationType, jobId, srcs, stack);
126c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
127c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        try {
128c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            wakeLock.acquire();
129c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
130c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            mNotificationManager.notify(job.id, 0, job.getSetupNotification());
131c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            job.run(this);
132c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
133c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        } catch (Exception e) {
134c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            // Catch-all to prevent any copy errors from wedging the app.
135c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            Log.e(TAG, "Exceptions occurred during copying", e);
136c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        } finally {
137c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            if (DEBUG) Log.d(TAG, "Cleaning up after copy");
138c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
139c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            job.cleanup();
140c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            wakeLock.release();
141c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
142c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            // Dismiss the ongoing copy notification when the copy is done.
143c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            mNotificationManager.cancel(job.id, 0);
144c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
145c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            if (job.failed()) {
146c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                Log.e(TAG, job.failedFiles.size() + " files failed to copy");
147c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                mNotificationManager.notify(job.id, 0, job.getFailureNotification());
148c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            }
149c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
150c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            // TEST ONLY CODE...<raised eyebrows>
151c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            if (mJobFinishedListener != null) {
152c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                mJobFinishedListener.onFinished(job.failedFiles);
153c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            }
154c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
155c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            deleteJob(job);
156c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            if (DEBUG) Log.d(TAG, "Done cleaning up");
157c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        }
158c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
159c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
160c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    /**
161c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * Cancels the operation corresponding to job id, identified in "EXTRA_JOB_ID".
162c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     *
163c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * @param intent The cancellation intent.
164c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     */
165c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    private void handleCancel(Intent intent) {
166c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        checkArgument(intent.hasExtra(EXTRA_CANCEL));
167c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        String jobId = checkNotNull(intent.getStringExtra(EXTRA_JOB_ID));
168c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
169c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // Do nothing if the cancelled ID doesn't match the current job ID. This prevents racey
170c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // cancellation requests from affecting unrelated copy jobs.  However, if the current job ID
171c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // is null, the service most likely crashed and was revived by the incoming cancel intent.
172c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // In that case, always allow the cancellation to proceed.
173c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (mJob != null && Objects.equal(jobId, mJob.id)) {
174c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            mJob.cancel();
175c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        }
176c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
177c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // Dismiss the progress notification here rather than in the copy loop. This preserves
178c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // interactivity for the user in case the copy loop is stalled.
179c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // Try to cancel it even if we don't have a job id...in case there is some sad
180c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        // orphan notification.
181c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        mNotificationManager.cancel(jobId, 0);
182c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
183c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
184c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public static String createJobId() {
185c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        return String.valueOf(elapsedRealtime());
186c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
187c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
188c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    Job createJob(
189c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            @OpType int operationType, String id, ArrayList<DocumentInfo> srcs,
190c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            DocumentStack stack) {
191c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
192c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        checkState(mJob == null);
193c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
194c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        switch (operationType) {
195c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            case OPERATION_COPY:
196c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                mJob = new CopyJob(this, getApplicationContext(), this, id, stack, srcs);
197c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                break;
198c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            case OPERATION_MOVE:
199c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                mJob = new MoveJob(this, getApplicationContext(), this, id, stack, srcs);
200c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                break;
201c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            case OPERATION_DELETE:
202c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                throw new UnsupportedOperationException();
203c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay            default:
204c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay                throw new UnsupportedOperationException();
205c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        }
206c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
207c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        return checkNotNull(mJob);
208c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
209c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
210c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    void deleteJob(Job job) {
211c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        checkArgument(job == mJob);
212c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        mJob = null;
213c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
214c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
215c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Override
216c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public void onProgress(CopyJob job) {
217c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (DEBUG) Log.d(TAG, "On copy progress...");
218c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        mNotificationManager.notify(job.id, 0, job.getProgressNotification());
219c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
220c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
221c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @Override
222c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    public void onProgress(MoveJob job) {
223c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        if (DEBUG) Log.d(TAG, "On move progress...");
224c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        mNotificationManager.notify(job.id, 0, job.getProgressNotification());
225c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
226c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
227c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    /**
228c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * Sets a callback to be run when the next run job is finished.
229c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * This is test ONLY instrumentation. The alternative is for us to add
230c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * broadcast intents SOLELY for the purpose of testing.
231c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * @param listener
232c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     */
233c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @VisibleForTesting
234c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    void addFinishedListener(TestOnlyListener listener) {
235c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        this.mJobFinishedListener = listener;
236c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
237c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay
238c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    /**
239c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     * Only used for testing. Is that obvious enough?
240c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay     */
241c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    @VisibleForTesting
242c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    interface TestOnlyListener {
243c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay        void onFinished(List<DocumentInfo> failed);
244c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay    }
245c83baa0574ee9e34c0e06bda1ff08928d880ee36Steve McKay}
246