[go: nahoru, domu]

114e827a958f713d9cdec550e0b1099622cda9479Steve McKay/*
214e827a958f713d9cdec550e0b1099622cda9479Steve McKay * Copyright (C) 2016 The Android Open Source Project
314e827a958f713d9cdec550e0b1099622cda9479Steve McKay *
414e827a958f713d9cdec550e0b1099622cda9479Steve McKay * Licensed under the Apache License, Version 2.0 (the "License");
514e827a958f713d9cdec550e0b1099622cda9479Steve McKay * you may not use this file except in compliance with the License.
614e827a958f713d9cdec550e0b1099622cda9479Steve McKay * You may obtain a copy of the License at
714e827a958f713d9cdec550e0b1099622cda9479Steve McKay *
814e827a958f713d9cdec550e0b1099622cda9479Steve McKay *      http://www.apache.org/licenses/LICENSE-2.0
914e827a958f713d9cdec550e0b1099622cda9479Steve McKay *
1014e827a958f713d9cdec550e0b1099622cda9479Steve McKay * Unless required by applicable law or agreed to in writing, software
1114e827a958f713d9cdec550e0b1099622cda9479Steve McKay * distributed under the License is distributed on an "AS IS" BASIS,
1214e827a958f713d9cdec550e0b1099622cda9479Steve McKay * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1314e827a958f713d9cdec550e0b1099622cda9479Steve McKay * See the License for the specific language governing permissions and
1414e827a958f713d9cdec550e0b1099622cda9479Steve McKay * limitations under the License.
1514e827a958f713d9cdec550e0b1099622cda9479Steve McKay */
1614e827a958f713d9cdec550e0b1099622cda9479Steve McKay
1714e827a958f713d9cdec550e0b1099622cda9479Steve McKaypackage com.android.documentsui.services;
1814e827a958f713d9cdec550e0b1099622cda9479Steve McKay
193564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKayimport static com.android.documentsui.DocumentsApplication.acquireUnstableProviderOrThrow;
20ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKayimport static com.android.documentsui.services.FileOperationService.EXTRA_CANCEL;
21748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewskiimport static com.android.documentsui.services.FileOperationService.EXTRA_DIALOG_TYPE;
22ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKayimport static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID;
23ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKayimport static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION;
24ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKayimport static com.android.documentsui.services.FileOperationService.EXTRA_SRC_LIST;
2514e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport static com.android.documentsui.services.FileOperationService.OPERATION_UNKNOWN;
2614e827a958f713d9cdec550e0b1099622cda9479Steve McKay
2714e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.annotation.DrawableRes;
2814e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.annotation.PluralsRes;
2914e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.app.Notification;
3014e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.app.Notification.Builder;
3114e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.app.PendingIntent;
323564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKayimport android.content.ContentProviderClient;
3314e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.content.ContentResolver;
3414e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.content.Context;
3514e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.content.Intent;
36cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewskiimport android.net.Uri;
3714e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.os.Parcelable;
3814e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.os.RemoteException;
3914e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport android.provider.DocumentsContract;
403564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKayimport android.util.Log;
4114e827a958f713d9cdec550e0b1099622cda9479Steve McKay
4214e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport com.android.documentsui.FilesActivity;
43d5b2af1544629d48175d857785db32f2b8957f3aBen Kwaimport com.android.documentsui.Metrics;
44748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewskiimport com.android.documentsui.OperationDialogFragment;
4514e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport com.android.documentsui.R;
4614e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport com.android.documentsui.Shared;
4714e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport com.android.documentsui.model.DocumentInfo;
4814e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport com.android.documentsui.model.DocumentStack;
4914e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport com.android.documentsui.services.FileOperationService.OpType;
5014e827a958f713d9cdec550e0b1099622cda9479Steve McKay
5114e827a958f713d9cdec550e0b1099622cda9479Steve McKayimport java.util.ArrayList;
523564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKayimport java.util.HashMap;
53ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKayimport java.util.List;
543564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKayimport java.util.Map;
5514e827a958f713d9cdec550e0b1099622cda9479Steve McKay
56ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay/**
57ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay * A mashup of work item and ui progress update factory. Used by {@link FileOperationService}
58ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay * to do work and show progress relating to this work.
59ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay */
60748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewskiabstract public class Job implements Runnable {
613564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    private static final String TAG = "Job";
62cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski
63cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski    static final String INTENT_TAG_WARNING = "warning";
64cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski    static final String INTENT_TAG_FAILURE = "failure";
65cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski    static final String INTENT_TAG_PROGRESS = "progress";
66cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski    static final String INTENT_TAG_CANCEL = "cancel";
67cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski
68ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    final Context service;
6914e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final Context appContext;
7014e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final Listener listener;
7114e827a958f713d9cdec550e0b1099622cda9479Steve McKay
72ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    final @OpType int operationType;
7314e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final String id;
7414e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final DocumentStack stack;
7514e827a958f713d9cdec550e0b1099622cda9479Steve McKay
7614e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final ArrayList<DocumentInfo> failedFiles = new ArrayList<>();
7714e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final Notification.Builder mProgressBuilder;
7814e827a958f713d9cdec550e0b1099622cda9479Steve McKay
793564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    private final Map<String, ContentProviderClient> mClients = new HashMap<>();
8014e827a958f713d9cdec550e0b1099622cda9479Steve McKay    private volatile boolean mCanceled;
8114e827a958f713d9cdec550e0b1099622cda9479Steve McKay
8214e827a958f713d9cdec550e0b1099622cda9479Steve McKay    /**
8314e827a958f713d9cdec550e0b1099622cda9479Steve McKay     * A simple progressable job, much like an AsyncTask, but with support
8414e827a958f713d9cdec550e0b1099622cda9479Steve McKay     * for providing various related notification, progress and navigation information.
85ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay     * @param operationType
8614e827a958f713d9cdec550e0b1099622cda9479Steve McKay     *
87ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay     * @param service The service context in which this job is running.
8814e827a958f713d9cdec550e0b1099622cda9479Steve McKay     * @param appContext The context of the invoking application. This is usually
8914e827a958f713d9cdec550e0b1099622cda9479Steve McKay     *     just {@code getApplicationContext()}.
9014e827a958f713d9cdec550e0b1099622cda9479Steve McKay     * @param listener
9114e827a958f713d9cdec550e0b1099622cda9479Steve McKay     * @param id Arbitrary string ID
9214e827a958f713d9cdec550e0b1099622cda9479Steve McKay     * @param stack The documents stack context relating to this request. This is the
9314e827a958f713d9cdec550e0b1099622cda9479Steve McKay     *     destination in the Files app where the user will be take when the
9414e827a958f713d9cdec550e0b1099622cda9479Steve McKay     *     navigation intent is invoked (presumably from notification).
9514e827a958f713d9cdec550e0b1099622cda9479Steve McKay     */
96ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    Job(Context service, Context appContext, Listener listener,
97ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay            @OpType int operationType, String id, DocumentStack stack) {
98ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay
99a1f7680f535a30aa816d129c072870031c8a2eb6Steve McKay        assert(operationType != OPERATION_UNKNOWN);
10014e827a958f713d9cdec550e0b1099622cda9479Steve McKay
101ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        this.service = service;
10214e827a958f713d9cdec550e0b1099622cda9479Steve McKay        this.appContext = appContext;
10314e827a958f713d9cdec550e0b1099622cda9479Steve McKay        this.listener = listener;
104ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        this.operationType = operationType;
10514e827a958f713d9cdec550e0b1099622cda9479Steve McKay
10614e827a958f713d9cdec550e0b1099622cda9479Steve McKay        this.id = id;
10714e827a958f713d9cdec550e0b1099622cda9479Steve McKay        this.stack = stack;
10814e827a958f713d9cdec550e0b1099622cda9479Steve McKay
10914e827a958f713d9cdec550e0b1099622cda9479Steve McKay        mProgressBuilder = createProgressBuilder();
11014e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
11114e827a958f713d9cdec550e0b1099622cda9479Steve McKay
112ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    @Override
113ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    public final void run() {
114ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        listener.onStart(this);
115ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        try {
116ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay            start();
1170fa97e85e45505b58e5b5956541277f61aa5458bTomasz Mikolajewski        } catch (RuntimeException e) {
1180fa97e85e45505b58e5b5956541277f61aa5458bTomasz Mikolajewski            // No exceptions should be thrown here, as all calls to the provider must be
1190fa97e85e45505b58e5b5956541277f61aa5458bTomasz Mikolajewski            // handled within Job implementations. However, just in case catch them here.
1200fa97e85e45505b58e5b5956541277f61aa5458bTomasz Mikolajewski            Log.e(TAG, "Operation failed due to an unhandled runtime exception.", e);
121d5b2af1544629d48175d857785db32f2b8957f3aBen Kwa            Metrics.logFileOperationErrors(service, operationType, failedFiles);
122ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        } finally {
123748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski            listener.onFinished(this);
124ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        }
12514e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
12614e827a958f713d9cdec550e0b1099622cda9479Steve McKay
1270fa97e85e45505b58e5b5956541277f61aa5458bTomasz Mikolajewski    abstract void start();
128ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay
12914e827a958f713d9cdec550e0b1099622cda9479Steve McKay    abstract Notification getSetupNotification();
13014e827a958f713d9cdec550e0b1099622cda9479Steve McKay    // TODO: Progress notification for deletes.
13114e827a958f713d9cdec550e0b1099622cda9479Steve McKay    // abstract Notification getProgressNotification(long bytesCopied);
13214e827a958f713d9cdec550e0b1099622cda9479Steve McKay    abstract Notification getFailureNotification();
13314e827a958f713d9cdec550e0b1099622cda9479Steve McKay
134748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski    abstract Notification getWarningNotification();
135748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski
136cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski    Uri getDataUriForIntent(String tag) {
137cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski        return Uri.parse(String.format("data,%s-%s", tag, id));
138cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski    }
139cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski
1403564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    ContentProviderClient getClient(DocumentInfo doc) throws RemoteException {
1413564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        ContentProviderClient client = mClients.get(doc.authority);
1423564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        if (client == null) {
1433564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay            // Acquire content providers.
1443564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay            client = acquireUnstableProviderOrThrow(
1453564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay                    getContentResolver(),
1463564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay                    doc.authority);
1473564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay
1483564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay            mClients.put(doc.authority, client);
1493564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        }
1503564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay
151a1f7680f535a30aa816d129c072870031c8a2eb6Steve McKay        assert(client != null);
152a1f7680f535a30aa816d129c072870031c8a2eb6Steve McKay        return client;
1533564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    }
1543564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay
1553564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    final void cleanup() {
1563564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        for (ContentProviderClient client : mClients.values()) {
1573564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay            ContentProviderClient.releaseQuietly(client);
1583564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        }
1593564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    }
1603564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay
16114e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final void cancel() {
16214e827a958f713d9cdec550e0b1099622cda9479Steve McKay        mCanceled = true;
163d5b2af1544629d48175d857785db32f2b8957f3aBen Kwa        Metrics.logFileOperationCancelled(service, operationType);
16414e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
16514e827a958f713d9cdec550e0b1099622cda9479Steve McKay
16614e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final boolean isCanceled() {
16714e827a958f713d9cdec550e0b1099622cda9479Steve McKay        return mCanceled;
16814e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
16914e827a958f713d9cdec550e0b1099622cda9479Steve McKay
17014e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final ContentResolver getContentResolver() {
171ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        return service.getContentResolver();
17214e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
17314e827a958f713d9cdec550e0b1099622cda9479Steve McKay
17414e827a958f713d9cdec550e0b1099622cda9479Steve McKay    void onFileFailed(DocumentInfo file) {
17514e827a958f713d9cdec550e0b1099622cda9479Steve McKay        failedFiles.add(file);
17614e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
17714e827a958f713d9cdec550e0b1099622cda9479Steve McKay
178748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski    final boolean hasFailures() {
17914e827a958f713d9cdec550e0b1099622cda9479Steve McKay        return !failedFiles.isEmpty();
18014e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
18114e827a958f713d9cdec550e0b1099622cda9479Steve McKay
182748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski    boolean hasWarnings() {
183748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski        return false;
184748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski    }
185748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski
186db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski    final void deleteDocument(DocumentInfo doc, DocumentInfo parent) throws ResourceException {
1873564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        try {
188db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski            if (doc.isRemoveSupported()) {
189db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski                DocumentsContract.removeDocument(getClient(doc), doc.derivedUri, parent.derivedUri);
190db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski            } else if (doc.isDeleteSupported()) {
191db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski                DocumentsContract.deleteDocument(getClient(doc), doc.derivedUri);
192db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski            } else {
193db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski                throw new ResourceException("Unable to delete source document as the file is " +
194db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski                        "not deletable nor removable: %s.", doc.derivedUri);
195db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski            }
196db87543d82f1916b0372f732045f0bc2860ed3f2Tomasz Mikolajewski        } catch (RemoteException | RuntimeException e) {
1970fa97e85e45505b58e5b5956541277f61aa5458bTomasz Mikolajewski            throw new ResourceException("Failed to delete file %s due to an exception.",
1980fa97e85e45505b58e5b5956541277f61aa5458bTomasz Mikolajewski                    doc.derivedUri, e);
1993564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        }
2003564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    }
2013564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay
20214e827a958f713d9cdec550e0b1099622cda9479Steve McKay    Notification getSetupNotification(String content) {
2037a3b811122b6db0ac103630c2b7acde1d9f4c128Steve McKay        mProgressBuilder.setProgress(0, 0, true)
2047a3b811122b6db0ac103630c2b7acde1d9f4c128Steve McKay                .setContentText(content);
20514e827a958f713d9cdec550e0b1099622cda9479Steve McKay        return mProgressBuilder.build();
20614e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
20714e827a958f713d9cdec550e0b1099622cda9479Steve McKay
20814e827a958f713d9cdec550e0b1099622cda9479Steve McKay    Notification getFailureNotification(@PluralsRes int titleId, @DrawableRes int icon) {
209cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski        final Intent navigateIntent = buildNavigateIntent(INTENT_TAG_FAILURE);
210748ea8cc785b6f037518703308ffd3eb2a151c5aTomasz Mikolajewski        navigateIntent.putExtra(EXTRA_DIALOG_TYPE, OperationDialogFragment.DIALOG_TYPE_FAILURE);
211ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        navigateIntent.putExtra(EXTRA_OPERATION, operationType);
212ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, failedFiles);
21314e827a958f713d9cdec550e0b1099622cda9479Steve McKay
214ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        final Notification.Builder errorBuilder = new Notification.Builder(service)
215ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay                .setContentTitle(service.getResources().getQuantityString(titleId,
21614e827a958f713d9cdec550e0b1099622cda9479Steve McKay                        failedFiles.size(), failedFiles.size()))
217ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay                .setContentText(service.getString(R.string.notification_touch_for_details))
21814e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setContentIntent(PendingIntent.getActivity(appContext, 0, navigateIntent,
21914e827a958f713d9cdec550e0b1099622cda9479Steve McKay                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT))
22014e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setCategory(Notification.CATEGORY_ERROR)
22114e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setSmallIcon(icon)
22214e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setAutoCancel(true);
2237a3b811122b6db0ac103630c2b7acde1d9f4c128Steve McKay
22414e827a958f713d9cdec550e0b1099622cda9479Steve McKay        return errorBuilder.build();
22514e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
22614e827a958f713d9cdec550e0b1099622cda9479Steve McKay
22714e827a958f713d9cdec550e0b1099622cda9479Steve McKay    abstract Builder createProgressBuilder();
22814e827a958f713d9cdec550e0b1099622cda9479Steve McKay
22914e827a958f713d9cdec550e0b1099622cda9479Steve McKay    final Builder createProgressBuilder(
23014e827a958f713d9cdec550e0b1099622cda9479Steve McKay            String title, @DrawableRes int icon,
23114e827a958f713d9cdec550e0b1099622cda9479Steve McKay            String actionTitle, @DrawableRes int actionIcon) {
232ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        Notification.Builder progressBuilder = new Notification.Builder(service)
23314e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setContentTitle(title)
23414e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setContentIntent(
235cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski                        PendingIntent.getActivity(appContext, 0,
236cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski                                buildNavigateIntent(INTENT_TAG_PROGRESS), 0))
23714e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setCategory(Notification.CATEGORY_PROGRESS)
23814e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setSmallIcon(icon)
23914e827a958f713d9cdec550e0b1099622cda9479Steve McKay                .setOngoing(true);
24014e827a958f713d9cdec550e0b1099622cda9479Steve McKay
24114e827a958f713d9cdec550e0b1099622cda9479Steve McKay        final Intent cancelIntent = createCancelIntent();
24214e827a958f713d9cdec550e0b1099622cda9479Steve McKay
24314e827a958f713d9cdec550e0b1099622cda9479Steve McKay        progressBuilder.addAction(
24414e827a958f713d9cdec550e0b1099622cda9479Steve McKay                actionIcon,
24514e827a958f713d9cdec550e0b1099622cda9479Steve McKay                actionTitle,
24614e827a958f713d9cdec550e0b1099622cda9479Steve McKay                PendingIntent.getService(
247ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay                        service,
24814e827a958f713d9cdec550e0b1099622cda9479Steve McKay                        0,
24914e827a958f713d9cdec550e0b1099622cda9479Steve McKay                        cancelIntent,
25014e827a958f713d9cdec550e0b1099622cda9479Steve McKay                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT));
25114e827a958f713d9cdec550e0b1099622cda9479Steve McKay
25214e827a958f713d9cdec550e0b1099622cda9479Steve McKay        return progressBuilder;
25314e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
25414e827a958f713d9cdec550e0b1099622cda9479Steve McKay
25514e827a958f713d9cdec550e0b1099622cda9479Steve McKay    /**
25614e827a958f713d9cdec550e0b1099622cda9479Steve McKay     * Creates an intent for navigating back to the destination directory.
25714e827a958f713d9cdec550e0b1099622cda9479Steve McKay     */
258cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski    Intent buildNavigateIntent(String tag) {
259ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        Intent intent = new Intent(service, FilesActivity.class);
260cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
26114e827a958f713d9cdec550e0b1099622cda9479Steve McKay        intent.setAction(DocumentsContract.ACTION_BROWSE);
262cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski        intent.setData(getDataUriForIntent(tag));
26314e827a958f713d9cdec550e0b1099622cda9479Steve McKay        intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
26414e827a958f713d9cdec550e0b1099622cda9479Steve McKay        return intent;
26514e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
26614e827a958f713d9cdec550e0b1099622cda9479Steve McKay
26714e827a958f713d9cdec550e0b1099622cda9479Steve McKay    Intent createCancelIntent() {
268ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        final Intent cancelIntent = new Intent(service, FileOperationService.class);
269cd270153ff8844f37c2ddc3c3dc1246700f4974cTomasz Mikolajewski        cancelIntent.setData(getDataUriForIntent(INTENT_TAG_CANCEL));
270ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        cancelIntent.putExtra(EXTRA_CANCEL, true);
271ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        cancelIntent.putExtra(EXTRA_JOB_ID, id);
27214e827a958f713d9cdec550e0b1099622cda9479Steve McKay        return cancelIntent;
27314e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
27414e827a958f713d9cdec550e0b1099622cda9479Steve McKay
2753564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    @Override
2763564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    public String toString() {
2773564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        return new StringBuilder()
2783564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay                .append("Job")
2793564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay                .append("{")
2803564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay                .append("id=" + id)
2813564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay                .append("}")
2823564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay                .toString();
2833564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay    }
2843564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay
285ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    /**
286ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay     * Factory class that facilitates our testing FileOperationService.
287ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay     */
288ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    static class Factory {
289ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay
290ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        static final Factory instance = new Factory();
291ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay
292ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        Job createCopy(Context service, Context appContext, Listener listener,
293ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay                String id, DocumentStack stack, List<DocumentInfo> srcs) {
29455625a675fcbf85654027bf1c8ff89d0084fa24fSteve McKay            assert(!srcs.isEmpty());
29555625a675fcbf85654027bf1c8ff89d0084fa24fSteve McKay            assert(stack.peek().isCreateSupported());
296ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay            return new CopyJob(service, appContext, listener, id, stack, srcs);
297ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        }
298ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay
299ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        Job createMove(Context service, Context appContext, Listener listener,
300b8436af1b84a31b2ba3d50510ebf10bb721f2b6cTomasz Mikolajewski                String id, DocumentStack stack, List<DocumentInfo> srcs,
301b8436af1b84a31b2ba3d50510ebf10bb721f2b6cTomasz Mikolajewski                DocumentInfo srcParent) {
30255625a675fcbf85654027bf1c8ff89d0084fa24fSteve McKay            assert(!srcs.isEmpty());
30355625a675fcbf85654027bf1c8ff89d0084fa24fSteve McKay            assert(stack.peek().isCreateSupported());
304b8436af1b84a31b2ba3d50510ebf10bb721f2b6cTomasz Mikolajewski            return new MoveJob(service, appContext, listener, id, stack, srcs, srcParent);
305ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        }
3063564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay
3073564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        Job createDelete(Context service, Context appContext, Listener listener,
308b8436af1b84a31b2ba3d50510ebf10bb721f2b6cTomasz Mikolajewski                String id, DocumentStack stack, List<DocumentInfo> srcs,
309b8436af1b84a31b2ba3d50510ebf10bb721f2b6cTomasz Mikolajewski                DocumentInfo srcParent) {
31055625a675fcbf85654027bf1c8ff89d0084fa24fSteve McKay            assert(!srcs.isEmpty());
31101958b11ef37c220940e816667556e8259024df8Garfield, Tan            // stack is empty if we delete docs from recent.
31201958b11ef37c220940e816667556e8259024df8Garfield, Tan            // we can't currently delete from archives.
31301958b11ef37c220940e816667556e8259024df8Garfield, Tan            assert(stack.isEmpty() || stack.peek().isDirectory());
314b8436af1b84a31b2ba3d50510ebf10bb721f2b6cTomasz Mikolajewski            return new DeleteJob(service, appContext, listener, id, stack, srcs, srcParent);
3153564543e1ece2211a2d5b729fbbf8dd7ac94944cSteve McKay        }
316ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    }
317ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay
318ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay    /**
319ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay     * Listener interface employed by the service that owns us as well as tests.
320ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay     */
32114e827a958f713d9cdec550e0b1099622cda9479Steve McKay    interface Listener {
322ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        void onStart(Job job);
323ecbf3c504c5ec7ccb3e2be7f4dd175ebe634139dSteve McKay        void onFinished(Job job);
32414e827a958f713d9cdec550e0b1099622cda9479Steve McKay        void onProgress(CopyJob job);
32514e827a958f713d9cdec550e0b1099622cda9479Steve McKay    }
32614e827a958f713d9cdec550e0b1099622cda9479Steve McKay}
327