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