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