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