blob: dcda9c28bfaff33b46eb91f9beab50bdce269e48 [file] [log] [blame]
Michael Jurkaab48b682011-09-12 15:39:45 -07001/*
2 * Copyright (C) 2011 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
17package com.android.systemui.recent;
18
19import java.util.ArrayList;
20import java.util.HashSet;
21import java.util.List;
22import java.util.Map;
23import java.util.Set;
24
25import android.app.ActivityManager;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.pm.ActivityInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.ResolveInfo;
32import android.content.res.Resources;
33import android.graphics.Bitmap;
34import android.graphics.Canvas;
35import android.graphics.drawable.Drawable;
36import android.os.AsyncTask;
37import android.os.Handler;
38import android.os.Process;
39import android.os.SystemClock;
40import android.util.DisplayMetrics;
41import android.util.Log;
42import android.util.LruCache;
43
44import com.android.systemui.R;
45import com.android.systemui.statusbar.phone.PhoneStatusBar;
46import com.android.systemui.statusbar.tablet.TabletStatusBar;
47
48public class RecentTasksLoader {
49 static final String TAG = "RecentTasksLoader";
50 static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
51
52 private static final int DISPLAY_TASKS = 20;
53 private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
54
55 private Context mContext;
56 private RecentsPanelView mRecentsPanel;
57
58 private AsyncTask<Void, Integer, Void> mThumbnailLoader;
59 private final Handler mHandler;
60
61 private int mIconDpi;
62 private Bitmap mDefaultThumbnailBackground;
63
64 public RecentTasksLoader(Context context) {
65 mContext = context;
66
67 final Resources res = context.getResources();
68
69 // get the icon size we want -- on tablets, we use bigger icons
70 boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
71 int density = res.getDisplayMetrics().densityDpi;
72 if (isTablet) {
73 if (density == DisplayMetrics.DENSITY_LOW) {
74 mIconDpi = DisplayMetrics.DENSITY_MEDIUM;
75 } else if (density == DisplayMetrics.DENSITY_MEDIUM) {
76 mIconDpi = DisplayMetrics.DENSITY_HIGH;
77 } else if (density == DisplayMetrics.DENSITY_HIGH) {
78 mIconDpi = DisplayMetrics.DENSITY_XHIGH;
79 } else if (density == DisplayMetrics.DENSITY_XHIGH) {
80 // We'll need to use a denser icon, or some sort of a mipmap
81 mIconDpi = DisplayMetrics.DENSITY_XHIGH;
82 }
83 } else {
84 mIconDpi = res.getDisplayMetrics().densityDpi;
85 }
86 mIconDpi = isTablet ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
87
88 // Render the default thumbnail background
Michael Jurka412cba82011-10-17 09:05:00 -070089 int width = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
90 int height = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
Michael Jurkaab48b682011-09-12 15:39:45 -070091 int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
92
93 mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
94 Canvas c = new Canvas(mDefaultThumbnailBackground);
95 c.drawColor(color);
96
97 // If we're using the cache, begin listening to the activity manager for
98 // updated thumbnails
99 final ActivityManager am = (ActivityManager)
100 mContext.getSystemService(Context.ACTIVITY_SERVICE);
101
102 mHandler = new Handler();
103 }
104
105 public void setRecentsPanel(RecentsPanelView recentsPanel) {
106 mRecentsPanel = recentsPanel;
107 }
108
Michael Jurka412cba82011-10-17 09:05:00 -0700109 public Bitmap getDefaultThumbnail() {
110 return mDefaultThumbnailBackground;
111 }
112
Michael Jurkaab48b682011-09-12 15:39:45 -0700113 // Create an TaskDescription, returning null if the title or icon is null, or if it's the
114 // home activity
115 TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
116 ComponentName origActivity, CharSequence description, ActivityInfo homeInfo) {
117 Intent intent = new Intent(baseIntent);
118 if (origActivity != null) {
119 intent.setComponent(origActivity);
120 }
121 final PackageManager pm = mContext.getPackageManager();
122 if (homeInfo == null) {
123 homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
124 .resolveActivityInfo(pm, 0);
125 }
126
127 intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
128 | Intent.FLAG_ACTIVITY_NEW_TASK);
129 final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
130 if (resolveInfo != null) {
131 final ActivityInfo info = resolveInfo.activityInfo;
132 final String title = info.loadLabel(pm).toString();
133 Drawable icon = getFullResIcon(resolveInfo, pm);
134
135 if (title != null && title.length() > 0 && icon != null) {
136 if (DEBUG) Log.v(TAG, "creating activity desc for id="
137 + persistentTaskId + ", label=" + title);
138
139 TaskDescription item = new TaskDescription(taskId,
140 persistentTaskId, resolveInfo, baseIntent, info.packageName,
141 description);
142 item.setLabel(title);
143 item.setIcon(icon);
144
145 // Don't load the current home activity.
146 if (homeInfo != null
147 && homeInfo.packageName.equals(intent.getComponent().getPackageName())
148 && homeInfo.name.equals(intent.getComponent().getClassName())) {
149 return null;
150 }
151
152 return item;
153 } else {
154 if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId);
155 }
156 }
157 return null;
158 }
159
160 void loadThumbnail(TaskDescription td) {
161 final ActivityManager am = (ActivityManager)
162 mContext.getSystemService(Context.ACTIVITY_SERVICE);
163 ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(td.persistentTaskId);
164
165 if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
166 + td + ": " + thumbs.mainThumbnail);
167 synchronized (td) {
168 if (thumbs != null && thumbs.mainThumbnail != null) {
169 td.setThumbnail(thumbs.mainThumbnail);
170 } else {
171 td.setThumbnail(mDefaultThumbnailBackground);
172 }
173 }
174 }
175
176 Drawable getFullResDefaultActivityIcon() {
177 return getFullResIcon(Resources.getSystem(),
178 com.android.internal.R.mipmap.sym_def_app_icon);
179 }
180
181 Drawable getFullResIcon(Resources resources, int iconId) {
182 try {
183 return resources.getDrawableForDensity(iconId, mIconDpi);
184 } catch (Resources.NotFoundException e) {
185 return getFullResDefaultActivityIcon();
186 }
187 }
188
189 private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
190 Resources resources;
191 try {
192 resources = packageManager.getResourcesForApplication(
193 info.activityInfo.applicationInfo);
194 } catch (PackageManager.NameNotFoundException e) {
195 resources = null;
196 }
197 if (resources != null) {
198 int iconId = info.activityInfo.getIconResource();
199 if (iconId != 0) {
200 return getFullResIcon(resources, iconId);
201 }
202 }
203 return getFullResDefaultActivityIcon();
204 }
205
206 public void cancelLoadingThumbnails() {
207 if (mThumbnailLoader != null) {
208 mThumbnailLoader.cancel(false);
209 mThumbnailLoader = null;
210 }
211 }
212
213 // return a snapshot of the current list of recent apps
214 ArrayList<TaskDescription> getRecentTasks() {
215 cancelLoadingThumbnails();
216
217 ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
218 final PackageManager pm = mContext.getPackageManager();
219 final ActivityManager am = (ActivityManager)
220 mContext.getSystemService(Context.ACTIVITY_SERVICE);
221
222 final List<ActivityManager.RecentTaskInfo> recentTasks =
223 am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
224
225 ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
226 .resolveActivityInfo(pm, 0);
227
228 HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>();
229 int numTasks = recentTasks.size();
230
231 // skip the first task - assume it's either the home screen or the current activity.
232 final int first = 1;
233 recentTasksToKeepInCache.add(recentTasks.get(0).persistentId);
234 for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
235 final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
236
237 TaskDescription item = createTaskDescription(recentInfo.id,
238 recentInfo.persistentId, recentInfo.baseIntent,
239 recentInfo.origActivity, recentInfo.description, homeInfo);
240
241 if (item != null) {
242 tasks.add(item);
243 ++index;
244 }
245 }
246
247 // when we're not using the TaskDescription cache, we load the thumbnails in the
248 // background
249 loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks));
250 return tasks;
251 }
252
253 private void loadThumbnailsInBackground(final ArrayList<TaskDescription> descriptions) {
254 if (descriptions.size() > 0) {
255 if (DEBUG) Log.v(TAG, "Showing " + descriptions.size() + " tasks");
256 loadThumbnail(descriptions.get(0));
257 if (descriptions.size() > 1) {
258 mThumbnailLoader = new AsyncTask<Void, Integer, Void>() {
259 @Override
260 protected void onProgressUpdate(Integer... values) {
261 final TaskDescription td = descriptions.get(values[0]);
262 if (!isCancelled()) {
263 mRecentsPanel.onTaskThumbnailLoaded(td);
264 }
265 // This is to prevent the loader thread from getting ahead
266 // of our UI updates.
267 mHandler.post(new Runnable() {
268 @Override public void run() {
269 synchronized (td) {
270 td.notifyAll();
271 }
272 }
273 });
274 }
275
276 @Override
277 protected Void doInBackground(Void... params) {
278 final int origPri = Process.getThreadPriority(Process.myTid());
279 Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
280 long nextTime = SystemClock.uptimeMillis();
281 for (int i=1; i<descriptions.size(); i++) {
282 TaskDescription td = descriptions.get(i);
283 loadThumbnail(td);
284 long now = SystemClock.uptimeMillis();
Michael Jurka412cba82011-10-17 09:05:00 -0700285 nextTime += 0;
Michael Jurkaab48b682011-09-12 15:39:45 -0700286 if (nextTime > now) {
287 try {
288 Thread.sleep(nextTime-now);
289 } catch (InterruptedException e) {
290 }
291 }
292
293 if (isCancelled()) {
294 break;
295 }
296 synchronized (td) {
297 publishProgress(i);
298 try {
299 td.wait(500);
300 } catch (InterruptedException e) {
301 }
302 }
303 }
304 Process.setThreadPriority(origPri);
305 return null;
306 }
307 };
308 mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
309 }
310 }
311 }
312
313}