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