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