RecentsPanelView.java revision dee4eaf02d4054986afea01876f824461625ebbf
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.recent; 18 19import android.animation.Animator; 20import android.animation.LayoutTransition; 21import android.app.ActivityManager; 22import android.content.Context; 23import android.content.Intent; 24import android.content.res.Configuration; 25import android.content.res.Resources; 26import android.graphics.Bitmap; 27import android.graphics.Matrix; 28import android.graphics.Shader.TileMode; 29import android.graphics.drawable.BitmapDrawable; 30import android.net.Uri; 31import android.provider.Settings; 32import android.util.AttributeSet; 33import android.util.Log; 34import android.view.KeyEvent; 35import android.view.LayoutInflater; 36import android.view.MenuItem; 37import android.view.MotionEvent; 38import android.view.View; 39import android.view.ViewConfiguration; 40import android.view.ViewGroup; 41import android.view.accessibility.AccessibilityEvent; 42import android.view.animation.AnimationUtils; 43import android.widget.AdapterView; 44import android.widget.AdapterView.OnItemClickListener; 45import android.widget.BaseAdapter; 46import android.widget.HorizontalScrollView; 47import android.widget.ImageView; 48import android.widget.ImageView.ScaleType; 49import android.widget.PopupMenu; 50import android.widget.RelativeLayout; 51import android.widget.ScrollView; 52import android.widget.TextView; 53 54import com.android.systemui.R; 55import com.android.systemui.statusbar.StatusBar; 56import com.android.systemui.statusbar.phone.PhoneStatusBar; 57import com.android.systemui.statusbar.tablet.StatusBarPanel; 58import com.android.systemui.statusbar.tablet.TabletStatusBar; 59 60import java.util.ArrayList; 61 62public class RecentsPanelView extends RelativeLayout implements OnItemClickListener, RecentsCallback, 63 StatusBarPanel, Animator.AnimatorListener, View.OnTouchListener { 64 static final String TAG = "RecentsPanelView"; 65 static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false; 66 private Context mContext; 67 private StatusBar mBar; 68 private View mRecentsScrim; 69 private View mRecentsNoApps; 70 private ViewGroup mRecentsContainer; 71 private StatusBarTouchProxy mStatusBarTouchProxy; 72 73 private boolean mShowing; 74 private Choreographer mChoreo; 75 OnRecentsPanelVisibilityChangedListener mVisibilityChangedListener; 76 77 private RecentTasksLoader mRecentTasksLoader; 78 private ArrayList<TaskDescription> mRecentTaskDescriptions; 79 private Runnable mPreloadTasksRunnable; 80 private boolean mRecentTasksDirty = true; 81 private TaskDescriptionAdapter mListAdapter; 82 private int mThumbnailWidth; 83 private boolean mFitThumbnailToXY; 84 85 public static interface OnRecentsPanelVisibilityChangedListener { 86 public void onRecentsPanelVisibilityChanged(boolean visible); 87 } 88 89 private final class OnLongClickDelegate implements View.OnLongClickListener { 90 View mOtherView; 91 OnLongClickDelegate(View other) { 92 mOtherView = other; 93 } 94 public boolean onLongClick(View v) { 95 return mOtherView.performLongClick(); 96 } 97 } 98 99 /* package */ final static class ViewHolder { 100 View thumbnailView; 101 ImageView thumbnailViewImage; 102 Bitmap thumbnailViewImageBitmap; 103 ImageView iconView; 104 TextView labelView; 105 TextView descriptionView; 106 TaskDescription taskDescription; 107 } 108 109 /* package */ final class TaskDescriptionAdapter extends BaseAdapter { 110 private LayoutInflater mInflater; 111 112 public TaskDescriptionAdapter(Context context) { 113 mInflater = LayoutInflater.from(context); 114 } 115 116 public int getCount() { 117 return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0; 118 } 119 120 public Object getItem(int position) { 121 return position; // we only need the index 122 } 123 124 public long getItemId(int position) { 125 return position; // we just need something unique for this position 126 } 127 128 public View getView(int position, View convertView, ViewGroup parent) { 129 ViewHolder holder; 130 if (convertView == null) { 131 convertView = mInflater.inflate(R.layout.status_bar_recent_item, parent, false); 132 holder = new ViewHolder(); 133 holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail); 134 holder.thumbnailViewImage = (ImageView) convertView.findViewById( 135 R.id.app_thumbnail_image); 136 // If we set the default thumbnail now, we avoid an onLayout when we update 137 // the thumbnail later (if they both have the same dimensions) 138 updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false); 139 140 holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon); 141 holder.labelView = (TextView) convertView.findViewById(R.id.app_label); 142 holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description); 143 144 convertView.setTag(holder); 145 } else { 146 holder = (ViewHolder) convertView.getTag(); 147 } 148 149 // index is reverse since most recent appears at the bottom... 150 final int index = mRecentTaskDescriptions.size() - position - 1; 151 152 final TaskDescription td = mRecentTaskDescriptions.get(index); 153 holder.iconView.setImageDrawable(td.getIcon()); 154 holder.labelView.setText(td.getLabel()); 155 holder.thumbnailView.setContentDescription(td.getLabel()); 156 updateThumbnail(holder, td.getThumbnail(), true, false); 157 158 holder.thumbnailView.setTag(td); 159 holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView)); 160 holder.taskDescription = td; 161 162 return convertView; 163 } 164 } 165 166 @Override 167 public boolean onKeyUp(int keyCode, KeyEvent event) { 168 if (keyCode == KeyEvent.KEYCODE_BACK && !event.isCanceled()) { 169 show(false, true); 170 return true; 171 } 172 return super.onKeyUp(keyCode, event); 173 } 174 175 private boolean pointInside(int x, int y, View v) { 176 final int l = v.getLeft(); 177 final int r = v.getRight(); 178 final int t = v.getTop(); 179 final int b = v.getBottom(); 180 return x >= l && x < r && y >= t && y < b; 181 } 182 183 public boolean isInContentArea(int x, int y) { 184 if (pointInside(x, y, mRecentsContainer)) { 185 return true; 186 } else if (mStatusBarTouchProxy != null && 187 pointInside(x, y, mStatusBarTouchProxy)) { 188 return true; 189 } else { 190 return false; 191 } 192 } 193 194 public void show(boolean show, boolean animate) { 195 show(show, animate, null); 196 } 197 198 public void show(boolean show, boolean animate, 199 ArrayList<TaskDescription> recentTaskDescriptions) { 200 if (show) { 201 // Need to update list of recent apps before we set visibility so this view's 202 // content description is updated before it gets focus for TalkBack mode 203 refreshRecentTasksList(recentTaskDescriptions); 204 205 // if there are no apps, either bring up a "No recent apps" message, or just 206 // quit early 207 boolean noApps = (mRecentTaskDescriptions.size() == 0); 208 if (mRecentsNoApps != null) { 209 mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE); 210 } else { 211 if (noApps) { 212 if (DEBUG) Log.v(TAG, "Nothing to show"); 213 // Need to set recent tasks to dirty so that next time we load, we 214 // refresh the list of tasks 215 mRecentTasksLoader.cancelLoadingThumbnails(); 216 mRecentTasksDirty = true; 217 return; 218 } 219 } 220 } else { 221 // Need to set recent tasks to dirty so that next time we load, we 222 // refresh the list of tasks 223 mRecentTasksLoader.cancelLoadingThumbnails(); 224 mRecentTasksDirty = true; 225 } 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 onAnimationEnd(null); 239 } 240 if (show) { 241 setFocusable(true); 242 setFocusableInTouchMode(true); 243 requestFocus(); 244 } 245 } 246 247 public void dismiss() { 248 hide(true); 249 } 250 251 public void hide(boolean animate) { 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_solid); 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 clearRecentTasksList(); 283 } 284 } 285 286 public void onAnimationRepeat(Animator animation) { 287 } 288 289 public void onAnimationStart(Animator animation) { 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 328 public void setStatusBarView(View statusBarView) { 329 if (mStatusBarTouchProxy != null) { 330 mStatusBarTouchProxy.setStatusBar(statusBarView); 331 } 332 } 333 334 public void setRecentTasksLoader(RecentTasksLoader loader) { 335 mRecentTasksLoader = loader; 336 } 337 338 public void setOnVisibilityChangedListener(OnRecentsPanelVisibilityChangedListener l) { 339 mVisibilityChangedListener = l; 340 341 } 342 343 public void setVisibility(int visibility) { 344 if (mVisibilityChangedListener != null) { 345 mVisibilityChangedListener.onRecentsPanelVisibilityChanged(visibility == VISIBLE); 346 } 347 super.setVisibility(visibility); 348 } 349 350 public RecentsPanelView(Context context, AttributeSet attrs) { 351 this(context, attrs, 0); 352 } 353 354 public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) { 355 super(context, attrs, defStyle); 356 mContext = context; 357 updateValuesFromResources(); 358 } 359 360 public void updateValuesFromResources() { 361 final Resources res = mContext.getResources(); 362 mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width)); 363 mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy); 364 } 365 366 @Override 367 protected void onFinishInflate() { 368 super.onFinishInflate(); 369 mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 370 mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container); 371 mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy); 372 mListAdapter = new TaskDescriptionAdapter(mContext); 373 if (mRecentsContainer instanceof RecentsHorizontalScrollView){ 374 RecentsHorizontalScrollView scrollView 375 = (RecentsHorizontalScrollView) mRecentsContainer; 376 scrollView.setAdapter(mListAdapter); 377 scrollView.setCallback(this); 378 } else if (mRecentsContainer instanceof RecentsVerticalScrollView){ 379 RecentsVerticalScrollView scrollView 380 = (RecentsVerticalScrollView) mRecentsContainer; 381 scrollView.setAdapter(mListAdapter); 382 scrollView.setCallback(this); 383 } 384 else { 385 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 386 } 387 388 389 mRecentsScrim = findViewById(R.id.recents_bg_protect); 390 mRecentsNoApps = findViewById(R.id.recents_no_apps); 391 mChoreo = new Choreographer(this, mRecentsScrim, mRecentsContainer, mRecentsNoApps, this); 392 393 // In order to save space, we make the background texture repeat in the Y direction 394 if (mRecentsScrim != null && mRecentsScrim.getBackground() instanceof BitmapDrawable) { 395 ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT); 396 } 397 398 mPreloadTasksRunnable = new Runnable() { 399 public void run() { 400 if (!mShowing) { 401 setVisibility(INVISIBLE); 402 refreshRecentTasksList(); 403 } 404 } 405 }; 406 } 407 408 private void createCustomAnimations(LayoutTransition transitioner) { 409 transitioner.setDuration(200); 410 transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0); 411 transitioner.setAnimator(LayoutTransition.DISAPPEARING, null); 412 } 413 414 @Override 415 protected void onVisibilityChanged(View changedView, int visibility) { 416 super.onVisibilityChanged(changedView, visibility); 417 if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + changedView + ", " + visibility + ")"); 418 419 if (mRecentsContainer instanceof RecentsHorizontalScrollView) { 420 ((RecentsHorizontalScrollView) mRecentsContainer).onRecentsVisibilityChanged(); 421 } else if (mRecentsContainer instanceof RecentsVerticalScrollView) { 422 ((RecentsVerticalScrollView) mRecentsContainer).onRecentsVisibilityChanged(); 423 } else { 424 throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView"); 425 } 426 } 427 428 private void updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim) { 429 if (thumbnail != null) { 430 // Should remove the default image in the frame 431 // that this now covers, to improve scrolling speed. 432 // That can't be done until the anim is complete though. 433 h.thumbnailViewImage.setImageBitmap(thumbnail); 434 435 // scale the image to fill the full width of the ImageView. do this only if 436 // we haven't set a bitmap before, or if the bitmap size has changed 437 if (h.thumbnailViewImageBitmap == null || 438 h.thumbnailViewImageBitmap.getWidth() != thumbnail.getWidth() || 439 h.thumbnailViewImageBitmap.getHeight() != thumbnail.getHeight()) { 440 if (mFitThumbnailToXY) { 441 h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY); 442 } else { 443 Matrix scaleMatrix = new Matrix(); 444 float scale = mThumbnailWidth / (float) thumbnail.getWidth(); 445 scaleMatrix.setScale(scale, scale); 446 h.thumbnailViewImage.setScaleType(ScaleType.MATRIX); 447 h.thumbnailViewImage.setImageMatrix(scaleMatrix); 448 } 449 } 450 if (show && h.thumbnailView.getVisibility() != View.VISIBLE) { 451 if (anim) { 452 h.thumbnailView.setAnimation( 453 AnimationUtils.loadAnimation(mContext, R.anim.recent_appear)); 454 } 455 h.thumbnailView.setVisibility(View.VISIBLE); 456 } 457 h.thumbnailViewImageBitmap = thumbnail; 458 } 459 } 460 461 void onTaskThumbnailLoaded(TaskDescription ad) { 462 synchronized (ad) { 463 if (mRecentsContainer != null) { 464 ViewGroup container = mRecentsContainer; 465 if (container instanceof HorizontalScrollView 466 || container instanceof ScrollView) { 467 container = (ViewGroup)container.findViewById( 468 R.id.recents_linear_layout); 469 } 470 // Look for a view showing this thumbnail, to update. 471 for (int i=0; i<container.getChildCount(); i++) { 472 View v = container.getChildAt(i); 473 if (v.getTag() instanceof ViewHolder) { 474 ViewHolder h = (ViewHolder)v.getTag(); 475 if (h.taskDescription == ad) { 476 // only fade in the thumbnail if recents is already visible-- we 477 // show it immediately otherwise 478 boolean animateShow = mShowing && 479 mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD; 480 updateThumbnail(h, ad.getThumbnail(), true, animateShow); 481 } 482 } 483 } 484 } 485 } 486 } 487 488 // additional optimization when we have sofware system buttons - start loading the recent 489 // tasks on touch down 490 @Override 491 public boolean onTouch(View v, MotionEvent ev) { 492 if (!mShowing) { 493 int action = ev.getAction() & MotionEvent.ACTION_MASK; 494 if (action == MotionEvent.ACTION_DOWN) { 495 // If we set our visibility to INVISIBLE here, we avoid an extra call to 496 // onLayout later when we become visible (because onLayout is always called 497 // when going from GONE) 498 post(mPreloadTasksRunnable); 499 } else if (action == MotionEvent.ACTION_CANCEL) { 500 setVisibility(GONE); 501 clearRecentTasksList(); 502 // Remove the preloader if we haven't called it yet 503 removeCallbacks(mPreloadTasksRunnable); 504 } else if (action == MotionEvent.ACTION_UP) { 505 // Remove the preloader if we haven't called it yet 506 removeCallbacks(mPreloadTasksRunnable); 507 if (!v.isPressed()) { 508 setVisibility(GONE); 509 clearRecentTasksList(); 510 } 511 } 512 } 513 return false; 514 } 515 516 public void clearRecentTasksList() { 517 // Clear memory used by screenshots 518 if (mRecentTaskDescriptions != null) { 519 mRecentTasksLoader.cancelLoadingThumbnails(); 520 mRecentTaskDescriptions.clear(); 521 mListAdapter.notifyDataSetInvalidated(); 522 mRecentTasksDirty = true; 523 } 524 } 525 526 public void refreshRecentTasksList() { 527 refreshRecentTasksList(null); 528 } 529 530 private void refreshRecentTasksList(ArrayList<TaskDescription> recentTasksList) { 531 if (mRecentTasksDirty) { 532 if (recentTasksList != null) { 533 mRecentTaskDescriptions = recentTasksList; 534 } else { 535 mRecentTaskDescriptions = mRecentTasksLoader.getRecentTasks(); 536 } 537 mListAdapter.notifyDataSetInvalidated(); 538 updateUiElements(getResources().getConfiguration()); 539 mRecentTasksDirty = false; 540 } 541 } 542 543 public ArrayList<TaskDescription> getRecentTasksList() { 544 return mRecentTaskDescriptions; 545 } 546 547 private void updateUiElements(Configuration config) { 548 final int items = mRecentTaskDescriptions.size(); 549 550 mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE); 551 552 // Set description for accessibility 553 int numRecentApps = mRecentTaskDescriptions.size(); 554 String recentAppsAccessibilityDescription; 555 if (numRecentApps == 0) { 556 recentAppsAccessibilityDescription = 557 getResources().getString(R.string.status_bar_no_recent_apps); 558 } else { 559 recentAppsAccessibilityDescription = getResources().getQuantityString( 560 R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps); 561 } 562 setContentDescription(recentAppsAccessibilityDescription); 563 } 564 565 public void handleOnClick(View view) { 566 TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription; 567 final Context context = view.getContext(); 568 final ActivityManager am = (ActivityManager) 569 context.getSystemService(Context.ACTIVITY_SERVICE); 570 if (ad.taskId >= 0) { 571 // This is an active task; it should just go to the foreground. 572 am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME); 573 } else { 574 Intent intent = ad.intent; 575 intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 576 | Intent.FLAG_ACTIVITY_TASK_ON_HOME 577 | Intent.FLAG_ACTIVITY_NEW_TASK); 578 if (DEBUG) Log.v(TAG, "Starting activity " + intent); 579 context.startActivity(intent); 580 } 581 hide(true); 582 } 583 584 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 585 handleOnClick(view); 586 } 587 588 public void handleSwipe(View view) { 589 TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription; 590 if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel()); 591 mRecentTaskDescriptions.remove(ad); 592 593 // Handled by widget containers to enable LayoutTransitions properly 594 // mListAdapter.notifyDataSetChanged(); 595 596 if (mRecentTaskDescriptions.size() == 0) { 597 hide(false); 598 } 599 600 // Currently, either direction means the same thing, so ignore direction and remove 601 // the task. 602 final ActivityManager am = (ActivityManager) 603 mContext.getSystemService(Context.ACTIVITY_SERVICE); 604 am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS); 605 606 // Accessibility feedback 607 setContentDescription( 608 mContext.getString(R.string.accessibility_recents_item_dismissed, ad.getLabel())); 609 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 610 setContentDescription(null); 611 } 612 613 private void startApplicationDetailsActivity(String packageName) { 614 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 615 Uri.fromParts("package", packageName, null)); 616 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 617 getContext().startActivity(intent); 618 } 619 620 public void handleLongPress( 621 final View selectedView, final View anchorView, final View thumbnailView) { 622 thumbnailView.setSelected(true); 623 PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView); 624 popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu()); 625 popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { 626 public boolean onMenuItemClick(MenuItem item) { 627 if (item.getItemId() == R.id.recent_remove_item) { 628 mRecentsContainer.removeViewInLayout(selectedView); 629 } else if (item.getItemId() == R.id.recent_inspect_item) { 630 ViewHolder viewHolder = (ViewHolder) selectedView.getTag(); 631 if (viewHolder != null) { 632 final TaskDescription ad = viewHolder.taskDescription; 633 startApplicationDetailsActivity(ad.packageName); 634 mBar.animateCollapse(); 635 } else { 636 throw new IllegalStateException("Oops, no tag on view " + selectedView); 637 } 638 } else { 639 return false; 640 } 641 return true; 642 } 643 }); 644 popup.setOnDismissListener(new PopupMenu.OnDismissListener() { 645 public void onDismiss(PopupMenu menu) { 646 thumbnailView.setSelected(false); 647 } 648 }); 649 popup.show(); 650 } 651} 652