/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.documentsui.services; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; import android.os.RemoteException; import android.util.Log; import com.android.documentsui.archives.ArchivesProvider; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.Features; import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.UserId; import com.android.documentsui.clipping.UrisSupplier; import com.android.documentsui.services.FileOperationService.OpType; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Abstract job that resolves all resource URIs into mResolvedDocs. This provides * uniform error handling and reporting on resource resolution failures, as well * as an easy path for sub-classes to recover and continue past partial failures. */ public abstract class ResolvedResourcesJob extends Job { private static final String TAG = "ResolvedResourcesJob"; final List mResolvedDocs; final List mAcquiredArchivedUris = new ArrayList<>(); ResolvedResourcesJob(Context service, Listener listener, String id, @OpType int opType, DocumentStack destination, UrisSupplier srcs, Features features) { super(service, listener, id, opType, destination, srcs, features); assert(srcs.getItemCount() > 0); // Delay the initialization of it to setUp() because it may be IO extensive. mResolvedDocs = new ArrayList<>(srcs.getItemCount()); } boolean setUp() { if (!super.setUp()) { return false; } // Acquire all source archived documents, so they are not gone while copying from. try { Iterable uris = mResourceUris.getUris(appContext); for (Uri uri : uris) { try { if (ArchivesProvider.AUTHORITY.equals(uri.getAuthority())) { ArchivesProvider.acquireArchive(getClient(uri), uri); mAcquiredArchivedUris.add(uri); } } catch (RemoteException e) { Log.e(TAG, "Failed to acquire an archive."); return false; } } } catch (IOException e) { Log.e(TAG, "Failed to read list of target resource Uris. Cannot continue.", e); return false; } int docsResolved = buildDocumentList(); if (!isCanceled() && docsResolved < mResourceUris.getItemCount()) { if (docsResolved == 0) { Log.e(TAG, "Failed to load any documents. Aborting."); return false; } else { Log.e(TAG, "Failed to load some documents. Processing loaded documents only."); } } return true; } @Override void finish() { // Release all archived documents. for (Uri uri : mAcquiredArchivedUris) { try { ArchivesProvider.releaseArchive(getClient(uri), uri); } catch (RemoteException e) { Log.e(TAG, "Failed to release an archived document."); } } } /** * Allows sub-classes to exclude files from processing. * By default all files are eligible. */ boolean isEligibleDoc(DocumentInfo doc, RootInfo root) { return true; } /** * @return number of docs successfully loaded. */ protected int buildDocumentList() { final ContentResolver resolver = appContext.getContentResolver(); Iterable uris; try { uris = mResourceUris.getUris(appContext); } catch (IOException e) { Log.e(TAG, "Failed to read list of target resource Uris. Cannot continue.", e); failureCount = this.mResourceUris.getItemCount(); return 0; } int docsLoaded = 0; for (Uri uri : uris) { DocumentInfo doc; try { doc = DocumentInfo.fromUri(resolver, uri, UserId.DEFAULT_USER); } catch (FileNotFoundException e) { Log.e(TAG, "Failed to resolve content from Uri: " + uri + ". Skipping to next resource.", e); onResolveFailed(uri); continue; } if (isEligibleDoc(doc, stack.getRoot())) { mResolvedDocs.add(doc); } else { onFileFailed(doc); } docsLoaded++; if (isCanceled()) { break; } } return docsLoaded; } }