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