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