RecentsPanelView.java revision 328310c6fac6066d338926bb43d359862cae36d2
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        return pointInside(x, y, mRecentsContainer) || pointInside(x, y, mStatusBarTouchProxy);
185    }
186
187    public void show(boolean show, boolean animate) {
188        show(show, animate, null);
189    }
190
191    public void show(boolean show, boolean animate,
192            ArrayList<TaskDescription> recentTaskDescriptions) {
193        if (show) {
194            // Need to update list of recent apps before we set visibility so this view's
195            // content description is updated before it gets focus for TalkBack mode
196            refreshRecentTasksList(recentTaskDescriptions);
197
198            // if there are no apps, either bring up a "No recent apps" message, or just
199            // quit early
200            boolean noApps = (mRecentTaskDescriptions.size() == 0);
201            if (mRecentsNoApps != null) {
202                mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
203            } else {
204                if (noApps) {
205                    if (DEBUG) Log.v(TAG, "Nothing to show");
206                    // Need to set recent tasks to dirty so that next time we load, we
207                    // refresh the list of tasks
208                    mRecentTasksLoader.cancelLoadingThumbnails();
209                    mRecentTasksDirty = true;
210                    return;
211                }
212            }
213        } else {
214            // Need to set recent tasks to dirty so that next time we load, we
215            // refresh the list of tasks
216            mRecentTasksLoader.cancelLoadingThumbnails();
217            mRecentTasksDirty = true;
218        }
219        if (animate) {
220            if (mShowing != show) {
221                mShowing = show;
222                if (show) {
223                    setVisibility(View.VISIBLE);
224                }
225                mChoreo.startAnimation(show);
226            }
227        } else {
228            mShowing = show;
229            setVisibility(show ? View.VISIBLE : View.GONE);
230            mChoreo.jumpTo(show);
231            onAnimationEnd(null);
232        }
233        if (show) {
234            setFocusable(true);
235            setFocusableInTouchMode(true);
236            requestFocus();
237        }
238    }
239
240    public void dismiss() {
241        hide(true);
242    }
243
244    public void hide(boolean animate) {
245        if (!animate) {
246            setVisibility(View.GONE);
247        }
248        if (mBar != null) {
249            mBar.animateCollapse();
250        }
251    }
252
253    public void handleShowBackground(boolean show) {
254        if (show) {
255            mRecentsScrim.setBackgroundResource(R.drawable.status_bar_recents_background_solid);
256        } else {
257            mRecentsScrim.setBackgroundDrawable(null);
258        }
259    }
260
261    public boolean isRecentsVisible() {
262        return getVisibility() == VISIBLE;
263    }
264
265    public void onAnimationCancel(Animator animation) {
266    }
267
268    public void onAnimationEnd(Animator animation) {
269        if (mShowing) {
270            final LayoutTransition transitioner = new LayoutTransition();
271            ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner);
272            createCustomAnimations(transitioner);
273        } else {
274            ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
275            clearRecentTasksList();
276        }
277    }
278
279    public void onAnimationRepeat(Animator animation) {
280    }
281
282    public void onAnimationStart(Animator animation) {
283    }
284
285    /**
286     * We need to be aligned at the bottom.  LinearLayout can't do this, so instead,
287     * let LinearLayout do all the hard work, and then shift everything down to the bottom.
288     */
289    @Override
290    protected void onLayout(boolean changed, int l, int t, int r, int b) {
291        super.onLayout(changed, l, t, r, b);
292        mChoreo.setPanelHeight(mRecentsContainer.getHeight());
293    }
294
295    @Override
296    public boolean dispatchHoverEvent(MotionEvent event) {
297        // Ignore hover events outside of this panel bounds since such events
298        // generate spurious accessibility events with the panel content when
299        // tapping outside of it, thus confusing the user.
300        final int x = (int) event.getX();
301        final int y = (int) event.getY();
302        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
303            return super.dispatchHoverEvent(event);
304        }
305        return true;
306    }
307
308    /**
309     * Whether the panel is showing, or, if it's animating, whether it will be
310     * when the animation is done.
311     */
312    public boolean isShowing() {
313        return mShowing;
314    }
315
316    public void setBar(StatusBar bar) {
317        mBar = bar;
318
319    }
320
321    public void setStatusBarView(View statusBarView) {
322        if (mStatusBarTouchProxy != null) {
323            mStatusBarTouchProxy.setStatusBar(statusBarView);
324        }
325    }
326
327    public void setRecentTasksLoader(RecentTasksLoader loader) {
328        mRecentTasksLoader = loader;
329    }
330
331    public void setOnVisibilityChangedListener(OnRecentsPanelVisibilityChangedListener l) {
332        mVisibilityChangedListener = l;
333
334    }
335
336    public void setVisibility(int visibility) {
337        if (mVisibilityChangedListener != null) {
338            mVisibilityChangedListener.onRecentsPanelVisibilityChanged(visibility == VISIBLE);
339        }
340        super.setVisibility(visibility);
341    }
342
343    public RecentsPanelView(Context context, AttributeSet attrs) {
344        this(context, attrs, 0);
345    }
346
347    public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
348        super(context, attrs, defStyle);
349        mContext = context;
350        updateValuesFromResources();
351    }
352
353    public void updateValuesFromResources() {
354        final Resources res = mContext.getResources();
355        mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width));
356        mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy);
357    }
358
359    @Override
360    protected void onFinishInflate() {
361        super.onFinishInflate();
362        mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
363        mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container);
364        mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy);
365        mListAdapter = new TaskDescriptionAdapter(mContext);
366        if (mRecentsContainer instanceof RecentsHorizontalScrollView){
367            RecentsHorizontalScrollView scrollView
368                    = (RecentsHorizontalScrollView) mRecentsContainer;
369            scrollView.setAdapter(mListAdapter);
370            scrollView.setCallback(this);
371        } else if (mRecentsContainer instanceof RecentsVerticalScrollView){
372            RecentsVerticalScrollView scrollView
373                    = (RecentsVerticalScrollView) mRecentsContainer;
374            scrollView.setAdapter(mListAdapter);
375            scrollView.setCallback(this);
376        }
377        else {
378            throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
379        }
380
381
382        mRecentsScrim = findViewById(R.id.recents_bg_protect);
383        mRecentsNoApps = findViewById(R.id.recents_no_apps);
384        mChoreo = new Choreographer(this, mRecentsScrim, mRecentsContainer, mRecentsNoApps, this);
385
386        // In order to save space, we make the background texture repeat in the Y direction
387        if (mRecentsScrim != null && mRecentsScrim.getBackground() instanceof BitmapDrawable) {
388            ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT);
389        }
390
391        mPreloadTasksRunnable = new Runnable() {
392            public void run() {
393                setVisibility(INVISIBLE);
394                refreshRecentTasksList();
395            }
396        };
397    }
398
399    private void createCustomAnimations(LayoutTransition transitioner) {
400        transitioner.setDuration(200);
401        transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
402        transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
403    }
404
405    @Override
406    protected void onVisibilityChanged(View changedView, int visibility) {
407        super.onVisibilityChanged(changedView, visibility);
408        if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + changedView + ", " + visibility + ")");
409
410        if (mRecentsContainer instanceof RecentsHorizontalScrollView) {
411            ((RecentsHorizontalScrollView) mRecentsContainer).onRecentsVisibilityChanged();
412        } else if (mRecentsContainer instanceof RecentsVerticalScrollView) {
413            ((RecentsVerticalScrollView) mRecentsContainer).onRecentsVisibilityChanged();
414        } else {
415            throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
416        }
417    }
418
419    private void updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim) {
420        if (thumbnail != null) {
421            // Should remove the default image in the frame
422            // that this now covers, to improve scrolling speed.
423            // That can't be done until the anim is complete though.
424            h.thumbnailViewImage.setImageBitmap(thumbnail);
425
426            // scale the image to fill the full width of the ImageView. do this only if
427            // we haven't set a bitmap before, or if the bitmap size has changed
428            if (h.thumbnailViewImageBitmap == null ||
429                h.thumbnailViewImageBitmap.getWidth() != thumbnail.getWidth() ||
430                h.thumbnailViewImageBitmap.getHeight() != thumbnail.getHeight()) {
431                if (mFitThumbnailToXY) {
432                    h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY);
433                } else {
434                    Matrix scaleMatrix = new Matrix();
435                    float scale = mThumbnailWidth / (float) thumbnail.getWidth();
436                    scaleMatrix.setScale(scale, scale);
437                    h.thumbnailViewImage.setScaleType(ScaleType.MATRIX);
438                    h.thumbnailViewImage.setImageMatrix(scaleMatrix);
439                }
440            }
441            if (show && h.thumbnailView.getVisibility() != View.VISIBLE) {
442                if (anim) {
443                    h.thumbnailView.setAnimation(
444                            AnimationUtils.loadAnimation(mContext, R.anim.recent_appear));
445                }
446                h.thumbnailView.setVisibility(View.VISIBLE);
447            }
448            h.thumbnailViewImageBitmap = thumbnail;
449        }
450    }
451
452    void onTaskThumbnailLoaded(TaskDescription ad) {
453        synchronized (ad) {
454            if (mRecentsContainer != null) {
455                ViewGroup container = mRecentsContainer;
456                if (container instanceof HorizontalScrollView
457                        || container instanceof ScrollView) {
458                    container = (ViewGroup)container.findViewById(
459                            R.id.recents_linear_layout);
460                }
461                // Look for a view showing this thumbnail, to update.
462                for (int i=0; i<container.getChildCount(); i++) {
463                    View v = container.getChildAt(i);
464                    if (v.getTag() instanceof ViewHolder) {
465                        ViewHolder h = (ViewHolder)v.getTag();
466                        if (h.taskDescription == ad) {
467                            // only fade in the thumbnail if recents is already visible-- we
468                            // show it immediately otherwise
469                            boolean animateShow = mShowing &&
470                                mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD;
471                            updateThumbnail(h, ad.getThumbnail(), true, animateShow);
472                        }
473                    }
474                }
475            }
476        }
477    }
478
479    // additional optimization when we have sofware system buttons - start loading the recent
480    // tasks on touch down
481    @Override
482    public boolean onTouch(View v, MotionEvent ev) {
483        if (!mShowing) {
484            int action = ev.getAction() & MotionEvent.ACTION_MASK;
485            if (action == MotionEvent.ACTION_DOWN) {
486                // If we set our visibility to INVISIBLE here, we avoid an extra call to
487                // onLayout later when we become visible (because onLayout is always called
488                // when going from GONE)
489                post(mPreloadTasksRunnable);
490            } else if (action == MotionEvent.ACTION_CANCEL) {
491                setVisibility(GONE);
492                clearRecentTasksList();
493                // Remove the preloader if we haven't called it yet
494                removeCallbacks(mPreloadTasksRunnable);
495            } else if (action == MotionEvent.ACTION_UP) {
496                // Remove the preloader if we haven't called it yet
497                removeCallbacks(mPreloadTasksRunnable);
498                if (!v.isPressed()) {
499                    setVisibility(GONE);
500                    clearRecentTasksList();
501                }
502            }
503        }
504        return false;
505    }
506
507    public void clearRecentTasksList() {
508        // Clear memory used by screenshots
509        if (mRecentTaskDescriptions != null) {
510            mRecentTasksLoader.cancelLoadingThumbnails();
511            mRecentTaskDescriptions.clear();
512            mListAdapter.notifyDataSetInvalidated();
513            mRecentTasksDirty = true;
514        }
515    }
516
517    public void refreshRecentTasksList() {
518        refreshRecentTasksList(null);
519    }
520
521    private void refreshRecentTasksList(ArrayList<TaskDescription> recentTasksList) {
522        if (mRecentTasksDirty) {
523            if (recentTasksList != null) {
524                mRecentTaskDescriptions = recentTasksList;
525            } else {
526                mRecentTaskDescriptions = mRecentTasksLoader.getRecentTasks();
527            }
528            mListAdapter.notifyDataSetInvalidated();
529            updateUiElements(getResources().getConfiguration());
530            mRecentTasksDirty = false;
531        }
532    }
533
534    public ArrayList<TaskDescription> getRecentTasksList() {
535        return mRecentTaskDescriptions;
536    }
537
538    private void updateUiElements(Configuration config) {
539        final int items = mRecentTaskDescriptions.size();
540
541        mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
542
543        // Set description for accessibility
544        int numRecentApps = mRecentTaskDescriptions.size();
545        String recentAppsAccessibilityDescription;
546        if (numRecentApps == 0) {
547            recentAppsAccessibilityDescription =
548                getResources().getString(R.string.status_bar_no_recent_apps);
549        } else {
550            recentAppsAccessibilityDescription = getResources().getQuantityString(
551                R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps);
552        }
553        setContentDescription(recentAppsAccessibilityDescription);
554    }
555
556    public void handleOnClick(View view) {
557        TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
558        final Context context = view.getContext();
559        final ActivityManager am = (ActivityManager)
560                context.getSystemService(Context.ACTIVITY_SERVICE);
561        if (ad.taskId >= 0) {
562            // This is an active task; it should just go to the foreground.
563            am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME);
564        } else {
565            Intent intent = ad.intent;
566            intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
567                    | Intent.FLAG_ACTIVITY_TASK_ON_HOME
568                    | Intent.FLAG_ACTIVITY_NEW_TASK);
569            if (DEBUG) Log.v(TAG, "Starting activity " + intent);
570            context.startActivity(intent);
571        }
572        hide(true);
573    }
574
575    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
576        handleOnClick(view);
577    }
578
579    public void handleSwipe(View view) {
580        TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
581        if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
582        mRecentTaskDescriptions.remove(ad);
583
584        // Handled by widget containers to enable LayoutTransitions properly
585        // mListAdapter.notifyDataSetChanged();
586
587        if (mRecentTaskDescriptions.size() == 0) {
588            hide(false);
589        }
590
591        // Currently, either direction means the same thing, so ignore direction and remove
592        // the task.
593        final ActivityManager am = (ActivityManager)
594                mContext.getSystemService(Context.ACTIVITY_SERVICE);
595        am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
596
597        // Accessibility feedback
598        setContentDescription(
599                mContext.getString(R.string.accessibility_recents_item_dismissed, ad.getLabel()));
600        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
601        setContentDescription(null);
602    }
603
604    private void startApplicationDetailsActivity(String packageName) {
605        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
606                Uri.fromParts("package", packageName, null));
607        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
608        getContext().startActivity(intent);
609    }
610
611    public void handleLongPress(
612            final View selectedView, final View anchorView, final View thumbnailView) {
613        thumbnailView.setSelected(true);
614        PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView);
615        popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
616        popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
617            public boolean onMenuItemClick(MenuItem item) {
618                if (item.getItemId() == R.id.recent_remove_item) {
619                    mRecentsContainer.removeViewInLayout(selectedView);
620                } else if (item.getItemId() == R.id.recent_inspect_item) {
621                    ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
622                    if (viewHolder != null) {
623                        final TaskDescription ad = viewHolder.taskDescription;
624                        startApplicationDetailsActivity(ad.packageName);
625                        mBar.animateCollapse();
626                    } else {
627                        throw new IllegalStateException("Oops, no tag on view " + selectedView);
628                    }
629                } else {
630                    return false;
631                }
632                return true;
633            }
634        });
635        popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
636            public void onDismiss(PopupMenu menu) {
637                thumbnailView.setSelected(false);
638            }
639        });
640        popup.show();
641    }
642}
643