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