blob: fd83be9e361f2cb82536b4d5ee39c67eca3b48e3 [file] [log] [blame]
/*
* 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;
}
}
}