1 /*
2  * Copyright (C) 2017 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 package com.android.documentsui;
17 
18 import static android.content.ContentResolver.wrap;
19 
20 import static com.android.documentsui.base.SharedMinimal.VERBOSE;
21 
22 import android.content.ContentProviderClient;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.graphics.Bitmap;
26 import android.graphics.Point;
27 import android.net.Uri;
28 import android.os.AsyncTask;
29 import android.os.CancellationSignal;
30 import android.os.FileUtils;
31 import android.os.OperationCanceledException;
32 import android.provider.DocumentsContract;
33 import android.util.Log;
34 import android.view.View;
35 import android.widget.ImageView;
36 
37 import com.android.documentsui.ProviderExecutor.Preemptable;
38 import com.android.documentsui.base.UserId;
39 
40 import java.util.function.BiConsumer;
41 import java.util.function.Consumer;
42 
43 /**
44  *  Loads a Thumbnails asynchronously then animates from the mime icon to the thumbnail
45  */
46 public final class ThumbnailLoader extends AsyncTask<Uri, Void, Bitmap> implements Preemptable {
47 
48     private static final String TAG = ThumbnailLoader.class.getCanonicalName();
49 
50     /**
51      * Two animations applied to image views. The first is used to switch mime icon and thumbnail.
52      * The second is used when we need to update thumbnail.
53      */
54     public static final BiConsumer<View, View> ANIM_FADE_IN = (mime, thumb) -> {
55         float alpha = mime.getAlpha();
56         mime.animate().alpha(0f).start();
57         thumb.setAlpha(0f);
58         thumb.animate().alpha(alpha).start();
59     };
60     public static final BiConsumer<View, View> ANIM_NO_OP = (mime, thumb) -> {};
61 
62     private final ImageView mIconThumb;
63     private final Point mThumbSize;
64     private final Uri mUri;
65     private final UserId mUserId;
66     private final long mLastModified;
67     private final Consumer<Bitmap> mCallback;
68     private final boolean mAddToCache;
69     private final CancellationSignal mSignal;
70 
71     /**
72      * @param uri - to a thumbnail.
73      * @param userId - user of the uri.
74      * @param iconThumb - ImageView to display the thumbnail.
75      * @param thumbSize - size of the thumbnail.
76      * @param lastModified - used for updating thumbnail caches.
77      * @param addToCache - flag that determines if the loader saves the thumbnail to the cache.
78      */
ThumbnailLoader(Uri uri, UserId userId, ImageView iconThumb, Point thumbSize, long lastModified, Consumer<Bitmap> callback, boolean addToCache)79     public ThumbnailLoader(Uri uri, UserId userId, ImageView iconThumb, Point thumbSize,
80             long lastModified, Consumer<Bitmap> callback, boolean addToCache) {
81 
82         mUri = uri;
83         mUserId = userId;
84         mIconThumb = iconThumb;
85         mThumbSize = thumbSize;
86         mLastModified = lastModified;
87         mCallback = callback;
88         mAddToCache = addToCache;
89         mSignal = new CancellationSignal();
90         mIconThumb.setTag(this);
91 
92         if (VERBOSE) Log.v(TAG, "Starting icon loader task for " + mUri);
93     }
94 
95     @Override
preempt()96     public void preempt() {
97         if (VERBOSE) Log.v(TAG, "Icon loader task for " + mUri + " was cancelled.");
98         cancel(false);
99         mSignal.cancel();
100     }
101 
102     @Override
doInBackground(Uri... params)103     protected Bitmap doInBackground(Uri... params) {
104         if (isCancelled()) {
105             return null;
106         }
107 
108         final Context context = mIconThumb.getContext();
109         final ContentResolver resolver = mUserId.getContentResolver(context);
110 
111         ContentProviderClient client = null;
112         Bitmap result = null;
113         try {
114             client = DocumentsApplication.acquireUnstableProviderOrThrow(
115                 resolver, mUri.getAuthority());
116             result = DocumentsContract.getDocumentThumbnail(wrap(client),
117                     mUri, mThumbSize, mSignal);
118             if (result != null && mAddToCache) {
119                 final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context);
120                 cache.putThumbnail(mUri, mUserId, mThumbSize, result, mLastModified);
121             }
122         } catch (Exception e) {
123             if (!(e instanceof OperationCanceledException)) {
124                 Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e);
125             }
126         } finally {
127             FileUtils.closeQuietly(client);
128         }
129         return result;
130     }
131 
132     @Override
onPostExecute(Bitmap result)133     protected void onPostExecute(Bitmap result) {
134         if (VERBOSE) Log.v(TAG, "Loader task for " + mUri + " completed");
135 
136         if (mIconThumb.getTag() == this) {
137             mIconThumb.setTag(null);
138             mCallback.accept(result);
139         }
140     }
141 }