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