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