RecentsPanelView.java revision cb2522c86d75fff277dc38ec7e444a5b5f5130ea
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.app.ActivityManagerNative;
23import android.app.ActivityOptions;
24import android.app.TaskStackBuilder;
25import android.content.Context;
26import android.content.Intent;
27import android.content.res.Configuration;
28import android.content.res.Resources;
29import android.content.res.TypedArray;
30import android.graphics.Bitmap;
31import android.graphics.Matrix;
32import android.graphics.Shader.TileMode;
33import android.graphics.drawable.BitmapDrawable;
34import android.graphics.drawable.Drawable;
35import android.net.Uri;
36import android.os.RemoteException;
37import android.os.UserHandle;
38import android.provider.Settings;
39import android.util.AttributeSet;
40import android.util.Log;
41import android.view.Display;
42import android.view.KeyEvent;
43import android.view.LayoutInflater;
44import android.view.MenuItem;
45import android.view.MotionEvent;
46import android.view.View;
47import android.view.ViewGroup;
48import android.view.WindowManager;
49import android.view.WindowManagerGlobal;
50import android.view.accessibility.AccessibilityEvent;
51import android.view.animation.AnimationUtils;
52import android.widget.AdapterView;
53import android.widget.AdapterView.OnItemClickListener;
54import android.widget.BaseAdapter;
55import android.widget.FrameLayout;
56import android.widget.ImageView;
57import android.widget.ImageView.ScaleType;
58import android.widget.PopupMenu;
59import android.widget.TextView;
60
61import com.android.systemui.R;
62import com.android.systemui.statusbar.BaseStatusBar;
63import com.android.systemui.statusbar.CommandQueue;
64import com.android.systemui.statusbar.phone.PhoneStatusBar;
65import com.android.systemui.statusbar.tablet.StatusBarPanel;
66import com.android.systemui.statusbar.tablet.TabletStatusBar;
67
68import java.util.ArrayList;
69
70public class RecentsPanelView extends FrameLayout implements OnItemClickListener, RecentsCallback,
71        StatusBarPanel, Animator.AnimatorListener {
72    static final String TAG = "RecentsPanelView";
73    static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
74    private Context mContext;
75    private BaseStatusBar mBar;
76    private PopupMenu mPopup;
77    private View mRecentsScrim;
78    private View mRecentsNoApps;
79    private ViewGroup mRecentsContainer;
80    private StatusBarTouchProxy mStatusBarTouchProxy;
81
82    private boolean mShowing;
83    private boolean mWaitingToShow;
84    private int mNumItemsWaitingForThumbnailsAndIcons;
85
86    private RecentTasksLoader mRecentTasksLoader;
87    private ArrayList<TaskDescription> mRecentTaskDescriptions;
88    private TaskDescriptionAdapter mListAdapter;
89    private int mThumbnailWidth;
90    private boolean mFitThumbnailToXY;
91    private int mRecentItemLayoutId;
92    private boolean mHighEndGfx;
93
94    public static interface RecentsScrollView {
95        public int numItemsInOneScreenful();
96        public void setAdapter(TaskDescriptionAdapter adapter);
97        public void setCallback(RecentsCallback callback);
98        public void setMinSwipeAlpha(float minAlpha);
99        public View findViewForTask(TaskDescription task);
100    }
101
102    private final class OnLongClickDelegate implements View.OnLongClickListener {
103        View mOtherView;
104        OnLongClickDelegate(View other) {
105            mOtherView = other;
106        }
107        public boolean onLongClick(View v) {
108            return mOtherView.performLongClick();
109        }
110    }
111
112    /* package */ final static class ViewHolder {
113        View thumbnailView;
114        ImageView thumbnailViewImage;
115        Bitmap thumbnailViewImageBitmap;
116        ImageView iconView;
117        TextView labelView;
118        TextView descriptionView;
119        TaskDescription taskDescription;
120        boolean loadedThumbnailAndIcon;
121    }
122
123    /* package */ final class TaskDescriptionAdapter extends BaseAdapter {
124        private LayoutInflater mInflater;
125
126        public TaskDescriptionAdapter(Context context) {
127            mInflater = LayoutInflater.from(context);
128        }
129
130        public int getCount() {
131            return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0;
132        }
133
134        public Object getItem(int position) {
135            return position; // we only need the index
136        }
137
138        public long getItemId(int position) {
139            return position; // we just need something unique for this position
140        }
141
142        public View createView(ViewGroup parent) {
143            View convertView = mInflater.inflate(mRecentItemLayoutId, parent, false);
144            ViewHolder holder = new ViewHolder();
145            holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
146            holder.thumbnailViewImage =
147                    (ImageView) convertView.findViewById(R.id.app_thumbnail_image);
148            // If we set the default thumbnail now, we avoid an onLayout when we update
149            // the thumbnail later (if they both have the same dimensions)
150            if (mRecentTasksLoader != null) {
151                updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
152            }
153            holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
154            if (mRecentTasksLoader != null) {
155                holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon());
156            }
157            holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
158            holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
159
160            convertView.setTag(holder);
161            return convertView;
162        }
163
164        public View getView(int position, View convertView, ViewGroup parent) {
165            if (convertView == null) {
166                convertView = createView(parent);
167                if (convertView.getParent() != null) {
168                    throw new RuntimeException("Recycled child has parent");
169                }
170            } else {
171                if (convertView.getParent() != null) {
172                    throw new RuntimeException("Recycled child has parent");
173                }
174            }
175            ViewHolder holder = (ViewHolder) convertView.getTag();
176
177            // index is reverse since most recent appears at the bottom...
178            final int index = mRecentTaskDescriptions.size() - position - 1;
179
180            final TaskDescription td = mRecentTaskDescriptions.get(index);
181
182            holder.labelView.setText(td.getLabel());
183            holder.thumbnailView.setContentDescription(td.getLabel());
184            holder.loadedThumbnailAndIcon = td.isLoaded();
185            if (td.isLoaded()) {
186                updateThumbnail(holder, td.getThumbnail(), true, false);
187                updateIcon(holder, td.getIcon(), true, false);
188                mNumItemsWaitingForThumbnailsAndIcons--;
189            }
190
191            holder.thumbnailView.setTag(td);
192            holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
193            holder.taskDescription = td;
194            return convertView;
195        }
196
197        public void recycleView(View v) {
198            ViewHolder holder = (ViewHolder) v.getTag();
199            updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
200            holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon());
201            holder.iconView.setVisibility(INVISIBLE);
202            holder.labelView.setText(null);
203            holder.thumbnailView.setContentDescription(null);
204            holder.thumbnailView.setTag(null);
205            holder.thumbnailView.setOnLongClickListener(null);
206            holder.thumbnailView.setVisibility(INVISIBLE);
207            holder.taskDescription = null;
208            holder.loadedThumbnailAndIcon = false;
209        }
210    }
211
212    public RecentsPanelView(Context context, AttributeSet attrs) {
213        this(context, attrs, 0);
214    }
215
216    public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
217        super(context, attrs, defStyle);
218        mContext = context;
219        updateValuesFromResources();
220
221        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecentsPanelView,
222                defStyle, 0);
223
224        mRecentItemLayoutId = a.getResourceId(R.styleable.RecentsPanelView_recentItemLayout, 0);
225        a.recycle();
226    }
227
228    public int numItemsInOneScreenful() {
229        if (mRecentsContainer instanceof RecentsScrollView){
230            RecentsScrollView scrollView
231                    = (RecentsScrollView) mRecentsContainer;
232            return scrollView.numItemsInOneScreenful();
233        }  else {
234            throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
235        }
236    }
237
238    private boolean pointInside(int x, int y, View v) {
239        final int l = v.getLeft();
240        final int r = v.getRight();
241        final int t = v.getTop();
242        final int b = v.getBottom();
243        return x >= l && x < r && y >= t && y < b;
244    }
245
246    public boolean isInContentArea(int x, int y) {
247        if (pointInside(x, y, mRecentsContainer)) {
248            return true;
249        } else if (mStatusBarTouchProxy != null &&
250                pointInside(x, y, mStatusBarTouchProxy)) {
251            return true;
252        } else {
253            return false;
254        }
255    }
256
257    public void show(boolean show) {
258        show(show, null, false);
259    }
260
261    public void show(boolean show, ArrayList<TaskDescription> recentTaskDescriptions,
262            boolean firstScreenful) {
263        if (show) {
264            mWaitingToShow = true;
265            refreshRecentTasksList(recentTaskDescriptions, firstScreenful);
266            showIfReady();
267        } else {
268            showImpl(false);
269        }
270    }
271
272    private void showIfReady() {
273        // mWaitingToShow => there was a touch up on the recents button
274        // mRecentTaskDescriptions != null => we've created views for the first screenful of items
275        if (mWaitingToShow && mRecentTaskDescriptions != null) {
276            showImpl(true);
277        }
278    }
279
280    static void sendCloseSystemWindows(Context context, String reason) {
281        if (ActivityManagerNative.isSystemReady()) {
282            try {
283                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
284            } catch (RemoteException e) {
285            }
286        }
287    }
288
289    private void showImpl(boolean show) {
290        sendCloseSystemWindows(mContext, BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
291
292        mShowing = show;
293
294        if (show) {
295            // if there are no apps, bring up a "No recent apps" message
296            boolean noApps = mRecentTaskDescriptions != null
297                    && (mRecentTaskDescriptions.size() == 0);
298            mRecentsNoApps.setAlpha(1f);
299            mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
300
301            onAnimationEnd(null);
302            setFocusable(true);
303            setFocusableInTouchMode(true);
304            requestFocus();
305        } else {
306            mWaitingToShow = false;
307            // call onAnimationEnd() and clearRecentTasksList() in onUiHidden()
308            if (mPopup != null) {
309                mPopup.dismiss();
310            }
311        }
312    }
313
314    public void onUiHidden() {
315        if (!mShowing && mRecentTaskDescriptions != null) {
316            onAnimationEnd(null);
317            clearRecentTasksList();
318        }
319    }
320
321    public void dismiss() {
322        ((RecentsActivity) mContext).dismissAndGoHome();
323    }
324
325    public void dismissAndGoBack() {
326        ((RecentsActivity) mContext).dismissAndGoBack();
327    }
328
329    public void onAnimationCancel(Animator animation) {
330    }
331
332    public void onAnimationEnd(Animator animation) {
333        if (mShowing) {
334            final LayoutTransition transitioner = new LayoutTransition();
335            ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner);
336            createCustomAnimations(transitioner);
337        } else {
338            ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
339        }
340    }
341
342    public void onAnimationRepeat(Animator animation) {
343    }
344
345    public void onAnimationStart(Animator animation) {
346    }
347
348    @Override
349    public boolean dispatchHoverEvent(MotionEvent event) {
350        // Ignore hover events outside of this panel bounds since such events
351        // generate spurious accessibility events with the panel content when
352        // tapping outside of it, thus confusing the user.
353        final int x = (int) event.getX();
354        final int y = (int) event.getY();
355        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
356            return super.dispatchHoverEvent(event);
357        }
358        return true;
359    }
360
361    /**
362     * Whether the panel is showing, or, if it's animating, whether it will be
363     * when the animation is done.
364     */
365    public boolean isShowing() {
366        return mShowing;
367    }
368
369    public void setBar(BaseStatusBar bar) {
370        mBar = bar;
371
372    }
373
374    public void setStatusBarView(View statusBarView) {
375        if (mStatusBarTouchProxy != null) {
376            mStatusBarTouchProxy.setStatusBar(statusBarView);
377        }
378    }
379
380    public void setRecentTasksLoader(RecentTasksLoader loader) {
381        mRecentTasksLoader = loader;
382    }
383
384    public void updateValuesFromResources() {
385        final Resources res = mContext.getResources();
386        mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width));
387        mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy);
388    }
389
390    @Override
391    protected void onFinishInflate() {
392        super.onFinishInflate();
393
394        mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
395        mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container);
396        mStatusBarTouchProxy = (StatusBarTouchProxy) findViewById(R.id.status_bar_touch_proxy);
397        mListAdapter = new TaskDescriptionAdapter(mContext);
398        if (mRecentsContainer instanceof RecentsScrollView){
399            RecentsScrollView scrollView
400                    = (RecentsScrollView) mRecentsContainer;
401            scrollView.setAdapter(mListAdapter);
402            scrollView.setCallback(this);
403        } else {
404            throw new IllegalArgumentException("missing Recents[Horizontal]ScrollView");
405        }
406
407        mRecentsScrim = findViewById(R.id.recents_bg_protect);
408        mRecentsNoApps = findViewById(R.id.recents_no_apps);
409
410        if (mRecentsScrim != null) {
411            mHighEndGfx = ActivityManager.isHighEndGfx();
412            if (!mHighEndGfx) {
413                mRecentsScrim.setBackground(null);
414            } else if (mRecentsScrim.getBackground() instanceof BitmapDrawable) {
415                // In order to save space, we make the background texture repeat in the Y direction
416                ((BitmapDrawable) mRecentsScrim.getBackground()).setTileModeY(TileMode.REPEAT);
417            }
418        }
419    }
420
421    public void setMinSwipeAlpha(float minAlpha) {
422        if (mRecentsContainer instanceof RecentsScrollView){
423            RecentsScrollView scrollView
424                = (RecentsScrollView) mRecentsContainer;
425            scrollView.setMinSwipeAlpha(minAlpha);
426        }
427    }
428
429    private void createCustomAnimations(LayoutTransition transitioner) {
430        transitioner.setDuration(200);
431        transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
432        transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
433    }
434
435    private void updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim) {
436        if (icon != null) {
437            h.iconView.setImageDrawable(icon);
438            if (show && h.iconView.getVisibility() != View.VISIBLE) {
439                if (anim) {
440                    h.iconView.setAnimation(
441                            AnimationUtils.loadAnimation(mContext, R.anim.recent_appear));
442                }
443                h.iconView.setVisibility(View.VISIBLE);
444            }
445        }
446    }
447
448    private void updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim) {
449        if (thumbnail != null) {
450            // Should remove the default image in the frame
451            // that this now covers, to improve scrolling speed.
452            // That can't be done until the anim is complete though.
453            h.thumbnailViewImage.setImageBitmap(thumbnail);
454
455            // scale the image to fill the full width of the ImageView. do this only if
456            // we haven't set a bitmap before, or if the bitmap size has changed
457            if (h.thumbnailViewImageBitmap == null ||
458                h.thumbnailViewImageBitmap.getWidth() != thumbnail.getWidth() ||
459                h.thumbnailViewImageBitmap.getHeight() != thumbnail.getHeight()) {
460                if (mFitThumbnailToXY) {
461                    h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY);
462                } else {
463                    Matrix scaleMatrix = new Matrix();
464                    float scale = mThumbnailWidth / (float) thumbnail.getWidth();
465                    scaleMatrix.setScale(scale, scale);
466                    h.thumbnailViewImage.setScaleType(ScaleType.MATRIX);
467                    h.thumbnailViewImage.setImageMatrix(scaleMatrix);
468                }
469            }
470            if (show && h.thumbnailView.getVisibility() != View.VISIBLE) {
471                if (anim) {
472                    h.thumbnailView.setAnimation(
473                            AnimationUtils.loadAnimation(mContext, R.anim.recent_appear));
474                }
475                h.thumbnailView.setVisibility(View.VISIBLE);
476            }
477            h.thumbnailViewImageBitmap = thumbnail;
478        }
479    }
480
481    void onTaskThumbnailLoaded(TaskDescription td) {
482        synchronized (td) {
483            if (mRecentsContainer != null) {
484                ViewGroup container = mRecentsContainer;
485                if (container instanceof RecentsScrollView) {
486                    container = (ViewGroup) container.findViewById(
487                            R.id.recents_linear_layout);
488                }
489                // Look for a view showing this thumbnail, to update.
490                for (int i=0; i < container.getChildCount(); i++) {
491                    View v = container.getChildAt(i);
492                    if (v.getTag() instanceof ViewHolder) {
493                        ViewHolder h = (ViewHolder)v.getTag();
494                        if (!h.loadedThumbnailAndIcon && h.taskDescription == td) {
495                            // only fade in the thumbnail if recents is already visible-- we
496                            // show it immediately otherwise
497                            //boolean animateShow = mShowing &&
498                            //    mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD;
499                            boolean animateShow = false;
500                            updateIcon(h, td.getIcon(), true, animateShow);
501                            updateThumbnail(h, td.getThumbnail(), true, animateShow);
502                            h.loadedThumbnailAndIcon = true;
503                            mNumItemsWaitingForThumbnailsAndIcons--;
504                        }
505                    }
506                }
507            }
508            }
509        showIfReady();
510    }
511
512    public void clearRecentTasksList() {
513        // Clear memory used by screenshots
514        if (mRecentTaskDescriptions != null) {
515            mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(this);
516            onTaskLoadingCancelled();
517        }
518    }
519
520    public void onTaskLoadingCancelled() {
521        // Gets called by RecentTasksLoader when it's cancelled
522        if (mRecentTaskDescriptions != null) {
523            mRecentTaskDescriptions = null;
524            mListAdapter.notifyDataSetInvalidated();
525        }
526    }
527
528    public void refreshRecentTasksList() {
529        refreshRecentTasksList(null, false);
530    }
531
532    private void refreshRecentTasksList(
533            ArrayList<TaskDescription> recentTasksList, boolean firstScreenful) {
534        if (mRecentTaskDescriptions == null && recentTasksList != null) {
535            onTasksLoaded(recentTasksList, firstScreenful);
536        } else {
537            mRecentTasksLoader.loadTasksInBackground();
538        }
539    }
540
541    public void onTasksLoaded(ArrayList<TaskDescription> tasks, boolean firstScreenful) {
542        mNumItemsWaitingForThumbnailsAndIcons = firstScreenful
543                ? tasks.size() : mRecentTaskDescriptions == null
544                        ? 0 : mRecentTaskDescriptions.size();
545        if (mRecentTaskDescriptions == null) {
546            mRecentTaskDescriptions = new ArrayList<TaskDescription>(tasks);
547        } else {
548            mRecentTaskDescriptions.addAll(tasks);
549        }
550        mListAdapter.notifyDataSetInvalidated();
551        updateUiElements(getResources().getConfiguration());
552        showIfReady();
553    }
554
555    private void updateUiElements(Configuration config) {
556        final int items = mRecentTaskDescriptions.size();
557
558        mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
559
560        // Set description for accessibility
561        int numRecentApps = mRecentTaskDescriptions.size();
562        String recentAppsAccessibilityDescription;
563        if (numRecentApps == 0) {
564            recentAppsAccessibilityDescription =
565                getResources().getString(R.string.status_bar_no_recent_apps);
566        } else {
567            recentAppsAccessibilityDescription = getResources().getQuantityString(
568                R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps);
569        }
570        setContentDescription(recentAppsAccessibilityDescription);
571    }
572
573    public boolean simulateClick(TaskDescription task) {
574        if (mRecentsContainer instanceof RecentsScrollView){
575            RecentsScrollView scrollView
576                = (RecentsScrollView) mRecentsContainer;
577            View v = scrollView.findViewForTask(task);
578            if (v != null) {
579                handleOnClick(v);
580                return true;
581            }
582        }
583        return false;
584    }
585
586    public void handleOnClick(View view) {
587        ViewHolder holder = (ViewHolder)view.getTag();
588        TaskDescription ad = holder.taskDescription;
589        final Context context = view.getContext();
590        final ActivityManager am = (ActivityManager)
591                context.getSystemService(Context.ACTIVITY_SERVICE);
592        Bitmap bm = holder.thumbnailViewImageBitmap;
593        boolean usingDrawingCache;
594        if (bm.getWidth() == holder.thumbnailViewImage.getWidth() &&
595                bm.getHeight() == holder.thumbnailViewImage.getHeight()) {
596            usingDrawingCache = false;
597        } else {
598            holder.thumbnailViewImage.setDrawingCacheEnabled(true);
599            bm = holder.thumbnailViewImage.getDrawingCache();
600            usingDrawingCache = true;
601        }
602
603        ActivityOptions opts = ActivityOptions.makeThumbnailScaleUpAnimation(
604                holder.thumbnailViewImage, bm, 0, 0, null);
605
606        show(false);
607        if (ad.taskId >= 0) {
608            // This is an active task; it should just go to the foreground.
609            am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
610                    opts.toBundle());
611        } else {
612            Intent intent = ad.intent;
613            intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
614                    | Intent.FLAG_ACTIVITY_TASK_ON_HOME
615                    | Intent.FLAG_ACTIVITY_NEW_TASK);
616            if (DEBUG) Log.v(TAG, "Starting activity " + intent);
617            context.startActivityAsUser(intent, opts.toBundle(),
618                    new UserHandle(UserHandle.USER_CURRENT));
619        }
620        if (usingDrawingCache) {
621            holder.thumbnailViewImage.setDrawingCacheEnabled(false);
622        }
623    }
624
625    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
626        handleOnClick(view);
627    }
628
629    public void handleSwipe(View view) {
630        TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
631        if (ad == null) {
632            Log.v(TAG, "Not able to find activity description for swiped task; view=" + view +
633                    " tag=" + view.getTag());
634            return;
635        }
636        if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
637        mRecentTaskDescriptions.remove(ad);
638
639        // Handled by widget containers to enable LayoutTransitions properly
640        // mListAdapter.notifyDataSetChanged();
641
642        if (mRecentTaskDescriptions.size() == 0) {
643            dismissAndGoBack();
644        }
645
646        // Currently, either direction means the same thing, so ignore direction and remove
647        // the task.
648        final ActivityManager am = (ActivityManager)
649                mContext.getSystemService(Context.ACTIVITY_SERVICE);
650        if (am != null) {
651            am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
652
653            // Accessibility feedback
654            setContentDescription(
655                    mContext.getString(R.string.accessibility_recents_item_dismissed, ad.getLabel()));
656            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
657            setContentDescription(null);
658        }
659    }
660
661    private void startApplicationDetailsActivity(String packageName) {
662        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
663                Uri.fromParts("package", packageName, null));
664        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
665        TaskStackBuilder.create(getContext())
666                .addNextIntentWithParentStack(intent).startActivities();
667    }
668
669    public boolean onInterceptTouchEvent(MotionEvent ev) {
670        if (mPopup != null) {
671            return true;
672        } else {
673            return super.onInterceptTouchEvent(ev);
674        }
675    }
676
677    public void handleLongPress(
678            final View selectedView, final View anchorView, final View thumbnailView) {
679        thumbnailView.setSelected(true);
680        final PopupMenu popup =
681            new PopupMenu(mContext, anchorView == null ? selectedView : anchorView);
682        mPopup = popup;
683        popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
684        popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
685            public boolean onMenuItemClick(MenuItem item) {
686                if (item.getItemId() == R.id.recent_remove_item) {
687                    mRecentsContainer.removeViewInLayout(selectedView);
688                } else if (item.getItemId() == R.id.recent_inspect_item) {
689                    ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
690                    if (viewHolder != null) {
691                        final TaskDescription ad = viewHolder.taskDescription;
692                        startApplicationDetailsActivity(ad.packageName);
693                        show(false);
694                    } else {
695                        throw new IllegalStateException("Oops, no tag on view " + selectedView);
696                    }
697                } else {
698                    return false;
699                }
700                return true;
701            }
702        });
703        popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
704            public void onDismiss(PopupMenu menu) {
705                thumbnailView.setSelected(false);
706                mPopup = null;
707            }
708        });
709        popup.show();
710    }
711}
712