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