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