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