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