RecentTasksLoader.java revision 4f0a49e6b9ad1b00972dbe8a751263aa6c482538
1/* 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 android.app.ActivityManager; 20import android.app.AppGlobals; 21import android.content.ComponentName; 22import android.content.Context; 23import android.content.Intent; 24import android.content.pm.ActivityInfo; 25import android.content.pm.IPackageManager; 26import android.content.pm.PackageManager; 27import android.content.pm.ResolveInfo; 28import android.content.res.Resources; 29import android.graphics.Bitmap; 30import android.graphics.drawable.BitmapDrawable; 31import android.graphics.drawable.Drawable; 32import android.os.AsyncTask; 33import android.os.Handler; 34import android.os.Process; 35import android.os.RemoteException; 36import android.os.UserHandle; 37import android.os.UserManager; 38import android.util.Log; 39import android.view.MotionEvent; 40import android.view.View; 41 42import com.android.systemui.R; 43import com.android.systemui.statusbar.phone.PhoneStatusBar; 44 45import java.util.ArrayList; 46import java.util.List; 47import java.util.concurrent.BlockingQueue; 48import java.util.concurrent.LinkedBlockingQueue; 49 50public class RecentTasksLoader implements View.OnTouchListener { 51 static final String TAG = "RecentTasksLoader"; 52 static final boolean DEBUG = PhoneStatusBar.DEBUG || false; 53 54 private static final int DISPLAY_TASKS = 20; 55 private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps 56 57 private Context mContext; 58 private RecentsPanelView mRecentsPanel; 59 60 private Object mFirstTaskLock = new Object(); 61 private TaskDescription mFirstTask; 62 private boolean mFirstTaskLoaded; 63 64 private AsyncTask<Void, ArrayList<TaskDescription>, Void> mTaskLoader; 65 private AsyncTask<Void, TaskDescription, Void> mThumbnailLoader; 66 private Handler mHandler; 67 68 private int mIconDpi; 69 private ColorDrawableWithDimensions mDefaultThumbnailBackground; 70 private ColorDrawableWithDimensions mDefaultIconBackground; 71 private int mNumTasksInFirstScreenful = Integer.MAX_VALUE; 72 73 private boolean mFirstScreenful; 74 private ArrayList<TaskDescription> mLoadedTasks; 75 76 private enum State { LOADING, LOADED, CANCELLED }; 77 private State mState = State.CANCELLED; 78 79 80 private static RecentTasksLoader sInstance; 81 public static RecentTasksLoader getInstance(Context context) { 82 if (sInstance == null) { 83 sInstance = new RecentTasksLoader(context); 84 } 85 return sInstance; 86 } 87 88 private RecentTasksLoader(Context context) { 89 mContext = context; 90 mHandler = new Handler(); 91 92 final Resources res = context.getResources(); 93 94 // get the icon size we want -- on tablets, we use bigger icons 95 boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets); 96 if (isTablet) { 97 ActivityManager activityManager = 98 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 99 mIconDpi = activityManager.getLauncherLargeIconDensity(); 100 } else { 101 mIconDpi = res.getDisplayMetrics().densityDpi; 102 } 103 104 // Render default icon (just a blank image) 105 int defaultIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.app_icon_size); 106 int iconSize = (int) (defaultIconSize * mIconDpi / res.getDisplayMetrics().densityDpi); 107 mDefaultIconBackground = new ColorDrawableWithDimensions(0x00000000, iconSize, iconSize); 108 109 // Render the default thumbnail background 110 int thumbnailWidth = 111 (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); 112 int thumbnailHeight = 113 (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); 114 int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background); 115 116 mDefaultThumbnailBackground = 117 new ColorDrawableWithDimensions(color, thumbnailWidth, thumbnailHeight); 118 } 119 120 public void setRecentsPanel(RecentsPanelView newRecentsPanel, RecentsPanelView caller) { 121 // Only allow clearing mRecentsPanel if the caller is the current recentsPanel 122 if (newRecentsPanel != null || mRecentsPanel == caller) { 123 mRecentsPanel = newRecentsPanel; 124 if (mRecentsPanel != null) { 125 mNumTasksInFirstScreenful = mRecentsPanel.numItemsInOneScreenful(); 126 } 127 } 128 } 129 130 public Drawable getDefaultThumbnail() { 131 return mDefaultThumbnailBackground; 132 } 133 134 public Drawable getDefaultIcon() { 135 return mDefaultIconBackground; 136 } 137 138 public ArrayList<TaskDescription> getLoadedTasks() { 139 return mLoadedTasks; 140 } 141 142 public void remove(TaskDescription td) { 143 mLoadedTasks.remove(td); 144 } 145 146 public boolean isFirstScreenful() { 147 return mFirstScreenful; 148 } 149 150 private boolean isCurrentHomeActivity(ComponentName component, ActivityInfo homeInfo) { 151 if (homeInfo == null) { 152 final PackageManager pm = mContext.getPackageManager(); 153 homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) 154 .resolveActivityInfo(pm, 0); 155 } 156 return homeInfo != null 157 && homeInfo.packageName.equals(component.getPackageName()) 158 && homeInfo.name.equals(component.getClassName()); 159 } 160 161 // Create an TaskDescription, returning null if the title or icon is null 162 TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent, 163 ComponentName origActivity, CharSequence description, int userId) { 164 Intent intent = new Intent(baseIntent); 165 if (origActivity != null) { 166 intent.setComponent(origActivity); 167 } 168 final PackageManager pm = mContext.getPackageManager(); 169 final IPackageManager ipm = AppGlobals.getPackageManager(); 170 intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) 171 | Intent.FLAG_ACTIVITY_NEW_TASK); 172 ResolveInfo resolveInfo = null; 173 try { 174 resolveInfo = ipm.resolveIntent(intent, null, 0, userId); 175 } catch (RemoteException re) { 176 } 177 if (resolveInfo != null) { 178 final ActivityInfo info = resolveInfo.activityInfo; 179 final String title = info.loadLabel(pm).toString(); 180 181 if (title != null && title.length() > 0) { 182 if (DEBUG) Log.v(TAG, "creating activity desc for id=" 183 + persistentTaskId + ", label=" + title); 184 185 TaskDescription item = new TaskDescription(taskId, 186 persistentTaskId, resolveInfo, baseIntent, info.packageName, 187 description, userId); 188 item.setLabel(title); 189 190 return item; 191 } else { 192 if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId); 193 } 194 } 195 return null; 196 } 197 198 void loadThumbnailAndIcon(TaskDescription td) { 199 final ActivityManager am = (ActivityManager) 200 mContext.getSystemService(Context.ACTIVITY_SERVICE); 201 final PackageManager pm = mContext.getPackageManager(); 202 Bitmap thumbnail = am.getTaskTopThumbnail(td.persistentTaskId); 203 Drawable icon = getFullResIcon(td.resolveInfo, pm); 204 if (td.userId != UserHandle.myUserId()) { 205 // Need to badge the icon 206 final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); 207 icon = um.getBadgedDrawableForUser(icon, new UserHandle(td.userId)); 208 } 209 if (DEBUG) Log.v(TAG, "Loaded bitmap for task " 210 + td + ": " + thumbnail); 211 synchronized (td) { 212 if (thumbnail != null) { 213 td.setThumbnail(new BitmapDrawable(mContext.getResources(), thumbnail)); 214 } else { 215 td.setThumbnail(mDefaultThumbnailBackground); 216 } 217 if (icon != null) { 218 td.setIcon(icon); 219 } 220 td.setLoaded(true); 221 } 222 } 223 224 Drawable getFullResDefaultActivityIcon() { 225 return getFullResIcon(Resources.getSystem(), 226 com.android.internal.R.mipmap.sym_def_app_icon); 227 } 228 229 Drawable getFullResIcon(Resources resources, int iconId) { 230 try { 231 return resources.getDrawableForDensity(iconId, mIconDpi); 232 } catch (Resources.NotFoundException e) { 233 return getFullResDefaultActivityIcon(); 234 } 235 } 236 237 private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) { 238 Resources resources; 239 try { 240 resources = packageManager.getResourcesForApplication( 241 info.activityInfo.applicationInfo); 242 } catch (PackageManager.NameNotFoundException e) { 243 resources = null; 244 } 245 if (resources != null) { 246 int iconId = info.activityInfo.getIconResource(); 247 if (iconId != 0) { 248 return getFullResIcon(resources, iconId); 249 } 250 } 251 return getFullResDefaultActivityIcon(); 252 } 253 254 Runnable mPreloadTasksRunnable = new Runnable() { 255 public void run() { 256 loadTasksInBackground(); 257 } 258 }; 259 260 // additional optimization when we have software system buttons - start loading the recent 261 // tasks on touch down 262 @Override 263 public boolean onTouch(View v, MotionEvent ev) { 264 int action = ev.getAction() & MotionEvent.ACTION_MASK; 265 if (action == MotionEvent.ACTION_DOWN) { 266 preloadRecentTasksList(); 267 } else if (action == MotionEvent.ACTION_CANCEL) { 268 cancelPreloadingRecentTasksList(); 269 } else if (action == MotionEvent.ACTION_UP) { 270 // Remove the preloader if we haven't called it yet 271 mHandler.removeCallbacks(mPreloadTasksRunnable); 272 if (!v.isPressed()) { 273 cancelLoadingThumbnailsAndIcons(); 274 } 275 276 } 277 return false; 278 } 279 280 public void preloadRecentTasksList() { 281 mHandler.post(mPreloadTasksRunnable); 282 } 283 284 public void cancelPreloadingRecentTasksList() { 285 cancelLoadingThumbnailsAndIcons(); 286 mHandler.removeCallbacks(mPreloadTasksRunnable); 287 } 288 289 public void cancelLoadingThumbnailsAndIcons(RecentsPanelView caller) { 290 // Only oblige this request if it comes from the current RecentsPanel 291 // (eg when you rotate, the old RecentsPanel request should be ignored) 292 if (mRecentsPanel == caller) { 293 cancelLoadingThumbnailsAndIcons(); 294 } 295 } 296 297 298 private void cancelLoadingThumbnailsAndIcons() { 299 if (mRecentsPanel != null && mRecentsPanel.isShowing()) { 300 return; 301 } 302 303 if (mTaskLoader != null) { 304 mTaskLoader.cancel(false); 305 mTaskLoader = null; 306 } 307 if (mThumbnailLoader != null) { 308 mThumbnailLoader.cancel(false); 309 mThumbnailLoader = null; 310 } 311 mLoadedTasks = null; 312 if (mRecentsPanel != null) { 313 mRecentsPanel.onTaskLoadingCancelled(); 314 } 315 mFirstScreenful = false; 316 mState = State.CANCELLED; 317 } 318 319 private void clearFirstTask() { 320 synchronized (mFirstTaskLock) { 321 mFirstTask = null; 322 mFirstTaskLoaded = false; 323 } 324 } 325 326 public void preloadFirstTask() { 327 Thread bgLoad = new Thread() { 328 public void run() { 329 TaskDescription first = loadFirstTask(); 330 synchronized(mFirstTaskLock) { 331 if (mCancelPreloadingFirstTask) { 332 clearFirstTask(); 333 } else { 334 mFirstTask = first; 335 mFirstTaskLoaded = true; 336 } 337 mPreloadingFirstTask = false; 338 } 339 } 340 }; 341 synchronized(mFirstTaskLock) { 342 if (!mPreloadingFirstTask) { 343 clearFirstTask(); 344 mPreloadingFirstTask = true; 345 bgLoad.start(); 346 } 347 } 348 } 349 350 public void cancelPreloadingFirstTask() { 351 synchronized(mFirstTaskLock) { 352 if (mPreloadingFirstTask) { 353 mCancelPreloadingFirstTask = true; 354 } else { 355 clearFirstTask(); 356 } 357 } 358 } 359 360 boolean mPreloadingFirstTask; 361 boolean mCancelPreloadingFirstTask; 362 public TaskDescription getFirstTask() { 363 while(true) { 364 synchronized(mFirstTaskLock) { 365 if (mFirstTaskLoaded) { 366 return mFirstTask; 367 } else if (!mFirstTaskLoaded && !mPreloadingFirstTask) { 368 mFirstTask = loadFirstTask(); 369 mFirstTaskLoaded = true; 370 return mFirstTask; 371 } 372 } 373 try { 374 Thread.sleep(3); 375 } catch (InterruptedException e) { 376 } 377 } 378 } 379 380 public TaskDescription loadFirstTask() { 381 final ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 382 383 final List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasksForUser(1, 384 ActivityManager.RECENT_IGNORE_UNAVAILABLE | ActivityManager.RECENT_INCLUDE_PROFILES, 385 UserHandle.CURRENT.getIdentifier()); 386 TaskDescription item = null; 387 if (recentTasks.size() > 0) { 388 ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); 389 390 Intent intent = new Intent(recentInfo.baseIntent); 391 if (recentInfo.origActivity != null) { 392 intent.setComponent(recentInfo.origActivity); 393 } 394 395 // Don't load the current home activity. 396 if (isCurrentHomeActivity(intent.getComponent(), null)) { 397 return null; 398 } 399 400 // Don't load ourselves 401 if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) { 402 return null; 403 } 404 405 item = createTaskDescription(recentInfo.id, 406 recentInfo.persistentId, recentInfo.baseIntent, 407 recentInfo.origActivity, recentInfo.description, 408 recentInfo.userId); 409 if (item != null) { 410 loadThumbnailAndIcon(item); 411 } 412 return item; 413 } 414 return null; 415 } 416 417 public void loadTasksInBackground() { 418 loadTasksInBackground(false); 419 } 420 public void loadTasksInBackground(final boolean zeroeth) { 421 if (mState != State.CANCELLED) { 422 return; 423 } 424 mState = State.LOADING; 425 mFirstScreenful = true; 426 427 final LinkedBlockingQueue<TaskDescription> tasksWaitingForThumbnails = 428 new LinkedBlockingQueue<TaskDescription>(); 429 mTaskLoader = new AsyncTask<Void, ArrayList<TaskDescription>, Void>() { 430 @Override 431 protected void onProgressUpdate(ArrayList<TaskDescription>... values) { 432 if (!isCancelled()) { 433 ArrayList<TaskDescription> newTasks = values[0]; 434 // do a callback to RecentsPanelView to let it know we have more values 435 // how do we let it know we're all done? just always call back twice 436 if (mRecentsPanel != null) { 437 mRecentsPanel.onTasksLoaded(newTasks, mFirstScreenful); 438 } 439 if (mLoadedTasks == null) { 440 mLoadedTasks = new ArrayList<TaskDescription>(); 441 } 442 mLoadedTasks.addAll(newTasks); 443 mFirstScreenful = false; 444 } 445 } 446 @Override 447 protected Void doInBackground(Void... params) { 448 // We load in two stages: first, we update progress with just the first screenful 449 // of items. Then, we update with the rest of the items 450 final int origPri = Process.getThreadPriority(Process.myTid()); 451 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 452 final PackageManager pm = mContext.getPackageManager(); 453 final ActivityManager am = (ActivityManager) 454 mContext.getSystemService(Context.ACTIVITY_SERVICE); 455 456 final List<ActivityManager.RecentTaskInfo> recentTasks = 457 am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE 458 | ActivityManager.RECENT_INCLUDE_PROFILES); 459 int numTasks = recentTasks.size(); 460 ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN) 461 .addCategory(Intent.CATEGORY_HOME).resolveActivityInfo(pm, 0); 462 463 boolean firstScreenful = true; 464 ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>(); 465 466 // skip the first task - assume it's either the home screen or the current activity. 467 final int first = 0; 468 for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) { 469 if (isCancelled()) { 470 break; 471 } 472 final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i); 473 474 Intent intent = new Intent(recentInfo.baseIntent); 475 if (recentInfo.origActivity != null) { 476 intent.setComponent(recentInfo.origActivity); 477 } 478 479 // Don't load the current home activity. 480 if (isCurrentHomeActivity(intent.getComponent(), homeInfo)) { 481 continue; 482 } 483 484 // Don't load ourselves 485 if (intent.getComponent().getPackageName().equals(mContext.getPackageName())) { 486 continue; 487 } 488 489 TaskDescription item = createTaskDescription(recentInfo.id, 490 recentInfo.persistentId, recentInfo.baseIntent, 491 recentInfo.origActivity, recentInfo.description, 492 recentInfo.userId); 493 494 if (item != null) { 495 while (true) { 496 try { 497 tasksWaitingForThumbnails.put(item); 498 break; 499 } catch (InterruptedException e) { 500 } 501 } 502 tasks.add(item); 503 if (firstScreenful && tasks.size() == mNumTasksInFirstScreenful) { 504 publishProgress(tasks); 505 tasks = new ArrayList<TaskDescription>(); 506 firstScreenful = false; 507 //break; 508 } 509 ++index; 510 } 511 } 512 513 if (!isCancelled()) { 514 publishProgress(tasks); 515 if (firstScreenful) { 516 // always should publish two updates 517 publishProgress(new ArrayList<TaskDescription>()); 518 } 519 } 520 521 while (true) { 522 try { 523 tasksWaitingForThumbnails.put(new TaskDescription()); 524 break; 525 } catch (InterruptedException e) { 526 } 527 } 528 529 Process.setThreadPriority(origPri); 530 return null; 531 } 532 }; 533 mTaskLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 534 loadThumbnailsAndIconsInBackground(tasksWaitingForThumbnails); 535 } 536 537 private void loadThumbnailsAndIconsInBackground( 538 final BlockingQueue<TaskDescription> tasksWaitingForThumbnails) { 539 // continually read items from tasksWaitingForThumbnails and load 540 // thumbnails and icons for them. finish thread when cancelled or there 541 // is a null item in tasksWaitingForThumbnails 542 mThumbnailLoader = new AsyncTask<Void, TaskDescription, Void>() { 543 @Override 544 protected void onProgressUpdate(TaskDescription... values) { 545 if (!isCancelled()) { 546 TaskDescription td = values[0]; 547 if (td.isNull()) { // end sentinel 548 mState = State.LOADED; 549 } else { 550 if (mRecentsPanel != null) { 551 mRecentsPanel.onTaskThumbnailLoaded(td); 552 } 553 } 554 } 555 } 556 @Override 557 protected Void doInBackground(Void... params) { 558 final int origPri = Process.getThreadPriority(Process.myTid()); 559 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 560 561 while (true) { 562 if (isCancelled()) { 563 break; 564 } 565 TaskDescription td = null; 566 while (td == null) { 567 try { 568 td = tasksWaitingForThumbnails.take(); 569 } catch (InterruptedException e) { 570 } 571 } 572 if (td.isNull()) { // end sentinel 573 publishProgress(td); 574 break; 575 } 576 loadThumbnailAndIcon(td); 577 578 publishProgress(td); 579 } 580 581 Process.setThreadPriority(origPri); 582 return null; 583 } 584 }; 585 mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 586 } 587} 588