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 17 package com.android.systemui.recent; 18 19 import android.animation.Animator; 20 import android.animation.LayoutTransition; 21 import android.animation.TimeInterpolator; 22 import android.app.ActivityManager; 23 import android.app.ActivityManagerNative; 24 import android.app.ActivityOptions; 25 import android.app.TaskStackBuilder; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.res.Configuration; 29 import android.content.res.Resources; 30 import android.content.res.TypedArray; 31 import android.graphics.Bitmap; 32 import android.graphics.Matrix; 33 import android.graphics.Shader.TileMode; 34 import android.graphics.drawable.BitmapDrawable; 35 import android.graphics.drawable.Drawable; 36 import android.net.Uri; 37 import android.os.Bundle; 38 import android.os.RemoteException; 39 import android.os.UserHandle; 40 import android.provider.Settings; 41 import android.util.AttributeSet; 42 import android.util.Log; 43 import android.view.LayoutInflater; 44 import android.view.MenuItem; 45 import android.view.MotionEvent; 46 import android.view.View; 47 import android.view.ViewGroup; 48 import android.view.ViewPropertyAnimator; 49 import android.view.ViewRootImpl; 50 import android.view.accessibility.AccessibilityEvent; 51 import android.view.animation.AnimationUtils; 52 import android.view.animation.DecelerateInterpolator; 53 import android.widget.AdapterView; 54 import android.widget.AdapterView.OnItemClickListener; 55 import android.widget.BaseAdapter; 56 import android.widget.FrameLayout; 57 import android.widget.ImageView; 58 import android.widget.ImageView.ScaleType; 59 import android.widget.PopupMenu; 60 import android.widget.TextView; 61 62 import com.android.systemui.R; 63 import com.android.systemui.statusbar.BaseStatusBar; 64 import com.android.systemui.statusbar.phone.PhoneStatusBar; 65 import com.android.systemui.statusbar.tablet.StatusBarPanel; 66 import com.android.systemui.statusbar.tablet.TabletStatusBar; 67 68 import java.util.ArrayList; 69 70 public 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 = holder; 199 int translation = -getResources().getDimensionPixelSize( 200 R.dimen.status_bar_recents_app_icon_translate_distance); 201 final Configuration config = getResources().getConfiguration(); 202 if (config.orientation == Configuration.ORIENTATION_PORTRAIT) { 203 if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { 204 translation = -translation; 205 } 206 holder.iconView.setAlpha(0f); 207 holder.iconView.setTranslationX(translation); 208 holder.labelView.setAlpha(0f); 209 holder.labelView.setTranslationX(translation); 210 holder.calloutLine.setAlpha(0f); 211 holder.calloutLine.setTranslationX(translation); 212 } else { 213 holder.iconView.setAlpha(0f); 214 holder.iconView.setTranslationY(translation); 215 } 216 if (!mWaitingForWindowAnimation) { 217 animateInIconOfFirstTask(); 218 } 219 } 220 } 221 222 holder.thumbnailView.setTag(td); 223 holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView)); 224 holder.taskDescription = td; 225 return convertView; 226 } 227 228 public void recycleView(View v) { 229 ViewHolder holder = (ViewHolder) v.getTag(); 230 updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false); 231 holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon()); 232 holder.iconView.setVisibility(INVISIBLE); 233 holder.iconView.animate().cancel(); 234 holder.labelView.setText(null); 235 holder.labelView.animate().cancel(); 236 holder.thumbnailView.setContentDescription(null); 237 holder.thumbnailView.setTag(null); 238 holder.thumbnailView.setOnLongClickListener(null); 239 holder.thumbnailView.setVisibility(INVISIBLE); 240 holder.iconView.setAlpha(1f); 241 holder.iconView.setTranslationX(0f); 242 holder.iconView.setTranslationY(0f); 243 holder.labelView.setAlpha(1f); 244 holder.labelView.setTranslationX(0f); 245 holder.labelView.setTranslationY(0f); 246 if (holder.calloutLine != null) { 247 holder.calloutLine.setAlpha(1f); 248 holder.calloutLine.setTranslationX(0f); 249 holder.calloutLine.setTranslationY(0f); 250 holder.calloutLine.animate().cancel(); 251 } 252 holder.taskDescription = null; 253 holder.loadedThumbnailAndIcon = false; 254 } 255 } 256 257 public RecentsPanelView(Context context, AttributeSet attrs) { 258 this(context, attrs, 0); 259 } 260 261 public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) { 262 super(context, attrs, defStyle); 263 updateValuesFromResources(); 264 265 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecentsPanelView, 266 defStyle, 0); 267 268 mRecentItemLayoutId = a.getResourceId(R.styleable.RecentsPanelView_recentItemLayout, 0); 269 mRecentTasksLoader = RecentTasksLoader.getInstance(context); 270 a.recycle(); 271 } 272 273 public int numItemsInOneScreenful() { 274 if (mRecentsContainer instanceof RecentsScrollView){ 275 RecentsScrollView scrollView 276 = (RecentsScrollView) mRecentsContainer; 277 return scrollView.numItemsInOneScreenful(); 278 } else { 279 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 280 } 281 } 282 283 private boolean pointInside(int x, int y, View v) { 284 final int l = v.getLeft(); 285 final int r = v.getRight(); 286 final int t = v.getTop(); 287 final int b = v.getBottom(); 288 return x >= l && x < r && y >= t && y < b; 289 } 290 291 public boolean isInContentArea(int x, int y) { 292 if (pointInside(x, y, mRecentsContainer)) { 293 return true; 294 } else if (mStatusBarTouchProxy != null && 295 pointInside(x, y, mStatusBarTouchProxy)) { 296 return true; 297 } else { 298 return false; 299 } 300 } 301 302 public void show(boolean show) { 303 show(show, null, false, false); 304 } 305 306 public void show(boolean show, ArrayList<TaskDescription> recentTaskDescriptions, 307 boolean firstScreenful, boolean animateIconOfFirstTask) { 308 if (show && mCallUiHiddenBeforeNextReload) { 309 onUiHidden(); 310 recentTaskDescriptions = null; 311 mAnimateIconOfFirstTask = false; 312 mWaitingForWindowAnimation = false; 313 } else { 314 mAnimateIconOfFirstTask = animateIconOfFirstTask; 315 mWaitingForWindowAnimation = animateIconOfFirstTask; 316 } 317 if (show) { 318 mWaitingToShow = true; 319 refreshRecentTasksList(recentTaskDescriptions, firstScreenful); 320 showIfReady(); 321 } else { 322 showImpl(false); 323 } 324 } 325 326 private void showIfReady() { 327 // mWaitingToShow => there was a touch up on the recents button 328 // mRecentTaskDescriptions != null => we've created views for the first screenful of items 329 if (mWaitingToShow && mRecentTaskDescriptions != null) { 330 showImpl(true); 331 } 332 } 333 334 static void sendCloseSystemWindows(Context context, String reason) { 335 if (ActivityManagerNative.isSystemReady()) { 336 try { 337 ActivityManagerNative.getDefault().closeSystemDialogs(reason); 338 } catch (RemoteException e) { 339 } 340 } 341 } 342 343 private void showImpl(boolean show) { 344 sendCloseSystemWindows(mContext, BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS); 345 346 mShowing = show; 347 348 if (show) { 349 // if there are no apps, bring up a "No recent apps" message 350 boolean noApps = mRecentTaskDescriptions != null 351 && (mRecentTaskDescriptions.size() == 0); 352 mRecentsNoApps.setAlpha(1f); 353 mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE); 354 355 onAnimationEnd(null); 356 setFocusable(true); 357 setFocusableInTouchMode(true); 358 requestFocus(); 359 } else { 360 mWaitingToShow = false; 361 // call onAnimationEnd() and clearRecentTasksList() in onUiHidden() 362 mCallUiHiddenBeforeNextReload = true; 363 if (mPopup != null) { 364 mPopup.dismiss(); 365 } 366 } 367 } 368 369 protected void onAttachedToWindow () { 370 super.onAttachedToWindow(); 371 final ViewRootImpl root = getViewRootImpl(); 372 if (root != null) { 373 root.setDrawDuringWindowsAnimating(true); 374 } 375 } 376 377 public void onUiHidden() { 378 mCallUiHiddenBeforeNextReload = false; 379 if (!mShowing && mRecentTaskDescriptions != null) { 380 onAnimationEnd(null); 381 clearRecentTasksList(); 382 } 383 } 384 385 public void dismiss() { 386 ((RecentsActivity) mContext).dismissAndGoHome(); 387 } 388 389 public void dismissAndGoBack() { 390 ((RecentsActivity) mContext).dismissAndGoBack(); 391 } 392 393 public void onAnimationCancel(Animator animation) { 394 } 395 396 public void onAnimationEnd(Animator animation) { 397 if (mShowing) { 398 final LayoutTransition transitioner = new LayoutTransition(); 399 ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner); 400 createCustomAnimations(transitioner); 401 } else { 402 ((ViewGroup)mRecentsContainer).setLayoutTransition(null); 403 } 404 } 405 406 public void onAnimationRepeat(Animator animation) { 407 } 408 409 public void onAnimationStart(Animator animation) { 410 } 411 412 @Override 413 public boolean dispatchHoverEvent(MotionEvent event) { 414 // Ignore hover events outside of this panel bounds since such events 415 // generate spurious accessibility events with the panel content when 416 // tapping outside of it, thus confusing the user. 417 final int x = (int) event.getX(); 418 final int y = (int) event.getY(); 419 if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { 420 return super.dispatchHoverEvent(event); 421 } 422 return true; 423 } 424 425 /** 426 * Whether the panel is showing, or, if it's animating, whether it will be 427 * when the animation is done. 428 */ 429 public boolean isShowing() { 430 return mShowing; 431 } 432 433 public void setStatusBarView(View statusBarView) { 434 if (mStatusBarTouchProxy != null) { 435 mStatusBarTouchProxy.setStatusBar(statusBarView); 436 } 437 } 438 439 public void setRecentTasksLoader(RecentTasksLoader loader) { 440 mRecentTasksLoader = loader; 441 } 442 443 public void updateValuesFromResources() { 444 final Resources res = mContext.getResources(); 445 mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width)); 446 mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy); 447 } 448 449 @Override 450 protected void onFinishInflate() { 451 super.onFinishInflate(); 452 453 mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container); 454 mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy); 455 mListAdapter = new TaskDescriptionAdapter(mContext); 456 if (mRecentsContainer instanceof RecentsScrollView){ 457 RecentsScrollView scrollView 458 = (RecentsScrollView) mRecentsContainer; 459 scrollView.setAdapter(mListAdapter); 460 scrollView.setCallback(this); 461 } else { 462 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 463 } 464 465 mRecentsScrim = findViewById(R.id.recents_bg_protect); 466 mRecentsNoApps = findViewById(R.id.recents_no_apps); 467 468 if (mRecentsScrim != null) { 469 mHighEndGfx = ActivityManager.isHighEndGfx(); 470 if (!mHighEndGfx) { 471 mRecentsScrim.setBackground(null); 472 } else if (mRecentsScrim.getBackground() instanceof BitmapDrawable) { 473 // In order to save space, we make the background texture repeat in the Y direction 474 ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT); 475 } 476 } 477 } 478 479 public void setMinSwipeAlpha(float minAlpha) { 480 if (mRecentsContainer instanceof RecentsScrollView){ 481 RecentsScrollView scrollView 482 = (RecentsScrollView) mRecentsContainer; 483 scrollView.setMinSwipeAlpha(minAlpha); 484 } 485 } 486 487 private void createCustomAnimations(LayoutTransition transitioner) { 488 transitioner.setDuration(200); 489 transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0); 490 transitioner.setAnimator(LayoutTransition.DISAPPEARING, null); 491 } 492 493 private void updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim) { 494 if (icon != null) { 495 h.iconView.setImageDrawable(icon); 496 if (show && h.iconView.getVisibility() != View.VISIBLE) { 497 if (anim) { 498 h.iconView.setAnimation( 499 AnimationUtils.loadAnimation(mContext, R.anim.recent_appear)); 500 } 501 h.iconView.setVisibility(View.VISIBLE); 502 } 503 } 504 } 505 506 private void updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim) { 507 if (thumbnail != null) { 508 // Should remove the default image in the frame 509 // that this now covers, to improve scrolling speed. 510 // That can't be done until the anim is complete though. 511 h.thumbnailViewImage.setImageBitmap(thumbnail); 512 513 // scale the image to fill the full width of the ImageView. do this only if 514 // we haven't set a bitmap before, or if the bitmap size has changed 515 if (h.thumbnailViewImageBitmap == null || 516 h.thumbnailViewImageBitmap.getWidth() != thumbnail.getWidth() || 517 h.thumbnailViewImageBitmap.getHeight() != thumbnail.getHeight()) { 518 if (mFitThumbnailToXY) { 519 h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY); 520 } else { 521 Matrix scaleMatrix = new Matrix(); 522 float scale = mThumbnailWidth / (float) thumbnail.getWidth(); 523 scaleMatrix.setScale(scale, scale); 524 h.thumbnailViewImage.setScaleType(ScaleType.MATRIX); 525 h.thumbnailViewImage.setImageMatrix(scaleMatrix); 526 } 527 } 528 if (show && h.thumbnailView.getVisibility() != View.VISIBLE) { 529 if (anim) { 530 h.thumbnailView.setAnimation( 531 AnimationUtils.loadAnimation(mContext, R.anim.recent_appear)); 532 } 533 h.thumbnailView.setVisibility(View.VISIBLE); 534 } 535 h.thumbnailViewImageBitmap = thumbnail; 536 } 537 } 538 539 void onTaskThumbnailLoaded(TaskDescription td) { 540 synchronized (td) { 541 if (mRecentsContainer != null) { 542 ViewGroup container = mRecentsContainer; 543 if (container instanceof RecentsScrollView) { 544 container = (ViewGroup) container.findViewById( 545 R.id.recents_linear_layout); 546 } 547 // Look for a view showing this thumbnail, to update. 548 for (int i=0; i < container.getChildCount(); i++) { 549 View v = container.getChildAt(i); 550 if (v.getTag() instanceof ViewHolder) { 551 ViewHolder h = (ViewHolder)v.getTag(); 552 if (!h.loadedThumbnailAndIcon && h.taskDescription == td) { 553 // only fade in the thumbnail if recents is already visible-- we 554 // show it immediately otherwise 555 //boolean animateShow = mShowing && 556 // mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD; 557 boolean animateShow = false; 558 updateIcon(h, td.getIcon(), true, animateShow); 559 updateThumbnail(h, td.getThumbnail(), true, animateShow); 560 h.loadedThumbnailAndIcon = true; 561 } 562 } 563 } 564 } 565 } 566 showIfReady(); 567 } 568 569 private void animateInIconOfFirstTask() { 570 if (mItemToAnimateInWhenWindowAnimationIsFinished != null && 571 !mRecentTasksLoader.isFirstScreenful()) { 572 int timeSinceWindowAnimation = 573 (int) (System.currentTimeMillis() - mWindowAnimationStartTime); 574 final int minStartDelay = 150; 575 final int startDelay = Math.max(0, Math.min( 576 minStartDelay - timeSinceWindowAnimation, minStartDelay)); 577 final int duration = 250; 578 final ViewHolder holder = mItemToAnimateInWhenWindowAnimationIsFinished; 579 final TimeInterpolator cubic = new DecelerateInterpolator(1.5f); 580 FirstFrameAnimatorHelper.initializeDrawListener(holder.iconView); 581 for (View v : 582 new View[] { holder.iconView, holder.labelView, holder.calloutLine }) { 583 if (v != null) { 584 ViewPropertyAnimator vpa = v.animate().translationX(0).translationY(0) 585 .alpha(1f).setStartDelay(startDelay) 586 .setDuration(duration).setInterpolator(cubic); 587 FirstFrameAnimatorHelper h = new FirstFrameAnimatorHelper(vpa, v); 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 try { 713 context.startActivityAsUser(intent, opts, 714 new UserHandle(UserHandle.USER_CURRENT)); 715 } catch (SecurityException e) { 716 Log.e(TAG, "Recents does not have the permission to launch " + intent, e); 717 } 718 } 719 if (usingDrawingCache) { 720 holder.thumbnailViewImage.setDrawingCacheEnabled(false); 721 } 722 } 723 724 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 725 handleOnClick(view); 726 } 727 728 public void handleSwipe(View view) { 729 TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription; 730 if (ad == null) { 731 Log.v(TAG, "Not able to find activity description for swiped task; view=" + view + 732 " tag=" + view.getTag()); 733 return; 734 } 735 if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel()); 736 mRecentTaskDescriptions.remove(ad); 737 mRecentTasksLoader.remove(ad); 738 739 // Handled by widget containers to enable LayoutTransitions properly 740 // mListAdapter.notifyDataSetChanged(); 741 742 if (mRecentTaskDescriptions.size() == 0) { 743 dismissAndGoBack(); 744 } 745 746 // Currently, either direction means the same thing, so ignore direction and remove 747 // the task. 748 final ActivityManager am = (ActivityManager) 749 mContext.getSystemService(Context.ACTIVITY_SERVICE); 750 if (am != null) { 751 am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS); 752 753 // Accessibility feedback 754 setContentDescription( 755 mContext.getString(R.string.accessibility_recents_item_dismissed, ad.getLabel())); 756 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 757 setContentDescription(null); 758 } 759 } 760 761 private void startApplicationDetailsActivity(String packageName) { 762 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 763 Uri.fromParts("package", packageName, null)); 764 intent.setComponent(intent.resolveActivity(mContext.getPackageManager())); 765 TaskStackBuilder.create(getContext()) 766 .addNextIntentWithParentStack(intent).startActivities(); 767 } 768 769 public boolean onInterceptTouchEvent(MotionEvent ev) { 770 if (mPopup != null) { 771 return true; 772 } else { 773 return super.onInterceptTouchEvent(ev); 774 } 775 } 776 777 public void handleLongPress( 778 final View selectedView, final View anchorView, final View thumbnailView) { 779 thumbnailView.setSelected(true); 780 final PopupMenu popup = 781 new PopupMenu(mContext, anchorView == null ? selectedView : anchorView); 782 mPopup = popup; 783 popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu()); 784 popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 785 public boolean onMenuItemClick(MenuItem item) { 786 if (item.getItemId() == R.id.recent_remove_item) { 787 mRecentsContainer.removeViewInLayout(selectedView); 788 } else if (item.getItemId() == R.id.recent_inspect_item) { 789 ViewHolder viewHolder = (ViewHolder) selectedView.getTag(); 790 if (viewHolder != null) { 791 final TaskDescription ad = viewHolder.taskDescription; 792 startApplicationDetailsActivity(ad.packageName); 793 show(false); 794 } else { 795 throw new IllegalStateException("Oops, no tag on view " + selectedView); 796 } 797 } else { 798 return false; 799 } 800 return true; 801 } 802 }); 803 popup.setOnDismissListener(new PopupMenu.OnDismissListener() { 804 public void onDismiss(PopupMenu menu) { 805 thumbnailView.setSelected(false); 806 mPopup = null; 807 } 808 }); 809 popup.show(); 810 } 811 } 812