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