Loading packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java +66 −36 Original line number Diff line number Diff line Loading @@ -65,24 +65,19 @@ public class ThumbnailCache { * @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; return Result.obtainMiss(); } // 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; Entry entry = mCache.get(cacheKey); if (entry != null) { return Result.obtain(Result.CACHE_HIT_EXACT, size, entry); } } Loading @@ -92,12 +87,9 @@ public class ThumbnailCache { 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; Entry entry = mCache.get(cacheKey); if (entry != null) { return Result.obtain(Result.CACHE_HIT_LARGER, otherSize, entry); } } } Loading @@ -108,21 +100,18 @@ public class ThumbnailCache { 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; Entry entry = mCache.get(cacheKey); if (entry != null) { return Result.obtain(Result.CACHE_HIT_SMALLER, otherSize, entry); } } } // Cache miss. return result; return Result.obtainMiss(); } public void putThumbnail(Uri uri, Point size, Bitmap thumbnail) { public void putThumbnail(Uri uri, Point size, Bitmap thumbnail, long lastModified) { Pair<Uri, Point> cacheKey = Pair.create(uri, size); TreeMap<Point, Pair<Uri, Point>> sizeMap; Loading @@ -134,17 +123,28 @@ public class ThumbnailCache { } } mCache.put(cacheKey, thumbnail); Entry entry = new Entry(thumbnail, lastModified); mCache.put(cacheKey, entry); synchronized (sizeMap) { sizeMap.put(size, cacheKey); } } public void onTrimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { private void removeKey(Uri uri, Point size) { TreeMap<Point, Pair<Uri, Point>> sizeMap; synchronized (mSizeIndex) { mSizeIndex.clear(); sizeMap = mSizeIndex.get(uri); } // LruCache tells us to remove a key, which should exist, so sizeMap can't be null. assert (sizeMap != null); synchronized (sizeMap) { sizeMap.remove(size); } } public void onTrimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { mCache.evictAll(); } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { mCache.trimToSize(mCache.size() / 2); Loading @@ -159,7 +159,6 @@ public class ThumbnailCache { @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. */ Loading @@ -182,30 +181,38 @@ public class ThumbnailCache { private static final Pools.SimplePool<Result> sPool = new Pools.SimplePool<>(1); private @Status int mStatus; private @Nullable Bitmap mThumbnail; private @Nullable Point mSize; private long mLastModified; private static Result obtainMiss() { return obtain(CACHE_MISS, null, null, 0); } private static Result obtain(@Status int status, Point size, Entry entry) { return obtain(status, entry.mThumbnail, size, entry.mLastModified); } private static Result obtain(@Status int status, @Nullable Bitmap thumbnail, @Nullable Point size) { @Nullable Point size, long lastModified) { Result instance = sPool.acquire(); instance = (instance != null ? instance : new Result()); instance.mStatus = status; instance.mThumbnail = thumbnail; instance.mSize = size; instance.mLastModified = lastModified; return instance; } private Result() { } private Result() {} public void recycle() { mStatus = -1; mThumbnail = null; mSize = null; mLastModified = -1; boolean released = sPool.release(this); // This assert is used to guarantee we won't generate too many instances that can't be Loading @@ -228,6 +235,10 @@ public class ThumbnailCache { return mSize; } public long getLastModified() { return mLastModified; } public boolean isHit() { return (mStatus != CACHE_MISS); } Loading @@ -237,14 +248,33 @@ public class ThumbnailCache { } } private static final class Cache extends LruCache<Pair<Uri, Point>, Bitmap> { private static final class Entry { private final Bitmap mThumbnail; private final long mLastModified; private Entry(Bitmap thumbnail, long lastModified) { mThumbnail = thumbnail; mLastModified = lastModified; } } private final class Cache extends LruCache<Pair<Uri, Point>, Entry> { private Cache(int maxSizeBytes) { super(maxSizeBytes); } @Override protected int sizeOf(Pair<Uri, Point> key, Bitmap value) { return value.getByteCount(); protected int sizeOf(Pair<Uri, Point> key, Entry value) { return value.mThumbnail.getByteCount(); } @Override protected void entryRemoved( boolean evicted, Pair<Uri, Point> key, Entry oldValue, Entry newValue) { if (newValue == null) { removeKey(key.first, key.second); } } } Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java +2 −1 Original line number Diff line number Diff line Loading @@ -135,7 +135,8 @@ final class GridDocumentHolder extends DocumentHolder { mIconThumb.setAlpha(0f); final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); mIconHelper.load(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMimeLg, mIconMimeSm); mIconHelper.load(uri, docMimeType, docFlags, docIcon, docLastModified, mIconThumb, mIconMimeLg, mIconMimeSm); if (mHideTitles) { mTitle.setVisibility(View.GONE); Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/IconHelper.java +16 −9 Original line number Diff line number Diff line Loading @@ -143,6 +143,7 @@ public class IconHelper { private final ImageView mIconMime; private final ImageView mIconThumb; private final Point mThumbSize; private final long mLastModified; // A callback to apply animation to image views after the thumbnail is loaded. private final BiConsumer<View, View> mImageAnimator; Loading @@ -150,12 +151,13 @@ public class IconHelper { private final CancellationSignal mSignal; public LoaderTask(Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize, BiConsumer<View, View> animator) { Point thumbSize, long lastModified, BiConsumer<View, View> animator) { mUri = uri; mIconMime = iconMime; mIconThumb = iconThumb; mThumbSize = thumbSize; mImageAnimator = animator; mLastModified = lastModified; mSignal = new CancellationSignal(); if (DEBUG) Log.d(TAG, "Starting icon loader task for " + mUri); } Loading Loading @@ -184,7 +186,7 @@ public class IconHelper { result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal); if (result != null) { final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context); cache.putThumbnail(mUri, mThumbSize, result); cache.putThumbnail(mUri, mThumbSize, result, mLastModified); } } catch (Exception e) { if (!(e instanceof OperationCanceledException)) { Loading Loading @@ -216,12 +218,13 @@ public class IconHelper { * @param mimeType The mime type of the file being represented. * @param docFlags Flags for the file being represented. * @param docIcon Custom icon (if any) for the file being requested. * @param docLastModified the last modified value of the file being requested. * @param iconThumb The itemview's thumbnail icon. * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown. * @param subIconMime The second itemview's mime icon. Always visible. * @return */ public void load(Uri uri, String mimeType, int docFlags, int docIcon, public void load(Uri uri, String mimeType, int docFlags, int docIcon, long docLastModified, ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime) { boolean loadedThumbnail = false; Loading @@ -232,7 +235,8 @@ public class IconHelper { || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mimeType); final boolean showThumbnail = supportsThumbnail && allowThumbnail && mThumbnailsEnabled; if (showThumbnail) { loadedThumbnail = loadThumbnail(uri, docAuthority, iconThumb, iconMime); loadedThumbnail = loadThumbnail(uri, docAuthority, docLastModified, iconThumb, iconMime); } final Drawable mimeIcon = getDocumentIcon(mContext, docAuthority, Loading @@ -250,18 +254,21 @@ public class IconHelper { } } private boolean loadThumbnail(Uri uri, String docAuthority, ImageView iconThumb, ImageView iconMime) { private boolean loadThumbnail(Uri uri, String docAuthority, long docLastModified, ImageView iconThumb, ImageView iconMime) { final Result result = mThumbnailCache.getThumbnail(uri, mCurrentSize); final Bitmap cachedThumbnail = result.getThumbnail(); iconThumb.setImageBitmap(cachedThumbnail); if (!result.isExactHit()) { boolean stale = (docLastModified > result.getLastModified()); if (DEBUG) Log.d(TAG, String.format("Load thumbnail for %s, got result %d and stale %b.", uri.toString(), result.getStatus(), stale)); if (!result.isExactHit() || stale) { final BiConsumer<View, View> animator = (cachedThumbnail == null ? ANIM_FADE_IN : ANIM_NO_OP); final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mCurrentSize, animator); final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mCurrentSize, docLastModified, animator); iconThumb.setTag(task); Loading packages/DocumentsUI/src/com/android/documentsui/dirlist/ListDocumentHolder.java +2 −1 Original line number Diff line number Diff line Loading @@ -133,7 +133,8 @@ final class ListDocumentHolder extends DocumentHolder { mIconThumb.setAlpha(0f); final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); mIconHelper.load(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime, null); mIconHelper.load(uri, docMimeType, docFlags, docIcon, docLastModified, mIconThumb, mIconMime, null); mTitle.setText(docDisplayName, TextView.BufferType.SPANNABLE); mTitle.setVisibility(View.VISIBLE); Loading packages/DocumentsUI/tests/src/com/android/documentsui/ItemDragListenerTest.java +2 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,7 @@ import android.view.View; import com.android.documentsui.testing.ClipDatas; import com.android.documentsui.testing.DragEvents; import com.android.documentsui.testing.TestTimer; import com.android.documentsui.testing.TestViews; import com.android.documentsui.testing.Views; import org.junit.Before; import org.junit.Test; Loading @@ -53,7 +53,7 @@ public class ItemDragListenerTest { @Before public void setUp() { mTestView = TestViews.createTestView(); mTestView = Views.createTestView(); mTestTimer = new TestTimer(); mTestDragHost = new TestDragHost(); Loading Loading
packages/DocumentsUI/src/com/android/documentsui/ThumbnailCache.java +66 −36 Original line number Diff line number Diff line Loading @@ -65,24 +65,19 @@ public class ThumbnailCache { * @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; return Result.obtainMiss(); } // 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; Entry entry = mCache.get(cacheKey); if (entry != null) { return Result.obtain(Result.CACHE_HIT_EXACT, size, entry); } } Loading @@ -92,12 +87,9 @@ public class ThumbnailCache { 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; Entry entry = mCache.get(cacheKey); if (entry != null) { return Result.obtain(Result.CACHE_HIT_LARGER, otherSize, entry); } } } Loading @@ -108,21 +100,18 @@ public class ThumbnailCache { 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; Entry entry = mCache.get(cacheKey); if (entry != null) { return Result.obtain(Result.CACHE_HIT_SMALLER, otherSize, entry); } } } // Cache miss. return result; return Result.obtainMiss(); } public void putThumbnail(Uri uri, Point size, Bitmap thumbnail) { public void putThumbnail(Uri uri, Point size, Bitmap thumbnail, long lastModified) { Pair<Uri, Point> cacheKey = Pair.create(uri, size); TreeMap<Point, Pair<Uri, Point>> sizeMap; Loading @@ -134,17 +123,28 @@ public class ThumbnailCache { } } mCache.put(cacheKey, thumbnail); Entry entry = new Entry(thumbnail, lastModified); mCache.put(cacheKey, entry); synchronized (sizeMap) { sizeMap.put(size, cacheKey); } } public void onTrimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { private void removeKey(Uri uri, Point size) { TreeMap<Point, Pair<Uri, Point>> sizeMap; synchronized (mSizeIndex) { mSizeIndex.clear(); sizeMap = mSizeIndex.get(uri); } // LruCache tells us to remove a key, which should exist, so sizeMap can't be null. assert (sizeMap != null); synchronized (sizeMap) { sizeMap.remove(size); } } public void onTrimMemory(int level) { if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { mCache.evictAll(); } else if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { mCache.trimToSize(mCache.size() / 2); Loading @@ -159,7 +159,6 @@ public class ThumbnailCache { @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. */ Loading @@ -182,30 +181,38 @@ public class ThumbnailCache { private static final Pools.SimplePool<Result> sPool = new Pools.SimplePool<>(1); private @Status int mStatus; private @Nullable Bitmap mThumbnail; private @Nullable Point mSize; private long mLastModified; private static Result obtainMiss() { return obtain(CACHE_MISS, null, null, 0); } private static Result obtain(@Status int status, Point size, Entry entry) { return obtain(status, entry.mThumbnail, size, entry.mLastModified); } private static Result obtain(@Status int status, @Nullable Bitmap thumbnail, @Nullable Point size) { @Nullable Point size, long lastModified) { Result instance = sPool.acquire(); instance = (instance != null ? instance : new Result()); instance.mStatus = status; instance.mThumbnail = thumbnail; instance.mSize = size; instance.mLastModified = lastModified; return instance; } private Result() { } private Result() {} public void recycle() { mStatus = -1; mThumbnail = null; mSize = null; mLastModified = -1; boolean released = sPool.release(this); // This assert is used to guarantee we won't generate too many instances that can't be Loading @@ -228,6 +235,10 @@ public class ThumbnailCache { return mSize; } public long getLastModified() { return mLastModified; } public boolean isHit() { return (mStatus != CACHE_MISS); } Loading @@ -237,14 +248,33 @@ public class ThumbnailCache { } } private static final class Cache extends LruCache<Pair<Uri, Point>, Bitmap> { private static final class Entry { private final Bitmap mThumbnail; private final long mLastModified; private Entry(Bitmap thumbnail, long lastModified) { mThumbnail = thumbnail; mLastModified = lastModified; } } private final class Cache extends LruCache<Pair<Uri, Point>, Entry> { private Cache(int maxSizeBytes) { super(maxSizeBytes); } @Override protected int sizeOf(Pair<Uri, Point> key, Bitmap value) { return value.getByteCount(); protected int sizeOf(Pair<Uri, Point> key, Entry value) { return value.mThumbnail.getByteCount(); } @Override protected void entryRemoved( boolean evicted, Pair<Uri, Point> key, Entry oldValue, Entry newValue) { if (newValue == null) { removeKey(key.first, key.second); } } } Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java +2 −1 Original line number Diff line number Diff line Loading @@ -135,7 +135,8 @@ final class GridDocumentHolder extends DocumentHolder { mIconThumb.setAlpha(0f); final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); mIconHelper.load(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMimeLg, mIconMimeSm); mIconHelper.load(uri, docMimeType, docFlags, docIcon, docLastModified, mIconThumb, mIconMimeLg, mIconMimeSm); if (mHideTitles) { mTitle.setVisibility(View.GONE); Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/IconHelper.java +16 −9 Original line number Diff line number Diff line Loading @@ -143,6 +143,7 @@ public class IconHelper { private final ImageView mIconMime; private final ImageView mIconThumb; private final Point mThumbSize; private final long mLastModified; // A callback to apply animation to image views after the thumbnail is loaded. private final BiConsumer<View, View> mImageAnimator; Loading @@ -150,12 +151,13 @@ public class IconHelper { private final CancellationSignal mSignal; public LoaderTask(Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize, BiConsumer<View, View> animator) { Point thumbSize, long lastModified, BiConsumer<View, View> animator) { mUri = uri; mIconMime = iconMime; mIconThumb = iconThumb; mThumbSize = thumbSize; mImageAnimator = animator; mLastModified = lastModified; mSignal = new CancellationSignal(); if (DEBUG) Log.d(TAG, "Starting icon loader task for " + mUri); } Loading Loading @@ -184,7 +186,7 @@ public class IconHelper { result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal); if (result != null) { final ThumbnailCache cache = DocumentsApplication.getThumbnailCache(context); cache.putThumbnail(mUri, mThumbSize, result); cache.putThumbnail(mUri, mThumbSize, result, mLastModified); } } catch (Exception e) { if (!(e instanceof OperationCanceledException)) { Loading Loading @@ -216,12 +218,13 @@ public class IconHelper { * @param mimeType The mime type of the file being represented. * @param docFlags Flags for the file being represented. * @param docIcon Custom icon (if any) for the file being requested. * @param docLastModified the last modified value of the file being requested. * @param iconThumb The itemview's thumbnail icon. * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown. * @param subIconMime The second itemview's mime icon. Always visible. * @return */ public void load(Uri uri, String mimeType, int docFlags, int docIcon, public void load(Uri uri, String mimeType, int docFlags, int docIcon, long docLastModified, ImageView iconThumb, ImageView iconMime, @Nullable ImageView subIconMime) { boolean loadedThumbnail = false; Loading @@ -232,7 +235,8 @@ public class IconHelper { || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mimeType); final boolean showThumbnail = supportsThumbnail && allowThumbnail && mThumbnailsEnabled; if (showThumbnail) { loadedThumbnail = loadThumbnail(uri, docAuthority, iconThumb, iconMime); loadedThumbnail = loadThumbnail(uri, docAuthority, docLastModified, iconThumb, iconMime); } final Drawable mimeIcon = getDocumentIcon(mContext, docAuthority, Loading @@ -250,18 +254,21 @@ public class IconHelper { } } private boolean loadThumbnail(Uri uri, String docAuthority, ImageView iconThumb, ImageView iconMime) { private boolean loadThumbnail(Uri uri, String docAuthority, long docLastModified, ImageView iconThumb, ImageView iconMime) { final Result result = mThumbnailCache.getThumbnail(uri, mCurrentSize); final Bitmap cachedThumbnail = result.getThumbnail(); iconThumb.setImageBitmap(cachedThumbnail); if (!result.isExactHit()) { boolean stale = (docLastModified > result.getLastModified()); if (DEBUG) Log.d(TAG, String.format("Load thumbnail for %s, got result %d and stale %b.", uri.toString(), result.getStatus(), stale)); if (!result.isExactHit() || stale) { final BiConsumer<View, View> animator = (cachedThumbnail == null ? ANIM_FADE_IN : ANIM_NO_OP); final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mCurrentSize, animator); final LoaderTask task = new LoaderTask(uri, iconMime, iconThumb, mCurrentSize, docLastModified, animator); iconThumb.setTag(task); Loading
packages/DocumentsUI/src/com/android/documentsui/dirlist/ListDocumentHolder.java +2 −1 Original line number Diff line number Diff line Loading @@ -133,7 +133,8 @@ final class ListDocumentHolder extends DocumentHolder { mIconThumb.setAlpha(0f); final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); mIconHelper.load(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime, null); mIconHelper.load(uri, docMimeType, docFlags, docIcon, docLastModified, mIconThumb, mIconMime, null); mTitle.setText(docDisplayName, TextView.BufferType.SPANNABLE); mTitle.setVisibility(View.VISIBLE); Loading
packages/DocumentsUI/tests/src/com/android/documentsui/ItemDragListenerTest.java +2 −2 Original line number Diff line number Diff line Loading @@ -30,7 +30,7 @@ import android.view.View; import com.android.documentsui.testing.ClipDatas; import com.android.documentsui.testing.DragEvents; import com.android.documentsui.testing.TestTimer; import com.android.documentsui.testing.TestViews; import com.android.documentsui.testing.Views; import org.junit.Before; import org.junit.Test; Loading @@ -53,7 +53,7 @@ public class ItemDragListenerTest { @Before public void setUp() { mTestView = TestViews.createTestView(); mTestView = Views.createTestView(); mTestTimer = new TestTimer(); mTestDragHost = new TestDragHost(); Loading