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