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