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