blob: 77a6466956e7857baa8fe45e1f94e31c6d250f48 [file] [log] [blame]
Ben Kwa8e3fd762015-12-17 10:37:00 -08001/*
2 * Copyright (C) 2015 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
17package com.android.documentsui.dirlist;
18
Steve McKay30535bc2016-11-04 14:16:58 -070019import static com.android.documentsui.base.Shared.VERBOSE;
Steve McKayd9caa6a2016-09-15 16:36:45 -070020import static com.android.documentsui.base.State.MODE_GRID;
21import static com.android.documentsui.base.State.MODE_LIST;
Ben Kwa8e3fd762015-12-17 10:37:00 -080022
23import android.content.ContentProviderClient;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.graphics.Bitmap;
27import android.graphics.Point;
28import android.graphics.drawable.Drawable;
29import android.net.Uri;
30import android.os.AsyncTask;
31import android.os.CancellationSignal;
32import android.os.OperationCanceledException;
33import android.provider.DocumentsContract;
34import android.provider.DocumentsContract.Document;
Tomasz Mikolajewski8f91d782016-02-16 12:28:43 +090035import android.support.annotation.Nullable;
Ben Kwa8e3fd762015-12-17 10:37:00 -080036import android.util.Log;
Garfield, Tanc8099c02016-05-02 12:01:30 -070037import android.view.View;
Ben Kwa8e3fd762015-12-17 10:37:00 -080038import android.widget.ImageView;
39
40import com.android.documentsui.DocumentsApplication;
41import com.android.documentsui.IconUtils;
Ben Kwa8e3fd762015-12-17 10:37:00 -080042import com.android.documentsui.ProviderExecutor;
43import com.android.documentsui.ProviderExecutor.Preemptable;
44import com.android.documentsui.R;
45import com.android.documentsui.ThumbnailCache;
Garfield, Tanc8099c02016-05-02 12:01:30 -070046import com.android.documentsui.ThumbnailCache.Result;
Steve McKayd0805062016-09-15 14:30:38 -070047import com.android.documentsui.base.DocumentInfo;
Steve McKayd0718952016-10-10 13:43:36 -070048import com.android.documentsui.base.MimeTypes;
Steve McKayd9caa6a2016-09-15 16:36:45 -070049import com.android.documentsui.base.State;
50import com.android.documentsui.base.State.ViewMode;
Garfield, Tanc8099c02016-05-02 12:01:30 -070051
52import java.util.function.BiConsumer;
Ben Kwa8e3fd762015-12-17 10:37:00 -080053
54/**
55 * A class to assist with loading and managing the Images (i.e. thumbnails and icons) associated
56 * with items in the directory listing.
57 */
58public class IconHelper {
Garfield, Tanc8099c02016-05-02 12:01:30 -070059 private static final String TAG = "IconHelper";
60
61 // Two animations applied to image views. The first is used to switch mime icon and thumbnail.
62 // The second is used when we need to update thumbnail.
63 private static final BiConsumer<View, View> ANIM_FADE_IN = (mime, thumb) -> {
64 float alpha = mime.getAlpha();
65 mime.animate().alpha(0f).start();
66 thumb.setAlpha(0f);
67 thumb.animate().alpha(alpha).start();
68 };
69 private static final BiConsumer<View, View> ANIM_NO_OP = (mime, thumb) -> {};
Ben Kwa8e3fd762015-12-17 10:37:00 -080070
Steve McKayea4f93b2016-01-29 15:23:27 -080071 private final Context mContext;
Garfield, Tanc8099c02016-05-02 12:01:30 -070072 private final ThumbnailCache mThumbnailCache;
Steve McKayea4f93b2016-01-29 15:23:27 -080073
Ben Kwa8e3fd762015-12-17 10:37:00 -080074 // The display mode (MODE_GRID, MODE_LIST, etc).
75 private int mMode;
Garfield, Tanc8099c02016-05-02 12:01:30 -070076 private Point mCurrentSize;
Ben Kwa8e3fd762015-12-17 10:37:00 -080077 private boolean mThumbnailsEnabled = true;
78
79 /**
80 * @param context
81 * @param mode MODE_GRID or MODE_LIST
82 */
83 public IconHelper(Context context, int mode) {
84 mContext = context;
Steve McKay7776aa52016-01-25 19:00:22 -080085 setViewMode(mode);
Garfield, Tanc8099c02016-05-02 12:01:30 -070086 mThumbnailCache = DocumentsApplication.getThumbnailCache(context);
Ben Kwa8e3fd762015-12-17 10:37:00 -080087 }
88
89 /**
90 * Enables or disables thumbnails. When thumbnails are disabled, mime icons (or custom icons, if
91 * specified by the document) are used instead.
92 *
93 * @param enabled
94 */
95 public void setThumbnailsEnabled(boolean enabled) {
96 mThumbnailsEnabled = enabled;
97 }
98
99 /**
Garfield, Tanc8099c02016-05-02 12:01:30 -0700100 * Sets the current display mode. This affects the thumbnail sizes that are loaded.
101 *
Ben Kwa8e3fd762015-12-17 10:37:00 -0800102 * @param mode See {@link State.MODE_LIST} and {@link State.MODE_GRID}.
103 */
Steve McKay7776aa52016-01-25 19:00:22 -0800104 public void setViewMode(@ViewMode int mode) {
Steve McKayea4f93b2016-01-29 15:23:27 -0800105 mMode = mode;
106 int thumbSize = getThumbSize(mode);
Garfield, Tanc8099c02016-05-02 12:01:30 -0700107 mCurrentSize = new Point(thumbSize, thumbSize);
Steve McKayea4f93b2016-01-29 15:23:27 -0800108 }
109
110 private int getThumbSize(int mode) {
Ben Kwa8e3fd762015-12-17 10:37:00 -0800111 int thumbSize;
112 switch (mode) {
113 case MODE_GRID:
114 thumbSize = mContext.getResources().getDimensionPixelSize(R.dimen.grid_width);
115 break;
116 case MODE_LIST:
Ben Kwae59f1db2016-01-07 18:50:34 -0800117 thumbSize = mContext.getResources().getDimensionPixelSize(
118 R.dimen.list_item_thumbnail_size);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800119 break;
Ben Kwa8e3fd762015-12-17 10:37:00 -0800120 default:
121 throw new IllegalArgumentException("Unsupported layout mode: " + mode);
122 }
Steve McKayea4f93b2016-01-29 15:23:27 -0800123 return thumbSize;
Ben Kwa8e3fd762015-12-17 10:37:00 -0800124 }
125
126 /**
127 * Cancels any ongoing load operations associated with the given ImageView.
Garfield, Tanc8099c02016-05-02 12:01:30 -0700128 *
Ben Kwa8e3fd762015-12-17 10:37:00 -0800129 * @param icon
130 */
131 public void stopLoading(ImageView icon) {
132 final LoaderTask oldTask = (LoaderTask) icon.getTag();
133 if (oldTask != null) {
134 oldTask.preempt();
135 icon.setTag(null);
136 }
137 }
138
139 /** Internal task for loading thumbnails asynchronously. */
140 private static class LoaderTask
141 extends AsyncTask<Uri, Void, Bitmap>
142 implements Preemptable {
143 private final Uri mUri;
144 private final ImageView mIconMime;
145 private final ImageView mIconThumb;
146 private final Point mThumbSize;
Garfield, Tan16502a82016-05-19 15:36:36 -0700147 private final long mLastModified;
Garfield, Tanc8099c02016-05-02 12:01:30 -0700148
149 // A callback to apply animation to image views after the thumbnail is loaded.
150 private final BiConsumer<View, View> mImageAnimator;
151
Ben Kwa8e3fd762015-12-17 10:37:00 -0800152 private final CancellationSignal mSignal;
153
154 public LoaderTask(Uri uri, ImageView iconMime, ImageView iconThumb,
Garfield, Tan16502a82016-05-19 15:36:36 -0700155 Point thumbSize, long lastModified, BiConsumer<View, View> animator) {
Ben Kwa8e3fd762015-12-17 10:37:00 -0800156 mUri = uri;
157 mIconMime = iconMime;
158 mIconThumb = iconThumb;
159 mThumbSize = thumbSize;
Garfield, Tanc8099c02016-05-02 12:01:30 -0700160 mImageAnimator = animator;
Garfield, Tan16502a82016-05-19 15:36:36 -0700161 mLastModified = lastModified;
Ben Kwa8e3fd762015-12-17 10:37:00 -0800162 mSignal = new CancellationSignal();
Steve McKay30535bc2016-11-04 14:16:58 -0700163 if (VERBOSE) Log.v(TAG, "Starting icon loader task for " + mUri);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800164 }
165
166 @Override
167 public void preempt() {
Steve McKay30535bc2016-11-04 14:16:58 -0700168 if (VERBOSE) Log.v(TAG, "Icon loader task for " + mUri + " was cancelled.");
Ben Kwa8e3fd762015-12-17 10:37:00 -0800169 cancel(false);
170 mSignal.cancel();
171 }
172
173 @Override
174 protected Bitmap doInBackground(Uri... params) {
Garfield, Tanc8099c02016-05-02 12:01:30 -0700175 if (isCancelled()) {
Ben Kwa8e3fd762015-12-17 10:37:00 -0800176 return null;
Garfield, Tanc8099c02016-05-02 12:01:30 -0700177 }
Ben Kwa8e3fd762015-12-17 10:37:00 -0800178
179 final Context context = mIconThumb.getContext();
180 final ContentResolver resolver = context.getContentResolver();
181
182 ContentProviderClient client = null;
183 Bitmap result = null;
184 try {
185 client = DocumentsApplication.acquireUnstableProviderOrThrow(
186 resolver, mUri.getAuthority());
187 result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal);
188 if (result != null) {
Garfield, Tanc8099c02016-05-02 12:01:30 -0700189 final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context);
Garfield, Tan16502a82016-05-19 15:36:36 -0700190 cache.putThumbnail(mUri, mThumbSize, result, mLastModified);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800191 }
192 } catch (Exception e) {
193 if (!(e instanceof OperationCanceledException)) {
194 Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e);
195 }
196 } finally {
197 ContentProviderClient.releaseQuietly(client);
198 }
199 return result;
200 }
201
202 @Override
203 protected void onPostExecute(Bitmap result) {
Steve McKay30535bc2016-11-04 14:16:58 -0700204 if (VERBOSE) Log.v(TAG, "Loader task for " + mUri + " completed");
Ben Kwa8e3fd762015-12-17 10:37:00 -0800205
206 if (mIconThumb.getTag() == this && result != null) {
207 mIconThumb.setTag(null);
208 mIconThumb.setImageBitmap(result);
209
Garfield, Tanc8099c02016-05-02 12:01:30 -0700210 mImageAnimator.accept(mIconMime, mIconThumb);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800211 }
212 }
213 }
214
215 /**
216 * Load thumbnails for a directory list item.
Garfield, Tanc8099c02016-05-02 12:01:30 -0700217 *
Steve McKay358c3ec2016-10-21 09:16:57 -0700218 * @param doc The document
219 * @param iconThumb The itemview's thumbnail icon.
220 * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown.
221 * @param subIconMime The second itemview's mime icon. Always visible.
222 * @return
223 */
224 public void load(
225 DocumentInfo doc,
226 ImageView iconThumb,
227 ImageView iconMime,
228 @Nullable ImageView subIconMime) {
229 load(doc.derivedUri, doc.mimeType, doc.flags, doc.icon, doc.lastModified,
230 iconThumb, iconMime, subIconMime);
231 }
232
233 /**
234 * Load thumbnails for a directory list item.
235 *
Ben Kwa8e3fd762015-12-17 10:37:00 -0800236 * @param uri The URI for the file being represented.
237 * @param mimeType The mime type of the file being represented.
238 * @param docFlags Flags for the file being represented.
239 * @param docIcon Custom icon (if any) for the file being requested.
Garfield, Tan16502a82016-05-19 15:36:36 -0700240 * @param docLastModified the last modified value of the file being requested.
Ben Kwa8e3fd762015-12-17 10:37:00 -0800241 * @param iconThumb The itemview's thumbnail icon.
Tomasz Mikolajewski8f91d782016-02-16 12:28:43 +0900242 * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown.
243 * @param subIconMime The second itemview's mime icon. Always visible.
Ben Kwa8e3fd762015-12-17 10:37:00 -0800244 * @return
245 */
Garfield, Tan16502a82016-05-19 15:36:36 -0700246 public void load(Uri uri, String mimeType, int docFlags, int docIcon, long docLastModified,
Tomasz Mikolajewski8f91d782016-02-16 12:28:43 +0900247 ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime) {
Garfield, Tanc8099c02016-05-02 12:01:30 -0700248 boolean loadedThumbnail = false;
Ben Kwa8e3fd762015-12-17 10:37:00 -0800249
250 final String docAuthority = uri.getAuthority();
251
252 final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
253 final boolean allowThumbnail = (mMode == MODE_GRID)
Steve McKayd0718952016-10-10 13:43:36 -0700254 || MimeTypes.mimeMatches(MimeTypes.VISUAL_MIMES, mimeType);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800255 final boolean showThumbnail = supportsThumbnail && allowThumbnail && mThumbnailsEnabled;
256 if (showThumbnail) {
Garfield, Tan16502a82016-05-19 15:36:36 -0700257 loadedThumbnail =
258 loadThumbnail(uri, docAuthority, docLastModified, iconThumb, iconMime);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800259 }
260
Garfield, Tanc8099c02016-05-02 12:01:30 -0700261 final Drawable mimeIcon = getDocumentIcon(mContext, docAuthority,
Tomasz Mikolajewski8f91d782016-02-16 12:28:43 +0900262 DocumentsContract.getDocumentId(uri), mimeType, docIcon);
263 if (subIconMime != null) {
Garfield, Tanc8099c02016-05-02 12:01:30 -0700264 setMimeIcon(subIconMime, mimeIcon);
Tomasz Mikolajewski8f91d782016-02-16 12:28:43 +0900265 }
266
Garfield, Tanc8099c02016-05-02 12:01:30 -0700267 if (loadedThumbnail) {
268 hideImageView(iconMime);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800269 } else {
Garfield, Tanc8099c02016-05-02 12:01:30 -0700270 // Add a mime icon if the thumbnail is not shown.
271 setMimeIcon(iconMime, mimeIcon);
272 hideImageView(iconThumb);
Ben Kwa8e3fd762015-12-17 10:37:00 -0800273 }
274 }
275
Garfield, Tan16502a82016-05-19 15:36:36 -0700276 private boolean loadThumbnail(Uri uri, String docAuthority, long docLastModified,
277 ImageView iconThumb, ImageView iconMime) {
Garfield, Tanc8099c02016-05-02 12:01:30 -0700278 final Result result = mThumbnailCache.getThumbnail(uri, mCurrentSize);
279
Garfield, Tanc2923782016-06-07 15:09:51 -0700280 try {
281 final Bitmap cachedThumbnail = result.getThumbnail();
282 iconThumb.setImageBitmap(cachedThumbnail);
Garfield, Tanc8099c02016-05-02 12:01:30 -0700283
Garfield, Tanc2923782016-06-07 15:09:51 -0700284 boolean stale = (docLastModified > result.getLastModified());
Steve McKay30535bc2016-11-04 14:16:58 -0700285 if (VERBOSE) Log.v(TAG,
Garfield, Tanc2923782016-06-07 15:09:51 -0700286 String.format("Load thumbnail for %s, got result %d and stale %b.",
287 uri.toString(), result.getStatus(), stale));
288 if (!result.isExactHit() || stale) {
289 final BiConsumer<View, View> animator =
290 (cachedThumbnail == null ? ANIM_FADE_IN : ANIM_NO_OP);
291 final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mCurrentSize,
292 docLastModified, animator);
Garfield, Tanc8099c02016-05-02 12:01:30 -0700293
Garfield, Tanc2923782016-06-07 15:09:51 -0700294 iconThumb.setTag(task);
Garfield, Tanc8099c02016-05-02 12:01:30 -0700295
Garfield, Tanc2923782016-06-07 15:09:51 -0700296 ProviderExecutor.forAuthority(docAuthority).execute(task);
297 }
298
299 return result.isHit();
300 } finally {
301 result.recycle();
Garfield, Tanc8099c02016-05-02 12:01:30 -0700302 }
Garfield, Tanc8099c02016-05-02 12:01:30 -0700303 }
304
305 private void setMimeIcon(ImageView view, Drawable icon) {
306 view.setImageDrawable(icon);
307 view.setAlpha(1f);
308 }
309
310 private void hideImageView(ImageView view) {
311 view.setImageDrawable(null);
312 view.setAlpha(0f);
313 }
314
Steve McKay84769b82016-06-09 10:46:07 -0700315 private Drawable getDocumentIcon(
316 Context context, String authority, String id, String mimeType, int icon) {
Ben Kwa8e3fd762015-12-17 10:37:00 -0800317 if (icon != 0) {
318 return IconUtils.loadPackageIcon(context, authority, icon);
319 } else {
320 return IconUtils.loadMimeIcon(context, mimeType, authority, id, mMode);
321 }
322 }
Steve McKay84769b82016-06-09 10:46:07 -0700323
324 /**
325 * Returns a mime icon or package icon for a {@link DocumentInfo}.
326 */
327 public Drawable getDocumentIcon(Context context, DocumentInfo doc) {
328 return getDocumentIcon(
329 context, doc.authority, doc.documentId, doc.mimeType, doc.icon);
330 }
Ben Kwa8e3fd762015-12-17 10:37:00 -0800331}