RecentsPanelView.java revision c6461ca5a0892d9b5a47649d49af69165e05b87f
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 java.util.ArrayList; 20import java.util.List; 21 22import android.animation.Animator; 23import android.animation.LayoutTransition; 24import android.app.ActivityManager; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.ActivityInfo; 28import android.content.pm.PackageManager; 29import android.content.pm.ResolveInfo; 30import android.content.res.Configuration; 31import android.content.res.Resources; 32import android.graphics.Bitmap; 33import android.graphics.BitmapFactory; 34import android.graphics.Canvas; 35import android.graphics.Matrix; 36import android.graphics.Paint; 37import android.graphics.Rect; 38import android.graphics.RectF; 39import android.graphics.Shader.TileMode; 40import android.graphics.drawable.BitmapDrawable; 41import android.graphics.drawable.Drawable; 42import android.net.Uri; 43import android.os.AsyncTask; 44import android.os.Handler; 45import android.os.Process; 46import android.os.SystemClock; 47import android.provider.Settings; 48import android.util.AttributeSet; 49import android.util.DisplayMetrics; 50import android.util.Log; 51import android.view.KeyEvent; 52import android.view.LayoutInflater; 53import android.view.MenuItem; 54import android.view.MotionEvent; 55import android.view.View; 56import android.view.ViewGroup; 57import android.view.animation.AnimationUtils; 58import android.widget.AdapterView; 59import android.widget.BaseAdapter; 60import android.widget.HorizontalScrollView; 61import android.widget.ImageView; 62import android.widget.PopupMenu; 63import android.widget.RelativeLayout; 64import android.widget.ScrollView; 65import android.widget.TextView; 66import android.widget.AdapterView.OnItemClickListener; 67 68import com.android.systemui.R; 69import com.android.systemui.statusbar.StatusBar; 70import com.android.systemui.statusbar.phone.PhoneStatusBar; 71import com.android.systemui.statusbar.tablet.StatusBarPanel; 72import com.android.systemui.statusbar.tablet.TabletStatusBar; 73 74public class RecentsPanelView extends RelativeLayout 75 implements OnItemClickListener, RecentsCallback, StatusBarPanel, Animator.AnimatorListener { 76 static final String TAG = "RecentsPanelView"; 77 static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false; 78 private static final int DISPLAY_TASKS = 20; 79 private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps 80 private StatusBar mBar; 81 private ArrayList<ActivityDescription> mActivityDescriptions; 82 private AsyncTask<Void, Integer, Void> mThumbnailLoader; 83 private int mIconDpi; 84 private View mRecentsScrim; 85 private View mRecentsGlowView; 86 private ViewGroup mRecentsContainer; 87 private Bitmap mAppThumbnailBackground; 88 89 private boolean mShowing; 90 private Choreographer mChoreo; 91 private View mRecentsDismissButton; 92 private ActivityDescriptionAdapter mListAdapter; 93 private final Handler mHandler = new Handler(); 94 95 /* package */ final class ActivityDescription { 96 final ActivityManager.RecentTaskInfo recentTaskInfo; 97 final ResolveInfo resolveInfo; 98 int taskId; // application task id for curating apps 99 Intent intent; // launch intent for application 100 Matrix matrix; // arbitrary rotation matrix to correct orientation 101 String packageName; // used to override animations (see onClick()) 102 int position; // position in list 103 104 private Bitmap mThumbnail; // generated by Activity.onCreateThumbnail() 105 private Drawable mIcon; // application package icon 106 private CharSequence mLabel; // application package label 107 108 public ActivityDescription(ActivityManager.RecentTaskInfo _recentInfo, 109 ResolveInfo _resolveInfo, Intent _intent, 110 int _id, int _pos, String _packageName) { 111 recentTaskInfo = _recentInfo; 112 resolveInfo = _resolveInfo; 113 intent = _intent; 114 taskId = _id; 115 position = _pos; 116 packageName = _packageName; 117 } 118 119 public CharSequence getLabel() { 120 return mLabel; 121 } 122 123 public Drawable getIcon() { 124 return mIcon; 125 } 126 127 public void setThumbnail(Bitmap thumbnail) { 128 mThumbnail = compositeBitmap(mAppThumbnailBackground, thumbnail); 129 } 130 131 public Bitmap getThumbnail() { 132 return mThumbnail; 133 } 134 } 135 136 private final class OnLongClickDelegate implements View.OnLongClickListener { 137 View mOtherView; 138 OnLongClickDelegate(View other) { 139 mOtherView = other; 140 } 141 public boolean onLongClick(View v) { 142 return mOtherView.performLongClick(); 143 } 144 } 145 146 /* package */ final static class ViewHolder { 147 View thumbnailView; 148 ImageView thumbnailViewImage; 149 ImageView iconView; 150 TextView labelView; 151 TextView descriptionView; 152 ActivityDescription activityDescription; 153 } 154 155 /* package */ final class ActivityDescriptionAdapter extends BaseAdapter { 156 private LayoutInflater mInflater; 157 158 public ActivityDescriptionAdapter(Context context) { 159 mInflater = LayoutInflater.from(context); 160 } 161 162 public int getCount() { 163 return mActivityDescriptions != null ? mActivityDescriptions.size() : 0; 164 } 165 166 public Object getItem(int position) { 167 return position; // we only need the index 168 } 169 170 public long getItemId(int position) { 171 return position; // we just need something unique for this position 172 } 173 174 public View getView(int position, View convertView, ViewGroup parent) { 175 ViewHolder holder; 176 if (convertView == null) { 177 convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false); 178 holder = new ViewHolder(); 179 holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail); 180 holder.thumbnailViewImage = (ImageView) convertView.findViewById( 181 R.id.app_thumbnail_image); 182 holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon); 183 holder.labelView = (TextView) convertView.findViewById(R.id.app_label); 184 holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description); 185 convertView.setTag(holder); 186 } else { 187 holder = (ViewHolder) convertView.getTag(); 188 } 189 190 // activityId is reverse since most recent appears at the bottom... 191 final int activityId = mActivityDescriptions.size() - position - 1; 192 193 final ActivityDescription activityDescription = mActivityDescriptions.get(activityId); 194 holder.thumbnailViewImage.setImageBitmap(activityDescription.getThumbnail()); 195 holder.iconView.setImageDrawable(activityDescription.getIcon()); 196 holder.labelView.setText(activityDescription.getLabel()); 197 holder.descriptionView.setText(activityDescription.recentTaskInfo.description); 198 holder.thumbnailView.setTag(activityDescription); 199 holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView)); 200 holder.activityDescription = activityDescription; 201 202 return convertView; 203 } 204 } 205 206 @Override 207 public boolean onKeyUp(int keyCode, KeyEvent event) { 208 if (keyCode == KeyEvent.KEYCODE_BACK && !event.isCanceled()) { 209 show(false, true); 210 return true; 211 } 212 return super.onKeyUp(keyCode, event); 213 } 214 215 public boolean isInContentArea(int x, int y) { 216 // use mRecentsContainer's exact bounds to determine horizontal position 217 final int l = mRecentsContainer.getLeft(); 218 final int r = mRecentsContainer.getRight(); 219 // use surrounding mRecentsGlowView's position in parent determine vertical bounds 220 final int t = mRecentsGlowView.getTop(); 221 final int b = mRecentsGlowView.getBottom(); 222 return x >= l && x < r && y >= t && y < b; 223 } 224 225 public void show(boolean show, boolean animate) { 226 if (animate) { 227 if (mShowing != show) { 228 mShowing = show; 229 if (show) { 230 setVisibility(View.VISIBLE); 231 } 232 mChoreo.startAnimation(show); 233 } 234 } else { 235 mShowing = show; 236 setVisibility(show ? View.VISIBLE : View.GONE); 237 mChoreo.jumpTo(show); 238 } 239 if (show) { 240 setFocusable(true); 241 setFocusableInTouchMode(true); 242 requestFocus(); 243 } 244 } 245 246 public void dismiss() { 247 hide(true); 248 } 249 250 public void hide(boolean animate) { 251 if (!animate) { 252 setVisibility(View.GONE); 253 } 254 if (mBar != null) { 255 mBar.animateCollapse(); 256 } 257 } 258 259 public void handleShowBackground(boolean show) { 260 if (show) { 261 mRecentsScrim.setBackgroundResource(R.drawable.status_bar_recents_background); 262 } else { 263 mRecentsScrim.setBackgroundDrawable(null); 264 } 265 } 266 267 public boolean isRecentsVisible() { 268 return getVisibility() == VISIBLE; 269 } 270 271 public void onAnimationCancel(Animator animation) { 272 } 273 274 public void onAnimationEnd(Animator animation) { 275 if (mShowing) { 276 final LayoutTransition transitioner = new LayoutTransition(); 277 ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner); 278 createCustomAnimations(transitioner); 279 } else { 280 ((ViewGroup)mRecentsContainer).setLayoutTransition(null); 281 } 282 } 283 284 public void onAnimationRepeat(Animator animation) { 285 } 286 287 public void onAnimationStart(Animator animation) { 288 } 289 290 291 /** 292 * We need to be aligned at the bottom. LinearLayout can't do this, so instead, 293 * let LinearLayout do all the hard work, and then shift everything down to the bottom. 294 */ 295 @Override 296 protected void onLayout(boolean changed, int l, int t, int r, int b) { 297 super.onLayout(changed, l, t, r, b); 298 mChoreo.setPanelHeight(mRecentsContainer.getHeight()); 299 } 300 301 @Override 302 public boolean dispatchHoverEvent(MotionEvent event) { 303 // Ignore hover events outside of this panel bounds since such events 304 // generate spurious accessibility events with the panel content when 305 // tapping outside of it, thus confusing the user. 306 final int x = (int) event.getX(); 307 final int y = (int) event.getY(); 308 if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { 309 return super.dispatchHoverEvent(event); 310 } 311 return true; 312 } 313 314 /** 315 * Whether the panel is showing, or, if it's animating, whether it will be 316 * when the animation is done. 317 */ 318 public boolean isShowing() { 319 return mShowing; 320 } 321 322 public void setBar(StatusBar bar) { 323 mBar = bar; 324 } 325 326 public RecentsPanelView(Context context, AttributeSet attrs) { 327 this(context, attrs, 0); 328 } 329 330 public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) { 331 super(context, attrs, defStyle); 332 333 Resources res = context.getResources(); 334 boolean xlarge = (res.getConfiguration().screenLayout 335 & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE; 336 337 mIconDpi = xlarge ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi; 338 339 int width = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_width); 340 int height = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_height); 341 int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background); 342 mAppThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 343 Canvas c = new Canvas(mAppThumbnailBackground); 344 c.drawColor(color); 345 } 346 347 @Override 348 protected void onFinishInflate() { 349 super.onFinishInflate(); 350 mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 351 mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container); 352 mListAdapter = new ActivityDescriptionAdapter(mContext); 353 if (mRecentsContainer instanceof RecentsHorizontalScrollView){ 354 RecentsHorizontalScrollView scrollView 355 = (RecentsHorizontalScrollView) mRecentsContainer; 356 scrollView.setAdapter(mListAdapter); 357 scrollView.setCallback(this); 358 } else if (mRecentsContainer instanceof RecentsVerticalScrollView){ 359 RecentsVerticalScrollView scrollView 360 = (RecentsVerticalScrollView) mRecentsContainer; 361 scrollView.setAdapter(mListAdapter); 362 scrollView.setCallback(this); 363 } 364 else { 365 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 366 } 367 368 369 mRecentsGlowView = findViewById(R.id.recents_glow); 370 mRecentsScrim = (View) findViewById(R.id.recents_bg_protect); 371 mChoreo = new Choreographer(this, mRecentsScrim, mRecentsGlowView, this); 372 mRecentsDismissButton = findViewById(R.id.recents_dismiss_button); 373 mRecentsDismissButton.setOnClickListener(new OnClickListener() { 374 public void onClick(View v) { 375 hide(true); 376 } 377 }); 378 379 // In order to save space, we make the background texture repeat in the Y direction 380 if (mRecentsScrim != null && mRecentsScrim.getBackground() instanceof BitmapDrawable) { 381 ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT); 382 } 383 } 384 385 private void createCustomAnimations(LayoutTransition transitioner) { 386 transitioner.setDuration(200); 387 transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0); 388 transitioner.setAnimator(LayoutTransition.DISAPPEARING, null); 389 } 390 391 @Override 392 protected void onVisibilityChanged(View changedView, int visibility) { 393 super.onVisibilityChanged(changedView, visibility); 394 if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + changedView + ", " + visibility + ")"); 395 if (visibility == View.VISIBLE && changedView == this) { 396 refreshApplicationList(); 397 } 398 399 if (mRecentsContainer instanceof RecentsHorizontalScrollView) { 400 ((RecentsHorizontalScrollView) mRecentsContainer).onRecentsVisibilityChanged(); 401 } else if (mRecentsContainer instanceof RecentsVerticalScrollView) { 402 ((RecentsVerticalScrollView) mRecentsContainer).onRecentsVisibilityChanged(); 403 } else { 404 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 405 } 406 } 407 408 Drawable getFullResDefaultActivityIcon() { 409 return getFullResIcon(Resources.getSystem(), 410 com.android.internal.R.mipmap.sym_def_app_icon); 411 } 412 413 Drawable getFullResIcon(Resources resources, int iconId) { 414 try { 415 return resources.getDrawableForDensity(iconId, mIconDpi); 416 } catch (Resources.NotFoundException e) { 417 return getFullResDefaultActivityIcon(); 418 } 419 } 420 421 private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) { 422 Resources resources; 423 try { 424 resources = packageManager.getResourcesForApplication( 425 info.activityInfo.applicationInfo); 426 } catch (PackageManager.NameNotFoundException e) { 427 resources = null; 428 } 429 if (resources != null) { 430 int iconId = info.activityInfo.getIconResource(); 431 if (iconId != 0) { 432 return getFullResIcon(resources, iconId); 433 } 434 } 435 return getFullResDefaultActivityIcon(); 436 } 437 438 private ArrayList<ActivityDescription> getRecentTasks() { 439 ArrayList<ActivityDescription> activityDescriptions = new ArrayList<ActivityDescription>(); 440 final PackageManager pm = mContext.getPackageManager(); 441 final ActivityManager am = (ActivityManager) 442 mContext.getSystemService(Context.ACTIVITY_SERVICE); 443 444 final List<ActivityManager.RecentTaskInfo> recentTasks = 445 am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE); 446 447 ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) 448 .resolveActivityInfo(pm, 0); 449 450 int numTasks = recentTasks.size(); 451 452 // skip the first activity - assume it's either the home screen or the current app. 453 final int first = 1; 454 for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) { 455 final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i); 456 457 Intent intent = new Intent(recentInfo.baseIntent); 458 if (recentInfo.origActivity != null) { 459 intent.setComponent(recentInfo.origActivity); 460 } 461 462 // Skip the current home activity. 463 if (homeInfo != null 464 && homeInfo.packageName.equals(intent.getComponent().getPackageName()) 465 && homeInfo.name.equals(intent.getComponent().getClassName())) { 466 continue; 467 } 468 469 intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) 470 | Intent.FLAG_ACTIVITY_NEW_TASK); 471 final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0); 472 if (resolveInfo != null) { 473 final ActivityInfo info = resolveInfo.activityInfo; 474 final String title = info.loadLabel(pm).toString(); 475 // Drawable icon = info.loadIcon(pm); 476 Drawable icon = getFullResIcon(resolveInfo, pm); 477 int id = recentInfo.id; 478 if (title != null && title.length() > 0 && icon != null) { 479 if (DEBUG) Log.v(TAG, "creating activity desc for id=" + id + ", label=" + title); 480 ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails( 481 recentInfo.persistentId); 482 ActivityDescription item = new ActivityDescription(recentInfo, 483 resolveInfo, intent, id, index, info.packageName); 484 activityDescriptions.add(item); 485 ++index; 486 } else { 487 if (DEBUG) Log.v(TAG, "SKIPPING item " + id); 488 } 489 } 490 } 491 return activityDescriptions; 492 } 493 494 ActivityDescription findActivityDescription(int id) 495 { 496 ActivityDescription desc = null; 497 for (int i = 0; i < mActivityDescriptions.size(); i++) { 498 ActivityDescription item = mActivityDescriptions.get(i); 499 if (item != null && item.taskId == id) { 500 desc = item; 501 break; 502 } 503 } 504 return desc; 505 } 506 507 void loadActivityDescription(ActivityDescription ad, int index) { 508 final ActivityManager am = (ActivityManager) 509 mContext.getSystemService(Context.ACTIVITY_SERVICE); 510 final PackageManager pm = mContext.getPackageManager(); 511 ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails( 512 ad.recentTaskInfo.persistentId); 513 CharSequence label = ad.resolveInfo.activityInfo.loadLabel(pm); 514 Drawable icon = getFullResIcon(ad.resolveInfo, pm); 515 if (DEBUG) Log.v(TAG, "Loaded bitmap for #" + index + " in " 516 + ad + ": " + thumbs.mainThumbnail); 517 synchronized (ad) { 518 ad.mLabel = label; 519 ad.mIcon = icon; 520 ad.setThumbnail(thumbs != null ? thumbs.mainThumbnail : null); 521 } 522 } 523 524 void applyActivityDescription(ActivityDescription ad, int index, boolean anim) { 525 synchronized (ad) { 526 if (mRecentsContainer != null) { 527 ViewGroup container = mRecentsContainer; 528 if (container instanceof HorizontalScrollView 529 || container instanceof ScrollView) { 530 container = (ViewGroup)container.findViewById( 531 R.id.recents_linear_layout); 532 } 533 // Look for a view showing this thumbnail, to update. 534 for (int i=0; i<container.getChildCount(); i++) { 535 View v = container.getChildAt(i); 536 if (v.getTag() instanceof ViewHolder) { 537 ViewHolder h = (ViewHolder)v.getTag(); 538 if (h.activityDescription == ad) { 539 if (DEBUG) Log.v(TAG, "Updatating thumbnail #" + index + " in " 540 + h.activityDescription 541 + ": " + ad.getThumbnail()); 542 h.iconView.setImageDrawable(ad.getIcon()); 543 if (anim) { 544 h.iconView.setAnimation(AnimationUtils.loadAnimation( 545 mContext, R.anim.recent_appear)); 546 } 547 h.iconView.setVisibility(View.VISIBLE); 548 h.labelView.setText(ad.getLabel()); 549 if (anim) { 550 h.labelView.setAnimation(AnimationUtils.loadAnimation( 551 mContext, R.anim.recent_appear)); 552 } 553 h.labelView.setVisibility(View.VISIBLE); 554 Bitmap thumbnail = ad.getThumbnail(); 555 if (thumbnail != null) { 556 // Should remove the default image in the frame 557 // that this now covers, to improve scrolling speed. 558 // That can't be done until the anim is complete though. 559 h.thumbnailViewImage.setImageBitmap(thumbnail); 560 if (anim) { 561 h.thumbnailViewImage.setAnimation(AnimationUtils.loadAnimation( 562 mContext, R.anim.recent_appear)); 563 } 564 h.thumbnailViewImage.setVisibility(View.VISIBLE); 565 } 566 } 567 } 568 } 569 } 570 } 571 } 572 573 private void refreshApplicationList() { 574 if (mThumbnailLoader != null) { 575 mThumbnailLoader.cancel(false); 576 mThumbnailLoader = null; 577 } 578 mActivityDescriptions = getRecentTasks(); 579 for (ActivityDescription ad : mActivityDescriptions) { 580 ad.setThumbnail(mAppThumbnailBackground); 581 } 582 mListAdapter.notifyDataSetInvalidated(); 583 if (mActivityDescriptions.size() > 0) { 584 if (DEBUG) Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps"); 585 updateUiElements(getResources().getConfiguration()); 586 final ArrayList<ActivityDescription> descriptions = mActivityDescriptions; 587 loadActivityDescription(descriptions.get(0), 0); 588 applyActivityDescription(descriptions.get(0), 0, false); 589 if (descriptions.size() > 1) { 590 mThumbnailLoader = new AsyncTask<Void, Integer, Void>() { 591 @Override 592 protected void onProgressUpdate(Integer... values) { 593 final ActivityDescription ad = descriptions.get(values[0]); 594 if (!isCancelled()) { 595 applyActivityDescription(ad, values[0], true); 596 } 597 // This is to prevent the loader thread from getting ahead 598 // of our UI updates. 599 mHandler.post(new Runnable() { 600 @Override public void run() { 601 synchronized (ad) { 602 ad.notifyAll(); 603 } 604 } 605 }); 606 } 607 608 @Override 609 protected Void doInBackground(Void... params) { 610 final int origPri = Process.getThreadPriority(Process.myTid()); 611 Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE); 612 long nextTime = SystemClock.uptimeMillis(); 613 for (int i=1; i<descriptions.size(); i++) { 614 ActivityDescription ad = descriptions.get(i); 615 loadActivityDescription(ad, i); 616 long now = SystemClock.uptimeMillis(); 617 nextTime += 150; 618 if (nextTime > now) { 619 try { 620 Thread.sleep(nextTime-now); 621 } catch (InterruptedException e) { 622 } 623 } 624 if (isCancelled()) { 625 break; 626 } 627 synchronized (ad) { 628 publishProgress(i); 629 try { 630 ad.wait(500); 631 } catch (InterruptedException e) { 632 } 633 } 634 } 635 Process.setThreadPriority(origPri); 636 return null; 637 } 638 }; 639 mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 640 } 641 } else { 642 // Immediately hide this panel 643 if (DEBUG) Log.v(TAG, "Nothing to show"); 644 hide(false); 645 } 646 } 647 648 private Bitmap compositeBitmap(Bitmap background, Bitmap thumbnail) { 649 Bitmap outBitmap = background.copy(background.getConfig(), true); 650 if (thumbnail != null) { 651 Canvas canvas = new Canvas(outBitmap); 652 Paint paint = new Paint(); 653 paint.setAntiAlias(true); 654 paint.setFilterBitmap(true); 655 paint.setAlpha(255); 656 canvas.drawBitmap(thumbnail, null, 657 new RectF(0, 0, outBitmap.getWidth(), outBitmap.getHeight()), paint); 658 canvas.setBitmap(null); 659 } 660 return outBitmap; 661 } 662 663 private void updateUiElements(Configuration config) { 664 final int items = mActivityDescriptions.size(); 665 666 mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE); 667 mRecentsGlowView.setVisibility(items > 0 ? View.VISIBLE : View.GONE); 668 } 669 670 public void handleOnClick(View view) { 671 ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription; 672 final Context context = view.getContext(); 673 final ActivityManager am = (ActivityManager) 674 context.getSystemService(Context.ACTIVITY_SERVICE); 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 } else { 679 Intent intent = ad.intent; 680 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 681 | Intent.FLAG_ACTIVITY_TASK_ON_HOME); 682 if (DEBUG) Log.v(TAG, "Starting activity " + intent); 683 context.startActivity(intent); 684 } 685 hide(true); 686 } 687 688 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 689 handleOnClick(view); 690 } 691 692 public void handleSwipe(View view) { 693 ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription; 694 if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel()); 695 mActivityDescriptions.remove(ad); 696 697 // Handled by widget containers to enable LayoutTransitions properly 698 // mListAdapter.notifyDataSetChanged(); 699 700 if (mActivityDescriptions.size() == 0) { 701 hide(false); 702 } 703 704 // Currently, either direction means the same thing, so ignore direction and remove 705 // the task. 706 final ActivityManager am = (ActivityManager) 707 mContext.getSystemService(Context.ACTIVITY_SERVICE); 708 am.removeTask(ad.taskId, ActivityManager.REMOVE_TASK_KILL_PROCESS); 709 } 710 711 private void startApplicationDetailsActivity(String packageName) { 712 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 713 Uri.fromParts("package", packageName, null)); 714 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 715 getContext().startActivity(intent); 716 } 717 718 public void handleLongPress(final View selectedView, final View anchorView) { 719 PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView); 720 popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu()); 721 popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 722 public boolean onMenuItemClick(MenuItem item) { 723 if (item.getItemId() == R.id.recent_remove_item) { 724 mRecentsContainer.removeViewInLayout(selectedView); 725 } else if (item.getItemId() == R.id.recent_inspect_item) { 726 ViewHolder viewHolder = (ViewHolder) selectedView.getTag(); 727 if (viewHolder != null) { 728 final ActivityDescription ad = viewHolder.activityDescription; 729 startApplicationDetailsActivity(ad.packageName); 730 mBar.animateCollapse(); 731 } else { 732 throw new IllegalStateException("Oops, no tag on view " + selectedView); 733 } 734 } else { 735 return false; 736 } 737 return true; 738 } 739 }); 740 popup.show(); 741 } 742} 743