• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BackgroundLoaderManager(Context context)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      */
onLowMemory()81     public void onLowMemory() {
82         clear();
83     }
84 
clear()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      */
getTag()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      */
addCallback(Uri uri, ItemLoadedCallback callback)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 
cancelCallback(ItemLoadedCallback callback)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")
asList(Set<T> source)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 
BackgroundLoaderThreadFactory(String tag)151         public BackgroundLoaderThreadFactory(String tag) {
152             mTag = tag;
153         }
154 
newThread(final Runnable r)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