1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.documentsui.services;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.net.Uri;
22import android.os.RemoteException;
23import android.util.Log;
24
25import com.android.documentsui.archives.ArchivesProvider;
26import com.android.documentsui.base.DocumentInfo;
27import com.android.documentsui.base.DocumentStack;
28import com.android.documentsui.base.Features;
29import com.android.documentsui.base.RootInfo;
30import com.android.documentsui.clipping.UrisSupplier;
31import com.android.documentsui.services.FileOperationService.OpType;
32
33import java.io.FileNotFoundException;
34import java.io.IOException;
35import java.util.ArrayList;
36import java.util.List;
37
38/**
39 * Abstract job that resolves all resource URIs into mResolvedDocs. This provides
40 * uniform error handling and reporting on resource resolution failures, as well
41 * as an easy path for sub-classes to recover and continue past partial failures.
42 */
43public abstract class ResolvedResourcesJob extends Job {
44    private static final String TAG = "ResolvedResourcesJob";
45
46    final List<DocumentInfo> mResolvedDocs;
47    final List<Uri> mAcquiredArchivedUris = new ArrayList<>();
48
49    ResolvedResourcesJob(Context service, Listener listener, String id, @OpType int opType,
50            DocumentStack destination, UrisSupplier srcs, Features features) {
51        super(service, listener, id, opType, destination, srcs, features);
52
53        assert(srcs.getItemCount() > 0);
54
55        // Delay the initialization of it to setUp() because it may be IO extensive.
56        mResolvedDocs = new ArrayList<>(srcs.getItemCount());
57    }
58
59    boolean setUp() {
60        if (!super.setUp()) {
61            return false;
62        }
63
64        // Acquire all source archived documents, so they are not gone while copying from.
65        try {
66            Iterable<Uri> uris = mResourceUris.getUris(appContext);
67            for (Uri uri : uris) {
68                try {
69                    if (ArchivesProvider.AUTHORITY.equals(uri.getAuthority())) {
70                        ArchivesProvider.acquireArchive(getClient(uri), uri);
71                        mAcquiredArchivedUris.add(uri);
72                    }
73                } catch (RemoteException e) {
74                    Log.e(TAG, "Failed to acquire an archive.");
75                    return false;
76                }
77            }
78        } catch (IOException e) {
79            Log.e(TAG, "Failed to read list of target resource Uris. Cannot continue.", e);
80            return false;
81        }
82
83        int docsResolved = buildDocumentList();
84        if (!isCanceled() && docsResolved < mResourceUris.getItemCount()) {
85            if (docsResolved == 0) {
86                Log.e(TAG, "Failed to load any documents. Aborting.");
87                return false;
88            } else {
89                Log.e(TAG, "Failed to load some documents. Processing loaded documents only.");
90            }
91        }
92
93        return true;
94    }
95
96    @Override
97    void finish() {
98        // Release all archived documents.
99        for (Uri uri : mAcquiredArchivedUris) {
100            try {
101                ArchivesProvider.releaseArchive(getClient(uri), uri);
102            } catch (RemoteException e) {
103                Log.e(TAG, "Failed to release an archived document.");
104            }
105        }
106    }
107
108    /**
109     * Allows sub-classes to exclude files from processing.
110     * By default all files are eligible.
111     */
112    boolean isEligibleDoc(DocumentInfo doc, RootInfo root) {
113        return true;
114    }
115
116    /**
117     * @return number of docs successfully loaded.
118     */
119    protected int buildDocumentList() {
120        final ContentResolver resolver = appContext.getContentResolver();
121        Iterable<Uri> uris;
122        try {
123            uris = mResourceUris.getUris(appContext);
124        } catch (IOException e) {
125            Log.e(TAG, "Failed to read list of target resource Uris. Cannot continue.", e);
126            failureCount = this.mResourceUris.getItemCount();
127            return 0;
128        }
129
130        int docsLoaded = 0;
131        for (Uri uri : uris) {
132
133            DocumentInfo doc;
134            try {
135                doc = DocumentInfo.fromUri(resolver, uri);
136            } catch (FileNotFoundException e) {
137                Log.e(TAG, "Failed to resolve content from Uri: " + uri
138                        + ". Skipping to next resource.", e);
139                onResolveFailed(uri);
140                continue;
141            }
142
143            if (isEligibleDoc(doc, stack.getRoot())) {
144                mResolvedDocs.add(doc);
145            } else {
146                onFileFailed(doc);
147            }
148            docsLoaded++;
149
150            if (isCanceled()) {
151                break;
152            }
153        }
154
155        return docsLoaded;
156    }
157}
158