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