RecentsPanelView.java revision 4a6bc55cd5b2e1fdd04347813842d5bce64ee3fe
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.animation.TimeInterpolator;
22import android.app.ActivityManager;
23import android.app.ActivityManagerNative;
24import android.app.ActivityOptions;
25import android.app.TaskStackBuilder;
26import android.content.ActivityNotFoundException;
27import android.content.Context;
28import android.content.Intent;
29import android.content.res.Configuration;
30import android.content.res.Resources;
31import android.content.res.TypedArray;
32import android.graphics.Bitmap;
33import android.graphics.Canvas;
34import android.graphics.Matrix;
35import android.graphics.Shader.TileMode;
36import android.graphics.drawable.BitmapDrawable;
37import android.graphics.drawable.Drawable;
38import android.net.Uri;
39import android.os.Bundle;
40import android.os.RemoteException;
41import android.os.UserHandle;
42import android.provider.Settings;
43import android.util.AttributeSet;
44import android.util.Log;
45import android.view.LayoutInflater;
46import android.view.MenuItem;
47import android.view.MotionEvent;
48import android.view.View;
49import android.view.ViewGroup;
50import android.view.ViewPropertyAnimator;
51import android.view.ViewRootImpl;
52import android.view.accessibility.AccessibilityEvent;
53import android.view.animation.AnimationUtils;
54import android.view.animation.DecelerateInterpolator;
55import android.widget.AdapterView;
56import android.widget.AdapterView.OnItemClickListener;
57import android.widget.BaseAdapter;
58import android.widget.FrameLayout;
59import android.widget.ImageView;
60import android.widget.ImageView.ScaleType;
61import android.widget.PopupMenu;
62import android.widget.TextView;
63
64import com.android.systemui.R;
65import com.android.systemui.statusbar.BaseStatusBar;
66import com.android.systemui.statusbar.StatusBarPanel;
67import com.android.systemui.statusbar.phone.PhoneStatusBar;
68
69import java.util.ArrayList;
70
71public class RecentsPanelView extends FrameLayout implements OnItemClickListener, RecentsCallback,
72        StatusBarPanel, Animator.AnimatorListener {
73    static final String TAG = "RecentsPanelView";
74    static final boolean DEBUG = PhoneStatusBar.DEBUG || false;
75    private PopupMenu mPopup;
76    private View mRecentsScrim;
77    private View mRecentsNoApps;
78    private RecentsScrollView mRecentsContainer;
79
80    private boolean mShowing;
81    private boolean mWaitingToShow;
82    private ViewHolder mItemToAnimateInWhenWindowAnimationIsFinished;
83    private boolean mAnimateIconOfFirstTask;
84    private boolean mWaitingForWindowAnimation;
85    private long mWindowAnimationStartTime;
86    private boolean mCallUiHiddenBeforeNextReload;
87
88    private RecentTasksLoader mRecentTasksLoader;
89    private ArrayList<TaskDescription> mRecentTaskDescriptions;
90    private TaskDescriptionAdapter mListAdapter;
91    private int mThumbnailWidth;
92    private boolean mFitThumbnailToXY;
93    private int mRecentItemLayoutId;
94    private boolean mHighEndGfx;
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        public View findViewForTask(int persistentTaskId);
102        public void drawFadedEdges(Canvas c, int left, int right, int top, int bottom);
103        public void setOnScrollListener(Runnable listener);
104    }
105
106    private final class OnLongClickDelegate implements View.OnLongClickListener {
107        View mOtherView;
108        OnLongClickDelegate(View other) {
109            mOtherView = other;
110        }
111        public boolean onLongClick(View v) {
112            return mOtherView.performLongClick();
113        }
114    }
115
116    /* package */ final static class ViewHolder {
117        View thumbnailView;
118        ImageView thumbnailViewImage;
119        Bitmap thumbnailViewImageBitmap;
120        ImageView iconView;
121        TextView labelView;
122        TextView descriptionView;
123        View calloutLine;
124        TaskDescription taskDescription;
125        boolean loadedThumbnailAndIcon;
126    }
127
128    /* package */ final class TaskDescriptionAdapter extends BaseAdapter {
129        private LayoutInflater mInflater;
130
131        public TaskDescriptionAdapter(Context context) {
132            mInflater = LayoutInflater.from(context);
133        }
134
135        public int getCount() {
136            return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0;
137        }
138
139        public Object getItem(int position) {
140            return position; // we only need the index
141        }
142
143        public long getItemId(int position) {
144            return position; // we just need something unique for this position
145        }
146
147        public View createView(ViewGroup parent) {
148            View convertView = mInflater.inflate(mRecentItemLayoutId, parent, false);
149            ViewHolder holder = new ViewHolder();
150            holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
151            holder.thumbnailViewImage =
152                    (ImageView) convertView.findViewById(R.id.app_thumbnail_image);
153            // If we set the default thumbnail now, we avoid an onLayout when we update
154            // the thumbnail later (if they both have the same dimensions)
155            updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
156            holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
157            holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon());
158            holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
159            holder.calloutLine = convertView.findViewById(R.id.recents_callout_line);
160            holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
161
162            convertView.setTag(holder);
163            return convertView;
164        }
165
166        public View getView(int position, View convertView, ViewGroup parent) {
167            if (convertView == null) {
168                convertView = createView(parent);
169            }
170            final ViewHolder holder = (ViewHolder) convertView.getTag();
171
172            // index is reverse since most recent appears at the bottom...
173            final int index = mRecentTaskDescriptions.size() - position - 1;
174
175            final TaskDescription td = mRecentTaskDescriptions.get(index);
176
177            holder.labelView.setText(td.getLabel());
178            holder.thumbnailView.setContentDescription(td.getLabel());
179            holder.loadedThumbnailAndIcon = td.isLoaded();
180            if (td.isLoaded()) {
181                updateThumbnail(holder, td.getThumbnail(), true, false);
182                updateIcon(holder, td.getIcon(), true, false);
183            }
184            if (index == 0) {
185                if (mAnimateIconOfFirstTask) {
186                    ViewHolder oldHolder = mItemToAnimateInWhenWindowAnimationIsFinished;
187                    if (oldHolder != null) {
188                        oldHolder.iconView.setAlpha(1f);
189                        oldHolder.iconView.setTranslationX(0f);
190                        oldHolder.iconView.setTranslationY(0f);
191                        oldHolder.labelView.setAlpha(1f);
192                        oldHolder.labelView.setTranslationX(0f);
193                        oldHolder.labelView.setTranslationY(0f);
194                        if (oldHolder.calloutLine != null) {
195                            oldHolder.calloutLine.setAlpha(1f);
196                            oldHolder.calloutLine.setTranslationX(0f);
197                            oldHolder.calloutLine.setTranslationY(0f);
198                        }
199                    }
200                    mItemToAnimateInWhenWindowAnimationIsFinished = holder;
201                    int translation = -getResources().getDimensionPixelSize(
202                            R.dimen.status_bar_recents_app_icon_translate_distance);
203                    final Configuration config = getResources().getConfiguration();
204                    if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
205                        if (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
206                            translation = -translation;
207                        }
208                        holder.iconView.setAlpha(0f);
209                        holder.iconView.setTranslationX(translation);
210                        holder.labelView.setAlpha(0f);
211                        holder.labelView.setTranslationX(translation);
212                        holder.calloutLine.setAlpha(0f);
213                        holder.calloutLine.setTranslationX(translation);
214                    } else {
215                        holder.iconView.setAlpha(0f);
216                        holder.iconView.setTranslationY(translation);
217                    }
218                    if (!mWaitingForWindowAnimation) {
219                        animateInIconOfFirstTask();
220                    }
221                }
222            }
223
224            holder.thumbnailView.setTag(td);
225            holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
226            holder.taskDescription = td;
227            return convertView;
228        }
229
230        public void recycleView(View v) {
231            ViewHolder holder = (ViewHolder) v.getTag();
232            updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
233            holder.iconView.setImageBitmap(mRecentTasksLoader.getDefaultIcon());
234            holder.iconView.setVisibility(INVISIBLE);
235            holder.iconView.animate().cancel();
236            holder.labelView.setText(null);
237            holder.labelView.animate().cancel();
238            holder.thumbnailView.setContentDescription(null);
239            holder.thumbnailView.setTag(null);
240            holder.thumbnailView.setOnLongClickListener(null);
241            holder.thumbnailView.setVisibility(INVISIBLE);
242            holder.iconView.setAlpha(1f);
243            holder.iconView.setTranslationX(0f);
244            holder.iconView.setTranslationY(0f);
245            holder.labelView.setAlpha(1f);
246            holder.labelView.setTranslationX(0f);
247            holder.labelView.setTranslationY(0f);
248            if (holder.calloutLine != null) {
249                holder.calloutLine.setAlpha(1f);
250                holder.calloutLine.setTranslationX(0f);
251                holder.calloutLine.setTranslationY(0f);
252                holder.calloutLine.animate().cancel();
253            }
254            holder.taskDescription = null;
255            holder.loadedThumbnailAndIcon = false;
256        }
257    }
258
259    public RecentsPanelView(Context context, AttributeSet attrs) {
260        this(context, attrs, 0);
261    }
262
263    public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
264        super(context, attrs, defStyle);
265        updateValuesFromResources();
266
267        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecentsPanelView,
268                defStyle, 0);
269
270        mRecentItemLayoutId = a.getResourceId(R.styleable.RecentsPanelView_recentItemLayout, 0);
271        mRecentTasksLoader = RecentTasksLoader.getInstance(context);
272        a.recycle();
273    }
274
275    public int numItemsInOneScreenful() {
276        return mRecentsContainer.numItemsInOneScreenful();
277    }
278
279    private boolean pointInside(int x, int y, View v) {
280        final int l = v.getLeft();
281        final int r = v.getRight();
282        final int t = v.getTop();
283        final int b = v.getBottom();
284        return x >= l && x < r && y >= t && y < b;
285    }
286
287    public boolean isInContentArea(int x, int y) {
288        return pointInside(x, y, (View) mRecentsContainer);
289    }
290
291    public void show(boolean show) {
292        show(show, null, false, false);
293    }
294
295    public void show(boolean show, ArrayList<TaskDescription> recentTaskDescriptions,
296            boolean firstScreenful, boolean animateIconOfFirstTask) {
297        if (show && mCallUiHiddenBeforeNextReload) {
298            onUiHidden();
299            recentTaskDescriptions = null;
300            mAnimateIconOfFirstTask = false;
301            mWaitingForWindowAnimation = false;
302        } else {
303            mAnimateIconOfFirstTask = animateIconOfFirstTask;
304            mWaitingForWindowAnimation = animateIconOfFirstTask;
305        }
306        if (show) {
307            mWaitingToShow = true;
308            refreshRecentTasksList(recentTaskDescriptions, firstScreenful);
309            showIfReady();
310        } else {
311            showImpl(false);
312        }
313    }
314
315    private void showIfReady() {
316        // mWaitingToShow => there was a touch up on the recents button
317        // mRecentTaskDescriptions != null => we've created views for the first screenful of items
318        if (mWaitingToShow && mRecentTaskDescriptions != null) {
319            showImpl(true);
320        }
321    }
322
323    static void sendCloseSystemWindows(Context context, String reason) {
324        if (ActivityManagerNative.isSystemReady()) {
325            try {
326                ActivityManagerNative.getDefault().closeSystemDialogs(reason);
327            } catch (RemoteException e) {
328            }
329        }
330    }
331
332    private void showImpl(boolean show) {
333        sendCloseSystemWindows(mContext, BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
334
335        mShowing = show;
336
337        if (show) {
338            // if there are no apps, bring up a "No recent apps" message
339            boolean noApps = mRecentTaskDescriptions != null
340                    && (mRecentTaskDescriptions.size() == 0);
341            mRecentsNoApps.setAlpha(1f);
342            mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
343
344            onAnimationEnd(null);
345            setFocusable(true);
346            setFocusableInTouchMode(true);
347            requestFocus();
348        } else {
349            mWaitingToShow = false;
350            // call onAnimationEnd() and clearRecentTasksList() in onUiHidden()
351            mCallUiHiddenBeforeNextReload = true;
352            if (mPopup != null) {
353                mPopup.dismiss();
354            }
355        }
356    }
357
358    protected void onAttachedToWindow () {
359        super.onAttachedToWindow();
360        final ViewRootImpl root = getViewRootImpl();
361        if (root != null) {
362            root.setDrawDuringWindowsAnimating(true);
363        }
364    }
365
366    public void onUiHidden() {
367        mCallUiHiddenBeforeNextReload = false;
368        if (!mShowing && mRecentTaskDescriptions != null) {
369            onAnimationEnd(null);
370            clearRecentTasksList();
371        }
372    }
373
374    public void dismiss() {
375        ((RecentsActivity) mContext).dismissAndGoHome();
376    }
377
378    public void dismissAndGoBack() {
379        ((RecentsActivity) mContext).dismissAndGoBack();
380    }
381
382    public void onAnimationCancel(Animator animation) {
383    }
384
385    public void onAnimationEnd(Animator animation) {
386        if (mShowing) {
387            final LayoutTransition transitioner = new LayoutTransition();
388            ((ViewGroup)mRecentsContainer).setLayoutTransition(transitioner);
389            createCustomAnimations(transitioner);
390        } else {
391            ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
392        }
393    }
394
395    public void onAnimationRepeat(Animator animation) {
396    }
397
398    public void onAnimationStart(Animator animation) {
399    }
400
401    @Override
402    public boolean dispatchHoverEvent(MotionEvent event) {
403        // Ignore hover events outside of this panel bounds since such events
404        // generate spurious accessibility events with the panel content when
405        // tapping outside of it, thus confusing the user.
406        final int x = (int) event.getX();
407        final int y = (int) event.getY();
408        if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) {
409            return super.dispatchHoverEvent(event);
410        }
411        return true;
412    }
413
414    /**
415     * Whether the panel is showing, or, if it's animating, whether it will be
416     * when the animation is done.
417     */
418    public boolean isShowing() {
419        return mShowing;
420    }
421
422    public void setRecentTasksLoader(RecentTasksLoader loader) {
423        mRecentTasksLoader = loader;
424    }
425
426    public void updateValuesFromResources() {
427        final Resources res = mContext.getResources();
428        mThumbnailWidth = Math.round(res.getDimension(R.dimen.status_bar_recents_thumbnail_width));
429        mFitThumbnailToXY = res.getBoolean(R.bool.config_recents_thumbnail_image_fits_to_xy);
430    }
431
432    @Override
433    protected void onFinishInflate() {
434        super.onFinishInflate();
435
436        mRecentsContainer = (RecentsScrollView) findViewById(R.id.recents_container);
437        mRecentsContainer.setOnScrollListener(new Runnable() {
438            public void run() {
439                // need to redraw the faded edges
440                invalidate();
441            }
442        });
443        mListAdapter = new TaskDescriptionAdapter(mContext);
444        mRecentsContainer.setAdapter(mListAdapter);
445        mRecentsContainer.setCallback(this);
446
447        mRecentsScrim = findViewById(R.id.recents_bg_protect);
448        mRecentsNoApps = findViewById(R.id.recents_no_apps);
449
450        if (mRecentsScrim != null) {
451            mHighEndGfx = ActivityManager.isHighEndGfx();
452            if (!mHighEndGfx) {
453                mRecentsScrim.setBackground(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
461    public void setMinSwipeAlpha(float minAlpha) {
462        mRecentsContainer.setMinSwipeAlpha(minAlpha);
463    }
464
465    private void createCustomAnimations(LayoutTransition transitioner) {
466        transitioner.setDuration(200);
467        transitioner.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, 0);
468        transitioner.setAnimator(LayoutTransition.DISAPPEARING, null);
469    }
470
471    private void updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim) {
472        if (icon != null) {
473            h.iconView.setImageDrawable(icon);
474            if (show && h.iconView.getVisibility() != View.VISIBLE) {
475                if (anim) {
476                    h.iconView.setAnimation(
477                            AnimationUtils.loadAnimation(mContext, R.anim.recent_appear));
478                }
479                h.iconView.setVisibility(View.VISIBLE);
480            }
481        }
482    }
483
484    private void updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim) {
485        if (thumbnail != null) {
486            // Should remove the default image in the frame
487            // that this now covers, to improve scrolling speed.
488            // That can't be done until the anim is complete though.
489            h.thumbnailViewImage.setImageBitmap(thumbnail);
490
491            // scale the image to fill the full width of the ImageView. do this only if
492            // we haven't set a bitmap before, or if the bitmap size has changed
493            if (h.thumbnailViewImageBitmap == null ||
494                h.thumbnailViewImageBitmap.getWidth() != thumbnail.getWidth() ||
495                h.thumbnailViewImageBitmap.getHeight() != thumbnail.getHeight()) {
496                if (mFitThumbnailToXY) {
497                    h.thumbnailViewImage.setScaleType(ScaleType.FIT_XY);
498                } else {
499                    Matrix scaleMatrix = new Matrix();
500                    float scale = mThumbnailWidth / (float) thumbnail.getWidth();
501                    scaleMatrix.setScale(scale, scale);
502                    h.thumbnailViewImage.setScaleType(ScaleType.MATRIX);
503                    h.thumbnailViewImage.setImageMatrix(scaleMatrix);
504                }
505            }
506            if (show && h.thumbnailView.getVisibility() != View.VISIBLE) {
507                if (anim) {
508                    h.thumbnailView.setAnimation(
509                            AnimationUtils.loadAnimation(mContext, R.anim.recent_appear));
510                }
511                h.thumbnailView.setVisibility(View.VISIBLE);
512            }
513            h.thumbnailViewImageBitmap = thumbnail;
514        }
515    }
516
517    void onTaskThumbnailLoaded(TaskDescription td) {
518        synchronized (td) {
519            if (mRecentsContainer != null) {
520                ViewGroup container = (ViewGroup) mRecentsContainer;
521                if (container instanceof RecentsScrollView) {
522                    container = (ViewGroup) container.findViewById(
523                            R.id.recents_linear_layout);
524                }
525                // Look for a view showing this thumbnail, to update.
526                for (int i=0; i < container.getChildCount(); i++) {
527                    View v = container.getChildAt(i);
528                    if (v.getTag() instanceof ViewHolder) {
529                        ViewHolder h = (ViewHolder)v.getTag();
530                        if (!h.loadedThumbnailAndIcon && h.taskDescription == td) {
531                            // only fade in the thumbnail if recents is already visible-- we
532                            // show it immediately otherwise
533                            //boolean animateShow = mShowing &&
534                            //    mRecentsContainer.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD;
535                            boolean animateShow = false;
536                            updateIcon(h, td.getIcon(), true, animateShow);
537                            updateThumbnail(h, td.getThumbnail(), true, animateShow);
538                            h.loadedThumbnailAndIcon = true;
539                        }
540                    }
541                }
542            }
543        }
544        showIfReady();
545    }
546
547    private void animateInIconOfFirstTask() {
548        if (mItemToAnimateInWhenWindowAnimationIsFinished != null &&
549                !mRecentTasksLoader.isFirstScreenful()) {
550            int timeSinceWindowAnimation =
551                    (int) (System.currentTimeMillis() - mWindowAnimationStartTime);
552            final int minStartDelay = 150;
553            final int startDelay = Math.max(0, Math.min(
554                    minStartDelay - timeSinceWindowAnimation, minStartDelay));
555            final int duration = 250;
556            final ViewHolder holder = mItemToAnimateInWhenWindowAnimationIsFinished;
557            final TimeInterpolator cubic = new DecelerateInterpolator(1.5f);
558            FirstFrameAnimatorHelper.initializeDrawListener(holder.iconView);
559            for (View v :
560                new View[] { holder.iconView, holder.labelView, holder.calloutLine }) {
561                if (v != null) {
562                    ViewPropertyAnimator vpa = v.animate().translationX(0).translationY(0)
563                            .alpha(1f).setStartDelay(startDelay)
564                            .setDuration(duration).setInterpolator(cubic);
565                    FirstFrameAnimatorHelper h = new FirstFrameAnimatorHelper(vpa, v);
566                }
567            }
568            mItemToAnimateInWhenWindowAnimationIsFinished = null;
569            mAnimateIconOfFirstTask = false;
570        }
571    }
572
573    public void onWindowAnimationStart() {
574        mWaitingForWindowAnimation = false;
575        mWindowAnimationStartTime = System.currentTimeMillis();
576        animateInIconOfFirstTask();
577    }
578
579    public void clearRecentTasksList() {
580        // Clear memory used by screenshots
581        if (mRecentTaskDescriptions != null) {
582            mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(this);
583            onTaskLoadingCancelled();
584        }
585    }
586
587    public void onTaskLoadingCancelled() {
588        // Gets called by RecentTasksLoader when it's cancelled
589        if (mRecentTaskDescriptions != null) {
590            mRecentTaskDescriptions = null;
591            mListAdapter.notifyDataSetInvalidated();
592        }
593    }
594
595    public void refreshViews() {
596        mListAdapter.notifyDataSetInvalidated();
597        updateUiElements();
598        showIfReady();
599    }
600
601    public void refreshRecentTasksList() {
602        refreshRecentTasksList(null, false);
603    }
604
605    private void refreshRecentTasksList(
606            ArrayList<TaskDescription> recentTasksList, boolean firstScreenful) {
607        if (mRecentTaskDescriptions == null && recentTasksList != null) {
608            onTasksLoaded(recentTasksList, firstScreenful);
609        } else {
610            mRecentTasksLoader.loadTasksInBackground();
611        }
612    }
613
614    public void onTasksLoaded(ArrayList<TaskDescription> tasks, boolean firstScreenful) {
615        if (mRecentTaskDescriptions == null) {
616            mRecentTaskDescriptions = new ArrayList<TaskDescription>(tasks);
617        } else {
618            mRecentTaskDescriptions.addAll(tasks);
619        }
620        if (((RecentsActivity) mContext).isActivityShowing()) {
621            refreshViews();
622        }
623    }
624
625    private void updateUiElements() {
626        final int items = mRecentTaskDescriptions != null
627                ? mRecentTaskDescriptions.size() : 0;
628
629        ((View) mRecentsContainer).setVisibility(items > 0 ? View.VISIBLE : View.GONE);
630
631        // Set description for accessibility
632        int numRecentApps = mRecentTaskDescriptions != null
633                ? mRecentTaskDescriptions.size() : 0;
634        String recentAppsAccessibilityDescription;
635        if (numRecentApps == 0) {
636            recentAppsAccessibilityDescription =
637                getResources().getString(R.string.status_bar_no_recent_apps);
638        } else {
639            recentAppsAccessibilityDescription = getResources().getQuantityString(
640                R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps);
641        }
642        setContentDescription(recentAppsAccessibilityDescription);
643    }
644
645    public boolean simulateClick(int persistentTaskId) {
646        View v = mRecentsContainer.findViewForTask(persistentTaskId);
647        if (v != null) {
648            handleOnClick(v);
649            return true;
650        }
651        return false;
652    }
653
654    public void handleOnClick(View view) {
655        ViewHolder holder = (ViewHolder) view.getTag();
656        TaskDescription ad = holder.taskDescription;
657        final Context context = view.getContext();
658        final ActivityManager am = (ActivityManager)
659                context.getSystemService(Context.ACTIVITY_SERVICE);
660        Bitmap bm = holder.thumbnailViewImageBitmap;
661        boolean usingDrawingCache;
662        if (bm.getWidth() == holder.thumbnailViewImage.getWidth() &&
663                bm.getHeight() == holder.thumbnailViewImage.getHeight()) {
664            usingDrawingCache = false;
665        } else {
666            holder.thumbnailViewImage.setDrawingCacheEnabled(true);
667            bm = holder.thumbnailViewImage.getDrawingCache();
668            usingDrawingCache = true;
669        }
670        Bundle opts = (bm == null) ?
671                null :
672                ActivityOptions.makeThumbnailScaleUpAnimation(
673                        holder.thumbnailViewImage, bm, 0, 0, null).toBundle();
674
675        show(false);
676        if (ad.taskId >= 0) {
677            // This is an active task; it should just go to the foreground.
678            am.moveTaskToFront(ad.taskId, ActivityManager.MOVE_TASK_WITH_HOME,
679                    opts);
680        } else {
681            Intent intent = ad.intent;
682            intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
683                    | Intent.FLAG_ACTIVITY_TASK_ON_HOME
684                    | Intent.FLAG_ACTIVITY_NEW_TASK);
685            if (DEBUG) Log.v(TAG, "Starting activity " + intent);
686            try {
687                context.startActivityAsUser(intent, opts,
688                        new UserHandle(UserHandle.USER_CURRENT));
689            } catch (SecurityException e) {
690                Log.e(TAG, "Recents does not have the permission to launch " + intent, e);
691            } catch (ActivityNotFoundException e) {
692                Log.e(TAG, "Error launching activity " + intent, e);
693            }
694        }
695        if (usingDrawingCache) {
696            holder.thumbnailViewImage.setDrawingCacheEnabled(false);
697        }
698    }
699
700    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
701        handleOnClick(view);
702    }
703
704    public void handleSwipe(View view) {
705        TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
706        if (ad == null) {
707            Log.v(TAG, "Not able to find activity description for swiped task; view=" + view +
708                    " tag=" + view.getTag());
709            return;
710        }
711        if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
712        mRecentTaskDescriptions.remove(ad);
713        mRecentTasksLoader.remove(ad);
714
715        // Handled by widget containers to enable LayoutTransitions properly
716        // mListAdapter.notifyDataSetChanged();
717
718        if (mRecentTaskDescriptions.size() == 0) {
719            dismissAndGoBack();
720        }
721
722        // Currently, either direction means the same thing, so ignore direction and remove
723        // the task.
724        final ActivityManager am = (ActivityManager)
725                mContext.getSystemService(Context.ACTIVITY_SERVICE);
726        if (am != null) {
727            am.removeTask(ad.persistentTaskId, ActivityManager.REMOVE_TASK_KILL_PROCESS);
728
729            // Accessibility feedback
730            setContentDescription(
731                    mContext.getString(R.string.accessibility_recents_item_dismissed, ad.getLabel()));
732            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
733            setContentDescription(null);
734        }
735    }
736
737    private void startApplicationDetailsActivity(String packageName) {
738        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
739                Uri.fromParts("package", packageName, null));
740        intent.setComponent(intent.resolveActivity(mContext.getPackageManager()));
741        TaskStackBuilder.create(getContext())
742                .addNextIntentWithParentStack(intent).startActivities();
743    }
744
745    public boolean onInterceptTouchEvent(MotionEvent ev) {
746        if (mPopup != null) {
747            return true;
748        } else {
749            return super.onInterceptTouchEvent(ev);
750        }
751    }
752
753    public void handleLongPress(
754            final View selectedView, final View anchorView, final View thumbnailView) {
755        thumbnailView.setSelected(true);
756        final PopupMenu popup =
757            new PopupMenu(mContext, anchorView == null ? selectedView : anchorView);
758        mPopup = popup;
759        popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
760        popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
761            public boolean onMenuItemClick(MenuItem item) {
762                if (item.getItemId() == R.id.recent_remove_item) {
763                    ((ViewGroup) mRecentsContainer).removeViewInLayout(selectedView);
764                } else if (item.getItemId() == R.id.recent_inspect_item) {
765                    ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
766                    if (viewHolder != null) {
767                        final TaskDescription ad = viewHolder.taskDescription;
768                        startApplicationDetailsActivity(ad.packageName);
769                        show(false);
770                    } else {
771                        throw new IllegalStateException("Oops, no tag on view " + selectedView);
772                    }
773                } else {
774                    return false;
775                }
776                return true;
777            }
778        });
779        popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
780            public void onDismiss(PopupMenu menu) {
781                thumbnailView.setSelected(false);
782                mPopup = null;
783            }
784        });
785        popup.show();
786    }
787
788    @Override
789    protected void dispatchDraw(Canvas canvas) {
790        super.dispatchDraw(canvas);
791
792        int paddingLeft = mPaddingLeft;
793        final boolean offsetRequired = isPaddingOffsetRequired();
794        if (offsetRequired) {
795            paddingLeft += getLeftPaddingOffset();
796        }
797
798        int left = mScrollX + paddingLeft;
799        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
800        int top = mScrollY + getFadeTop(offsetRequired);
801        int bottom = top + getFadeHeight(offsetRequired);
802
803        if (offsetRequired) {
804            right += getRightPaddingOffset();
805            bottom += getBottomPaddingOffset();
806        }
807        mRecentsContainer.drawFadedEdges(canvas, left, right, top, bottom);
808    }
809}
810