// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.download.ui; import android.graphics.Bitmap; import android.os.Handler; import android.os.Looper; import android.support.annotation.Nullable; import android.support.v4.util.LruCache; import android.text.TextUtils; import org.chromium.base.ThreadUtils; import org.chromium.base.annotations.CalledByNative; import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.Deque; /** * Concrete implementation of {@link ThumbnailProvider}. * * Thumbnails are cached and shared across all ThumbnailProviderImpls. The cache itself is LRU and * limited in size. It is automatically garbage collected under memory pressure. * * A queue of requests is maintained in FIFO order. Missing thumbnails are retrieved asynchronously * by the native ThumbnailProvider, which is owned and destroyed by the Java class. * * TODO(dfalcantara): Figure out how to send requests simultaneously to the utility process without * duplicating work to decode the same image for two different requests. */ public class ThumbnailProviderImpl implements ThumbnailProvider { /** 5 MB of thumbnails should be enough for everyone. */ private static final int MAX_CACHE_BYTES = 5 * 1024 * 1024; /** Weakly referenced cache containing thumbnails that can be deleted under memory pressure. */ private static WeakReference<LruCache<String, Bitmap>> sBitmapCache = new WeakReference<>(null); /** Enqueues requests. */ private final Handler mHandler; /** Maximum size in pixels of the smallest side of the thumbnail. */ private final int mIconSizePx; /** Queue of files to retrieve thumbnails for. */ private final Deque<ThumbnailRequest> mRequestQueue; /** The native side pointer that is owned and destroyed by the Java class. */ private long mNativeThumbnailProvider; /** Request that is currently having its thumbnail retrieved. */ private ThumbnailRequest mCurrentRequest; public ThumbnailProviderImpl(int iconSizePx) { mIconSizePx = iconSizePx; mHandler = new Handler(Looper.getMainLooper()); mRequestQueue = new ArrayDeque<>(); mNativeThumbnailProvider = nativeInit(); } @Override public void destroy() { ThreadUtils.assertOnUiThread(); nativeDestroy(mNativeThumbnailProvider); mNativeThumbnailProvider = 0; } @Override public Bitmap getThumbnail(ThumbnailRequest request) { String filePath = request.getFilePath(); if (TextUtils.isEmpty(filePath)) return null; Bitmap cachedBitmap = getBitmapCache().get(filePath); if (cachedBitmap != null) return cachedBitmap; mRequestQueue.offer(request); processQueue(); return null; } /** Removes a particular file from the pending queue. */ @Override public void cancelRetrieval(ThumbnailRequest request) { if (mRequestQueue.contains(request)) mRequestQueue.remove(request); } private void processQueue() { mHandler.post(new Runnable() { @Override public void run() { processNextRequest(); } }); } private void processNextRequest() { if (!isInitialized() || mCurrentRequest != null || mRequestQueue.isEmpty()) return; mCurrentRequest = mRequestQueue.poll(); String currentFilePath = mCurrentRequest.getFilePath(); Bitmap cachedBitmap = getBitmapCache().get(currentFilePath); if (cachedBitmap == null) { // Asynchronously process the file to make a thumbnail. nativeRetrieveThumbnail(mNativeThumbnailProvider, currentFilePath, mIconSizePx); } else { // Send back the already-processed file. onThumbnailRetrieved(currentFilePath, cachedBitmap); } } @CalledByNative private void onThumbnailRetrieved(String filePath, @Nullable Bitmap bitmap) { if (bitmap != null) { getBitmapCache().put(filePath, bitmap); mCurrentRequest.onThumbnailRetrieved(filePath, bitmap); } mCurrentRequest = null; processQueue(); } private boolean isInitialized() { return mNativeThumbnailProvider != 0; } private static LruCache<String, Bitmap> getBitmapCache() { ThreadUtils.assertOnUiThread(); LruCache<String, Bitmap> cache = sBitmapCache == null ? null : sBitmapCache.get(); if (cache != null) return cache; // Create a new weakly-referenced cache. cache = new LruCache<String, Bitmap>(MAX_CACHE_BYTES) { @Override protected int sizeOf(String key, Bitmap thumbnail) { return thumbnail == null ? 0 : thumbnail.getByteCount(); } }; sBitmapCache = new WeakReference<>(cache); return cache; } private native long nativeInit(); private native void nativeDestroy(long nativeThumbnailProvider); private native void nativeRetrieveThumbnail( long nativeThumbnailProvider, String filePath, int thumbnailSize); }