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.clipping.UrisSupplier;
31 import com.android.documentsui.services.FileOperationService.OpType;
32 
33 import java.io.FileNotFoundException;
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import 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  */
43 public 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 
ResolvedResourcesJob(Context service, Listener listener, String id, @OpType int opType, DocumentStack destination, UrisSupplier srcs, Features features)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 
setUp()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
finish()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      */
isEligibleDoc(DocumentInfo doc, RootInfo root)112     boolean isEligibleDoc(DocumentInfo doc, RootInfo root) {
113         return true;
114     }
115 
116     /**
117      * @return number of docs successfully loaded.
118      */
buildDocumentList()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