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