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