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