RecentsPanelView.java revision 8078d8c8a282ca81344febe7256f63b1e805e3aa
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.animation.Animator; 20import android.animation.LayoutTransition; 21import android.app.ActivityManager; 22import android.app.ActivityOptions; 23import android.content.Context; 24import android.content.Intent; 25import android.content.res.Configuration; 26import android.content.res.Resources; 27import android.graphics.Bitmap; 28import android.graphics.Matrix; 29import android.graphics.Shader.TileMode; 30import android.graphics.drawable.BitmapDrawable; 31import android.graphics.drawable.Drawable; 32import android.net.Uri; 33import android.provider.Settings; 34import android.util.AttributeSet; 35import android.util.Log; 36import android.view.Display; 37import android.view.KeyEvent; 38import android.view.LayoutInflater; 39import android.view.MenuItem; 40import android.view.MotionEvent; 41import android.view.View; 42import android.view.ViewGroup; 43import android.view.WindowManager; 44import android.view.accessibility.AccessibilityEvent; 45import android.view.animation.AnimationUtils; 46import android.widget.AdapterView; 47import android.widget.AdapterView.OnItemClickListener; 48import android.widget.BaseAdapter; 49import android.widget.FrameLayout; 50import android.widget.HorizontalScrollView; 51import android.widget.ImageView; 52import android.widget.ImageView.ScaleType; 53import android.widget.PopupMenu; 54import android.widget.ScrollView; 55import android.widget.TextView; 56 57import com.android.systemui.R; 58import com.android.systemui.statusbar.BaseStatusBar; 59import com.android.systemui.statusbar.phone.PhoneStatusBar; 60import com.android.systemui.statusbar.tablet.StatusBarPanel; 61import com.android.systemui.statusbar.tablet.TabletStatusBar; 62 63import java.util.ArrayList; 64 65public class RecentsPanelView extends FrameLayout implements OnItemClickListener, RecentsCallback, 66 StatusBarPanel, Animator.AnimatorListener, View.OnTouchListener { 67 static final String TAG = "RecentsPanelView"; 68 static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false; 69 private Context mContext; 70 private BaseStatusBar mBar; 71 private View mRecentsScrim; 72 private View mRecentsNoApps; 73 private ViewGroup mRecentsContainer; 74 private StatusBarTouchProxy mStatusBarTouchProxy; 75 76 private boolean mShowing; 77 private boolean mWaitingToShow; 78 private boolean mWaitingToShowAnimated; 79 private boolean mReadyToShow; 80 private int mNumItemsWaitingForThumbnailsAndIcons; 81 private Choreographer mChoreo; 82 OnRecentsPanelVisibilityChangedListener mVisibilityChangedListener; 83 84 private RecentTasksLoader mRecentTasksLoader; 85 private ArrayList<TaskDescription> mRecentTaskDescriptions; 86 private Runnable mPreloadTasksRunnable; 87 private boolean mRecentTasksDirty = true; 88 private TaskDescriptionAdapter mListAdapter; 89 private int mThumbnailWidth; 90 private boolean mFitThumbnailToXY; 91 92 public static interface OnRecentsPanelVisibilityChangedListener { 93 public void onRecentsPanelVisibilityChanged(boolean visible); 94 } 95 96 public static interface RecentsScrollView { 97 public int numItemsInOneScreenful(); 98 public void setAdapter(TaskDescriptionAdapter adapter); 99 public void setCallback(RecentsCallback callback); 100 public void setMinSwipeAlpha(float minAlpha); 101 } 102 103 private final class OnLongClickDelegate implements View.OnLongClickListener { 104 View mOtherView; 105 OnLongClickDelegate(View other) { 106 mOtherView = other; 107 } 108 public boolean onLongClick(View v) { 109 return mOtherView.performLongClick(); 110 } 111 } 112 113 /* package */ final static class ViewHolder { 114 View thumbnailView; 115 ImageView thumbnailViewImage; 116 Bitmap thumbnailViewImageBitmap; 117 ImageView iconView; 118 TextView labelView; 119 TextView descriptionView; 120 TaskDescription taskDescription; 121 boolean loadedThumbnailAndIcon; 122 } 123 124 /* package */ final class TaskDescriptionAdapter extends BaseAdapter { 125 private LayoutInflater mInflater; 126 127 public TaskDescriptionAdapter(Context context) { 128 mInflater = LayoutInflater.from(context); 129 } 130 131 public int getCount() { 132 return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0; 133 } 134 135 public Object getItem(int position) { 136 return position; // we only need the index 137 } 138 139 public long getItemId(int position) { 140 return position; // we just need something unique for this position 141 } 142 143 public View createView(ViewGroup parent) { 144 View convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false); 145 ViewHolder holder = new ViewHolder(); 146 holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail); 147 holder.thumbnailViewImage = 148 (ImageView) convertView.findViewById(R.id.app_thumbnail_image); 149 // If we set the default thumbnail now, we avoid an onLayout when we update 150 // the thumbnail later (if they both have the same dimensions) 151 if (mRecentTasksLoader != null) { 152 updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false); 153 } 154 holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon); 155 if (mRecentTasksLoader != null) { 156 holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon()); 157 } 158 holder.labelView = (TextView) convertView.findViewById(R.id.app_label); 159 holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description); 160 161 convertView.setTag(holder); 162 return convertView; 163 } 164 165 public View getView(int position, View convertView, ViewGroup parent) { 166 if (convertView == null) { 167 convertView = createView(parent); 168 } 169 ViewHolder holder = (ViewHolder) convertView.getTag(); 170 171 // index is reverse since most recent appears at the bottom... 172 final int index = mRecentTaskDescriptions.size() - position - 1; 173 174 final TaskDescription td = mRecentTaskDescriptions.get(index); 175 176 holder.labelView.setText(td.getLabel()); 177 holder.thumbnailView.setContentDescription(td.getLabel()); 178 holder.loadedThumbnailAndIcon = td.isLoaded(); 179 if (td.isLoaded()) { 180 updateThumbnail(holder, td.getThumbnail(), true, false); 181 updateIcon(holder, td.getIcon(), true, false); 182 mNumItemsWaitingForThumbnailsAndIcons--; 183 } 184 185 holder.thumbnailView.setTag(td); 186 holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView)); 187 holder.taskDescription = td; 188 return convertView; 189 } 190 191 public void recycleView(View v) { 192 ViewHolder holder = (ViewHolder) v.getTag(); 193 updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false); 194 holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon()); 195 holder.iconView.setVisibility(INVISIBLE); 196 holder.labelView.setText(null); 197 holder.thumbnailView.setContentDescription(null); 198 holder.thumbnailView.setTag(null); 199 holder.thumbnailView.setOnLongClickListener(null); 200 holder.thumbnailView.setVisibility(INVISIBLE); 201 holder.taskDescription = null; 202 holder.loadedThumbnailAndIcon = false; 203 } 204 } 205 206 public int numItemsInOneScreenful() { 207 if (mRecentsContainer instanceof RecentsScrollView){ 208 RecentsScrollView scrollView 209 = (RecentsScrollView) mRecentsContainer; 210 return scrollView.numItemsInOneScreenful(); 211 } else { 212 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 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 private boolean pointInside(int x, int y, View v) { 226 final int l = v.getLeft(); 227 final int r = v.getRight(); 228 final int t = v.getTop(); 229 final int b = v.getBottom(); 230 return x >= l && x < r && y >= t && y < b; 231 } 232 233 public boolean isInContentArea(int x, int y) { 234 if (pointInside(x, y, mRecentsContainer)) { 235 return true; 236 } else if (mStatusBarTouchProxy != null && 237 pointInside(x, y, mStatusBarTouchProxy)) { 238 return true; 239 } else { 240 return false; 241 } 242 } 243 244 public void show(boolean show, boolean animate) { 245 if (show) { 246 refreshRecentTasksList(null, true); 247 mWaitingToShow = true; 248 mWaitingToShowAnimated = animate; 249 showIfReady(); 250 } else { 251 show(show, animate, null, false); 252 } 253 } 254 255 private void showIfReady() { 256 // mWaitingToShow = there was a touch up on the recents button 257 // mReadyToShow = we've created views for the first screenful of items 258 if (mWaitingToShow && mReadyToShow) { // && mNumItemsWaitingForThumbnailsAndIcons <= 0 259 show(true, mWaitingToShowAnimated, null, false); 260 } 261 } 262 263 public void show(boolean show, boolean animate, 264 ArrayList<TaskDescription> recentTaskDescriptions, boolean firstScreenful) { 265 // For now, disable animations. We may want to re-enable in the future 266 animate = false; 267 if (show) { 268 // Need to update list of recent apps before we set visibility so this view's 269 // content description is updated before it gets focus for TalkBack mode 270 refreshRecentTasksList(recentTaskDescriptions, firstScreenful); 271 272 // if there are no apps, either bring up a "No recent apps" message, or just 273 // quit early 274 boolean noApps = (mRecentTaskDescriptions.size() == 0); 275 if (mRecentsNoApps != null) { 276 mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE); 277 } else { 278 if (noApps) { 279 if (DEBUG) Log.v(TAG, "Nothing to show"); 280 // Need to set recent tasks to dirty so that next time we load, we 281 // refresh the list of tasks 282 mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(); 283 mRecentTasksDirty = true; 284 285 mWaitingToShow = false; 286 mReadyToShow = false; 287 return; 288 } 289 } 290 } else { 291 // Need to set recent tasks to dirty so that next time we load, we 292 // refresh the list of tasks 293 mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(); 294 mRecentTasksDirty = true; 295 mWaitingToShow = false; 296 mReadyToShow = false; 297 } 298 if (animate) { 299 if (mShowing != show) { 300 mShowing = show; 301 if (show) { 302 setVisibility(View.VISIBLE); 303 } 304 mChoreo.startAnimation(show); 305 } 306 } else { 307 mShowing = show; 308 setVisibility(show ? View.VISIBLE : View.GONE); 309 mChoreo.jumpTo(show); 310 onAnimationEnd(null); 311 } 312 if (show) { 313 setFocusable(true); 314 setFocusableInTouchMode(true); 315 requestFocus(); 316 } 317 } 318 319 public void dismiss() { 320 hide(true); 321 } 322 323 public void hide(boolean animate) { 324 if (!animate) { 325 setVisibility(View.GONE); 326 } 327 if (mBar != null) { 328 mBar.animateCollapse(); 329 } 330 } 331 332 public void onAnimationCancel(Animator animation) { 333 } 334 335 public void onAnimationEnd(Animator animation) { 336 if (mShowing) { 337 final LayoutTransition transitioner = new LayoutTransition(); 338 ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner); 339 createCustomAnimations(transitioner); 340 } else { 341 ((ViewGroup)mRecentsContainer).setLayoutTransition(null); 342 clearRecentTasksList(); 343 } 344 } 345 346 public void onAnimationRepeat(Animator animation) { 347 } 348 349 public void onAnimationStart(Animator animation) { 350 } 351 352 /** 353 * We need to be aligned at the bottom. LinearLayout can't do this, so instead, 354 * let LinearLayout do all the hard work, and then shift everything down to the bottom. 355 */ 356 @Override 357 protected void onLayout(boolean changed, int l, int t, int r, int b) { 358 super.onLayout(changed, l, t, r, b); 359 mChoreo.setPanelHeight(mRecentsContainer.getHeight()); 360 } 361 362 @Override 363 public boolean dispatchHoverEvent(MotionEvent event) { 364 // Ignore hover events outside of this panel bounds since such events 365 // generate spurious accessibility events with the panel content when 366 // tapping outside of it, thus confusing the user. 367 final int x = (int) event.getX(); 368 final int y = (int) event.getY(); 369 if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { 370 return super.dispatchHoverEvent(event); 371 } 372 return true; 373 } 374 375 /** 376 * Whether the panel is showing, or, if it's animating, whether it will be 377 * when the animation is done. 378 */ 379 public boolean isShowing() { 380 return mShowing; 381 } 382 383 public void setBar(BaseStatusBar bar) { 384 mBar = bar; 385 386 } 387 388 public void setStatusBarView(View statusBarView) { 389 if (mStatusBarTouchProxy != null) { 390 mStatusBarTouchProxy.setStatusBar(statusBarView); 391 } 392 } 393 394 public void setRecentTasksLoader(RecentTasksLoader loader) { 395 mRecentTasksLoader = loader; 396 } 397 398 public void setOnVisibilityChangedListener(OnRecentsPanelVisibilityChangedListener l) { 399 mVisibilityChangedListener = l; 400 401 } 402 403 public void setVisibility(int visibility) { 404 if (mVisibilityChangedListener != null) { 405 mVisibilityChangedListener.onRecentsPanelVisibilityChanged(visibility == VISIBLE); 406 } 407 super.setVisibility(visibility); 408 } 409 410 public RecentsPanelView(Context context, AttributeSet attrs) { 411 this(context, attrs, 0); 412 } 413 414 public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) { 415 super(context, attrs, defStyle); 416 mContext = context; 417 updateValuesFromResources(); 418 } 419 420 public void updateValuesFromResources() { 421 final Resources res = mContext.getResources(); 422 mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width)); 423 mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy); 424 } 425 426 @Override 427 protected void onFinishInflate() { 428 super.onFinishInflate(); 429 mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 430 mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container); 431 mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy); 432 mListAdapter = new TaskDescriptionAdapter(mContext); 433 if (mRecentsContainer instanceof RecentsScrollView){ 434 RecentsScrollView scrollView 435 = (RecentsScrollView) mRecentsContainer; 436 scrollView.setAdapter(mListAdapter); 437 scrollView.setCallback(this); 438 } else { 439 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 440 } 441 442 mRecentsScrim = findViewById(R.id.recents_bg_protect); 443 mRecentsNoApps = findViewById(R.id.recents_no_apps); 444 mChoreo = new Choreographer(this, mRecentsScrim, mRecentsContainer, mRecentsNoApps, this); 445 446 if (mRecentsScrim != null) { 447 Display d = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)) 448 .getDefaultDisplay(); 449 if (!ActivityManager.isHighEndGfx(d)) { 450 mRecentsScrim.setBackgroundDrawable(null); 451 } else if (mRecentsScrim.getBackground() instanceof BitmapDrawable) { 452 // In order to save space, we make the background texture repeat in the Y direction 453 ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT); 454 } 455 } 456 457 mPreloadTasksRunnable = new Runnable() { 458 public void run() { 459 if (!mShowing) { 460 setVisibility(INVISIBLE); 461 refreshRecentTasksList(); 462 } 463 } 464 }; 465 } 466 467 public void setMinSwipeAlpha(float minAlpha) { 468 if (mRecentsContainer instanceof RecentsScrollView){ 469 RecentsScrollView scrollView 470 = (RecentsScrollView) mRecentsContainer; 471 scrollView.setMinSwipeAlpha(minAlpha); 472 } 473 } 474 475 private void createCustomAnimations(LayoutTransition transitioner) { 476 transitioner.setDuration(200); 477 transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0); 478 transitioner.setAnimator(LayoutTransition.DISAPPEARING, null); 479 } 480 481 private void updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim) { 482 if (icon != null) { 483 h.iconView.setImageDrawable(icon); 484 if (show && h.iconView.getVisibility() != View.VISIBLE) { 485 if (anim) { 486 h.iconView.setAnimation( 487 AnimationUtils.loadAnimation(mContext, R.anim.recent_appear)); 488 } 489 h.iconView.setVisibility(View.VISIBLE); 490 } 491 } 492 } 493 494 private void updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim) { 495 if (thumbnail != null) { 496 // Should remove the default image in the frame 497 // that this now covers, to improve scrolling speed. 498 // That can't be done until the anim is complete though. 499 h.thumbnailViewImage.setImageBitmap(thumbnail); 500 501 // scale the image to fill the full width of the ImageView. do this only if 502 // we haven't set a bitmap before, or if the bitmap size has changed 503 if (h.thumbnailViewImageBitmap == null || 504 h.thumbnailViewImageBitmap.getWidth() != thumbnail.getWidth() || 505 h.thumbnailViewImageBitmap.getHeight() != thumbnail.getHeight()) { 506 if (mFitThumbnailToXY) { 507 h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY); 508 } else { 509 Matrix scaleMatrix = new Matrix(); 510 float scale = mThumbnailWidth / (float) thumbnail.getWidth(); 511 scaleMatrix.setScale(scale, scale); 512 h.thumbnailViewImage.setScaleType(ScaleType.MATRIX); 513 h.thumbnailViewImage.setImageMatrix(scaleMatrix); 514 } 515 } 516 if (show && h.thumbnailView.getVisibility() != View.VISIBLE) { 517 if (anim) { 518 h.thumbnailView.setAnimation( 519 AnimationUtils.loadAnimation(mContext, R.anim.recent_appear)); 520 } 521 h.thumbnailView.setVisibility(View.VISIBLE); 522 } 523 h.thumbnailViewImageBitmap = thumbnail; 524 } 525 } 526 527 void onTaskThumbnailLoaded(TaskDescription td) { 528 synchronized (td) { 529 if (mRecentsContainer != null) { 530 ViewGroup container = mRecentsContainer; 531 if (container instanceof RecentsScrollView) { 532 container = (ViewGroup) container.findViewById( 533 R.id.recents_linear_layout); 534 } 535 // Look for a view showing this thumbnail, to update. 536 for (int i=0; i < container.getChildCount(); i++) { 537 View v = container.getChildAt(i); 538 if (v.getTag() instanceof ViewHolder) { 539 ViewHolder h = (ViewHolder)v.getTag(); 540 if (!h.loadedThumbnailAndIcon && h.taskDescription == td) { 541 // only fade in the thumbnail if recents is already visible-- we 542 // show it immediately otherwise 543 //boolean animateShow = mShowing && 544 // mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD; 545 boolean animateShow = false; 546 updateIcon(h, td.getIcon(), true, animateShow); 547 updateThumbnail(h, td.getThumbnail(), true, animateShow); 548 h.loadedThumbnailAndIcon = true; 549 mNumItemsWaitingForThumbnailsAndIcons--; 550 } 551 } 552 } 553 } 554 } 555 showIfReady(); 556 } 557 558 // additional optimization when we have sofware system buttons - start loading the recent 559 // tasks on touch down 560 @Override 561 public boolean onTouch(View v, MotionEvent ev) { 562 if (!mShowing) { 563 int action = ev.getAction() & MotionEvent.ACTION_MASK; 564 if (action == MotionEvent.ACTION_DOWN) { 565 // If we set our visibility to INVISIBLE here, we avoid an extra call to 566 // onLayout later when we become visible (because onLayout is always called 567 // when going from GONE) 568 post(mPreloadTasksRunnable); 569 } else if (action == MotionEvent.ACTION_CANCEL) { 570 setVisibility(GONE); 571 clearRecentTasksList(); 572 // Remove the preloader if we haven't called it yet 573 removeCallbacks(mPreloadTasksRunnable); 574 } else if (action == MotionEvent.ACTION_UP) { 575 // Remove the preloader if we haven't called it yet 576 removeCallbacks(mPreloadTasksRunnable); 577 if (!v.isPressed()) { 578 setVisibility(GONE); 579 clearRecentTasksList(); 580 } 581 } 582 } 583 return false; 584 } 585 586 public void clearRecentTasksList() { 587 // Clear memory used by screenshots 588 if (mRecentTaskDescriptions != null) { 589 mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(); 590 mRecentTaskDescriptions.clear(); 591 mListAdapter.notifyDataSetInvalidated(); 592 mRecentTasksDirty = true; 593 } 594 } 595 596 public void refreshRecentTasksList() { 597 refreshRecentTasksList(null, false); 598 } 599 600 private void refreshRecentTasksList( 601 ArrayList<TaskDescription> recentTasksList, boolean firstScreenful) { 602 if (mRecentTasksDirty) { 603 if (recentTasksList != null) { 604 mFirstScreenful = true; 605 onTasksLoaded(recentTasksList); 606 } else { 607 mFirstScreenful = true; 608 mRecentTasksLoader.loadTasksInBackground(); 609 } 610 mRecentTasksDirty = false; 611 } 612 } 613 614 boolean mFirstScreenful; 615 public void onTasksLoaded(ArrayList<TaskDescription> tasks) { 616 if (!mFirstScreenful && tasks.size() == 0) { 617 return; 618 } 619 mNumItemsWaitingForThumbnailsAndIcons = 620 mFirstScreenful ? tasks.size() : mRecentTaskDescriptions.size(); 621 if (mRecentTaskDescriptions == null) { 622 mRecentTaskDescriptions = new ArrayList(tasks); 623 } else { 624 mRecentTaskDescriptions.addAll(tasks); 625 } 626 mListAdapter.notifyDataSetInvalidated(); 627 updateUiElements(getResources().getConfiguration()); 628 mReadyToShow = true; 629 mFirstScreenful = false; 630 showIfReady(); 631 } 632 633 public ArrayList<TaskDescription> getRecentTasksList() { 634 return mRecentTaskDescriptions; 635 } 636 637 public boolean getFirstScreenful() { 638 return mFirstScreenful; 639 } 640 641 private void updateUiElements(Configuration config) { 642 final int items = mRecentTaskDescriptions.size(); 643 644 mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE); 645 646 // Set description for accessibility 647 int numRecentApps = mRecentTaskDescriptions.size(); 648 String recentAppsAccessibilityDescription; 649 if (numRecentApps == 0) { 650 recentAppsAccessibilityDescription = 651 getResources().getString(R.string.status_bar_no_recent_apps); 652 } else { 653 recentAppsAccessibilityDescription = getResources().getQuantityString( 654 R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps); 655 } 656 setContentDescription(recentAppsAccessibilityDescription); 657 } 658 659 public void handleOnClick(View view) { 660 ViewHolder holder = (ViewHolder)view.getTag(); 661 TaskDescription ad = holder.taskDescription; 662 final Context context = view.getContext(); 663 final ActivityManager am = (ActivityManager) 664 context.getSystemService(Context.ACTIVITY_SERVICE); 665 holder.thumbnailViewImage.setDrawingCacheEnabled(true); 666 Bitmap bm = holder.thumbnailViewImage.getDrawingCache(); 667 ActivityOptions opts = ActivityOptions.makeThumbnailScaleUpAnimation( 668 holder.thumbnailViewImage, bm, 0, 0, 669 new ActivityOptions.OnAnimationStartedListener() { 670 @Override public void onAnimationStarted() { 671 hide(true); 672 } 673 }); 674 if (ad.taskId >= 0) { 675 // This is an active task; it should just go to the foreground. 676 am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME, 677 opts.toBundle()); 678 } else { 679 Intent intent = ad.intent; 680 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 681 | Intent.FLAG_ACTIVITY_TASK_ON_HOME 682 | Intent.FLAG_ACTIVITY_NEW_TASK); 683 if (DEBUG) Log.v(TAG, "Starting activity " + intent); 684 context.startActivity(intent, opts.toBundle()); 685 } 686 holder.thumbnailViewImage.setDrawingCacheEnabled(false); 687 } 688 689 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 690 handleOnClick(view); 691 } 692 693 public void handleSwipe(View view) { 694 TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription; 695 if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel()); 696 mRecentTaskDescriptions.remove(ad); 697 698 // Handled by widget containers to enable LayoutTransitions properly 699 // mListAdapter.notifyDataSetChanged(); 700 701 if (mRecentTaskDescriptions.size() == 0) { 702 hide(false); 703 } 704 705 // Currently, either direction means the same thing, so ignore direction and remove 706 // the task. 707 final ActivityManager am = (ActivityManager) 708 mContext.getSystemService(Context.ACTIVITY_SERVICE); 709 am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS); 710 711 // Accessibility feedback 712 setContentDescription( 713 mContext.getString(R.string.accessibility_recents_item_dismissed, ad.getLabel())); 714 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 715 setContentDescription(null); 716 } 717 718 private void startApplicationDetailsActivity(String packageName) { 719 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 720 Uri.fromParts("package", packageName, null)); 721 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 722 getContext().startActivity(intent); 723 } 724 725 public void handleLongPress( 726 final View selectedView, final View anchorView, final View thumbnailView) { 727 thumbnailView.setSelected(true); 728 PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView); 729 popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu()); 730 popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 731 public boolean onMenuItemClick(MenuItem item) { 732 if (item.getItemId() == R.id.recent_remove_item) { 733 mRecentsContainer.removeViewInLayout(selectedView); 734 } else if (item.getItemId() == R.id.recent_inspect_item) { 735 ViewHolder viewHolder = (ViewHolder) selectedView.getTag(); 736 if (viewHolder != null) { 737 final TaskDescription ad = viewHolder.taskDescription; 738 startApplicationDetailsActivity(ad.packageName); 739 mBar.animateCollapse(); 740 } else { 741 throw new IllegalStateException("Oops, no tag on view " + selectedView); 742 } 743 } else { 744 return false; 745 } 746 return true; 747 } 748 }); 749 popup.setOnDismissListener(new PopupMenu.OnDismissListener() { 750 public void onDismiss(PopupMenu menu) { 751 thumbnailView.setSelected(false); 752 } 753 }); 754 popup.show(); 755 } 756} 757