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