/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mms.util; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import android.content.Context; import android.net.Uri; import android.os.Handler; import android.util.Log; /** * Base class {@link BackgroundLoaderManager} used by {@link MessagingApplication} for loading * items (images, thumbnails, pdus, etc.) in the background off of the UI thread. * <p> * Public methods should only be used from a single thread (typically the UI * thread). Callbacks will be invoked on the thread where the ThumbnailManager * was instantiated. * <p> * Uses a thread-pool ExecutorService instead of AsyncTasks since clients may * request lots of images around the same time, and AsyncTask may reject tasks * in that case and has no way of bounding the number of threads used by those * tasks. * * Based on BooksImageManager by Virgil King. */ abstract class BackgroundLoaderManager { private static final String TAG = "BackgroundLoaderManager"; private static final int MAX_THREADS = 2; /** * URIs for which tasks are currently enqueued. Don't enqueue new tasks for * these, just add new callbacks. */ protected final Set<Uri> mPendingTaskUris; protected final HashMap<Uri, Set<ItemLoadedCallback>> mCallbacks; protected final Executor mExecutor; protected final Handler mCallbackHandler; BackgroundLoaderManager(Context context) { mPendingTaskUris = new HashSet<Uri>(); mCallbacks = new HashMap<Uri, Set<ItemLoadedCallback>>(); final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); final int poolSize = MAX_THREADS; mExecutor = new ThreadPoolExecutor( poolSize, poolSize, 5, TimeUnit.SECONDS, queue, new BackgroundLoaderThreadFactory(getTag())); mCallbackHandler = new Handler(); } /** * Release memory if possible. */ public void onLowMemory() { clear(); } public void clear() { } /** * Return a tag that will be used to name threads so they'll be visible in the debugger. */ public abstract String getTag(); /** * Attempts to add a callback for a resource. * * @param uri the {@link Uri} of the resource for which a callback is * desired. * @param callback the callback to register. * @return {@code true} if the callback is guaranteed to be invoked with * a non-null result (as long as there is no error and the * callback is not canceled), or {@code false} if the callback * cannot be registered with this task because the result for * the desired {@link Uri} has already been discarded due to * low-memory. * @throws NullPointerException if either argument is {@code null} */ public boolean addCallback(Uri uri, ItemLoadedCallback callback) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Adding image callback " + callback); } if (uri == null) { throw new NullPointerException("uri is null"); } if (callback == null) { throw new NullPointerException("callback is null"); } Set<ItemLoadedCallback> callbacks = mCallbacks.get(uri); if (callbacks == null) { callbacks = new HashSet<ItemLoadedCallback>(4); mCallbacks.put(uri, callbacks); } callbacks.add(callback); return true; } public void cancelCallback(ItemLoadedCallback callback) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Cancelling image callback " + callback); } for (final Uri uri : mCallbacks.keySet()) { final Set<ItemLoadedCallback> callbacks = mCallbacks.get(uri); callbacks.remove(callback); } } /** * Copies the elements of a {@link Set} into an {@link ArrayList}. */ @SuppressWarnings("unchecked") protected static <T> ArrayList<T> asList(Set<T> source) { return new ArrayList<T>(source); } /** * {@link ThreadFactory} which sets a meaningful name for the thread. */ private static class BackgroundLoaderThreadFactory implements ThreadFactory { private final AtomicInteger mCount = new AtomicInteger(1); private final String mTag; public BackgroundLoaderThreadFactory(String tag) { mTag = tag; } public Thread newThread(final Runnable r) { Thread t = new Thread(r, mTag + "-" + mCount.getAndIncrement()); if (t.getPriority() != Thread.MIN_PRIORITY) t.setPriority(Thread.MIN_PRIORITY); return t; } } }