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