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