1 /*
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 
17 package com.android.documentsui.dirlist;
18 
19 import static com.android.documentsui.base.SharedMinimal.VERBOSE;
20 import static com.android.documentsui.base.State.MODE_GRID;
21 import static com.android.documentsui.base.State.MODE_LIST;
22 
23 import android.content.Context;
24 import android.graphics.Bitmap;
25 import android.graphics.Point;
26 import android.graphics.drawable.Drawable;
27 import android.net.Uri;
28 import android.provider.DocumentsContract;
29 import android.provider.DocumentsContract.Document;
30 import android.util.Log;
31 import android.view.View;
32 import android.widget.ImageView;
33 
34 import androidx.annotation.Nullable;
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.documentsui.DocumentsApplication;
38 import com.android.documentsui.IconUtils;
39 import com.android.documentsui.ProviderExecutor;
40 import com.android.documentsui.R;
41 import com.android.documentsui.ThumbnailCache;
42 import com.android.documentsui.ThumbnailCache.Result;
43 import com.android.documentsui.ThumbnailLoader;
44 import com.android.documentsui.base.DocumentInfo;
45 import com.android.documentsui.base.MimeTypes;
46 import com.android.documentsui.base.State;
47 import com.android.documentsui.base.State.ViewMode;
48 import com.android.documentsui.base.UserId;
49 
50 import java.util.function.BiConsumer;
51 
52 /**
53  * A class to assist with loading and managing the Images (i.e. thumbnails and icons) associated
54  * with items in the directory listing.
55  */
56 public class IconHelper {
57     private static final String TAG = "IconHelper";
58 
59     private final Context mContext;
60     private final ThumbnailCache mThumbnailCache;
61 
62     // The display mode (MODE_GRID, MODE_LIST, etc).
63     private int mMode;
64     private Point mCurrentSize;
65     private boolean mThumbnailsEnabled = true;
66     private final boolean mMaybeShowBadge;
67     @Nullable
68     private final UserId mManagedUser;
69 
70     /**
71      * @param context
72      * @param mode MODE_GRID or MODE_LIST
73      */
IconHelper(Context context, int mode, boolean maybeShowBadge)74     public IconHelper(Context context, int mode, boolean maybeShowBadge) {
75         this(context, mode, maybeShowBadge, DocumentsApplication.getThumbnailCache(context),
76                 DocumentsApplication.getUserIdManager(context).getManagedUser());
77     }
78 
79     @VisibleForTesting
IconHelper(Context context, int mode, boolean maybeShowBadge, ThumbnailCache thumbnailCache, @Nullable UserId managedUser)80     IconHelper(Context context, int mode, boolean maybeShowBadge, ThumbnailCache thumbnailCache,
81             @Nullable UserId managedUser) {
82         mContext = context;
83         setViewMode(mode);
84         mThumbnailCache = thumbnailCache;
85         mManagedUser = managedUser;
86         mMaybeShowBadge = maybeShowBadge;
87     }
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      */
setThumbnailsEnabled(boolean enabled)95     public void setThumbnailsEnabled(boolean enabled) {
96         mThumbnailsEnabled = enabled;
97     }
98 
99     /**
100      * Sets the current display mode. This affects the thumbnail sizes that are loaded.
101      *
102      * @param mode See {@link State.MODE_LIST} and {@link State.MODE_GRID}.
103      */
setViewMode(@iewMode int mode)104     public void setViewMode(@ViewMode int mode) {
105         mMode = mode;
106         int thumbSize = getThumbSize(mode);
107         mCurrentSize = new Point(thumbSize, thumbSize);
108     }
109 
getThumbSize(int mode)110     private int getThumbSize(int mode) {
111         int thumbSize;
112         switch (mode) {
113             case MODE_GRID:
114                 thumbSize = mContext.getResources().getDimensionPixelSize(R.dimen.grid_width);
115                 break;
116             case MODE_LIST:
117                 thumbSize = mContext.getResources().getDimensionPixelSize(
118                         R.dimen.list_item_thumbnail_size);
119                 break;
120             default:
121                 throw new IllegalArgumentException("Unsupported layout mode: " + mode);
122         }
123         return thumbSize;
124     }
125 
126     /**
127      * Cancels any ongoing load operations associated with the given ImageView.
128      *
129      * @param icon
130      */
stopLoading(ImageView icon)131     public void stopLoading(ImageView icon) {
132         final ThumbnailLoader oldTask = (ThumbnailLoader) icon.getTag();
133         if (oldTask != null) {
134             oldTask.preempt();
135             icon.setTag(null);
136         }
137     }
138 
139     /**
140      * Load thumbnails for a directory list item.
141      *
142      * @param doc The document
143      * @param iconThumb The itemview's thumbnail icon.
144      * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown.
145      * @param subIconMime The second itemview's mime icon. Always visible.
146      * @return
147      */
load( DocumentInfo doc, ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime)148     public void load(
149             DocumentInfo doc,
150             ImageView iconThumb,
151             ImageView iconMime,
152             @Nullable ImageView subIconMime) {
153         load(doc.derivedUri, doc.userId, doc.mimeType, doc.flags, doc.icon, doc.lastModified,
154                 iconThumb, iconMime, subIconMime);
155     }
156 
157     /**
158      * Load thumbnails for a directory list item.
159      *
160      * @param uri The URI for the file being represented.
161      * @param mimeType The mime type of the file being represented.
162      * @param docFlags Flags for the file being represented.
163      * @param docIcon Custom icon (if any) for the file being requested.
164      * @param docLastModified the last modified value of the file being requested.
165      * @param iconThumb The itemview's thumbnail icon.
166      * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown.
167      * @param subIconMime The second itemview's mime icon. Always visible.
168      * @return
169      */
load(Uri uri, UserId userId, String mimeType, int docFlags, int docIcon, long docLastModified, ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime)170     public void load(Uri uri, UserId userId, String mimeType, int docFlags, int docIcon,
171             long docLastModified, ImageView iconThumb, ImageView iconMime,
172             @Nullable ImageView subIconMime) {
173         boolean loadedThumbnail = false;
174 
175         final String docAuthority = uri.getAuthority();
176 
177         final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
178         final boolean allowThumbnail = (mMode == MODE_GRID)
179                 || MimeTypes.mimeMatches(MimeTypes.VISUAL_MIMES, mimeType);
180         final boolean showThumbnail = supportsThumbnail && allowThumbnail && mThumbnailsEnabled;
181         if (showThumbnail) {
182             loadedThumbnail =
183                 loadThumbnail(uri, userId, docAuthority, docLastModified, iconThumb, iconMime);
184         }
185 
186         final Drawable mimeIcon = getDocumentIcon(mContext, userId, docAuthority,
187                 DocumentsContract.getDocumentId(uri), mimeType, docIcon);
188         if (subIconMime != null) {
189             setMimeIcon(subIconMime, mimeIcon);
190         }
191 
192         if (loadedThumbnail) {
193             hideImageView(iconMime);
194         } else {
195             // Add a mime icon if the thumbnail is not shown.
196             setMimeIcon(iconMime, mimeIcon);
197             hideImageView(iconThumb);
198         }
199     }
200 
loadThumbnail(Uri uri, UserId userId, String docAuthority, long docLastModified, ImageView iconThumb, ImageView iconMime)201     private boolean loadThumbnail(Uri uri, UserId userId, String docAuthority, long docLastModified,
202             ImageView iconThumb, ImageView iconMime) {
203         final Result result = mThumbnailCache.getThumbnail(uri, userId, mCurrentSize);
204 
205         try {
206             final Bitmap cachedThumbnail = result.getThumbnail();
207             iconThumb.setImageBitmap(cachedThumbnail);
208 
209             boolean stale = (docLastModified > result.getLastModified());
210             if (VERBOSE) Log.v(TAG,
211                     String.format("Load thumbnail for %s, got result %d and stale %b.",
212                             uri.toString(), result.getStatus(), stale));
213             if (!result.isExactHit() || stale) {
214                 final BiConsumer<View, View> animator =
215                         (cachedThumbnail == null ? ThumbnailLoader.ANIM_FADE_IN :
216                                 ThumbnailLoader.ANIM_NO_OP);
217 
218                 final ThumbnailLoader task = new ThumbnailLoader(uri, userId, iconThumb,
219                         mCurrentSize, docLastModified,
220                         bitmap -> {
221                             if (bitmap != null) {
222                                 iconThumb.setImageBitmap(bitmap);
223                                 animator.accept(iconMime, iconThumb);
224                             }
225                         }, true /* addToCache */);
226 
227                 ProviderExecutor.forAuthority(docAuthority).execute(task);
228             }
229 
230             return result.isHit();
231         } finally {
232             result.recycle();
233         }
234     }
235 
setMimeIcon(ImageView view, Drawable icon)236     private void setMimeIcon(ImageView view, Drawable icon) {
237         view.setImageDrawable(icon);
238         view.setAlpha(1f);
239     }
240 
hideImageView(ImageView view)241     private void hideImageView(ImageView view) {
242         view.setImageDrawable(null);
243         view.setAlpha(0f);
244     }
245 
getDocumentIcon(Context context, UserId userId, String authority, String id, String mimeType, int icon)246     private Drawable getDocumentIcon(Context context, UserId userId, String authority, String id,
247             String mimeType, int icon) {
248         if (icon != 0) {
249             return IconUtils.loadPackageIcon(context, userId, authority, icon, mMaybeShowBadge);
250         } else {
251             return IconUtils.loadMimeIcon(context, mimeType, authority, id, mMode);
252         }
253     }
254 
255     /**
256      * Returns a mime icon or package icon for a {@link DocumentInfo}.
257      */
getDocumentIcon(Context context, DocumentInfo doc)258     public Drawable getDocumentIcon(Context context, DocumentInfo doc) {
259         return getDocumentIcon(
260                 context, doc.userId, doc.authority, doc.documentId, doc.mimeType, doc.icon);
261     }
262 
263     /**
264      * Returns true if we should show a briefcase icon for the given user.
265      */
shouldShowBadge(int userIdIdentifier)266     public boolean shouldShowBadge(int userIdIdentifier) {
267         return mMaybeShowBadge && mManagedUser != null
268                 && mManagedUser.getIdentifier() == userIdIdentifier;
269     }
270 }
271