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