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