• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onRecentsPanelVisibilityChanged(boolean visible)106         public void onRecentsPanelVisibilityChanged(boolean visible);
107     }
108 
109     public static interface RecentsScrollView {
numItemsInOneScreenful()110         public int numItemsInOneScreenful();
setAdapter(TaskDescriptionAdapter adapter)111         public void setAdapter(TaskDescriptionAdapter adapter);
setCallback(RecentsCallback callback)112         public void setCallback(RecentsCallback callback);
setMinSwipeAlpha(float minAlpha)113         public void setMinSwipeAlpha(float minAlpha);
114     }
115 
116     private final class OnLongClickDelegate implements View.OnLongClickListener {
117         View mOtherView;
OnLongClickDelegate(View other)118         OnLongClickDelegate(View other) {
119             mOtherView = other;
120         }
onLongClick(View v)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 
TaskDescriptionAdapter(Context context)140         public TaskDescriptionAdapter(Context context) {
141             mInflater = LayoutInflater.from(context);
142         }
143 
getCount()144         public int getCount() {
145             return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0;
146         }
147 
getItem(int position)148         public Object getItem(int position) {
149             return position; // we only need the index
150         }
151 
getItemId(int position)152         public long getItemId(int position) {
153             return position; // we just need something unique for this position
154         }
155 
createView(ViewGroup parent)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 
getView(int position, View convertView, ViewGroup parent)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 
recycleView(View v)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 
RecentsPanelView(Context context, AttributeSet attrs)226     public RecentsPanelView(Context context, AttributeSet attrs) {
227         this(context, attrs, 0);
228     }
229 
RecentsPanelView(Context context, AttributeSet attrs, int defStyle)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 
numItemsInOneScreenful()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
onKeyUp(int keyCode, KeyEvent event)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 
pointInside(int x, int y, View v)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 
isInContentArea(int x, int y)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 
show(boolean show, boolean animate)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 
showIfReady()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 
sendCloseSystemWindows(Context context, String reason)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 
show(boolean show, boolean animate, ArrayList<TaskDescription> recentTaskDescriptions, boolean firstScreenful)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 
dismiss()369     public void dismiss() {
370         hide(true);
371     }
372 
hide(boolean animate)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 
onAnimationCancel(Animator animation)383     public void onAnimationCancel(Animator animation) {
384     }
385 
onAnimationEnd(Animator animation)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 
onAnimationRepeat(Animator animation)397     public void onAnimationRepeat(Animator animation) {
398     }
399 
onAnimationStart(Animator animation)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
onLayout(boolean changed, int l, int t, int r, int b)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
dispatchHoverEvent(MotionEvent event)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      */
isShowing()430     public boolean isShowing() {
431         return mShowing;
432     }
433 
setBar(BaseStatusBar bar)434     public void setBar(BaseStatusBar bar) {
435         mBar = bar;
436 
437     }
438 
setStatusBarView(View statusBarView)439     public void setStatusBarView(View statusBarView) {
440         if (mStatusBarTouchProxy != null) {
441             mStatusBarTouchProxy.setStatusBar(statusBarView);
442         }
443     }
444 
setRecentTasksLoader(RecentTasksLoader loader)445     public void setRecentTasksLoader(RecentTasksLoader loader) {
446         mRecentTasksLoader = loader;
447     }
448 
setOnVisibilityChangedListener(OnRecentsPanelVisibilityChangedListener l)449     public void setOnVisibilityChangedListener(OnRecentsPanelVisibilityChangedListener l) {
450         mVisibilityChangedListener = l;
451 
452     }
453 
setVisibility(int visibility)454     public void setVisibility(int visibility) {
455         if (mVisibilityChangedListener != null) {
456             mVisibilityChangedListener.onRecentsPanelVisibilityChanged(visibility == VISIBLE);
457         }
458         super.setVisibility(visibility);
459     }
460 
updateValuesFromResources()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
onFinishInflate()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 
setMinSwipeAlpha(float minAlpha)513     public void setMinSwipeAlpha(float minAlpha) {
514         if (mRecentsContainer instanceof RecentsScrollView){
515             RecentsScrollView scrollView
516                 = (RecentsScrollView) mRecentsContainer;
517             scrollView.setMinSwipeAlpha(minAlpha);
518         }
519     }
520 
createCustomAnimations(LayoutTransition transitioner)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 
updateIcon(ViewHolder h, Drawable icon, boolean show, boolean anim)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 
updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim)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 
onTaskThumbnailLoaded(TaskDescription td)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
onTouch(View v, MotionEvent ev)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 
preloadRecentTasksList()629     public void preloadRecentTasksList() {
630         if (!mShowing) {
631             mPreloadTasksRunnable.run();
632         }
633     }
634 
clearRecentTasksList()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 
refreshRecentTasksList()645     public void refreshRecentTasksList() {
646         refreshRecentTasksList(null, false);
647     }
648 
refreshRecentTasksList( ArrayList<TaskDescription> recentTasksList, boolean firstScreenful)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 
onTasksLoaded(ArrayList<TaskDescription> tasks)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 
getRecentTasksList()682     public ArrayList<TaskDescription> getRecentTasksList() {
683         return mRecentTaskDescriptions;
684     }
685 
getFirstScreenful()686     public boolean getFirstScreenful() {
687         return mFirstScreenful;
688     }
689 
updateUiElements(Configuration config)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;
handleOnClick(View view)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 
hideWindow()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 
onItemClick(AdapterView<?> parent, View view, int position, long id)809     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
810         handleOnClick(view);
811     }
812 
handleSwipe(View view)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 
startApplicationDetailsActivity(String packageName)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 
onInterceptTouchEvent(MotionEvent ev)852     public boolean onInterceptTouchEvent(MotionEvent ev) {
853         if (mPopup != null) {
854             return true;
855         } else {
856             return super.onInterceptTouchEvent(ev);
857         }
858     }
859 
handleLongPress( final View selectedView, final View anchorView, final View thumbnailView)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