Loading packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java +6 −16 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Point; import android.net.Uri; import android.os.RemoteException; import android.text.format.DateUtils; Loading @@ -33,21 +32,16 @@ public class DocumentsApplication extends Application { private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS; private RootsCache mRoots; private Point mThumbnailsSize; private ThumbnailCache mThumbnails; private ThumbnailCache mThumbnailCache; public static RootsCache getRootsCache(Context context) { return ((DocumentsApplication) context.getApplicationContext()).mRoots; } public static ThumbnailCache getThumbnailsCache(Context context, Point size) { public static ThumbnailCache getThumbnailCache(Context context) { final DocumentsApplication app = (DocumentsApplication) context.getApplicationContext(); final ThumbnailCache thumbnails = app.mThumbnails; if (!size.equals(app.mThumbnailsSize)) { thumbnails.evictAll(); app.mThumbnailsSize = size; } return thumbnails; return app.mThumbnailCache; } public static ContentProviderClient acquireUnstableProviderOrThrow( Loading @@ -71,7 +65,7 @@ public class DocumentsApplication extends Application { mRoots = new RootsCache(this); mRoots.updateAsync(false); mThumbnails = new ThumbnailCache(memoryClassBytes / 4); mThumbnailCache = new ThumbnailCache(memoryClassBytes / 4); final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); Loading @@ -90,11 +84,7 @@ public class DocumentsApplication extends Application { public void onTrimMemory(int level) { super.onTrimMemory(level); if (level >= TRIM_MEMORY_MODERATE) { mThumbnails.evictAll(); } else if (level >= TRIM_MEMORY_BACKGROUND) { mThumbnails.trimToSize(mThumbnails.size() / 2); } mThumbnailCache.onTrimMemory(level); } private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() { Loading packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java +233 −7 Original line number Diff line number Diff line /* * Copyright (C) 2013 The Android Open Source Project * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. Loading @@ -16,17 +16,243 @@ package com.android.documentsui; import android.annotation.IntDef; import android.annotation.Nullable; import android.content.ComponentCallbacks2; import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; import android.util.LruCache; import android.util.Pair; import android.util.Pools; public class ThumbnailCache extends LruCache<Uri, Bitmap> { public ThumbnailCache(int maxSizeBytes) { import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Comparator; import java.util.HashMap; import java.util.TreeMap; /** * An LRU cache that supports finding the thumbnail of the requested uri with a different size than * the requested one. */ public class ThumbnailCache { private static final SizeComparator SIZE_COMPARATOR = new SizeComparator(); /** * A 2-dimensional index into {@link #mCache} entries. Pair<Uri, Point> is the key to * {@link #mCache}. TreeMap is used to search the closest size to a given size and a given uri. */ private final HashMap<Uri, TreeMap<Point, Pair<Uri, Point>>> mSizeIndex; private final Cache mCache; /** * Creates a thumbnail LRU cache. * * @param maxCacheSizeInBytes the maximum size of thumbnails in bytes this cache can hold. */ public ThumbnailCache(int maxCacheSizeInBytes) { mSizeIndex = new HashMap<>(); mCache = new Cache(maxCacheSizeInBytes); } /** * Obtains thumbnail given a uri and a size. * * @param uri the uri of the thumbnail in need * @param size the desired size of the thumbnail * @return the thumbnail result */ public Result getThumbnail(Uri uri, Point size) { Result result = Result.obtain(Result.CACHE_MISS, null, null); TreeMap<Point, Pair<Uri, Point>> sizeMap; sizeMap = mSizeIndex.get(uri); if (sizeMap == null || sizeMap.isEmpty()) { // There is not any thumbnail for this uri. return result; } // Look for thumbnail of the same size. Pair<Uri, Point> cacheKey = sizeMap.get(size); if (cacheKey != null) { Bitmap thumbnail = mCache.get(cacheKey); if (thumbnail != null) { result.mStatus = Result.CACHE_HIT_EXACT; result.mThumbnail = thumbnail; result.mSize = size; return result; } } // Look for thumbnail of bigger sizes. Point otherSize = sizeMap.higherKey(size); if (otherSize != null) { cacheKey = sizeMap.get(otherSize); if (cacheKey != null) { Bitmap thumbnail = mCache.get(cacheKey); if (thumbnail != null) { result.mStatus = Result.CACHE_HIT_LARGER; result.mThumbnail = thumbnail; result.mSize = otherSize; return result; } } } // Look for thumbnail of smaller sizes. otherSize = sizeMap.lowerKey(size); if (otherSize != null) { cacheKey = sizeMap.get(otherSize); if (cacheKey != null) { Bitmap thumbnail = mCache.get(cacheKey); if (thumbnail != null) { result.mStatus = Result.CACHE_HIT_SMALLER; result.mThumbnail = thumbnail; result.mSize = otherSize; return result; } } } // Cache miss. return result; } public void putThumbnail(Uri uri, Point size, Bitmap thumbnail) { Pair<Uri, Point> cacheKey = Pair.create(uri, size); TreeMap<Point, Pair<Uri, Point>> sizeMap; synchronized (mSizeIndex) { sizeMap = mSizeIndex.get(uri); if (sizeMap == null) { sizeMap = new TreeMap<>(SIZE_COMPARATOR); mSizeIndex.put(uri, sizeMap); } } mCache.put(cacheKey, thumbnail); synchronized (sizeMap) { sizeMap.put(size, cacheKey); } } public void onTrimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { synchronized (mSizeIndex) { mSizeIndex.clear(); } mCache.evictAll(); } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { mCache.trimToSize(mCache.size() / 2); } } /** * A class that holds thumbnail and cache status. */ public static final class Result { @Retention(RetentionPolicy.SOURCE) @IntDef({CACHE_MISS, CACHE_HIT_EXACT, CACHE_HIT_SMALLER, CACHE_HIT_LARGER}) @interface Status {} /** * Indicates there is no thumbnail for the requested uri. The thumbnail will be null. */ public static final int CACHE_MISS = 0; /** * Indicates the thumbnail matches the requested size and requested uri. */ public static final int CACHE_HIT_EXACT = 1; /** * Indicates the thumbnail is in a smaller size than the requested one from the requested * uri. */ public static final int CACHE_HIT_SMALLER = 2; /** * Indicates the thumbnail is in a larger size than the requested one from the requested * uri. */ public static final int CACHE_HIT_LARGER = 3; private static final Pools.SimplePool<Result> sPool = new Pools.SimplePool<>(1); private @Status int mStatus; private @Nullable Bitmap mThumbnail; private @Nullable Point mSize; private static Result obtain(@Status int status, @Nullable Bitmap thumbnail, @Nullable Point size) { Result instance = sPool.acquire(); instance = (instance != null ? instance : new Result()); instance.mStatus = status; instance.mThumbnail = thumbnail; instance.mSize = size; return instance; } private Result() { } public void recycle() { mStatus = -1; mThumbnail = null; mSize = null; boolean released = sPool.release(this); // This assert is used to guarantee we won't generate too many instances that can't be // held in the pool, which indicates our pool size is too small. // // Right now one instance is enough because we expect all instances are only used in // main thread. assert (released); } public @Status int getStatus() { return mStatus; } public @Nullable Bitmap getThumbnail() { return mThumbnail; } public @Nullable Point getSize() { return mSize; } public boolean isHit() { return (mStatus != CACHE_MISS); } public boolean isExactHit() { return (mStatus == CACHE_HIT_EXACT); } } private static final class Cache extends LruCache<Pair<Uri, Point>, Bitmap> { private Cache(int maxSizeBytes) { super(maxSizeBytes); } @Override protected int sizeOf(Uri key, Bitmap value) { protected int sizeOf(Pair<Uri, Point> key, Bitmap value) { return value.getByteCount(); } } private static final class SizeComparator implements Comparator<Point> { @Override public int compare(Point size0, Point size1) { // Assume all sizes are roughly square, so we only compare them in one dimension. return size0.x - size1.x; } } } packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java +1 −2 Original line number Diff line number Diff line Loading @@ -135,8 +135,7 @@ final class GridDocumentHolder extends DocumentHolder { mIconThumb.setAlpha(0f); final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMimeLg, mIconMimeSm); mIconHelper.load(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMimeLg, mIconMimeSm); if (mHideTitles) { mTitle.setVisibility(View.GONE); Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/IconHelper.java +77 −41 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.support.annotation.Nullable; import android.util.Log; import android.view.View; import android.widget.ImageView; import com.android.documentsui.DocumentsApplication; Loading @@ -45,21 +46,33 @@ import com.android.documentsui.R; import com.android.documentsui.State; import com.android.documentsui.State.ViewMode; import com.android.documentsui.ThumbnailCache; import com.android.documentsui.ThumbnailCache.Result; import java.util.function.BiConsumer; /** * A class to assist with loading and managing the Images (i.e. thumbnails and icons) associated * with items in the directory listing. */ public class IconHelper { private static String TAG = "IconHelper"; private static final String TAG = "IconHelper"; // Two animations applied to image views. The first is used to switch mime icon and thumbnail. // The second is used when we need to update thumbnail. private static final BiConsumer<View, View> ANIM_FADE_IN = (mime, thumb) -> { float alpha = mime.getAlpha(); mime.animate().alpha(0f).start(); thumb.setAlpha(0f); thumb.animate().alpha(alpha).start(); }; private static final BiConsumer<View, View> ANIM_NO_OP = (mime, thumb) -> {}; private final Context mContext; private final ThumbnailCache mThumbnailCache; // Updated when icon size is set. private ThumbnailCache mCache; private Point mThumbSize; // The display mode (MODE_GRID, MODE_LIST, etc). private int mMode; private Point mCurrentSize; private boolean mThumbnailsEnabled = true; /** Loading @@ -69,7 +82,7 @@ public class IconHelper { public IconHelper(Context context, int mode) { mContext = context; setViewMode(mode); mCache = DocumentsApplication.getThumbnailsCache(context, mThumbSize); mThumbnailCache = DocumentsApplication.getThumbnailCache(context); } /** Loading @@ -84,13 +97,13 @@ public class IconHelper { /** * Sets the current display mode. This affects the thumbnail sizes that are loaded. * * @param mode See {@link State.MODE_LIST} and {@link State.MODE_GRID}. */ public void setViewMode(@ViewMode int mode) { mMode = mode; int thumbSize = getThumbSize(mode); mThumbSize = new Point(thumbSize, thumbSize); mCache = DocumentsApplication.getThumbnailsCache(mContext, mThumbSize); mCurrentSize = new Point(thumbSize, thumbSize); } private int getThumbSize(int mode) { Loading @@ -111,6 +124,7 @@ public class IconHelper { /** * Cancels any ongoing load operations associated with the given ImageView. * * @param icon */ public void stopLoading(ImageView icon) { Loading @@ -129,14 +143,19 @@ public class IconHelper { private final ImageView mIconMime; private final ImageView mIconThumb; private final Point mThumbSize; // A callback to apply animation to image views after the thumbnail is loaded. private final BiConsumer<View, View> mImageAnimator; private final CancellationSignal mSignal; public LoaderTask(Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize) { Point thumbSize, BiConsumer<View, View> animator) { mUri = uri; mIconMime = iconMime; mIconThumb = iconThumb; mThumbSize = thumbSize; mImageAnimator = animator; mSignal = new CancellationSignal(); if (DEBUG) Log.d(TAG, "Starting icon loader task for " + mUri); } Loading @@ -150,8 +169,9 @@ public class IconHelper { @Override protected Bitmap doInBackground(Uri... params) { if (isCancelled()) if (isCancelled()) { return null; } final Context context = mIconThumb.getContext(); final ContentResolver resolver = context.getContentResolver(); Loading @@ -163,9 +183,8 @@ public class IconHelper { resolver, mUri.getAuthority()); result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal); if (result != null) { final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( context, mThumbSize); thumbs.put(mUri, result); final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context); cache.putThumbnail(mUri, mThumbSize, result); } } catch (Exception e) { if (!(e instanceof OperationCanceledException)) { Loading @@ -185,16 +204,14 @@ public class IconHelper { mIconThumb.setTag(null); mIconThumb.setImageBitmap(result); float alpha = mIconMime.getAlpha(); mIconMime.animate().alpha(0f).start(); mIconThumb.setAlpha(0f); mIconThumb.animate().alpha(alpha).start(); mImageAnimator.accept(mIconMime, mIconThumb); } } } /** * Load thumbnails for a directory list item. * * @param uri The URI for the file being represented. * @param mimeType The mime type of the file being represented. * @param docFlags Flags for the file being represented. Loading @@ -204,9 +221,9 @@ public class IconHelper { * @param subIconMime The second itemview's mime icon. Always visible. * @return */ public void loadThumbnail(Uri uri, String mimeType, int docFlags, int docIcon, public void load(Uri uri, String mimeType, int docFlags, int docIcon, ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime) { boolean cacheHit = false; boolean loadedThumbnail = false; final String docAuthority = uri.getAuthority(); Loading @@ -215,39 +232,59 @@ public class IconHelper { || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mimeType); final boolean showThumbnail = supportsThumbnail && allowThumbnail && mThumbnailsEnabled; if (showThumbnail) { final Bitmap cachedResult = mCache.get(uri); if (cachedResult != null) { iconThumb.setImageBitmap(cachedResult); cacheHit = true; } else { iconThumb.setImageDrawable(null); final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mThumbSize); iconThumb.setTag(task); ProviderExecutor.forAuthority(docAuthority).execute(task); } loadedThumbnail = loadThumbnail(uri, docAuthority, iconThumb, iconMime); } final Drawable icon = getDocumentIcon(mContext, docAuthority, final Drawable mimeIcon = getDocumentIcon(mContext, docAuthority, DocumentsContract.getDocumentId(uri), mimeType, docIcon); if (subIconMime != null) { subIconMime.setImageDrawable(icon); setMimeIcon(subIconMime, mimeIcon); } if (cacheHit) { iconMime.setImageDrawable(null); iconMime.setAlpha(0f); iconThumb.setAlpha(1f); if (loadedThumbnail) { hideImageView(iconMime); } else { // Add a mime icon if the thumbnail is being loaded in the background. iconThumb.setImageDrawable(null); iconMime.setImageDrawable(icon); iconMime.setAlpha(1f); iconThumb.setAlpha(0f); // Add a mime icon if the thumbnail is not shown. setMimeIcon(iconMime, mimeIcon); hideImageView(iconThumb); } } private boolean loadThumbnail(Uri uri, String docAuthority, ImageView iconThumb, ImageView iconMime) { final Result result = mThumbnailCache.getThumbnail(uri, mCurrentSize); final Bitmap cachedThumbnail = result.getThumbnail(); iconThumb.setImageBitmap(cachedThumbnail); if (!result.isExactHit()) { final BiConsumer<View, View> animator = (cachedThumbnail == null ? ANIM_FADE_IN : ANIM_NO_OP); final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mCurrentSize, animator); iconThumb.setTag(task); ProviderExecutor.forAuthority(docAuthority).execute(task); } result.recycle(); return result.isHit(); } private void setMimeIcon(ImageView view, Drawable icon) { view.setImageDrawable(icon); view.setAlpha(1f); } private void hideImageView(ImageView view) { view.setImageDrawable(null); view.setAlpha(0f); } /** * Gets a mime icon or package icon for a file. * * @param context * @param authority The authority string of the file. * @param id The document ID of the file. Loading @@ -263,5 +300,4 @@ public class IconHelper { return IconUtils.loadMimeIcon(context, mimeType, authority, id, mMode); } } } packages/DocumentsUI/src/com/android/documentsui/dirlist/ListDocumentHolder.java +1 −1 Original line number Diff line number Diff line Loading @@ -133,7 +133,7 @@ final class ListDocumentHolder extends DocumentHolder { mIconThumb.setAlpha(0f); final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime, null); mIconHelper.load(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime, null); mTitle.setText(docDisplayName, TextView.BufferType.SPANNABLE); mTitle.setVisibility(View.VISIBLE); Loading Loading
packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java +6 −16 Original line number Diff line number Diff line Loading @@ -24,7 +24,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Point; import android.net.Uri; import android.os.RemoteException; import android.text.format.DateUtils; Loading @@ -33,21 +32,16 @@ public class DocumentsApplication extends Application { private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS; private RootsCache mRoots; private Point mThumbnailsSize; private ThumbnailCache mThumbnails; private ThumbnailCache mThumbnailCache; public static RootsCache getRootsCache(Context context) { return ((DocumentsApplication) context.getApplicationContext()).mRoots; } public static ThumbnailCache getThumbnailsCache(Context context, Point size) { public static ThumbnailCache getThumbnailCache(Context context) { final DocumentsApplication app = (DocumentsApplication) context.getApplicationContext(); final ThumbnailCache thumbnails = app.mThumbnails; if (!size.equals(app.mThumbnailsSize)) { thumbnails.evictAll(); app.mThumbnailsSize = size; } return thumbnails; return app.mThumbnailCache; } public static ContentProviderClient acquireUnstableProviderOrThrow( Loading @@ -71,7 +65,7 @@ public class DocumentsApplication extends Application { mRoots = new RootsCache(this); mRoots.updateAsync(false); mThumbnails = new ThumbnailCache(memoryClassBytes / 4); mThumbnailCache = new ThumbnailCache(memoryClassBytes / 4); final IntentFilter packageFilter = new IntentFilter(); packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); Loading @@ -90,11 +84,7 @@ public class DocumentsApplication extends Application { public void onTrimMemory(int level) { super.onTrimMemory(level); if (level >= TRIM_MEMORY_MODERATE) { mThumbnails.evictAll(); } else if (level >= TRIM_MEMORY_BACKGROUND) { mThumbnails.trimToSize(mThumbnails.size() / 2); } mThumbnailCache.onTrimMemory(level); } private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() { Loading
packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java +233 −7 Original line number Diff line number Diff line /* * Copyright (C) 2013 The Android Open Source Project * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. Loading @@ -16,17 +16,243 @@ package com.android.documentsui; import android.annotation.IntDef; import android.annotation.Nullable; import android.content.ComponentCallbacks2; import android.graphics.Bitmap; import android.graphics.Point; import android.net.Uri; import android.util.LruCache; import android.util.Pair; import android.util.Pools; public class ThumbnailCache extends LruCache<Uri, Bitmap> { public ThumbnailCache(int maxSizeBytes) { import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Comparator; import java.util.HashMap; import java.util.TreeMap; /** * An LRU cache that supports finding the thumbnail of the requested uri with a different size than * the requested one. */ public class ThumbnailCache { private static final SizeComparator SIZE_COMPARATOR = new SizeComparator(); /** * A 2-dimensional index into {@link #mCache} entries. Pair<Uri, Point> is the key to * {@link #mCache}. TreeMap is used to search the closest size to a given size and a given uri. */ private final HashMap<Uri, TreeMap<Point, Pair<Uri, Point>>> mSizeIndex; private final Cache mCache; /** * Creates a thumbnail LRU cache. * * @param maxCacheSizeInBytes the maximum size of thumbnails in bytes this cache can hold. */ public ThumbnailCache(int maxCacheSizeInBytes) { mSizeIndex = new HashMap<>(); mCache = new Cache(maxCacheSizeInBytes); } /** * Obtains thumbnail given a uri and a size. * * @param uri the uri of the thumbnail in need * @param size the desired size of the thumbnail * @return the thumbnail result */ public Result getThumbnail(Uri uri, Point size) { Result result = Result.obtain(Result.CACHE_MISS, null, null); TreeMap<Point, Pair<Uri, Point>> sizeMap; sizeMap = mSizeIndex.get(uri); if (sizeMap == null || sizeMap.isEmpty()) { // There is not any thumbnail for this uri. return result; } // Look for thumbnail of the same size. Pair<Uri, Point> cacheKey = sizeMap.get(size); if (cacheKey != null) { Bitmap thumbnail = mCache.get(cacheKey); if (thumbnail != null) { result.mStatus = Result.CACHE_HIT_EXACT; result.mThumbnail = thumbnail; result.mSize = size; return result; } } // Look for thumbnail of bigger sizes. Point otherSize = sizeMap.higherKey(size); if (otherSize != null) { cacheKey = sizeMap.get(otherSize); if (cacheKey != null) { Bitmap thumbnail = mCache.get(cacheKey); if (thumbnail != null) { result.mStatus = Result.CACHE_HIT_LARGER; result.mThumbnail = thumbnail; result.mSize = otherSize; return result; } } } // Look for thumbnail of smaller sizes. otherSize = sizeMap.lowerKey(size); if (otherSize != null) { cacheKey = sizeMap.get(otherSize); if (cacheKey != null) { Bitmap thumbnail = mCache.get(cacheKey); if (thumbnail != null) { result.mStatus = Result.CACHE_HIT_SMALLER; result.mThumbnail = thumbnail; result.mSize = otherSize; return result; } } } // Cache miss. return result; } public void putThumbnail(Uri uri, Point size, Bitmap thumbnail) { Pair<Uri, Point> cacheKey = Pair.create(uri, size); TreeMap<Point, Pair<Uri, Point>> sizeMap; synchronized (mSizeIndex) { sizeMap = mSizeIndex.get(uri); if (sizeMap == null) { sizeMap = new TreeMap<>(SIZE_COMPARATOR); mSizeIndex.put(uri, sizeMap); } } mCache.put(cacheKey, thumbnail); synchronized (sizeMap) { sizeMap.put(size, cacheKey); } } public void onTrimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { synchronized (mSizeIndex) { mSizeIndex.clear(); } mCache.evictAll(); } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { mCache.trimToSize(mCache.size() / 2); } } /** * A class that holds thumbnail and cache status. */ public static final class Result { @Retention(RetentionPolicy.SOURCE) @IntDef({CACHE_MISS, CACHE_HIT_EXACT, CACHE_HIT_SMALLER, CACHE_HIT_LARGER}) @interface Status {} /** * Indicates there is no thumbnail for the requested uri. The thumbnail will be null. */ public static final int CACHE_MISS = 0; /** * Indicates the thumbnail matches the requested size and requested uri. */ public static final int CACHE_HIT_EXACT = 1; /** * Indicates the thumbnail is in a smaller size than the requested one from the requested * uri. */ public static final int CACHE_HIT_SMALLER = 2; /** * Indicates the thumbnail is in a larger size than the requested one from the requested * uri. */ public static final int CACHE_HIT_LARGER = 3; private static final Pools.SimplePool<Result> sPool = new Pools.SimplePool<>(1); private @Status int mStatus; private @Nullable Bitmap mThumbnail; private @Nullable Point mSize; private static Result obtain(@Status int status, @Nullable Bitmap thumbnail, @Nullable Point size) { Result instance = sPool.acquire(); instance = (instance != null ? instance : new Result()); instance.mStatus = status; instance.mThumbnail = thumbnail; instance.mSize = size; return instance; } private Result() { } public void recycle() { mStatus = -1; mThumbnail = null; mSize = null; boolean released = sPool.release(this); // This assert is used to guarantee we won't generate too many instances that can't be // held in the pool, which indicates our pool size is too small. // // Right now one instance is enough because we expect all instances are only used in // main thread. assert (released); } public @Status int getStatus() { return mStatus; } public @Nullable Bitmap getThumbnail() { return mThumbnail; } public @Nullable Point getSize() { return mSize; } public boolean isHit() { return (mStatus != CACHE_MISS); } public boolean isExactHit() { return (mStatus == CACHE_HIT_EXACT); } } private static final class Cache extends LruCache<Pair<Uri, Point>, Bitmap> { private Cache(int maxSizeBytes) { super(maxSizeBytes); } @Override protected int sizeOf(Uri key, Bitmap value) { protected int sizeOf(Pair<Uri, Point> key, Bitmap value) { return value.getByteCount(); } } private static final class SizeComparator implements Comparator<Point> { @Override public int compare(Point size0, Point size1) { // Assume all sizes are roughly square, so we only compare them in one dimension. return size0.x - size1.x; } } }
packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java +1 −2 Original line number Diff line number Diff line Loading @@ -135,8 +135,7 @@ final class GridDocumentHolder extends DocumentHolder { mIconThumb.setAlpha(0f); final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMimeLg, mIconMimeSm); mIconHelper.load(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMimeLg, mIconMimeSm); if (mHideTitles) { mTitle.setVisibility(View.GONE); Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/IconHelper.java +77 −41 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.support.annotation.Nullable; import android.util.Log; import android.view.View; import android.widget.ImageView; import com.android.documentsui.DocumentsApplication; Loading @@ -45,21 +46,33 @@ import com.android.documentsui.R; import com.android.documentsui.State; import com.android.documentsui.State.ViewMode; import com.android.documentsui.ThumbnailCache; import com.android.documentsui.ThumbnailCache.Result; import java.util.function.BiConsumer; /** * A class to assist with loading and managing the Images (i.e. thumbnails and icons) associated * with items in the directory listing. */ public class IconHelper { private static String TAG = "IconHelper"; private static final String TAG = "IconHelper"; // Two animations applied to image views. The first is used to switch mime icon and thumbnail. // The second is used when we need to update thumbnail. private static final BiConsumer<View, View> ANIM_FADE_IN = (mime, thumb) -> { float alpha = mime.getAlpha(); mime.animate().alpha(0f).start(); thumb.setAlpha(0f); thumb.animate().alpha(alpha).start(); }; private static final BiConsumer<View, View> ANIM_NO_OP = (mime, thumb) -> {}; private final Context mContext; private final ThumbnailCache mThumbnailCache; // Updated when icon size is set. private ThumbnailCache mCache; private Point mThumbSize; // The display mode (MODE_GRID, MODE_LIST, etc). private int mMode; private Point mCurrentSize; private boolean mThumbnailsEnabled = true; /** Loading @@ -69,7 +82,7 @@ public class IconHelper { public IconHelper(Context context, int mode) { mContext = context; setViewMode(mode); mCache = DocumentsApplication.getThumbnailsCache(context, mThumbSize); mThumbnailCache = DocumentsApplication.getThumbnailCache(context); } /** Loading @@ -84,13 +97,13 @@ public class IconHelper { /** * Sets the current display mode. This affects the thumbnail sizes that are loaded. * * @param mode See {@link State.MODE_LIST} and {@link State.MODE_GRID}. */ public void setViewMode(@ViewMode int mode) { mMode = mode; int thumbSize = getThumbSize(mode); mThumbSize = new Point(thumbSize, thumbSize); mCache = DocumentsApplication.getThumbnailsCache(mContext, mThumbSize); mCurrentSize = new Point(thumbSize, thumbSize); } private int getThumbSize(int mode) { Loading @@ -111,6 +124,7 @@ public class IconHelper { /** * Cancels any ongoing load operations associated with the given ImageView. * * @param icon */ public void stopLoading(ImageView icon) { Loading @@ -129,14 +143,19 @@ public class IconHelper { private final ImageView mIconMime; private final ImageView mIconThumb; private final Point mThumbSize; // A callback to apply animation to image views after the thumbnail is loaded. private final BiConsumer<View, View> mImageAnimator; private final CancellationSignal mSignal; public LoaderTask(Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize) { Point thumbSize, BiConsumer<View, View> animator) { mUri = uri; mIconMime = iconMime; mIconThumb = iconThumb; mThumbSize = thumbSize; mImageAnimator = animator; mSignal = new CancellationSignal(); if (DEBUG) Log.d(TAG, "Starting icon loader task for " + mUri); } Loading @@ -150,8 +169,9 @@ public class IconHelper { @Override protected Bitmap doInBackground(Uri... params) { if (isCancelled()) if (isCancelled()) { return null; } final Context context = mIconThumb.getContext(); final ContentResolver resolver = context.getContentResolver(); Loading @@ -163,9 +183,8 @@ public class IconHelper { resolver, mUri.getAuthority()); result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal); if (result != null) { final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( context, mThumbSize); thumbs.put(mUri, result); final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context); cache.putThumbnail(mUri, mThumbSize, result); } } catch (Exception e) { if (!(e instanceof OperationCanceledException)) { Loading @@ -185,16 +204,14 @@ public class IconHelper { mIconThumb.setTag(null); mIconThumb.setImageBitmap(result); float alpha = mIconMime.getAlpha(); mIconMime.animate().alpha(0f).start(); mIconThumb.setAlpha(0f); mIconThumb.animate().alpha(alpha).start(); mImageAnimator.accept(mIconMime, mIconThumb); } } } /** * Load thumbnails for a directory list item. * * @param uri The URI for the file being represented. * @param mimeType The mime type of the file being represented. * @param docFlags Flags for the file being represented. Loading @@ -204,9 +221,9 @@ public class IconHelper { * @param subIconMime The second itemview's mime icon. Always visible. * @return */ public void loadThumbnail(Uri uri, String mimeType, int docFlags, int docIcon, public void load(Uri uri, String mimeType, int docFlags, int docIcon, ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime) { boolean cacheHit = false; boolean loadedThumbnail = false; final String docAuthority = uri.getAuthority(); Loading @@ -215,39 +232,59 @@ public class IconHelper { || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mimeType); final boolean showThumbnail = supportsThumbnail && allowThumbnail && mThumbnailsEnabled; if (showThumbnail) { final Bitmap cachedResult = mCache.get(uri); if (cachedResult != null) { iconThumb.setImageBitmap(cachedResult); cacheHit = true; } else { iconThumb.setImageDrawable(null); final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mThumbSize); iconThumb.setTag(task); ProviderExecutor.forAuthority(docAuthority).execute(task); } loadedThumbnail = loadThumbnail(uri, docAuthority, iconThumb, iconMime); } final Drawable icon = getDocumentIcon(mContext, docAuthority, final Drawable mimeIcon = getDocumentIcon(mContext, docAuthority, DocumentsContract.getDocumentId(uri), mimeType, docIcon); if (subIconMime != null) { subIconMime.setImageDrawable(icon); setMimeIcon(subIconMime, mimeIcon); } if (cacheHit) { iconMime.setImageDrawable(null); iconMime.setAlpha(0f); iconThumb.setAlpha(1f); if (loadedThumbnail) { hideImageView(iconMime); } else { // Add a mime icon if the thumbnail is being loaded in the background. iconThumb.setImageDrawable(null); iconMime.setImageDrawable(icon); iconMime.setAlpha(1f); iconThumb.setAlpha(0f); // Add a mime icon if the thumbnail is not shown. setMimeIcon(iconMime, mimeIcon); hideImageView(iconThumb); } } private boolean loadThumbnail(Uri uri, String docAuthority, ImageView iconThumb, ImageView iconMime) { final Result result = mThumbnailCache.getThumbnail(uri, mCurrentSize); final Bitmap cachedThumbnail = result.getThumbnail(); iconThumb.setImageBitmap(cachedThumbnail); if (!result.isExactHit()) { final BiConsumer<View, View> animator = (cachedThumbnail == null ? ANIM_FADE_IN : ANIM_NO_OP); final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mCurrentSize, animator); iconThumb.setTag(task); ProviderExecutor.forAuthority(docAuthority).execute(task); } result.recycle(); return result.isHit(); } private void setMimeIcon(ImageView view, Drawable icon) { view.setImageDrawable(icon); view.setAlpha(1f); } private void hideImageView(ImageView view) { view.setImageDrawable(null); view.setAlpha(0f); } /** * Gets a mime icon or package icon for a file. * * @param context * @param authority The authority string of the file. * @param id The document ID of the file. Loading @@ -263,5 +300,4 @@ public class IconHelper { return IconUtils.loadMimeIcon(context, mimeType, authority, id, mMode); } } }
packages/DocumentsUI/src/com/android/documentsui/dirlist/ListDocumentHolder.java +1 −1 Original line number Diff line number Diff line Loading @@ -133,7 +133,7 @@ final class ListDocumentHolder extends DocumentHolder { mIconThumb.setAlpha(0f); final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime, null); mIconHelper.load(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime, null); mTitle.setText(docDisplayName, TextView.BufferType.SPANNABLE); mTitle.setVisibility(View.VISIBLE); Loading