Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1ec6f295 authored by Garfield, Tan's avatar Garfield, Tan Committed by Garfield Tan
Browse files

Load up-to-date thumbnail if the cached one is out of date.

Bug: 28557412
Change-Id: Ib3ef9962249305be22b7a1e49e26350f3596e430
(cherry picked from commit aacb9219a791d2e4415cbbcacc12caed424dff83)
parent ffcc6ac2
Loading
Loading
Loading
Loading
+66 −36
Original line number Diff line number Diff line
@@ -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);
            }
        }

@@ -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);
                }
            }
        }
@@ -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;
@@ -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);
@@ -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.
         */
@@ -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
@@ -228,6 +235,10 @@ public class ThumbnailCache {
            return mSize;
        }

        public long getLastModified() {
            return mLastModified;
        }

        public boolean isHit() {
            return (mStatus != CACHE_MISS);
        }
@@ -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);
            }
        }
    }

+2 −1
Original line number Diff line number Diff line
@@ -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);
+16 −9
Original line number Diff line number Diff line
@@ -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;
@@ -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);
        }
@@ -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)) {
@@ -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;

@@ -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,
@@ -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);

+2 −1
Original line number Diff line number Diff line
@@ -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);
+2 −2
Original line number Diff line number Diff line
@@ -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;
@@ -53,7 +53,7 @@ public class ItemDragListenerTest {

    @Before
    public void setUp() {
        mTestView = TestViews.createTestView();
        mTestView = Views.createTestView();

        mTestTimer = new TestTimer();
        mTestDragHost = new TestDragHost();
Loading