2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.recent;
19 import android.app.ActivityManager;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ActivityInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.content.res.Resources;
27 import android.graphics.Bitmap;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.graphics.drawable.Drawable;
30 import android.os.AsyncTask;
31 import android.os.Handler;
32 import android.os.Process;
33 import android.os.UserHandle;
34 import android.util.Log;
35 import android.view.MotionEvent;
36 import android.view.View;
38 import com.android.systemui.R;
39 import com.android.systemui.statusbar.phone.PhoneStatusBar;
41 import java.util.ArrayList;
42 import java.util.List;
43 import java.util.concurrent.BlockingQueue;
44 import java.util.concurrent.LinkedBlockingQueue;
46 public class RecentTasksLoader implements View.OnTouchListener {
47 static final String TAG = "RecentTasksLoader";
48 static final boolean DEBUG = PhoneStatusBar.DEBUG || false;
50 private static final int DISPLAY_TASKS = 20;
51 private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
53 private Context mContext;
54 private RecentsPanelView mRecentsPanel;
56 private Object mFirstTaskLock = new Object();
57 private TaskDescription mFirstTask;
58 private boolean mFirstTaskLoaded;
60 private AsyncTask<Void, ArrayList<TaskDescription>, Void> mTaskLoader;
61 private AsyncTask<Void, TaskDescription, Void> mThumbnailLoader;
62 private Handler mHandler;
65 private ColorDrawableWithDimensions mDefaultThumbnailBackground;
66 private ColorDrawableWithDimensions mDefaultIconBackground;
67 private int mNumTasksInFirstScreenful = Integer.MAX_VALUE;
69 private boolean mFirstScreenful;
70 private ArrayList<TaskDescription> mLoadedTasks;
72 private enum State { LOADING, LOADED, CANCELLED };
73 private State mState = State.CANCELLED;
76 private static RecentTasksLoader sInstance;
77 public static RecentTasksLoader getInstance(Context context) {
78 if (sInstance == null) {
79 sInstance = new RecentTasksLoader(context);
84 private RecentTasksLoader(Context context) {
86 mHandler = new Handler();
88 final Resources res = context.getResources();
90 // get the icon size we want -- on tablets, we use bigger icons
91 boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
93 ActivityManager activityManager =
94 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
95 mIconDpi = activityManager.getLauncherLargeIconDensity();
97 mIconDpi = res.getDisplayMetrics().densityDpi;
100 // Render default icon (just a blank image)
101 int defaultIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.app_icon_size);
102 int iconSize = (int) (defaultIconSize * mIconDpi / res.getDisplayMetrics().densityDpi);
103 mDefaultIconBackground = new ColorDrawableWithDimensions(0x00000000, iconSize, iconSize);
105 // Render the default thumbnail background
107 (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
108 int thumbnailHeight =
109 (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
110 int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
112 mDefaultThumbnailBackground =
113 new ColorDrawableWithDimensions(color, thumbnailWidth, thumbnailHeight);
116 public void setRecentsPanel(RecentsPanelView newRecentsPanel, RecentsPanelView caller) {
117 // Only allow clearing mRecentsPanel if the caller is the current recentsPanel
118 if (newRecentsPanel != null || mRecentsPanel == caller) {
119 mRecentsPanel = newRecentsPanel;
120 if (mRecentsPanel != null) {
121 mNumTasksInFirstScreenful = mRecentsPanel.numItemsInOneScreenful();
126 public Drawable getDefaultThumbnail() {
127 return mDefaultThumbnailBackground;
130 public Drawable getDefaultIcon() {
131 return mDefaultIconBackground;
134 public ArrayList<TaskDescription> getLoadedTasks() {
138 public void remove(TaskDescription td) {
139 mLoadedTasks.remove(td);
142 public boolean isFirstScreenful() {
143 return mFirstScreenful;
146 private boolean isCurrentHomeActivity(ComponentName component, ActivityInfo homeInfo) {
147 if (homeInfo == null) {
148 final PackageManager pm = mContext.getPackageManager();
149 homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
150 .resolveActivityInfo(pm, 0);
152 return homeInfo != null
153 && homeInfo.packageName.equals(component.getPackageName())
154 && homeInfo.name.equals(component.getClassName());
157 // Create an TaskDescription, returning null if the title or icon is null
158 TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
159 ComponentName origActivity, CharSequence description) {
160 Intent intent = new Intent(baseIntent);
161 if (origActivity != null) {
162 intent.setComponent(origActivity);
164 final PackageManager pm = mContext.getPackageManager();
165 intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
166 | Intent.FLAG_ACTIVITY_NEW_TASK);
167 final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
168 if (resolveInfo != null) {
169 final ActivityInfo info = resolveInfo.activityInfo;
170 final String title = info.loadLabel(pm).toString();
172 if (title != null && title.length() > 0) {
173 if (DEBUG) Log.v(TAG, "creating activity desc for id="
174 + persistentTaskId + ", label=" + title);
176 TaskDescription item = new TaskDescription(taskId,
177 persistentTaskId, resolveInfo, baseIntent, info.packageName,
179 item.setLabel(title);
183 if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId);
189 void loadThumbnailAndIcon(TaskDescription td) {
190 final ActivityManager am = (ActivityManager)
191 mContext.getSystemService(Context.ACTIVITY_SERVICE);
192 final PackageManager pm = mContext.getPackageManager();
193 Bitmap thumbnail = am.getTaskTopThumbnail(td.persistentTaskId);
194 Drawable icon = getFullResIcon(td.resolveInfo, pm);
196 if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
197 + td + ": " + thumbnail);
199 if (thumbnail != null) {
200 td.setThumbnail(new BitmapDrawable(mContext.getResources(), thumbnail));
202 td.setThumbnail(mDefaultThumbnailBackground);
211 Drawable getFullResDefaultActivityIcon() {
212 return getFullResIcon(Resources.getSystem(),
213 com.android.internal.R.mipmap.sym_def_app_icon);
216 Drawable getFullResIcon(Resources resources, int iconId) {
218 return resources.getDrawableForDensity(iconId, mIconDpi);
219 } catch (Resources.NotFoundException e) {
220 return getFullResDefaultActivityIcon();
224 private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
227 resources = packageManager.getResourcesForApplication(
228 info.activityInfo.applicationInfo);
229 } catch (PackageManager.NameNotFoundException e) {
232 if (resources != null) {
233 int iconId = info.activityInfo.getIconResource();
235 return getFullResIcon(resources, iconId);
238 return getFullResDefaultActivityIcon();
241 Runnable mPreloadTasksRunnable = new Runnable() {
243 loadTasksInBackground();
247 // additional optimization when we have software system buttons - start loading the recent
248 // tasks on touch down
250 public boolean onTouch(View v, MotionEvent ev) {
251 int action = ev.getAction() & MotionEvent.ACTION_MASK;
252 if (action == MotionEvent.ACTION_DOWN) {
253 preloadRecentTasksList();
254 } else if (action == MotionEvent.ACTION_CANCEL) {
255 cancelPreloadingRecentTasksList();
256 } else if (action == MotionEvent.ACTION_UP) {
257 // Remove the preloader if we haven't called it yet
258 mHandler.removeCallbacks(mPreloadTasksRunnable);
259 if (!v.isPressed()) {
260 cancelLoadingThumbnailsAndIcons();
267 public void preloadRecentTasksList() {
268 mHandler.post(mPreloadTasksRunnable);
271 public void cancelPreloadingRecentTasksList() {
272 cancelLoadingThumbnailsAndIcons();
273 mHandler.removeCallbacks(mPreloadTasksRunnable);
276 public void cancelLoadingThumbnailsAndIcons(RecentsPanelView caller) {
277 // Only oblige this request if it comes from the current RecentsPanel
278 // (eg when you rotate, the old RecentsPanel request should be ignored)
279 if (mRecentsPanel == caller) {
280 cancelLoadingThumbnailsAndIcons();
285 private void cancelLoadingThumbnailsAndIcons() {
286 if (mRecentsPanel != null && mRecentsPanel.isShowing()) {
290 if (mTaskLoader != null) {
291 mTaskLoader.cancel(false);
294 if (mThumbnailLoader != null) {
295 mThumbnailLoader.cancel(false);
296 mThumbnailLoader = null;
299 if (mRecentsPanel != null) {
300 mRecentsPanel.onTaskLoadingCancelled();
302 mFirstScreenful = false;
303 mState = State.CANCELLED;
306 private void clearFirstTask() {
307 synchronized (mFirstTaskLock) {
309 mFirstTaskLoaded = false;
313 public void preloadFirstTask() {
314 Thread bgLoad = new Thread() {
316 TaskDescription first = loadFirstTask();
317 synchronized(mFirstTaskLock) {
318 if (mCancelPreloadingFirstTask) {
322 mFirstTaskLoaded = true;
324 mPreloadingFirstTask = false;
328 synchronized(mFirstTaskLock) {
329 if (!mPreloadingFirstTask) {
331 mPreloadingFirstTask = true;
337 public void cancelPreloadingFirstTask() {
338 synchronized(mFirstTaskLock) {
339 if (mPreloadingFirstTask) {
340 mCancelPreloadingFirstTask = true;
347 boolean mPreloadingFirstTask;
348 boolean mCancelPreloadingFirstTask;
349 public TaskDescription getFirstTask() {
351 synchronized(mFirstTaskLock) {
352 if (mFirstTaskLoaded) {
354 } else if (!mFirstTaskLoaded && !mPreloadingFirstTask) {
355 mFirstTask = loadFirstTask();
356 mFirstTaskLoaded = true;
362 } catch (InterruptedException e) {
367 public TaskDescription loadFirstTask() {
368 final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
370 final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasksForUser(1,
371 ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_RELATED,
372 UserHandle.CURRENT.getIdentifier());
373 TaskDescription item = null;
374 if (recentTasks.size() > 0) {
375 ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0);
377 Intent intent = new Intent(recentInfo.baseIntent);
378 if (recentInfo.origActivity != null) {
379 intent.setComponent(recentInfo.origActivity);
382 // Don't load the current home activity.
383 if (isCurrentHomeActivity(intent.getComponent(), null)) {
387 // Don't load ourselves
388 if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
392 item = createTaskDescription(recentInfo.id,
393 recentInfo.persistentId, recentInfo.baseIntent,
394 recentInfo.origActivity, recentInfo.description);
396 loadThumbnailAndIcon(item);
403 public void loadTasksInBackground() {
404 loadTasksInBackground(false);
406 public void loadTasksInBackground(final boolean zeroeth) {
407 if (mState != State.CANCELLED) {
410 mState = State.LOADING;
411 mFirstScreenful = true;
413 final LinkedBlockingQueue<TaskDescription> tasksWaitingForThumbnails =
414 new LinkedBlockingQueue<TaskDescription>();
415 mTaskLoader = new AsyncTask<Void, ArrayList<TaskDescription>, Void>() {
417 protected void onProgressUpdate(ArrayList<TaskDescription>... values) {
418 if (!isCancelled()) {
419 ArrayList<TaskDescription> newTasks = values[0];
420 // do a callback to RecentsPanelView to let it know we have more values
421 // how do we let it know we're all done? just always call back twice
422 if (mRecentsPanel != null) {
423 mRecentsPanel.onTasksLoaded(newTasks, mFirstScreenful);
425 if (mLoadedTasks == null) {
426 mLoadedTasks = new ArrayList<TaskDescription>();
428 mLoadedTasks.addAll(newTasks);
429 mFirstScreenful = false;
433 protected Void doInBackground(Void... params) {
434 // We load in two stages: first, we update progress with just the first screenful
435 // of items. Then, we update with the rest of the items
436 final int origPri = Process.getThreadPriority(Process.myTid());
437 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
438 final PackageManager pm = mContext.getPackageManager();
439 final ActivityManager am = (ActivityManager)
440 mContext.getSystemService(Context.ACTIVITY_SERVICE);
442 final List<ActivityManager.RecentTaskInfo> recentTasks =
443 am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE
444 | ActivityManager.RECENT_INCLUDE_RELATED);
445 int numTasks = recentTasks.size();
446 ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN)
447 .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0);
449 boolean firstScreenful = true;
450 ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
452 // skip the first task - assume it's either the home screen or the current activity.
454 for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
458 final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
460 Intent intent = new Intent(recentInfo.baseIntent);
461 if (recentInfo.origActivity != null) {
462 intent.setComponent(recentInfo.origActivity);
465 // Don't load the current home activity.
466 if (isCurrentHomeActivity(intent.getComponent(), homeInfo)) {
470 // Don't load ourselves
471 if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) {
475 TaskDescription item = createTaskDescription(recentInfo.id,
476 recentInfo.persistentId, recentInfo.baseIntent,
477 recentInfo.origActivity, recentInfo.description);
482 tasksWaitingForThumbnails.put(item);
484 } catch (InterruptedException e) {
488 if (firstScreenful && tasks.size() == mNumTasksInFirstScreenful) {
489 publishProgress(tasks);
490 tasks = new ArrayList<TaskDescription>();
491 firstScreenful = false;
498 if (!isCancelled()) {
499 publishProgress(tasks);
500 if (firstScreenful) {
501 // always should publish two updates
502 publishProgress(new ArrayList<TaskDescription>());
508 tasksWaitingForThumbnails.put(new TaskDescription());
510 } catch (InterruptedException e) {
514 Process.setThreadPriority(origPri);
518 mTaskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
519 loadThumbnailsAndIconsInBackground(tasksWaitingForThumbnails);
522 private void loadThumbnailsAndIconsInBackground(
523 final BlockingQueue<TaskDescription> tasksWaitingForThumbnails) {
524 // continually read items from tasksWaitingForThumbnails and load
525 // thumbnails and icons for them. finish thread when cancelled or there
526 // is a null item in tasksWaitingForThumbnails
527 mThumbnailLoader = new AsyncTask<Void, TaskDescription, Void>() {
529 protected void onProgressUpdate(TaskDescription... values) {
530 if (!isCancelled()) {
531 TaskDescription td = values[0];
532 if (td.isNull()) { // end sentinel
533 mState = State.LOADED;
535 if (mRecentsPanel != null) {
536 mRecentsPanel.onTaskThumbnailLoaded(td);
542 protected Void doInBackground(Void... params) {
543 final int origPri = Process.getThreadPriority(Process.myTid());
544 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
550 TaskDescription td = null;
553 td = tasksWaitingForThumbnails.take();
554 } catch (InterruptedException e) {
557 if (td.isNull()) { // end sentinel
561 loadThumbnailAndIcon(td);
566 Process.setThreadPriority(origPri);
570 mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);