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