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