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