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