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