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