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