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