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