1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.mms.util; 18 19 import java.util.ArrayList; 20 import java.util.HashMap; 21 import java.util.HashSet; 22 import java.util.Set; 23 import java.util.concurrent.Executor; 24 import java.util.concurrent.LinkedBlockingQueue; 25 import java.util.concurrent.ThreadFactory; 26 import java.util.concurrent.ThreadPoolExecutor; 27 import java.util.concurrent.TimeUnit; 28 import java.util.concurrent.atomic.AtomicInteger; 29 30 import android.content.Context; 31 import android.net.Uri; 32 import android.os.Handler; 33 import android.util.Log; 34 35 /** 36 * Base class {@link BackgroundLoaderManager} used by {@link MessagingApplication} for loading 37 * items (images, thumbnails, pdus, etc.) in the background off of the UI thread. 38 * <p> 39 * Public methods should only be used from a single thread (typically the UI 40 * thread). Callbacks will be invoked on the thread where the ThumbnailManager 41 * was instantiated. 42 * <p> 43 * Uses a thread-pool ExecutorService instead of AsyncTasks since clients may 44 * request lots of images around the same time, and AsyncTask may reject tasks 45 * in that case and has no way of bounding the number of threads used by those 46 * tasks. 47 * 48 * Based on BooksImageManager by Virgil King. 49 */ 50 abstract class BackgroundLoaderManager { 51 private static final String TAG = "BackgroundLoaderManager"; 52 53 private static final int MAX_THREADS = 2; 54 55 /** 56 * URIs for which tasks are currently enqueued. Don't enqueue new tasks for 57 * these, just add new callbacks. 58 */ 59 protected final Set<Uri> mPendingTaskUris; 60 61 protected final HashMap<Uri, Set<ItemLoadedCallback>> mCallbacks; 62 63 protected final Executor mExecutor; 64 65 protected final Handler mCallbackHandler; 66 67 BackgroundLoaderManager(Context context) { 68 mPendingTaskUris = new HashSet<Uri>(); 69 mCallbacks = new HashMap<Uri, Set<ItemLoadedCallback>>(); 70 final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); 71 final int poolSize = MAX_THREADS; 72 mExecutor = new ThreadPoolExecutor( 73 poolSize, poolSize, 5, TimeUnit.SECONDS, queue, 74 new BackgroundLoaderThreadFactory(getTag())); 75 mCallbackHandler = new Handler(); 76 } 77 78 /** 79 * Release memory if possible. 80 */ 81 public void onLowMemory() { 82 clear(); 83 } 84 85 public void clear() { 86 } 87 88 /** 89 * Return a tag that will be used to name threads so they'll be visible in the debugger. 90 */ 91 public abstract String getTag(); 92 93 /** 94 * Attempts to add a callback for a resource. 95 * 96 * @param uri the {@link Uri} of the resource for which a callback is 97 * desired. 98 * @param callback the callback to register. 99 * @return {@code true} if the callback is guaranteed to be invoked with 100 * a non-null result (as long as there is no error and the 101 * callback is not canceled), or {@code false} if the callback 102 * cannot be registered with this task because the result for 103 * the desired {@link Uri} has already been discarded due to 104 * low-memory. 105 * @throws NullPointerException if either argument is {@code null} 106 */ 107 public boolean addCallback(Uri uri, ItemLoadedCallback callback) { 108 if (Log.isLoggable(TAG, Log.DEBUG)) { 109 Log.d(TAG, "Adding image callback " + callback); 110 } 111 if (uri == null) { 112 throw new NullPointerException("uri is null"); 113 } 114 if (callback == null) { 115 throw new NullPointerException("callback is null"); 116 } 117 Set<ItemLoadedCallback> callbacks = mCallbacks.get(uri); 118 if (callbacks == null) { 119 callbacks = new HashSet<ItemLoadedCallback>(4); 120 mCallbacks.put(uri, callbacks); 121 } 122 callbacks.add(callback); 123 return true; 124 } 125 126 public void cancelCallback(ItemLoadedCallback callback) { 127 if (Log.isLoggable(TAG, Log.DEBUG)) { 128 Log.d(TAG, "Cancelling image callback " + callback); 129 } 130 for (final Uri uri : mCallbacks.keySet()) { 131 final Set<ItemLoadedCallback> callbacks = mCallbacks.get(uri); 132 callbacks.remove(callback); 133 } 134 } 135 136 /** 137 * Copies the elements of a {@link Set} into an {@link ArrayList}. 138 */ 139 @SuppressWarnings("unchecked") 140 protected static <T> ArrayList<T> asList(Set<T> source) { 141 return new ArrayList<T>(source); 142 } 143 144 /** 145 * {@link ThreadFactory} which sets a meaningful name for the thread. 146 */ 147 private static class BackgroundLoaderThreadFactory implements ThreadFactory { 148 private final AtomicInteger mCount = new AtomicInteger(1); 149 private final String mTag; 150 151 public BackgroundLoaderThreadFactory(String tag) { 152 mTag = tag; 153 } 154 155 public Thread newThread(final Runnable r) { 156 Thread t = new Thread(r, mTag + "-" + mCount.getAndIncrement()); 157 158 if (t.getPriority() != Thread.MIN_PRIORITY) 159 t.setPriority(Thread.MIN_PRIORITY); 160 161 return t; 162 } 163 } 164 } 165