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