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