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