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