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