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