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