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