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