ImageLoader.java revision 65fda1eaa94968bb55d5ded10dcb0b3f37fb05f2
1816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/* 2816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Copyright (C) 2015 The Android Open Source Project 3816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 4816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License"); 5816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * you may not use this file except in compliance with the License. 6816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * You may obtain a copy of the License at 7816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 8816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * http://www.apache.org/licenses/LICENSE-2.0 9816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 10816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Unless required by applicable law or agreed to in writing, software 11816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS, 12816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * See the License for the specific language governing permissions and 14816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * limitations under the License. 15816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 16816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 17816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopackage com.android.tv.util; 18816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 19816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.content.Context; 20816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.graphics.Bitmap; 21816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.graphics.drawable.BitmapDrawable; 22816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.graphics.drawable.Drawable; 23816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.media.tv.TvInputInfo; 24816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.os.AsyncTask; 25ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.os.Handler; 26ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.os.Looper; 27ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport android.support.annotation.MainThread; 28816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.Nullable; 29816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.UiThread; 30816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.support.annotation.WorkerThread; 312e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport android.util.ArraySet; 32816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport android.util.Log; 33816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 34816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.R; 35816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport com.android.tv.util.BitmapUtils.ScaledBitmapInfo; 36816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 37ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport java.lang.ref.WeakReference; 38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.HashMap; 39816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Map; 40ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport java.util.Set; 412e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.BlockingQueue; 42816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.Executor; 432e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.LinkedBlockingQueue; 44816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.RejectedExecutionException; 452e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.ThreadFactory; 462e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.ThreadPoolExecutor; 472e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.TimeUnit; 48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 49816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/** 50816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * This class wraps up completing some arbitrary long running work when loading a bitmap. It 51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * handles things like using a memory cache, running the work in a background thread. 52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic final class ImageLoader { 54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String TAG = "ImageLoader"; 55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final boolean DEBUG = false; 56816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 572e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 582e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko // We want at least 2 threads and at most 4 threads in the core pool, 592e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko // preferring to have 1 less than the CPU count to avoid saturating 602e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko // the CPU with background work 612e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); 622e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; 632e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final int KEEP_ALIVE_SECONDS = 30; 642e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 652e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final ThreadFactory sThreadFactory = new NamedThreadFactory("ImageLoader"); 662e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 6765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(128); 682e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 692e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko /** 702e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * An private {@link Executor} that can be used to execute tasks in parallel. 712e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * 722e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * <p>{@code IMAGE_THREAD_POOL_EXECUTOR} setting are copied from {@link AsyncTask} 732e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * Since we do a lot of concurrent image loading we can exhaust a thread pool. 742e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * ImageLoader catches the error, and just leaves the image blank. 752e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * However other tasks will fail and crash the application. 762e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * 772e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * <p>Using a separate thread pool prevents image loading from causing other tasks to fail. 782e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko */ 792e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final Executor IMAGE_THREAD_POOL_EXECUTOR; 802e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 812e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko static { 822e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, 832e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, 842e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko sThreadFactory); 852e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko threadPoolExecutor.allowCoreThreadTimeOut(true); 862e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko IMAGE_THREAD_POOL_EXECUTOR = threadPoolExecutor; 872e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko } 882e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 89ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private static Handler sMainHandler; 90ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 91816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 92ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Handles when image loading is finished. 93ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * 94ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * <p>Use this to prevent leaking an Activity or other Context while image loading is 95ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * still pending. When you extend this class you <strong>MUST NOT</strong> use a non static 96ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * inner class, or the containing object will still be leaked. 97816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 98816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @UiThread 99ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public static abstract class ImageLoaderCallback<T> { 100ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private final WeakReference<T> mWeakReference; 101ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 102ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko /** 103ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Creates an callback keeping a weak reference to {@code referent}. 104ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * 105ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * <p> If the "referent" is no longer valid, it no longer makes sense to run the 106ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * callback. The referent is the View, or Activity or whatever that actually needs to 107ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * receive the Bitmap. If the referent has been GC, then no need to run the callback. 108ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko */ 109ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public ImageLoaderCallback(T referent) { 110ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mWeakReference = new WeakReference<>(referent); 111ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 112ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 113816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 114816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Called when bitmap is loaded. 115816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 116ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private void onBitmapLoaded(@Nullable Bitmap bitmap) { 117ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko T referent = mWeakReference.get(); 118ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (referent != null) { 119ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko onBitmapLoaded(referent, bitmap); 120ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } else { 121ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (DEBUG) Log.d(TAG, "onBitmapLoaded not called because weak reference is gone"); 122ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 123ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 124ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 125ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko /** 126ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Called when bitmap is loaded if the weak reference is still valid. 127ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko */ 128ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public abstract void onBitmapLoaded(T referent, @Nullable Bitmap bitmap); 129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final Map<String, LoadBitmapTask> sPendingListMap = new HashMap<>(); 132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Preload a bitmap image into the cache. 135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 136816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * <p>Not to make heavy CPU load, AsyncTask.SERIAL_EXECUTOR is used for the image loading. 137ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * <p>This method is thread safe. 138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 139ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public static void prefetchBitmap(Context context, final String uriString, final int maxWidth, 140ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko final int maxHeight) { 141ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (DEBUG) Log.d(TAG, "prefetchBitmap() " + uriString); 142ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (Looper.getMainLooper() == Looper.myLooper()) { 143ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko doLoadBitmap(context, uriString, maxWidth, maxHeight, null, AsyncTask.SERIAL_EXECUTOR); 144ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } else { 145ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko final Context appContext = context.getApplicationContext(); 146ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko getMainHandler().post(new Runnable() { 147ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @Override 148ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @MainThread 149ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public void run() { 150ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko // Calling from the main thread prevents a ConcurrentModificationException 151ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko // in LoadBitmapTask.onPostExecute 152ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko doLoadBitmap(appContext, uriString, maxWidth, maxHeight, null, 153ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko AsyncTask.SERIAL_EXECUTOR); 154ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 155ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko }); 156816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 157816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 158816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 159816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 160816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Load a bitmap image with the cache using a ContentResolver. 161816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 162816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * <p><b>Note</b> that the callback will be called synchronously if the bitmap already is in 163816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * the cache. 164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return {@code true} if the load is complete and the callback is executed. 166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @UiThread 168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static boolean loadBitmap(Context context, String uriString, 169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ImageLoaderCallback callback) { 170816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return loadBitmap(context, uriString, Integer.MAX_VALUE, Integer.MAX_VALUE, callback); 171816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Load a bitmap image with the cache and resize it with given params. 175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 176816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * <p><b>Note</b> that the callback will be called synchronously if the bitmap already is in 177816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * the cache. 178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return {@code true} if the load is complete and the callback is executed. 180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @UiThread 182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static boolean loadBitmap(Context context, String uriString, int maxWidth, int maxHeight, 183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ImageLoaderCallback callback) { 184816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) { 185816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.d(TAG, "loadBitmap() " + uriString); 186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return doLoadBitmap(context, uriString, maxWidth, maxHeight, callback, 1882e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko IMAGE_THREAD_POOL_EXECUTOR); 189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 190816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 191816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static boolean doLoadBitmap(Context context, String uriString, 192816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int maxWidth, int maxHeight, ImageLoaderCallback callback, Executor executor) { 193816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Check the cache before creating a Task. The cache will be checked again in doLoadBitmap 194816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // but checking a cache is much cheaper than creating an new task. 195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ImageCache imageCache = ImageCache.getInstance(); 196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo bitmapInfo = imageCache.get(uriString); 197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (bitmapInfo != null && !bitmapInfo.needToReload(maxWidth, maxHeight)) { 198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (callback != null) { 199816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko callback.onBitmapLoaded(bitmapInfo.bitmap); 200816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 203816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return doLoadBitmap(callback, executor, 204816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko new LoadBitmapFromUriTask(context, imageCache, uriString, maxWidth, maxHeight)); 205816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 206816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 207816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 208816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Load a bitmap image with the cache and resize it with given params. 209816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * <p>The LoadBitmapTask will be executed on a non ui thread. 211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return {@code true} if the load is complete and the callback is executed. 213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @UiThread 215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static boolean loadBitmap(ImageLoaderCallback callback, LoadBitmapTask loadBitmapTask) { 216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) { 217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.d(TAG, "loadBitmap() " + loadBitmapTask); 218816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 2192e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko return doLoadBitmap(callback, IMAGE_THREAD_POOL_EXECUTOR, loadBitmapTask); 220816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 221816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 222816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return {@code true} if the load is complete and the callback is executed. 224816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 225ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @UiThread 226816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static boolean doLoadBitmap(ImageLoaderCallback callback, Executor executor, 227816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko LoadBitmapTask loadBitmapTask) { 228816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo bitmapInfo = loadBitmapTask.getFromCache(); 229816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean needToReload = loadBitmapTask.isReloadNeeded(); 230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (bitmapInfo != null && !needToReload) { 231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (callback != null) { 232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko callback.onBitmapLoaded(bitmapInfo.bitmap); 233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko LoadBitmapTask existingTask = sPendingListMap.get(loadBitmapTask.getKey()); 237ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (existingTask != null && !loadBitmapTask.isReloadNeeded(existingTask)) { 238816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // The image loading is already scheduled and is large enough. 239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (callback != null) { 240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko existingTask.mCallbacks.add(callback); 241816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 242816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 243816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (callback != null) { 244816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko loadBitmapTask.mCallbacks.add(callback); 245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko sPendingListMap.put(loadBitmapTask.getKey(), loadBitmapTask); 247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko try { 248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko loadBitmapTask.executeOnExecutor(executor); 249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } catch (RejectedExecutionException e) { 250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.e(TAG, "Failed to create new image loader", e); 251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko sPendingListMap.remove(loadBitmapTask.getKey()); 252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 254816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 257ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko /** 258ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Loads and caches a a possibly scaled down version of a bitmap. 259ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * 260ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * <p>Implement {@link #doGetBitmapInBackground} to do the actual loading. 261ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko */ 262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static abstract class LoadBitmapTask extends AsyncTask<Void, Void, ScaledBitmapInfo> { 263ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko protected final Context mAppContext; 264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko protected final int mMaxWidth; 265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko protected final int mMaxHeight; 2662e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private final Set<ImageLoaderCallback> mCallbacks = new ArraySet<>(); 267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final ImageCache mImageCache; 268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final String mKey; 269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns true if a reload is needed compared to current results in the cache or false if 272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * there is not match in the cache. 273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 274816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean isReloadNeeded() { 275816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo bitmapInfo = getFromCache(); 276816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean needToReload = bitmapInfo != null && bitmapInfo 277816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .needToReload(mMaxWidth, mMaxHeight); 278816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) { 279816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (needToReload) { 280ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Log.d(TAG, "Bitmap needs to be reloaded. {" 281ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + "originalWidth=" + bitmapInfo.bitmap.getWidth() 282ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + ", originalHeight=" + bitmapInfo.bitmap.getHeight() 283ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + ", reqWidth=" + mMaxWidth 284ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + ", reqHeight=" + mMaxHeight 285ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + "}"); 286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return needToReload; 289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Checks if a reload would be needed if the results of other was available. 293816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 294816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean isReloadNeeded(LoadBitmapTask other) { 295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mMaxHeight >= other.mMaxHeight * 2 || mMaxWidth >= other.mMaxWidth * 2; 296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 297816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 298816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 299816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final ScaledBitmapInfo getFromCache() { 300816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mImageCache.get(mKey); 301816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 302816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 303ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public LoadBitmapTask(Context context, ImageCache imageCache, String key, int maxHeight, 304ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko int maxWidth) { 305816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (maxWidth == 0 || maxHeight == 0) { 306ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko throw new IllegalArgumentException( 307ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko "Image size should not be 0. {width=" + maxWidth + ", height=" + maxHeight 308ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + "}"); 309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 310ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mAppContext = context.getApplicationContext(); 311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mKey = key; 312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mImageCache = imageCache; 313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mMaxHeight = maxHeight; 314816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mMaxWidth = maxWidth; 315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 316816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 317816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Loads the bitmap returning a possibly scaled down version. 319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @WorkerThread 322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public abstract ScaledBitmapInfo doGetBitmapInBackground(); 323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 325816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 326816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final ScaledBitmapInfo doInBackground(Void... params) { 327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo bitmapInfo = getFromCache(); 328816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (bitmapInfo != null && !isReloadNeeded()) { 329816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return bitmapInfo; 330816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 331816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko bitmapInfo = doGetBitmapInBackground(); 332816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (bitmapInfo != null) { 333816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mImageCache.putIfNeeded(bitmapInfo); 334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 335816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return bitmapInfo; 336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final void onPostExecute(ScaledBitmapInfo scaledBitmapInfo) { 340ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (DEBUG) Log.d(ImageLoader.TAG, "Bitmap is loaded " + mKey); 341ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 342816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (ImageLoader.ImageLoaderCallback callback : mCallbacks) { 343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko callback.onBitmapLoaded(scaledBitmapInfo == null ? null : scaledBitmapInfo.bitmap); 344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ImageLoader.sPendingListMap.remove(mKey); 346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final String getKey() { 349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mKey; 350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 353816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public String toString() { 354ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return this.getClass().getSimpleName() + "(" + mKey + " " + mMaxWidth + "x" + mMaxHeight 355ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + ")"; 356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 358816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 359816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final class LoadBitmapFromUriTask extends LoadBitmapTask { 360816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private LoadBitmapFromUriTask(Context context, ImageCache imageCache, String uriString, 361816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko int maxWidth, int maxHeight) { 362ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko super(context, imageCache, uriString, maxHeight, maxWidth); 363816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 364816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 365816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 366816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 367816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final ScaledBitmapInfo doGetBitmapInBackground() { 368816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return BitmapUtils 369ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko .decodeSampledBitmapFromUriString(mAppContext, getKey(), mMaxWidth, mMaxHeight); 370816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 371816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 372816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 373816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 374816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Loads and caches the logo for a given {@link TvInputInfo} 375816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final class LoadTvInputLogoTask extends LoadBitmapTask { 377816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final TvInputInfo mInfo; 378816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 379816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public LoadTvInputLogoTask(Context context, ImageCache cache, TvInputInfo info) { 380ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko super(context, 381ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko cache, 38265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko getTvInputLogoKey(info.getId()), 383816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko context.getResources() 384816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size), 385816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko context.getResources() 386816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size) 387816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ); 388816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mInfo = info; 389816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 390816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 391816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 392816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 393816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public ScaledBitmapInfo doGetBitmapInBackground() { 394ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Drawable drawable = mInfo.loadIcon(mAppContext); 395816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!(drawable instanceof BitmapDrawable)) { 396816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return null; 397816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 398816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Bitmap original = ((BitmapDrawable) drawable).getBitmap(); 399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (original == null) { 400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return null; 401816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 402816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return BitmapUtils.createScaledBitmapInfo(getKey(), original, mMaxWidth, mMaxHeight); 403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 40465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 40565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko /** 40665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Returns key of TV input logo. 40765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */ 40865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static String getTvInputLogoKey(String inputId) { 40965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return inputId + "-logo"; 41065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 411816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 412816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 413ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private static synchronized Handler getMainHandler() { 414ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (sMainHandler == null) { 415ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko sMainHandler = new Handler(Looper.getMainLooper()); 416ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 417ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return sMainHandler; 418ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 419ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 420816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private ImageLoader() { 421816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 422816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko} 423