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