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