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