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