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