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 170cc0713c1bf8027642987b750b80217569d2932aLive Channels Teampackage com.android.tv.util.images; 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 Chalkoimport com.android.tv.R; 34944779887775bd950cf1abf348d2df461593f6abLive Channels Teamimport com.android.tv.common.concurrent.NamedThreadFactory; 350cc0713c1bf8027642987b750b80217569d2932aLive Channels Teamimport com.android.tv.util.images.BitmapUtils.ScaledBitmapInfo; 36ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport java.lang.ref.WeakReference; 37816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.HashMap; 38816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.Map; 39ba5845f23b8fbc985890f892961abc8b39886611Nick Chalkoimport java.util.Set; 402e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.BlockingQueue; 41816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.Executor; 422e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.LinkedBlockingQueue; 43816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkoimport java.util.concurrent.RejectedExecutionException; 442e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.ThreadFactory; 452e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.ThreadPoolExecutor; 462e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalkoimport java.util.concurrent.TimeUnit; 47816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 48816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko/** 4995961816a768da387f0b5523cf4363ace2044089Nick Chalko * This class wraps up completing some arbitrary long running work when loading a bitmap. It handles 5095961816a768da387f0b5523cf4363ace2044089Nick Chalko * things like using a memory cache, running the work in a background thread. 51816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 52816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalkopublic final class ImageLoader { 53816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final String TAG = "ImageLoader"; 54816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final boolean DEBUG = false; 55816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 562e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 572e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko // We want at least 2 threads and at most 4 threads in the core pool, 582e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko // preferring to have 1 less than the CPU count to avoid saturating 592e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko // the CPU with background work 602e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); 612e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; 622e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final int KEEP_ALIVE_SECONDS = 30; 632e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 642e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final ThreadFactory sThreadFactory = new NamedThreadFactory("ImageLoader"); 652e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 6665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(128); 672e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 682e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko /** 692e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * An private {@link Executor} that can be used to execute tasks in parallel. 702e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * 7195961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>{@code IMAGE_THREAD_POOL_EXECUTOR} setting are copied from {@link AsyncTask} Since we do a 7295961816a768da387f0b5523cf4363ace2044089Nick Chalko * lot of concurrent image loading we can exhaust a thread pool. ImageLoader catches the error, 7395961816a768da387f0b5523cf4363ace2044089Nick Chalko * and just leaves the image blank. However other tasks will fail and crash the application. 742e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * 752e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko * <p>Using a separate thread pool prevents image loading from causing other tasks to fail. 762e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko */ 772e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private static final Executor IMAGE_THREAD_POOL_EXECUTOR; 782e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 792e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko static { 8095961816a768da387f0b5523cf4363ace2044089Nick Chalko ThreadPoolExecutor threadPoolExecutor = 8195961816a768da387f0b5523cf4363ace2044089Nick Chalko new ThreadPoolExecutor( 8295961816a768da387f0b5523cf4363ace2044089Nick Chalko CORE_POOL_SIZE, 8395961816a768da387f0b5523cf4363ace2044089Nick Chalko MAXIMUM_POOL_SIZE, 8495961816a768da387f0b5523cf4363ace2044089Nick Chalko KEEP_ALIVE_SECONDS, 8595961816a768da387f0b5523cf4363ace2044089Nick Chalko TimeUnit.SECONDS, 8695961816a768da387f0b5523cf4363ace2044089Nick Chalko sPoolWorkQueue, 8795961816a768da387f0b5523cf4363ace2044089Nick Chalko sThreadFactory); 882e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko threadPoolExecutor.allowCoreThreadTimeOut(true); 892e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko IMAGE_THREAD_POOL_EXECUTOR = threadPoolExecutor; 902e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko } 912e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko 92ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private static Handler sMainHandler; 93ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 94816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 95ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Handles when image loading is finished. 96ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * 9795961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>Use this to prevent leaking an Activity or other Context while image loading is still 9895961816a768da387f0b5523cf4363ace2044089Nick Chalko * pending. When you extend this class you <strong>MUST NOT</strong> use a non static inner 9995961816a768da387f0b5523cf4363ace2044089Nick Chalko * class, or the containing object will still be leaked. 100816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 101816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @UiThread 10295961816a768da387f0b5523cf4363ace2044089Nick Chalko public abstract static class ImageLoaderCallback<T> { 103ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private final WeakReference<T> mWeakReference; 104ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 105ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko /** 106ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Creates an callback keeping a weak reference to {@code referent}. 107ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * 10895961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p>If the "referent" is no longer valid, it no longer makes sense to run the callback. 10995961816a768da387f0b5523cf4363ace2044089Nick Chalko * The referent is the View, or Activity or whatever that actually needs to receive the 11095961816a768da387f0b5523cf4363ace2044089Nick Chalko * Bitmap. If the referent has been GC, then no need to run the callback. 111ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko */ 112ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public ImageLoaderCallback(T referent) { 113ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mWeakReference = new WeakReference<>(referent); 114ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 115ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 11695961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Called when bitmap is loaded. */ 117ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private void onBitmapLoaded(@Nullable Bitmap bitmap) { 118ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko T referent = mWeakReference.get(); 119ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (referent != null) { 120ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko onBitmapLoaded(referent, bitmap); 121ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } else { 122ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (DEBUG) Log.d(TAG, "onBitmapLoaded not called because weak reference is gone"); 123ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 124ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 125ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 12695961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Called when bitmap is loaded if the weak reference is still valid. */ 127ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko public abstract void onBitmapLoaded(T referent, @Nullable Bitmap bitmap); 128816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 129816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 130816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final Map<String, LoadBitmapTask> sPendingListMap = new HashMap<>(); 131816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 132816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 133816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Preload a bitmap image into the cache. 134816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 135816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * <p>Not to make heavy CPU load, AsyncTask.SERIAL_EXECUTOR is used for the image loading. 13695961816a768da387f0b5523cf4363ace2044089Nick Chalko * 137ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * <p>This method is thread safe. 138816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 13995961816a768da387f0b5523cf4363ace2044089Nick Chalko public static void prefetchBitmap( 14095961816a768da387f0b5523cf4363ace2044089Nick Chalko Context context, final String uriString, final int maxWidth, 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(); 14695961816a768da387f0b5523cf4363ace2044089Nick Chalko getMainHandler() 14795961816a768da387f0b5523cf4363ace2044089Nick Chalko .post( 14895961816a768da387f0b5523cf4363ace2044089Nick Chalko new Runnable() { 14995961816a768da387f0b5523cf4363ace2044089Nick Chalko @Override 15095961816a768da387f0b5523cf4363ace2044089Nick Chalko @MainThread 15195961816a768da387f0b5523cf4363ace2044089Nick Chalko public void run() { 15295961816a768da387f0b5523cf4363ace2044089Nick Chalko // Calling from the main thread prevents a 15395961816a768da387f0b5523cf4363ace2044089Nick Chalko // ConcurrentModificationException 15495961816a768da387f0b5523cf4363ace2044089Nick Chalko // in LoadBitmapTask.onPostExecute 15595961816a768da387f0b5523cf4363ace2044089Nick Chalko doLoadBitmap( 15695961816a768da387f0b5523cf4363ace2044089Nick Chalko appContext, 15795961816a768da387f0b5523cf4363ace2044089Nick Chalko uriString, 15895961816a768da387f0b5523cf4363ace2044089Nick Chalko maxWidth, 15995961816a768da387f0b5523cf4363ace2044089Nick Chalko maxHeight, 16095961816a768da387f0b5523cf4363ace2044089Nick Chalko null, 16195961816a768da387f0b5523cf4363ace2044089Nick Chalko AsyncTask.SERIAL_EXECUTOR); 16295961816a768da387f0b5523cf4363ace2044089Nick Chalko } 16395961816a768da387f0b5523cf4363ace2044089Nick Chalko }); 164816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 165816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 166816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 167816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 168816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Load a bitmap image with the cache using a ContentResolver. 169816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 17095961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p><b>Note</b> that the callback will be called synchronously if the bitmap already is in the 17195961816a768da387f0b5523cf4363ace2044089Nick Chalko * cache. 172816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 173816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return {@code true} if the load is complete and the callback is executed. 174816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 175816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @UiThread 17695961816a768da387f0b5523cf4363ace2044089Nick Chalko public static boolean loadBitmap( 17795961816a768da387f0b5523cf4363ace2044089Nick Chalko Context context, String uriString, ImageLoaderCallback callback) { 178816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return loadBitmap(context, uriString, Integer.MAX_VALUE, Integer.MAX_VALUE, callback); 179816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 180816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 181816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 182816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Load a bitmap image with the cache and resize it with given params. 183816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 18495961816a768da387f0b5523cf4363ace2044089Nick Chalko * <p><b>Note</b> that the callback will be called synchronously if the bitmap already is in the 18595961816a768da387f0b5523cf4363ace2044089Nick Chalko * cache. 186816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 187816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return {@code true} if the load is complete and the callback is executed. 188816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 189816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @UiThread 19095961816a768da387f0b5523cf4363ace2044089Nick Chalko public static boolean loadBitmap( 19195961816a768da387f0b5523cf4363ace2044089Nick Chalko Context context, 19295961816a768da387f0b5523cf4363ace2044089Nick Chalko String uriString, 19395961816a768da387f0b5523cf4363ace2044089Nick Chalko int maxWidth, 19495961816a768da387f0b5523cf4363ace2044089Nick Chalko int maxHeight, 195816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ImageLoaderCallback callback) { 196816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) { 197816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.d(TAG, "loadBitmap() " + uriString); 198816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 19995961816a768da387f0b5523cf4363ace2044089Nick Chalko return doLoadBitmap( 20095961816a768da387f0b5523cf4363ace2044089Nick Chalko context, uriString, maxWidth, maxHeight, callback, IMAGE_THREAD_POOL_EXECUTOR); 201816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 202816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 20395961816a768da387f0b5523cf4363ace2044089Nick Chalko private static boolean doLoadBitmap( 20495961816a768da387f0b5523cf4363ace2044089Nick Chalko Context context, 20595961816a768da387f0b5523cf4363ace2044089Nick Chalko String uriString, 20695961816a768da387f0b5523cf4363ace2044089Nick Chalko int maxWidth, 20795961816a768da387f0b5523cf4363ace2044089Nick Chalko int maxHeight, 20895961816a768da387f0b5523cf4363ace2044089Nick Chalko ImageLoaderCallback callback, 20995961816a768da387f0b5523cf4363ace2044089Nick Chalko Executor executor) { 210816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // Check the cache before creating a Task. The cache will be checked again in doLoadBitmap 211816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // but checking a cache is much cheaper than creating an new task. 212816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ImageCache imageCache = ImageCache.getInstance(); 213816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo bitmapInfo = imageCache.get(uriString); 214816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (bitmapInfo != null && !bitmapInfo.needToReload(maxWidth, maxHeight)) { 215816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (callback != null) { 216816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko callback.onBitmapLoaded(bitmapInfo.bitmap); 217816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 218816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 219816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 22095961816a768da387f0b5523cf4363ace2044089Nick Chalko return doLoadBitmap( 22195961816a768da387f0b5523cf4363ace2044089Nick Chalko callback, 22295961816a768da387f0b5523cf4363ace2044089Nick Chalko executor, 223816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko new LoadBitmapFromUriTask(context, imageCache, uriString, maxWidth, maxHeight)); 224816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 225816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 226816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 227816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Load a bitmap image with the cache and resize it with given params. 228816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 229816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * <p>The LoadBitmapTask will be executed on a non ui thread. 230816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * 231816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * @return {@code true} if the load is complete and the callback is executed. 232816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 233816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @UiThread 234816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static boolean loadBitmap(ImageLoaderCallback callback, LoadBitmapTask loadBitmapTask) { 235816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) { 236816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.d(TAG, "loadBitmap() " + loadBitmapTask); 237816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 2382e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko return doLoadBitmap(callback, IMAGE_THREAD_POOL_EXECUTOR, loadBitmapTask); 239816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 240816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 24195961816a768da387f0b5523cf4363ace2044089Nick Chalko /** @return {@code true} if the load is complete and the callback is executed. */ 242ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko @UiThread 24395961816a768da387f0b5523cf4363ace2044089Nick Chalko private static boolean doLoadBitmap( 24495961816a768da387f0b5523cf4363ace2044089Nick Chalko ImageLoaderCallback callback, Executor executor, LoadBitmapTask loadBitmapTask) { 245816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo bitmapInfo = loadBitmapTask.getFromCache(); 246816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko boolean needToReload = loadBitmapTask.isReloadNeeded(); 247816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (bitmapInfo != null && !needToReload) { 248816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (callback != null) { 249816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko callback.onBitmapLoaded(bitmapInfo.bitmap); 250816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 251816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return true; 252816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 253816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko LoadBitmapTask existingTask = sPendingListMap.get(loadBitmapTask.getKey()); 254ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (existingTask != null && !loadBitmapTask.isReloadNeeded(existingTask)) { 255816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko // The image loading is already scheduled and is large enough. 256816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (callback != null) { 257816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko existingTask.mCallbacks.add(callback); 258816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 259816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } else { 260816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (callback != null) { 261816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko loadBitmapTask.mCallbacks.add(callback); 262816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 263816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko sPendingListMap.put(loadBitmapTask.getKey(), loadBitmapTask); 264816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko try { 265816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko loadBitmapTask.executeOnExecutor(executor); 266816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } catch (RejectedExecutionException e) { 267816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Log.e(TAG, "Failed to create new image loader", e); 268816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko sPendingListMap.remove(loadBitmapTask.getKey()); 269816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 270816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 271816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return false; 272816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 273816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 274ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko /** 275ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * Loads and caches a a possibly scaled down version of a bitmap. 276ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * 277ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko * <p>Implement {@link #doGetBitmapInBackground} to do the actual loading. 278ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko */ 27995961816a768da387f0b5523cf4363ace2044089Nick Chalko public abstract static class LoadBitmapTask extends AsyncTask<Void, Void, ScaledBitmapInfo> { 280ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko protected final Context mAppContext; 281816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko protected final int mMaxWidth; 282816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko protected final int mMaxHeight; 2832e1279b8bbe0603fb4399b25b73121bed5953c46Nick Chalko private final Set<ImageLoaderCallback> mCallbacks = new ArraySet<>(); 284816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final ImageCache mImageCache; 285816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final String mKey; 286816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 287816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko /** 288816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * Returns true if a reload is needed compared to current results in the cache or false if 289816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko * there is not match in the cache. 290816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko */ 291816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean isReloadNeeded() { 292816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo bitmapInfo = getFromCache(); 29395961816a768da387f0b5523cf4363ace2044089Nick Chalko boolean needToReload = 29495961816a768da387f0b5523cf4363ace2044089Nick Chalko bitmapInfo != null && bitmapInfo.needToReload(mMaxWidth, mMaxHeight); 295816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (DEBUG) { 296816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (needToReload) { 29795961816a768da387f0b5523cf4363ace2044089Nick Chalko Log.d( 29895961816a768da387f0b5523cf4363ace2044089Nick Chalko TAG, 29995961816a768da387f0b5523cf4363ace2044089Nick Chalko "Bitmap needs to be reloaded. {" 30095961816a768da387f0b5523cf4363ace2044089Nick Chalko + "originalWidth=" 30195961816a768da387f0b5523cf4363ace2044089Nick Chalko + bitmapInfo.bitmap.getWidth() 30295961816a768da387f0b5523cf4363ace2044089Nick Chalko + ", originalHeight=" 30395961816a768da387f0b5523cf4363ace2044089Nick Chalko + bitmapInfo.bitmap.getHeight() 30495961816a768da387f0b5523cf4363ace2044089Nick Chalko + ", reqWidth=" 30595961816a768da387f0b5523cf4363ace2044089Nick Chalko + mMaxWidth 30695961816a768da387f0b5523cf4363ace2044089Nick Chalko + ", reqHeight=" 30795961816a768da387f0b5523cf4363ace2044089Nick Chalko + mMaxHeight 30895961816a768da387f0b5523cf4363ace2044089Nick Chalko + "}"); 309816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 310816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 311816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return needToReload; 312816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 313816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 31495961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Checks if a reload would be needed if the results of other was available. */ 315816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private boolean isReloadNeeded(LoadBitmapTask other) { 316633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko return (other.mMaxHeight != Integer.MAX_VALUE && mMaxHeight >= other.mMaxHeight * 2) 317633eb826b8c97731dfc5ef12c7bf78a63734275dNick Chalko || (other.mMaxWidth != Integer.MAX_VALUE && mMaxWidth >= other.mMaxWidth * 2); 318816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 319816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 320816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 321816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final ScaledBitmapInfo getFromCache() { 322816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mImageCache.get(mKey); 323816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 324816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 32595961816a768da387f0b5523cf4363ace2044089Nick Chalko public LoadBitmapTask( 32695961816a768da387f0b5523cf4363ace2044089Nick Chalko Context context, ImageCache imageCache, String key, int maxHeight, int maxWidth) { 327816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (maxWidth == 0 || maxHeight == 0) { 328ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko throw new IllegalArgumentException( 32995961816a768da387f0b5523cf4363ace2044089Nick Chalko "Image size should not be 0. {width=" 33095961816a768da387f0b5523cf4363ace2044089Nick Chalko + maxWidth 33195961816a768da387f0b5523cf4363ace2044089Nick Chalko + ", height=" 33295961816a768da387f0b5523cf4363ace2044089Nick Chalko + maxHeight 333ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + "}"); 334816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 335ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko mAppContext = context.getApplicationContext(); 336816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mKey = key; 337816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mImageCache = imageCache; 338816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mMaxHeight = maxHeight; 339816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mMaxWidth = maxWidth; 340816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 341816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 34295961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Loads the bitmap returning a possibly scaled down version. */ 343816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 344816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @WorkerThread 345816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public abstract ScaledBitmapInfo doGetBitmapInBackground(); 346816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 347816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 348816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 349816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final ScaledBitmapInfo doInBackground(Void... params) { 350816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ScaledBitmapInfo bitmapInfo = getFromCache(); 351816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (bitmapInfo != null && !isReloadNeeded()) { 352816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return bitmapInfo; 353816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 354816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko bitmapInfo = doGetBitmapInBackground(); 355816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (bitmapInfo != null) { 356816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mImageCache.putIfNeeded(bitmapInfo); 357816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 358816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return bitmapInfo; 359816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 360816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 361816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 362816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final void onPostExecute(ScaledBitmapInfo scaledBitmapInfo) { 363ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (DEBUG) Log.d(ImageLoader.TAG, "Bitmap is loaded " + mKey); 364ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 365816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko for (ImageLoader.ImageLoaderCallback callback : mCallbacks) { 366816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko callback.onBitmapLoaded(scaledBitmapInfo == null ? null : scaledBitmapInfo.bitmap); 367816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 368816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko ImageLoader.sPendingListMap.remove(mKey); 369816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 370816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 371816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final String getKey() { 372816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return mKey; 373816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 374816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 375816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 376816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public String toString() { 37795961816a768da387f0b5523cf4363ace2044089Nick Chalko return this.getClass().getSimpleName() 37895961816a768da387f0b5523cf4363ace2044089Nick Chalko + "(" 37995961816a768da387f0b5523cf4363ace2044089Nick Chalko + mKey 38095961816a768da387f0b5523cf4363ace2044089Nick Chalko + " " 38195961816a768da387f0b5523cf4363ace2044089Nick Chalko + mMaxWidth 38295961816a768da387f0b5523cf4363ace2044089Nick Chalko + "x" 38395961816a768da387f0b5523cf4363ace2044089Nick Chalko + mMaxHeight 384ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko + ")"; 385816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 386816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 387816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 388816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private static final class LoadBitmapFromUriTask extends LoadBitmapTask { 38995961816a768da387f0b5523cf4363ace2044089Nick Chalko private LoadBitmapFromUriTask( 39095961816a768da387f0b5523cf4363ace2044089Nick Chalko Context context, 39195961816a768da387f0b5523cf4363ace2044089Nick Chalko ImageCache imageCache, 39295961816a768da387f0b5523cf4363ace2044089Nick Chalko String uriString, 39395961816a768da387f0b5523cf4363ace2044089Nick Chalko int maxWidth, 39495961816a768da387f0b5523cf4363ace2044089Nick Chalko int maxHeight) { 395ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko super(context, imageCache, uriString, maxHeight, maxWidth); 396816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 397816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 398816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 399816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 400816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public final ScaledBitmapInfo doGetBitmapInBackground() { 40195961816a768da387f0b5523cf4363ace2044089Nick Chalko return BitmapUtils.decodeSampledBitmapFromUriString( 40295961816a768da387f0b5523cf4363ace2044089Nick Chalko mAppContext, getKey(), mMaxWidth, mMaxHeight); 403816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 404816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 405816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 40695961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Loads and caches the logo for a given {@link TvInputInfo} */ 407816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public static final class LoadTvInputLogoTask extends LoadBitmapTask { 408816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko private final TvInputInfo mInfo; 409816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 410816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public LoadTvInputLogoTask(Context context, ImageCache cache, TvInputInfo info) { 41195961816a768da387f0b5523cf4363ace2044089Nick Chalko super( 41295961816a768da387f0b5523cf4363ace2044089Nick Chalko context, 413ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko cache, 41465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko getTvInputLogoKey(info.getId()), 415816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko context.getResources() 416816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size), 417816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko context.getResources() 41895961816a768da387f0b5523cf4363ace2044089Nick Chalko .getDimensionPixelSize(R.dimen.channel_banner_input_logo_size)); 419816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko mInfo = info; 420816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 421816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 422816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Nullable 423816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko @Override 424816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko public ScaledBitmapInfo doGetBitmapInBackground() { 425ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko Drawable drawable = mInfo.loadIcon(mAppContext); 426816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (!(drawable instanceof BitmapDrawable)) { 427816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return null; 428816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 429816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko Bitmap original = ((BitmapDrawable) drawable).getBitmap(); 430816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko if (original == null) { 431816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return null; 432816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 433816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko return BitmapUtils.createScaledBitmapInfo(getKey(), original, mMaxWidth, mMaxHeight); 434816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 43565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko 43695961816a768da387f0b5523cf4363ace2044089Nick Chalko /** Returns key of TV input logo. */ 43765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko public static String getTvInputLogoKey(String inputId) { 43865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko return inputId + "-logo"; 43965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko } 440816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko } 441816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko 442ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko private static synchronized Handler getMainHandler() { 443ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko if (sMainHandler == null) { 444ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko sMainHandler = new Handler(Looper.getMainLooper()); 445ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 446ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko return sMainHandler; 447ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko } 448ba5845f23b8fbc985890f892961abc8b39886611Nick Chalko 44995961816a768da387f0b5523cf4363ace2044089Nick Chalko private ImageLoader() {} 450816a4be1a0f34f6a48877c8afd3dbbca19eac435Nick Chalko} 451