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