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