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