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