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