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