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