RecentsPanelView.java revision 6f90f2a1aab5c4e56012b65b788b84370e231bc6
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 java.util.ArrayList; 20import java.util.List; 21 22import android.animation.Animator; 23import android.animation.LayoutTransition; 24import android.app.ActivityManager; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.ActivityInfo; 28import android.content.pm.PackageManager; 29import android.content.pm.ResolveInfo; 30import android.content.res.Configuration; 31import android.content.res.Resources; 32import android.graphics.Bitmap; 33import android.graphics.Canvas; 34import android.graphics.Matrix; 35import android.graphics.Paint; 36import android.graphics.RectF; 37import android.graphics.Shader.TileMode; 38import android.graphics.drawable.BitmapDrawable; 39import android.graphics.drawable.Drawable; 40import android.graphics.drawable.StateListDrawable; 41import android.net.Uri; 42import android.os.AsyncTask; 43import android.os.Handler; 44import android.os.Process; 45import android.os.SystemClock; 46import android.provider.Settings; 47import android.util.AttributeSet; 48import android.util.DisplayMetrics; 49import android.util.Log; 50import android.view.KeyEvent; 51import android.view.LayoutInflater; 52import android.view.MenuItem; 53import android.view.MotionEvent; 54import android.view.View; 55import android.view.ViewGroup; 56import android.view.animation.AnimationUtils; 57import android.widget.AdapterView; 58import android.widget.BaseAdapter; 59import android.widget.FrameLayout; 60import android.widget.HorizontalScrollView; 61import android.widget.ImageView; 62import android.widget.PopupMenu; 63import android.widget.RelativeLayout; 64import android.widget.ScrollView; 65import android.widget.TextView; 66import android.widget.AdapterView.OnItemClickListener; 67 68import com.android.systemui.R; 69import com.android.systemui.statusbar.StatusBar; 70import com.android.systemui.statusbar.phone.PhoneStatusBar; 71import com.android.systemui.statusbar.tablet.StatusBarPanel; 72import com.android.systemui.statusbar.tablet.TabletStatusBar; 73 74public class RecentsPanelView extends RelativeLayout 75 implements OnItemClickListener, RecentsCallback, StatusBarPanel, Animator.AnimatorListener { 76 static final String TAG = "RecentsPanelView"; 77 static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false; 78 private static final int DISPLAY_TASKS = 20; 79 private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps 80 private StatusBar mBar; 81 private ArrayList<ActivityDescription> mActivityDescriptions; 82 private AsyncTask<Void, Integer, Void> mThumbnailLoader; 83 private int mIconDpi; 84 private View mRecentsScrim; 85 private View mRecentsGlowView; 86 private ViewGroup mRecentsContainer; 87 private Bitmap mDefaultThumbnailBackground; 88 89 private boolean mShowing; 90 private Choreographer mChoreo; 91 private View mRecentsDismissButton; 92 private ActivityDescriptionAdapter mListAdapter; 93 private final Handler mHandler = new Handler(); 94 95 /* package */ final class ActivityDescription { 96 final ActivityManager.RecentTaskInfo recentTaskInfo; 97 final ResolveInfo resolveInfo; 98 final int taskId; // application task id for curating apps 99 final int persistentTaskId; // persistent id 100 final Intent intent; // launch intent for application 101 final String packageName; // used to override animations (see onClick()) 102 final int position; // position in list 103 104 Matrix matrix; // arbitrary rotation matrix to correct orientation 105 106 private Bitmap mThumbnail; // generated by Activity.onCreateThumbnail() 107 private Drawable mIcon; // application package icon 108 private CharSequence mLabel; // application package label 109 110 public ActivityDescription(ActivityManager.RecentTaskInfo _recentInfo, 111 ResolveInfo _resolveInfo, Intent _intent, 112 int _pos, String _packageName) { 113 recentTaskInfo = _recentInfo; 114 resolveInfo = _resolveInfo; 115 intent = _intent; 116 taskId = _recentInfo.id; 117 persistentTaskId = _recentInfo.persistentId; 118 position = _pos; 119 packageName = _packageName; 120 } 121 122 public CharSequence getLabel() { 123 return mLabel; 124 } 125 126 public Drawable getIcon() { 127 return mIcon; 128 } 129 130 public void setThumbnail(Bitmap thumbnail) { 131 mThumbnail = compositeBitmap(mDefaultThumbnailBackground, thumbnail); 132 } 133 134 public Bitmap getThumbnail() { 135 return mThumbnail; 136 } 137 } 138 139 private final class OnLongClickDelegate implements View.OnLongClickListener { 140 View mOtherView; 141 OnLongClickDelegate(View other) { 142 mOtherView = other; 143 } 144 public boolean onLongClick(View v) { 145 return mOtherView.performLongClick(); 146 } 147 } 148 149 /* package */ final static class ViewHolder { 150 View thumbnailView; 151 ImageView thumbnailViewImage; 152 ImageView iconView; 153 TextView labelView; 154 TextView descriptionView; 155 ActivityDescription activityDescription; 156 } 157 158 /* package */ final class ActivityDescriptionAdapter extends BaseAdapter { 159 private LayoutInflater mInflater; 160 161 public ActivityDescriptionAdapter(Context context) { 162 mInflater = LayoutInflater.from(context); 163 } 164 165 public int getCount() { 166 return mActivityDescriptions != null ? mActivityDescriptions.size() : 0; 167 } 168 169 public Object getItem(int position) { 170 return position; // we only need the index 171 } 172 173 public long getItemId(int position) { 174 return position; // we just need something unique for this position 175 } 176 177 public View getView(int position, View convertView, ViewGroup parent) { 178 ViewHolder holder; 179 if (convertView == null) { 180 convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false); 181 holder = new ViewHolder(); 182 holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail); 183 holder.thumbnailViewImage = (ImageView) convertView.findViewById( 184 R.id.app_thumbnail_image); 185 holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon); 186 holder.labelView = (TextView) convertView.findViewById(R.id.app_label); 187 holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description); 188 189 /* StateListDrawable thumbnailForegroundDrawable = new StateListDrawable(); 190 thumbnailForegroundDrawable.addState(new int[] { android.R.attr.state_pressed }, 191 mPressedDrawable); 192 thumbnailForegroundDrawable.addState(new int[] { android.R.attr.state_selected }, 193 mPressedDrawable); 194 ((FrameLayout)holder.thumbnailView).setForeground(thumbnailForegroundDrawable);*/ 195 convertView.setTag(holder); 196 } else { 197 holder = (ViewHolder) convertView.getTag(); 198 } 199 200 // activityId is reverse since most recent appears at the bottom... 201 final int activityId = mActivityDescriptions.size() - position - 1; 202 203 final ActivityDescription activityDescription = mActivityDescriptions.get(activityId); 204 holder.thumbnailViewImage.setImageBitmap(activityDescription.getThumbnail()); 205 holder.iconView.setImageDrawable(activityDescription.getIcon()); 206 holder.labelView.setText(activityDescription.getLabel()); 207 holder.descriptionView.setText(activityDescription.recentTaskInfo.description); 208 holder.thumbnailView.setTag(activityDescription); 209 holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView)); 210 holder.activityDescription = activityDescription; 211 212 return convertView; 213 } 214 } 215 216 @Override 217 public boolean onKeyUp(int keyCode, KeyEvent event) { 218 if (keyCode == KeyEvent.KEYCODE_BACK && !event.isCanceled()) { 219 show(false, true); 220 return true; 221 } 222 return super.onKeyUp(keyCode, event); 223 } 224 225 public boolean isInContentArea(int x, int y) { 226 // use mRecentsContainer's exact bounds to determine horizontal position 227 final int l = mRecentsContainer.getLeft(); 228 final int r = mRecentsContainer.getRight(); 229 // use surrounding mRecentsGlowView's position in parent determine vertical bounds 230 final int t = mRecentsGlowView.getTop(); 231 final int b = mRecentsGlowView.getBottom(); 232 return x >= l && x < r && y >= t && y < b; 233 } 234 235 public void show(boolean show, boolean animate) { 236 if (animate) { 237 if (mShowing != show) { 238 mShowing = show; 239 if (show) { 240 setVisibility(View.VISIBLE); 241 } 242 mChoreo.startAnimation(show); 243 } 244 } else { 245 mShowing = show; 246 setVisibility(show ? View.VISIBLE : View.GONE); 247 mChoreo.jumpTo(show); 248 } 249 if (show) { 250 setFocusable(true); 251 setFocusableInTouchMode(true); 252 requestFocus(); 253 } 254 } 255 256 public void dismiss() { 257 hide(true); 258 } 259 260 public void hide(boolean animate) { 261 if (!animate) { 262 setVisibility(View.GONE); 263 } 264 if (mBar != null) { 265 mBar.animateCollapse(); 266 } 267 } 268 269 public void handleShowBackground(boolean show) { 270 if (show) { 271 mRecentsScrim.setBackgroundResource(R.drawable.status_bar_recents_background); 272 } else { 273 mRecentsScrim.setBackgroundDrawable(null); 274 } 275 } 276 277 public boolean isRecentsVisible() { 278 return getVisibility() == VISIBLE; 279 } 280 281 public void onAnimationCancel(Animator animation) { 282 } 283 284 public void onAnimationEnd(Animator animation) { 285 if (mShowing) { 286 final LayoutTransition transitioner = new LayoutTransition(); 287 ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner); 288 createCustomAnimations(transitioner); 289 } else { 290 ((ViewGroup)mRecentsContainer).setLayoutTransition(null); 291 } 292 } 293 294 public void onAnimationRepeat(Animator animation) { 295 } 296 297 public void onAnimationStart(Animator animation) { 298 } 299 300 301 /** 302 * We need to be aligned at the bottom. LinearLayout can't do this, so instead, 303 * let LinearLayout do all the hard work, and then shift everything down to the bottom. 304 */ 305 @Override 306 protected void onLayout(boolean changed, int l, int t, int r, int b) { 307 super.onLayout(changed, l, t, r, b); 308 mChoreo.setPanelHeight(mRecentsContainer.getHeight()); 309 } 310 311 @Override 312 public boolean dispatchHoverEvent(MotionEvent event) { 313 // Ignore hover events outside of this panel bounds since such events 314 // generate spurious accessibility events with the panel content when 315 // tapping outside of it, thus confusing the user. 316 final int x = (int) event.getX(); 317 final int y = (int) event.getY(); 318 if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { 319 return super.dispatchHoverEvent(event); 320 } 321 return true; 322 } 323 324 /** 325 * Whether the panel is showing, or, if it's animating, whether it will be 326 * when the animation is done. 327 */ 328 public boolean isShowing() { 329 return mShowing; 330 } 331 332 public void setBar(StatusBar bar) { 333 mBar = bar; 334 } 335 336 public RecentsPanelView(Context context, AttributeSet attrs) { 337 this(context, attrs, 0); 338 } 339 340 public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) { 341 super(context, attrs, defStyle); 342 343 Resources res = context.getResources(); 344 boolean xlarge = (res.getConfiguration().screenLayout 345 & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE; 346 347 mIconDpi = xlarge ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi; 348 349 int width = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_width); 350 int height = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_height); 351 int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background); 352 353 // Render the default thumbnail background 354 mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 355 Canvas c = new Canvas(mDefaultThumbnailBackground); 356 c.drawColor(color); 357 } 358 359 @Override 360 protected void onFinishInflate() { 361 super.onFinishInflate(); 362 mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 363 mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container); 364 mListAdapter = new ActivityDescriptionAdapter(mContext); 365 if (mRecentsContainer instanceof RecentsHorizontalScrollView){ 366 RecentsHorizontalScrollView scrollView 367 = (RecentsHorizontalScrollView) mRecentsContainer; 368 scrollView.setAdapter(mListAdapter); 369 scrollView.setCallback(this); 370 } else if (mRecentsContainer instanceof RecentsVerticalScrollView){ 371 RecentsVerticalScrollView scrollView 372 = (RecentsVerticalScrollView) mRecentsContainer; 373 scrollView.setAdapter(mListAdapter); 374 scrollView.setCallback(this); 375 } 376 else { 377 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 378 } 379 380 381 mRecentsGlowView = findViewById(R.id.recents_glow); 382 mRecentsScrim = (View) findViewById(R.id.recents_bg_protect); 383 mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this); 384 mRecentsDismissButton = findViewById(R.id.recents_dismiss_button); 385 mRecentsDismissButton.setOnClickListener(new OnClickListener() { 386 public void onClick(View v) { 387 hide(true); 388 } 389 }); 390 391 // In order to save space, we make the background texture repeat in the Y direction 392 if (mRecentsScrim != null && mRecentsScrim.getBackground() instanceof BitmapDrawable) { 393 ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT); 394 } 395 } 396 397 private void createCustomAnimations(LayoutTransition transitioner) { 398 transitioner.setDuration(200); 399 transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0); 400 transitioner.setAnimator(LayoutTransition.DISAPPEARING, null); 401 } 402 403 @Override 404 protected void onVisibilityChanged(View changedView, int visibility) { 405 super.onVisibilityChanged(changedView, visibility); 406 if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + changedView + ", " + visibility + ")"); 407 if (visibility == View.VISIBLE && changedView == this) { 408 refreshApplicationList(); 409 } 410 411 if (mRecentsContainer instanceof RecentsHorizontalScrollView) { 412 ((RecentsHorizontalScrollView) mRecentsContainer).onRecentsVisibilityChanged(); 413 } else if (mRecentsContainer instanceof RecentsVerticalScrollView) { 414 ((RecentsVerticalScrollView) mRecentsContainer).onRecentsVisibilityChanged(); 415 } else { 416 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 417 } 418 } 419 420 Drawable getFullResDefaultActivityIcon() { 421 return getFullResIcon(Resources.getSystem(), 422 com.android.internal.R.mipmap.sym_def_app_icon); 423 } 424 425 Drawable getFullResIcon(Resources resources, int iconId) { 426 try { 427 return resources.getDrawableForDensity(iconId, mIconDpi); 428 } catch (Resources.NotFoundException e) { 429 return getFullResDefaultActivityIcon(); 430 } 431 } 432 433 private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) { 434 Resources resources; 435 try { 436 resources = packageManager.getResourcesForApplication( 437 info.activityInfo.applicationInfo); 438 } catch (PackageManager.NameNotFoundException e) { 439 resources = null; 440 } 441 if (resources != null) { 442 int iconId = info.activityInfo.getIconResource(); 443 if (iconId != 0) { 444 return getFullResIcon(resources, iconId); 445 } 446 } 447 return getFullResDefaultActivityIcon(); 448 } 449 450 private ArrayList<ActivityDescription> getRecentTasks() { 451 ArrayList<ActivityDescription> activityDescriptions = new ArrayList<ActivityDescription>(); 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 459 ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) 460 .resolveActivityInfo(pm, 0); 461 462 int numTasks = recentTasks.size(); 463 464 // skip the first activity - assume it's either the home screen or the current app. 465 final int first = 1; 466 for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) { 467 final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i); 468 469 Intent intent = new Intent(recentInfo.baseIntent); 470 if (recentInfo.origActivity != null) { 471 intent.setComponent(recentInfo.origActivity); 472 } 473 474 // Skip the current home activity. 475 if (homeInfo != null 476 && homeInfo.packageName.equals(intent.getComponent().getPackageName()) 477 && homeInfo.name.equals(intent.getComponent().getClassName())) { 478 continue; 479 } 480 481 intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) 482 | Intent.FLAG_ACTIVITY_NEW_TASK); 483 final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); 484 if (resolveInfo != null) { 485 final ActivityInfo info = resolveInfo.activityInfo; 486 final String title = info.loadLabel(pm).toString(); 487 // Drawable icon = info.loadIcon(pm); 488 Drawable icon = getFullResIcon(resolveInfo, pm); 489 if (title != null && title.length() > 0 && icon != null) { 490 if (DEBUG) Log.v(TAG, "creating activity desc for id=" 491 + recentInfo.id + ", label=" + title); 492 ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails( 493 recentInfo.persistentId); 494 ActivityDescription item = new ActivityDescription(recentInfo, 495 resolveInfo, intent, index, info.packageName); 496 activityDescriptions.add(item); 497 ++index; 498 } else { 499 if (DEBUG) Log.v(TAG, "SKIPPING item " + recentInfo.id); 500 } 501 } 502 } 503 return activityDescriptions; 504 } 505 506 ActivityDescription findActivityDescription(int id) 507 { 508 ActivityDescription desc = null; 509 for (int i = 0; i < mActivityDescriptions.size(); i++) { 510 ActivityDescription item = mActivityDescriptions.get(i); 511 if (item != null && item.taskId == id) { 512 desc = item; 513 break; 514 } 515 } 516 return desc; 517 } 518 519 void loadActivityDescription(ActivityDescription ad, int index) { 520 final ActivityManager am = (ActivityManager) 521 mContext.getSystemService(Context.ACTIVITY_SERVICE); 522 final PackageManager pm = mContext.getPackageManager(); 523 ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails( 524 ad.recentTaskInfo.persistentId); 525 CharSequence label = ad.resolveInfo.activityInfo.loadLabel(pm); 526 Drawable icon = getFullResIcon(ad.resolveInfo, pm); 527 if (DEBUG) Log.v(TAG, "Loaded bitmap for #" + index + " in " 528 + ad + ": " + thumbs.mainThumbnail); 529 synchronized (ad) { 530 ad.mLabel = label; 531 ad.mIcon = icon; 532 ad.setThumbnail(thumbs != null ? thumbs.mainThumbnail : mDefaultThumbnailBackground); 533 } 534 } 535 536 void applyActivityDescription(ActivityDescription ad, int index, boolean anim) { 537 synchronized (ad) { 538 if (mRecentsContainer != null) { 539 ViewGroup container = mRecentsContainer; 540 if (container instanceof HorizontalScrollView 541 || container instanceof ScrollView) { 542 container = (ViewGroup)container.findViewById( 543 R.id.recents_linear_layout); 544 } 545 // Look for a view showing this thumbnail, to update. 546 for (int i=0; i<container.getChildCount(); i++) { 547 View v = container.getChildAt(i); 548 if (v.getTag() instanceof ViewHolder) { 549 ViewHolder h = (ViewHolder)v.getTag(); 550 if (h.activityDescription == ad) { 551 if (DEBUG) Log.v(TAG, "Updatating thumbnail #" + index + " in " 552 + h.activityDescription 553 + ": " + ad.getThumbnail()); 554 h.iconView.setImageDrawable(ad.getIcon()); 555 if (anim) { 556 h.iconView.setAnimation(AnimationUtils.loadAnimation( 557 mContext, R.anim.recent_appear)); 558 } 559 h.iconView.setVisibility(View.VISIBLE); 560 h.labelView.setText(ad.getLabel()); 561 if (anim) { 562 h.labelView.setAnimation(AnimationUtils.loadAnimation( 563 mContext, R.anim.recent_appear)); 564 } 565 h.labelView.setVisibility(View.VISIBLE); 566 Bitmap thumbnail = ad.getThumbnail(); 567 if (thumbnail != null) { 568 // Should remove the default image in the frame 569 // that this now covers, to improve scrolling speed. 570 // That can't be done until the anim is complete though. 571 h.thumbnailViewImage.setImageBitmap(thumbnail); 572 if (anim) { 573 h.thumbnailViewImage.setAnimation(AnimationUtils.loadAnimation( 574 mContext, R.anim.recent_appear)); 575 } 576 h.thumbnailViewImage.setVisibility(View.VISIBLE); 577 } 578 } 579 } 580 } 581 } 582 } 583 } 584 585 private void refreshApplicationList() { 586 if (mThumbnailLoader != null) { 587 mThumbnailLoader.cancel(false); 588 mThumbnailLoader = null; 589 } 590 mActivityDescriptions = getRecentTasks(); 591 for (ActivityDescription ad : mActivityDescriptions) { 592 ad.setThumbnail(mDefaultThumbnailBackground); 593 } 594 mListAdapter.notifyDataSetInvalidated(); 595 if (mActivityDescriptions.size() > 0) { 596 if (DEBUG) Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps"); 597 updateUiElements(getResources().getConfiguration()); 598 final ArrayList<ActivityDescription> descriptions = mActivityDescriptions; 599 loadActivityDescription(descriptions.get(0), 0); 600 applyActivityDescription(descriptions.get(0), 0, false); 601 if (descriptions.size() > 1) { 602 mThumbnailLoader = new AsyncTask<Void, Integer, Void>() { 603 @Override 604 protected void onProgressUpdate(Integer... values) { 605 final ActivityDescription ad = descriptions.get(values[0]); 606 if (!isCancelled()) { 607 applyActivityDescription(ad, values[0], true); 608 } 609 // This is to prevent the loader thread from getting ahead 610 // of our UI updates. 611 mHandler.post(new Runnable() { 612 @Override public void run() { 613 synchronized (ad) { 614 ad.notifyAll(); 615 } 616 } 617 }); 618 } 619 620 @Override 621 protected Void doInBackground(Void... params) { 622 final int origPri = Process.getThreadPriority(Process.myTid()); 623 Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE); 624 long nextTime = SystemClock.uptimeMillis(); 625 for (int i=1; i<descriptions.size(); i++) { 626 ActivityDescription ad = descriptions.get(i); 627 loadActivityDescription(ad, i); 628 long now = SystemClock.uptimeMillis(); 629 nextTime += 150; 630 if (nextTime > now) { 631 try { 632 Thread.sleep(nextTime-now); 633 } catch (InterruptedException e) { 634 } 635 } 636 if (isCancelled()) { 637 break; 638 } 639 synchronized (ad) { 640 publishProgress(i); 641 try { 642 ad.wait(500); 643 } catch (InterruptedException e) { 644 } 645 } 646 } 647 Process.setThreadPriority(origPri); 648 return null; 649 } 650 }; 651 mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 652 } 653 } else { 654 // Immediately hide this panel 655 if (DEBUG) Log.v(TAG, "Nothing to show"); 656 hide(false); 657 } 658 } 659 660 private Bitmap compositeBitmap(Bitmap background, Bitmap thumbnail) { 661 Bitmap outBitmap = background.copy(background.getConfig(), true); 662 if (thumbnail != null) { 663 Canvas canvas = new Canvas(outBitmap); 664 Paint paint = new Paint(); 665 paint.setAntiAlias(true); 666 paint.setFilterBitmap(true); 667 paint.setAlpha(255); 668 canvas.drawBitmap(thumbnail, null, 669 new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint); 670 canvas.setBitmap(null); 671 } 672 return outBitmap; 673 } 674 675 private void updateUiElements(Configuration config) { 676 final int items = mActivityDescriptions.size(); 677 678 mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE); 679 mRecentsGlowView.setVisibility(items > 0 ? View.VISIBLE : View.GONE); 680 } 681 682 public void handleOnClick(View view) { 683 ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription; 684 final Context context = view.getContext(); 685 final ActivityManager am = (ActivityManager) 686 context.getSystemService(Context.ACTIVITY_SERVICE); 687 if (ad.taskId >= 0) { 688 // This is an active task; it should just go to the foreground. 689 am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME); 690 } else { 691 Intent intent = ad.intent; 692 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 693 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 694 if (DEBUG) Log.v(TAG, "Starting activity " + intent); 695 context.startActivity(intent); 696 } 697 hide(true); 698 } 699 700 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 701 handleOnClick(view); 702 } 703 704 public void handleSwipe(View view) { 705 ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription; 706 if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel()); 707 mActivityDescriptions.remove(ad); 708 709 // Handled by widget containers to enable LayoutTransitions properly 710 // mListAdapter.notifyDataSetChanged(); 711 712 if (mActivityDescriptions.size() == 0) { 713 hide(false); 714 } 715 716 // Currently, either direction means the same thing, so ignore direction and remove 717 // the task. 718 final ActivityManager am = (ActivityManager) 719 mContext.getSystemService(Context.ACTIVITY_SERVICE); 720 am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS); 721 } 722 723 private void startApplicationDetailsActivity(String packageName) { 724 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 725 Uri.fromParts("package", packageName, null)); 726 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 727 getContext().startActivity(intent); 728 } 729 730 public void handleLongPress( 731 final View selectedView, final View anchorView, final View thumbnailView) { 732 thumbnailView.setSelected(true); 733 PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView); 734 popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu()); 735 popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 736 public boolean onMenuItemClick(MenuItem item) { 737 if (item.getItemId() == R.id.recent_remove_item) { 738 mRecentsContainer.removeViewInLayout(selectedView); 739 } else if (item.getItemId() == R.id.recent_inspect_item) { 740 ViewHolder viewHolder = (ViewHolder) selectedView.getTag(); 741 if (viewHolder != null) { 742 final ActivityDescription ad = viewHolder.activityDescription; 743 startApplicationDetailsActivity(ad.packageName); 744 mBar.animateCollapse(); 745 } else { 746 throw new IllegalStateException("Oops, no tag on view " + selectedView); 747 } 748 } else { 749 return false; 750 } 751 return true; 752 } 753 }); 754 popup.setOnDismissListener(new PopupMenu.OnDismissListener() { 755 public void onDismiss(PopupMenu menu) { 756 thumbnailView.setSelected(false); 757 } 758 }); 759 popup.show(); 760 } 761} 762