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.Shared.DEBUG; 20 import static com.android.documentsui.State.MODE_GRID; 21 import static com.android.documentsui.State.MODE_LIST; 22 23 import android.content.ContentProviderClient; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.graphics.Bitmap; 27 import android.graphics.Point; 28 import android.graphics.drawable.Drawable; 29 import android.net.Uri; 30 import android.os.AsyncTask; 31 import android.os.CancellationSignal; 32 import android.os.OperationCanceledException; 33 import android.provider.DocumentsContract; 34 import android.provider.DocumentsContract.Document; 35 import android.support.annotation.Nullable; 36 import android.util.Log; 37 import android.widget.ImageView; 38 39 import com.android.documentsui.DocumentsApplication; 40 import com.android.documentsui.IconUtils; 41 import com.android.documentsui.MimePredicate; 42 import com.android.documentsui.ProviderExecutor; 43 import com.android.documentsui.ProviderExecutor.Preemptable; 44 import com.android.documentsui.R; 45 import com.android.documentsui.State; 46 import com.android.documentsui.State.ViewMode; 47 import com.android.documentsui.ThumbnailCache; 48 49 /** 50 * A class to assist with loading and managing the Images (i.e. thumbnails and icons) associated 51 * with items in the directory listing. 52 */ 53 public class IconHelper { 54 private static String TAG = "IconHelper"; 55 56 private final Context mContext; 57 58 // Updated when icon size is set. 59 private ThumbnailCache mCache; 60 private Point mThumbSize; 61 // The display mode (MODE_GRID, MODE_LIST, etc). 62 private int mMode; 63 private boolean mThumbnailsEnabled = true; 64 65 /** 66 * @param context 67 * @param mode MODE_GRID or MODE_LIST 68 */ IconHelper(Context context, int mode)69 public IconHelper(Context context, int mode) { 70 mContext = context; 71 setViewMode(mode); 72 mCache = DocumentsApplication.getThumbnailsCache(context, mThumbSize); 73 } 74 75 /** 76 * Enables or disables thumbnails. When thumbnails are disabled, mime icons (or custom icons, if 77 * specified by the document) are used instead. 78 * 79 * @param enabled 80 */ setThumbnailsEnabled(boolean enabled)81 public void setThumbnailsEnabled(boolean enabled) { 82 mThumbnailsEnabled = enabled; 83 } 84 85 /** 86 * Sets the current display mode. This affects the thumbnail sizes that are loaded. 87 * @param mode See {@link State.MODE_LIST} and {@link State.MODE_GRID}. 88 */ setViewMode(@iewMode int mode)89 public void setViewMode(@ViewMode int mode) { 90 mMode = mode; 91 int thumbSize = getThumbSize(mode); 92 mThumbSize = new Point(thumbSize, thumbSize); 93 mCache = DocumentsApplication.getThumbnailsCache(mContext, mThumbSize); 94 } 95 getThumbSize(int mode)96 private int getThumbSize(int mode) { 97 int thumbSize; 98 switch (mode) { 99 case MODE_GRID: 100 thumbSize = mContext.getResources().getDimensionPixelSize(R.dimen.grid_width); 101 break; 102 case MODE_LIST: 103 thumbSize = mContext.getResources().getDimensionPixelSize( 104 R.dimen.list_item_thumbnail_size); 105 break; 106 default: 107 throw new IllegalArgumentException("Unsupported layout mode: " + mode); 108 } 109 return thumbSize; 110 } 111 112 /** 113 * Cancels any ongoing load operations associated with the given ImageView. 114 * @param icon 115 */ stopLoading(ImageView icon)116 public void stopLoading(ImageView icon) { 117 final LoaderTask oldTask = (LoaderTask) icon.getTag(); 118 if (oldTask != null) { 119 oldTask.preempt(); 120 icon.setTag(null); 121 } 122 } 123 124 /** Internal task for loading thumbnails asynchronously. */ 125 private static class LoaderTask 126 extends AsyncTask<Uri, Void, Bitmap> 127 implements Preemptable { 128 private final Uri mUri; 129 private final ImageView mIconMime; 130 private final ImageView mIconThumb; 131 private final Point mThumbSize; 132 private final CancellationSignal mSignal; 133 LoaderTask(Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize)134 public LoaderTask(Uri uri, ImageView iconMime, ImageView iconThumb, 135 Point thumbSize) { 136 mUri = uri; 137 mIconMime = iconMime; 138 mIconThumb = iconThumb; 139 mThumbSize = thumbSize; 140 mSignal = new CancellationSignal(); 141 if (DEBUG) Log.d(TAG, "Starting icon loader task for " + mUri); 142 } 143 144 @Override preempt()145 public void preempt() { 146 if (DEBUG) Log.d(TAG, "Icon loader task for " + mUri + " was cancelled."); 147 cancel(false); 148 mSignal.cancel(); 149 } 150 151 @Override doInBackground(Uri... params)152 protected Bitmap doInBackground(Uri... params) { 153 if (isCancelled()) 154 return null; 155 156 final Context context = mIconThumb.getContext(); 157 final ContentResolver resolver = context.getContentResolver(); 158 159 ContentProviderClient client = null; 160 Bitmap result = null; 161 try { 162 client = DocumentsApplication.acquireUnstableProviderOrThrow( 163 resolver, mUri.getAuthority()); 164 result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal); 165 if (result != null) { 166 final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( 167 context, mThumbSize); 168 thumbs.put(mUri, result); 169 } 170 } catch (Exception e) { 171 if (!(e instanceof OperationCanceledException)) { 172 Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e); 173 } 174 } finally { 175 ContentProviderClient.releaseQuietly(client); 176 } 177 return result; 178 } 179 180 @Override onPostExecute(Bitmap result)181 protected void onPostExecute(Bitmap result) { 182 if (DEBUG) Log.d(TAG, "Loader task for " + mUri + " completed"); 183 184 if (mIconThumb.getTag() == this && result != null) { 185 mIconThumb.setTag(null); 186 mIconThumb.setImageBitmap(result); 187 188 float alpha = mIconMime.getAlpha(); 189 mIconMime.animate().alpha(0f).start(); 190 mIconThumb.setAlpha(0f); 191 mIconThumb.animate().alpha(alpha).start(); 192 } 193 } 194 } 195 196 /** 197 * Load thumbnails for a directory list item. 198 * @param uri The URI for the file being represented. 199 * @param mimeType The mime type of the file being represented. 200 * @param docFlags Flags for the file being represented. 201 * @param docIcon Custom icon (if any) for the file being requested. 202 * @param iconThumb The itemview's thumbnail icon. 203 * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown. 204 * @param subIconMime The second itemview's mime icon. Always visible. 205 * @return 206 */ loadThumbnail(Uri uri, String mimeType, int docFlags, int docIcon, ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime)207 public void loadThumbnail(Uri uri, String mimeType, int docFlags, int docIcon, 208 ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime) { 209 boolean cacheHit = false; 210 211 final String docAuthority = uri.getAuthority(); 212 213 final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0; 214 final boolean allowThumbnail = (mMode == MODE_GRID) 215 || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mimeType); 216 final boolean showThumbnail = supportsThumbnail && allowThumbnail && mThumbnailsEnabled; 217 if (showThumbnail) { 218 final Bitmap cachedResult = mCache.get(uri); 219 if (cachedResult != null) { 220 iconThumb.setImageBitmap(cachedResult); 221 cacheHit = true; 222 } else { 223 iconThumb.setImageDrawable(null); 224 final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mThumbSize); 225 iconThumb.setTag(task); 226 ProviderExecutor.forAuthority(docAuthority).execute(task); 227 } 228 } 229 230 final Drawable icon = getDocumentIcon(mContext, docAuthority, 231 DocumentsContract.getDocumentId(uri), mimeType, docIcon); 232 if (subIconMime != null) { 233 subIconMime.setImageDrawable(icon); 234 } 235 236 if (cacheHit) { 237 iconMime.setImageDrawable(null); 238 iconMime.setAlpha(0f); 239 iconThumb.setAlpha(1f); 240 } else { 241 // Add a mime icon if the thumbnail is being loaded in the background. 242 iconThumb.setImageDrawable(null); 243 iconMime.setImageDrawable(icon); 244 iconMime.setAlpha(1f); 245 iconThumb.setAlpha(0f); 246 } 247 } 248 249 /** 250 * Gets a mime icon or package icon for a file. 251 * @param context 252 * @param authority The authority string of the file. 253 * @param id The document ID of the file. 254 * @param mimeType The mime type of the file. 255 * @param icon The custom icon (if any) of the file. 256 * @return 257 */ getDocumentIcon(Context context, String authority, String id, String mimeType, int icon)258 public Drawable getDocumentIcon(Context context, String authority, String id, 259 String mimeType, int icon) { 260 if (icon != 0) { 261 return IconUtils.loadPackageIcon(context, authority, icon); 262 } else { 263 return IconUtils.loadMimeIcon(context, mimeType, authority, id, mMode); 264 } 265 } 266 267 } 268