• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.recents.views;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.annotation.Nullable;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.pm.ActivityInfo;
25 import android.content.res.Resources;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.ColorFilter;
29 import android.graphics.Paint;
30 import android.graphics.PixelFormat;
31 import android.graphics.PorterDuff;
32 import android.graphics.Rect;
33 import android.graphics.drawable.Drawable;
34 import android.graphics.drawable.RippleDrawable;
35 import android.os.CountDownTimer;
36 import android.support.v4.graphics.ColorUtils;
37 import android.util.AttributeSet;
38 import android.view.Gravity;
39 import android.view.View;
40 import android.view.ViewAnimationUtils;
41 import android.view.ViewDebug;
42 import android.view.ViewGroup;
43 import android.widget.FrameLayout;
44 import android.widget.ImageView;
45 import android.widget.ProgressBar;
46 import android.widget.TextView;
47 
48 import com.android.internal.logging.MetricsLogger;
49 import com.android.systemui.Interpolators;
50 import com.android.systemui.R;
51 import com.android.systemui.recents.Constants;
52 import com.android.systemui.recents.Recents;
53 import com.android.systemui.recents.events.EventBus;
54 import com.android.systemui.recents.events.activity.LaunchTaskEvent;
55 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
56 import com.android.systemui.recents.misc.SystemServicesProxy;
57 import com.android.systemui.recents.misc.Utilities;
58 import com.android.systemui.recents.model.Task;
59 
60 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
61 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
62 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
63 
64 /* The task bar view */
65 public class TaskViewHeader extends FrameLayout
66         implements View.OnClickListener, View.OnLongClickListener {
67 
68     private static final float HIGHLIGHT_LIGHTNESS_INCREMENT = 0.075f;
69     private static final float OVERLAY_LIGHTNESS_INCREMENT = -0.0625f;
70     private static final int OVERLAY_REVEAL_DURATION = 250;
71     private static final long FOCUS_INDICATOR_INTERVAL_MS = 30;
72 
73     /**
74      * A color drawable that draws a slight highlight at the top to help it stand out.
75      */
76     private class HighlightColorDrawable extends Drawable {
77 
78         private Paint mHighlightPaint = new Paint();
79         private Paint mBackgroundPaint = new Paint();
80         private int mColor;
81         private float mDimAlpha;
82 
HighlightColorDrawable()83         public HighlightColorDrawable() {
84             mBackgroundPaint.setColor(Color.argb(255, 0, 0, 0));
85             mBackgroundPaint.setAntiAlias(true);
86             mHighlightPaint.setColor(Color.argb(255, 255, 255, 255));
87             mHighlightPaint.setAntiAlias(true);
88         }
89 
setColorAndDim(int color, float dimAlpha)90         public void setColorAndDim(int color, float dimAlpha) {
91             if (mColor != color || Float.compare(mDimAlpha, dimAlpha) != 0) {
92                 mColor = color;
93                 mDimAlpha = dimAlpha;
94                 if (mShouldDarkenBackgroundColor) {
95                     color = getSecondaryColor(color, false /* useLightOverlayColor */);
96                 }
97                 mBackgroundPaint.setColor(color);
98 
99                 ColorUtils.colorToHSL(color, mTmpHSL);
100                 // TODO: Consider using the saturation of the color to adjust the lightness as well
101                 mTmpHSL[2] = Math.min(1f,
102                         mTmpHSL[2] + HIGHLIGHT_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
103                 mHighlightPaint.setColor(ColorUtils.HSLToColor(mTmpHSL));
104 
105                 invalidateSelf();
106             }
107         }
108 
109         @Override
setColorFilter(@ullable ColorFilter colorFilter)110         public void setColorFilter(@Nullable ColorFilter colorFilter) {
111             // Do nothing
112         }
113 
114         @Override
setAlpha(int alpha)115         public void setAlpha(int alpha) {
116             // Do nothing
117         }
118 
119         @Override
draw(Canvas canvas)120         public void draw(Canvas canvas) {
121             // Draw the highlight at the top edge (but put the bottom edge just out of view)
122             canvas.drawRoundRect(0, 0, mTaskViewRect.width(),
123                     2 * Math.max(mHighlightHeight, mCornerRadius),
124                     mCornerRadius, mCornerRadius, mHighlightPaint);
125 
126             // Draw the background with the rounded corners
127             canvas.drawRoundRect(0, mHighlightHeight, mTaskViewRect.width(),
128                     getHeight() + mCornerRadius,
129                     mCornerRadius, mCornerRadius, mBackgroundPaint);
130         }
131 
132         @Override
getOpacity()133         public int getOpacity() {
134             return PixelFormat.OPAQUE;
135         }
136 
getColor()137         public int getColor() {
138             return mColor;
139         }
140     }
141 
142     Task mTask;
143 
144     // Header views
145     ImageView mIconView;
146     TextView mTitleView;
147     ImageView mMoveTaskButton;
148     ImageView mDismissButton;
149     FrameLayout mAppOverlayView;
150     ImageView mAppIconView;
151     ImageView mAppInfoView;
152     TextView mAppTitleView;
153     ProgressBar mFocusTimerIndicator;
154 
155     // Header drawables
156     @ViewDebug.ExportedProperty(category="recents")
157     Rect mTaskViewRect = new Rect();
158     int mHeaderBarHeight;
159     int mHeaderButtonPadding;
160     int mCornerRadius;
161     int mHighlightHeight;
162     @ViewDebug.ExportedProperty(category="recents")
163     float mDimAlpha;
164     Drawable mLightDismissDrawable;
165     Drawable mDarkDismissDrawable;
166     Drawable mLightFreeformIcon;
167     Drawable mDarkFreeformIcon;
168     Drawable mLightFullscreenIcon;
169     Drawable mDarkFullscreenIcon;
170     Drawable mLightInfoIcon;
171     Drawable mDarkInfoIcon;
172     int mTaskBarViewLightTextColor;
173     int mTaskBarViewDarkTextColor;
174     int mDisabledTaskBarBackgroundColor;
175     int mMoveTaskTargetStackId = INVALID_STACK_ID;
176 
177     // Header background
178     private HighlightColorDrawable mBackground;
179     private HighlightColorDrawable mOverlayBackground;
180     private float[] mTmpHSL = new float[3];
181 
182     // Header dim, which is only used when task view hardware layers are not used
183     private Paint mDimLayerPaint = new Paint();
184 
185     // Whether the background color should be darkened to differentiate from the primary color.
186     // Used in grid layout.
187     private boolean mShouldDarkenBackgroundColor = false;
188 
189     private CountDownTimer mFocusTimerCountDown;
190 
TaskViewHeader(Context context)191     public TaskViewHeader(Context context) {
192         this(context, null);
193     }
194 
TaskViewHeader(Context context, AttributeSet attrs)195     public TaskViewHeader(Context context, AttributeSet attrs) {
196         this(context, attrs, 0);
197     }
198 
TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr)199     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
200         this(context, attrs, defStyleAttr, 0);
201     }
202 
TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)203     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
204         super(context, attrs, defStyleAttr, defStyleRes);
205         setWillNotDraw(false);
206 
207         // Load the dismiss resources
208         Resources res = context.getResources();
209         mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
210         mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
211         mCornerRadius = Recents.getConfiguration().isGridEnabled ?
212                 res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) :
213                 res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
214         mHighlightHeight = res.getDimensionPixelSize(R.dimen.recents_task_view_highlight);
215         mTaskBarViewLightTextColor = context.getColor(R.color.recents_task_bar_light_text_color);
216         mTaskBarViewDarkTextColor = context.getColor(R.color.recents_task_bar_dark_text_color);
217         mLightFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_light);
218         mDarkFreeformIcon = context.getDrawable(R.drawable.recents_move_task_freeform_dark);
219         mLightFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_light);
220         mDarkFullscreenIcon = context.getDrawable(R.drawable.recents_move_task_fullscreen_dark);
221         mLightInfoIcon = context.getDrawable(R.drawable.recents_info_light);
222         mDarkInfoIcon = context.getDrawable(R.drawable.recents_info_dark);
223         mDisabledTaskBarBackgroundColor =
224                 context.getColor(R.color.recents_task_bar_disabled_background_color);
225 
226         // Configure the background and dim
227         mBackground = new HighlightColorDrawable();
228         mBackground.setColorAndDim(Color.argb(255, 0, 0, 0), 0f);
229         setBackground(mBackground);
230         mOverlayBackground = new HighlightColorDrawable();
231         mDimLayerPaint.setColor(Color.argb(255, 0, 0, 0));
232         mDimLayerPaint.setAntiAlias(true);
233     }
234 
235     /**
236      * Resets this header along with the TaskView.
237      */
reset()238     public void reset() {
239         hideAppOverlay(true /* immediate */);
240     }
241 
242     @Override
onFinishInflate()243     protected void onFinishInflate() {
244         SystemServicesProxy ssp = Recents.getSystemServices();
245 
246         // Initialize the icon and description views
247         mIconView = findViewById(R.id.icon);
248         mIconView.setOnLongClickListener(this);
249         mTitleView = findViewById(R.id.title);
250         mDismissButton = findViewById(R.id.dismiss_task);
251         if (ssp.hasFreeformWorkspaceSupport()) {
252             mMoveTaskButton = findViewById(R.id.move_task);
253         }
254 
255         onConfigurationChanged();
256     }
257 
258     /**
259      * Programmatically sets the layout params for a header bar layout.  This is necessary because
260      * we can't get resources based on the current configuration, but instead need to get them
261      * based on the device configuration.
262      */
updateLayoutParams(View icon, View title, View secondaryButton, View button)263     private void updateLayoutParams(View icon, View title, View secondaryButton, View button) {
264         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
265                 ViewGroup.LayoutParams.MATCH_PARENT, mHeaderBarHeight, Gravity.TOP);
266         setLayoutParams(lp);
267         lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.START);
268         icon.setLayoutParams(lp);
269         lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
270                 ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.CENTER_VERTICAL);
271         lp.setMarginStart(mHeaderBarHeight);
272         lp.setMarginEnd(mMoveTaskButton != null
273                 ? 2 * mHeaderBarHeight
274                 : mHeaderBarHeight);
275         title.setLayoutParams(lp);
276         if (secondaryButton != null) {
277             lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
278             lp.setMarginEnd(mHeaderBarHeight);
279             secondaryButton.setLayoutParams(lp);
280             secondaryButton.setPadding(mHeaderButtonPadding, mHeaderButtonPadding,
281                     mHeaderButtonPadding, mHeaderButtonPadding);
282         }
283         lp = new FrameLayout.LayoutParams(mHeaderBarHeight, mHeaderBarHeight, Gravity.END);
284         button.setLayoutParams(lp);
285         button.setPadding(mHeaderButtonPadding, mHeaderButtonPadding, mHeaderButtonPadding,
286                 mHeaderButtonPadding);
287     }
288 
289     /**
290      * Update the header view when the configuration changes.
291      */
onConfigurationChanged()292     public void onConfigurationChanged() {
293         // Update the dimensions of everything in the header. We do this because we need to use
294         // resources for the display, and not the current configuration.
295         Resources res = getResources();
296         int headerBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
297                 R.dimen.recents_task_view_header_height,
298                 R.dimen.recents_task_view_header_height,
299                 R.dimen.recents_task_view_header_height,
300                 R.dimen.recents_task_view_header_height_tablet_land,
301                 R.dimen.recents_task_view_header_height,
302                 R.dimen.recents_task_view_header_height_tablet_land,
303                 R.dimen.recents_grid_task_view_header_height);
304         int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
305                 R.dimen.recents_task_view_header_button_padding,
306                 R.dimen.recents_task_view_header_button_padding,
307                 R.dimen.recents_task_view_header_button_padding,
308                 R.dimen.recents_task_view_header_button_padding_tablet_land,
309                 R.dimen.recents_task_view_header_button_padding,
310                 R.dimen.recents_task_view_header_button_padding_tablet_land,
311                 R.dimen.recents_grid_task_view_header_button_padding);
312         if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) {
313             mHeaderBarHeight = headerBarHeight;
314             mHeaderButtonPadding = headerButtonPadding;
315             updateLayoutParams(mIconView, mTitleView, mMoveTaskButton, mDismissButton);
316             if (mAppOverlayView != null) {
317                 updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
318             }
319         }
320     }
321 
322     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)323     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
324         super.onLayout(changed, left, top, right, bottom);
325 
326         // Since we update the position of children based on the width of the parent and this view
327         // recompute these changes with the new view size
328         onTaskViewSizeChanged(mTaskViewRect.width(), mTaskViewRect.height());
329     }
330 
331     /**
332      * Called when the task view frame changes, allowing us to move the contents of the header
333      * to match the frame changes.
334      */
onTaskViewSizeChanged(int width, int height)335     public void onTaskViewSizeChanged(int width, int height) {
336         mTaskViewRect.set(0, 0, width, height);
337 
338         boolean showTitle = true;
339         boolean showMoveIcon = true;
340         boolean showDismissIcon = true;
341         int rightInset = width - getMeasuredWidth();
342 
343         if (mTask != null && mTask.isFreeformTask()) {
344             // For freeform tasks, we always show the app icon, and only show the title, move-task
345             // icon, and the dismiss icon if there is room
346             int appIconWidth = mIconView.getMeasuredWidth();
347             int titleWidth = (int) mTitleView.getPaint().measureText(mTask.title);
348             int dismissWidth = mDismissButton.getMeasuredWidth();
349             int moveTaskWidth = mMoveTaskButton != null
350                     ? mMoveTaskButton.getMeasuredWidth()
351                     : 0;
352             showTitle = width >= (appIconWidth + dismissWidth + moveTaskWidth + titleWidth);
353             showMoveIcon = width >= (appIconWidth + dismissWidth + moveTaskWidth);
354             showDismissIcon = width >= (appIconWidth + dismissWidth);
355         }
356 
357         mTitleView.setVisibility(showTitle ? View.VISIBLE : View.INVISIBLE);
358         if (mMoveTaskButton != null) {
359             mMoveTaskButton.setVisibility(showMoveIcon ? View.VISIBLE : View.INVISIBLE);
360             mMoveTaskButton.setTranslationX(rightInset);
361         }
362         mDismissButton.setVisibility(showDismissIcon ? View.VISIBLE : View.INVISIBLE);
363         mDismissButton.setTranslationX(rightInset);
364 
365         setLeftTopRightBottom(0, 0, width, getMeasuredHeight());
366     }
367 
368     @Override
onDrawForeground(Canvas canvas)369     public void onDrawForeground(Canvas canvas) {
370         super.onDrawForeground(canvas);
371 
372         // Draw the dim layer with the rounded corners
373         canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
374                 mCornerRadius, mCornerRadius, mDimLayerPaint);
375     }
376 
377     /** Starts the focus timer. */
startFocusTimerIndicator(int duration)378     public void startFocusTimerIndicator(int duration) {
379         if (mFocusTimerIndicator == null) {
380             return;
381         }
382 
383         mFocusTimerIndicator.setVisibility(View.VISIBLE);
384         mFocusTimerIndicator.setMax(duration);
385         mFocusTimerIndicator.setProgress(duration);
386         if (mFocusTimerCountDown != null) {
387             mFocusTimerCountDown.cancel();
388         }
389         mFocusTimerCountDown = new CountDownTimer(duration,
390                 FOCUS_INDICATOR_INTERVAL_MS) {
391             public void onTick(long millisUntilFinished) {
392                 mFocusTimerIndicator.setProgress((int) millisUntilFinished);
393             }
394 
395             public void onFinish() {
396                 // Do nothing
397             }
398         }.start();
399     }
400 
401     /** Cancels the focus timer. */
cancelFocusTimerIndicator()402     public void cancelFocusTimerIndicator() {
403         if (mFocusTimerIndicator == null) {
404             return;
405         }
406 
407         if (mFocusTimerCountDown != null) {
408             mFocusTimerCountDown.cancel();
409             mFocusTimerIndicator.setProgress(0);
410             mFocusTimerIndicator.setVisibility(View.INVISIBLE);
411         }
412     }
413 
414     /** Only exposed for the workaround for b/27815919. */
getIconView()415     public ImageView getIconView() {
416         return mIconView;
417     }
418 
419     /** Returns the secondary color for a primary color. */
getSecondaryColor(int primaryColor, boolean useLightOverlayColor)420     int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
421         int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
422         return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
423     }
424 
425     /**
426      * Sets the dim alpha, only used when we are not using hardware layers.
427      * (see RecentsConfiguration.useHardwareLayers)
428      */
setDimAlpha(float dimAlpha)429     public void setDimAlpha(float dimAlpha) {
430         if (Float.compare(mDimAlpha, dimAlpha) != 0) {
431             mDimAlpha = dimAlpha;
432             mTitleView.setAlpha(1f - dimAlpha);
433             updateBackgroundColor(mBackground.getColor(), dimAlpha);
434         }
435     }
436 
437     /**
438      * Updates the background and highlight colors for this header.
439      */
updateBackgroundColor(int color, float dimAlpha)440     private void updateBackgroundColor(int color, float dimAlpha) {
441         if (mTask != null) {
442             mBackground.setColorAndDim(color, dimAlpha);
443             // TODO: Consider using the saturation of the color to adjust the lightness as well
444             ColorUtils.colorToHSL(color, mTmpHSL);
445             mTmpHSL[2] = Math.min(1f, mTmpHSL[2] + OVERLAY_LIGHTNESS_INCREMENT * (1.0f - dimAlpha));
446             mOverlayBackground.setColorAndDim(ColorUtils.HSLToColor(mTmpHSL), dimAlpha);
447             mDimLayerPaint.setAlpha((int) (dimAlpha * 255));
448             invalidate();
449         }
450     }
451 
452     /**
453      * Sets whether the background color should be darkened to differentiate from the primary color.
454      */
setShouldDarkenBackgroundColor(boolean flag)455     public void setShouldDarkenBackgroundColor(boolean flag) {
456         mShouldDarkenBackgroundColor = flag;
457     }
458 
459     /**
460      * Binds the bar view to the task.
461      */
bindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode)462     public void bindToTask(Task t, boolean touchExplorationEnabled, boolean disabledInSafeMode) {
463         mTask = t;
464 
465         int primaryColor = disabledInSafeMode
466                 ? mDisabledTaskBarBackgroundColor
467                 : t.colorPrimary;
468         if (mBackground.getColor() != primaryColor) {
469             updateBackgroundColor(primaryColor, mDimAlpha);
470         }
471         if (!mTitleView.getText().toString().equals(t.title)) {
472             mTitleView.setText(t.title);
473         }
474         mTitleView.setContentDescription(t.titleDescription);
475         mTitleView.setTextColor(t.useLightOnPrimaryColor ?
476                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
477         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
478                 mLightDismissDrawable : mDarkDismissDrawable);
479         mDismissButton.setContentDescription(t.dismissDescription);
480         mDismissButton.setOnClickListener(this);
481         mDismissButton.setClickable(false);
482         ((RippleDrawable) mDismissButton.getBackground()).setForceSoftware(true);
483 
484         // When freeform workspaces are enabled, then update the move-task button depending on the
485         // current task
486         if (mMoveTaskButton != null) {
487             if (t.isFreeformTask()) {
488                 mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
489                 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
490                         ? mLightFullscreenIcon
491                         : mDarkFullscreenIcon);
492             } else {
493                 mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
494                 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
495                         ? mLightFreeformIcon
496                         : mDarkFreeformIcon);
497             }
498             mMoveTaskButton.setOnClickListener(this);
499             mMoveTaskButton.setClickable(false);
500             ((RippleDrawable) mMoveTaskButton.getBackground()).setForceSoftware(true);
501         }
502 
503         if (Recents.getDebugFlags().isFastToggleRecentsEnabled()) {
504             if (mFocusTimerIndicator == null) {
505                 mFocusTimerIndicator = (ProgressBar) Utilities.findViewStubById(this,
506                         R.id.focus_timer_indicator_stub).inflate();
507             }
508             mFocusTimerIndicator.getProgressDrawable()
509                     .setColorFilter(
510                             getSecondaryColor(t.colorPrimary, t.useLightOnPrimaryColor),
511                             PorterDuff.Mode.SRC_IN);
512         }
513 
514         // In accessibility, a single click on the focused app info button will show it
515         if (touchExplorationEnabled) {
516             mIconView.setContentDescription(t.appInfoDescription);
517             mIconView.setOnClickListener(this);
518             mIconView.setClickable(true);
519         }
520     }
521 
522     /**
523      * Called when the bound task's data has loaded and this view should update to reflect the
524      * changes.
525      */
onTaskDataLoaded()526     public void onTaskDataLoaded() {
527         if (mTask != null && mTask.icon != null) {
528             mIconView.setImageDrawable(mTask.icon);
529         }
530     }
531 
532     /** Unbinds the bar view from the task */
unbindFromTask(boolean touchExplorationEnabled)533     void unbindFromTask(boolean touchExplorationEnabled) {
534         mTask = null;
535         mIconView.setImageDrawable(null);
536         if (touchExplorationEnabled) {
537             mIconView.setClickable(false);
538         }
539     }
540 
541     /** Animates this task bar if the user does not interact with the stack after a certain time. */
startNoUserInteractionAnimation()542     void startNoUserInteractionAnimation() {
543         int duration = getResources().getInteger(R.integer.recents_task_enter_from_app_duration);
544         mDismissButton.setVisibility(View.VISIBLE);
545         mDismissButton.setClickable(true);
546         if (mDismissButton.getVisibility() == VISIBLE) {
547             mDismissButton.animate()
548                     .alpha(1f)
549                     .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
550                     .setDuration(duration)
551                     .start();
552         } else {
553             mDismissButton.setAlpha(1f);
554         }
555         if (mMoveTaskButton != null) {
556             if (mMoveTaskButton.getVisibility() == VISIBLE) {
557                 mMoveTaskButton.setVisibility(View.VISIBLE);
558                 mMoveTaskButton.setClickable(true);
559                 mMoveTaskButton.animate()
560                         .alpha(1f)
561                         .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
562                         .setDuration(duration)
563                         .start();
564             } else {
565                 mMoveTaskButton.setAlpha(1f);
566             }
567         }
568     }
569 
570     /**
571      * Mark this task view that the user does has not interacted with the stack after a certain
572      * time.
573      */
setNoUserInteractionState()574     public void setNoUserInteractionState() {
575         mDismissButton.setVisibility(View.VISIBLE);
576         mDismissButton.animate().cancel();
577         mDismissButton.setAlpha(1f);
578         mDismissButton.setClickable(true);
579         if (mMoveTaskButton != null) {
580             mMoveTaskButton.setVisibility(View.VISIBLE);
581             mMoveTaskButton.animate().cancel();
582             mMoveTaskButton.setAlpha(1f);
583             mMoveTaskButton.setClickable(true);
584         }
585     }
586 
587     /**
588      * Resets the state tracking that the user has not interacted with the stack after a certain
589      * time.
590      */
resetNoUserInteractionState()591     void resetNoUserInteractionState() {
592         mDismissButton.setVisibility(View.INVISIBLE);
593         mDismissButton.setAlpha(0f);
594         mDismissButton.setClickable(false);
595         if (mMoveTaskButton != null) {
596             mMoveTaskButton.setVisibility(View.INVISIBLE);
597             mMoveTaskButton.setAlpha(0f);
598             mMoveTaskButton.setClickable(false);
599         }
600     }
601 
602     @Override
onCreateDrawableState(int extraSpace)603     protected int[] onCreateDrawableState(int extraSpace) {
604 
605         // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
606         // This is to prevent layer trashing when the view is pressed.
607         return new int[] {};
608     }
609 
610     @Override
onClick(View v)611     public void onClick(View v) {
612         if (v == mIconView) {
613             // In accessibility, a single click on the focused app info button will show it
614             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
615         } else if (v == mDismissButton) {
616             TaskView tv = Utilities.findParent(this, TaskView.class);
617             tv.dismissTask();
618 
619             // Keep track of deletions by the dismiss button
620             MetricsLogger.histogram(getContext(), "overview_task_dismissed_source",
621                     Constants.Metrics.DismissSourceHeaderButton);
622         } else if (v == mMoveTaskButton) {
623             TaskView tv = Utilities.findParent(this, TaskView.class);
624             EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
625                     mMoveTaskTargetStackId, false));
626         } else if (v == mAppInfoView) {
627             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
628         } else if (v == mAppIconView) {
629             hideAppOverlay(false /* immediate */);
630         }
631     }
632 
633     @Override
onLongClick(View v)634     public boolean onLongClick(View v) {
635         if (v == mIconView) {
636             showAppOverlay();
637             return true;
638         } else if (v == mAppIconView) {
639             hideAppOverlay(false /* immediate */);
640             return true;
641         }
642         return false;
643     }
644 
645     /**
646      * Shows the application overlay.
647      */
showAppOverlay()648     private void showAppOverlay() {
649         // Skip early if the task is invalid
650         SystemServicesProxy ssp = Recents.getSystemServices();
651         ComponentName cn = mTask.key.getComponent();
652         int userId = mTask.key.userId;
653         ActivityInfo activityInfo = ssp.getActivityInfo(cn, userId);
654         if (activityInfo == null) {
655             return;
656         }
657 
658         // Inflate the overlay if necessary
659         if (mAppOverlayView == null) {
660             mAppOverlayView = (FrameLayout) Utilities.findViewStubById(this,
661                     R.id.app_overlay_stub).inflate();
662             mAppOverlayView.setBackground(mOverlayBackground);
663             mAppIconView = (ImageView) mAppOverlayView.findViewById(R.id.app_icon);
664             mAppIconView.setOnClickListener(this);
665             mAppIconView.setOnLongClickListener(this);
666             mAppInfoView = (ImageView) mAppOverlayView.findViewById(R.id.app_info);
667             mAppInfoView.setOnClickListener(this);
668             mAppTitleView = (TextView) mAppOverlayView.findViewById(R.id.app_title);
669             updateLayoutParams(mAppIconView, mAppTitleView, null, mAppInfoView);
670         }
671 
672         // Update the overlay contents for the current app
673         mAppTitleView.setText(ssp.getBadgedApplicationLabel(activityInfo.applicationInfo, userId));
674         mAppTitleView.setTextColor(mTask.useLightOnPrimaryColor ?
675                 mTaskBarViewLightTextColor : mTaskBarViewDarkTextColor);
676         mAppIconView.setImageDrawable(ssp.getBadgedApplicationIcon(activityInfo.applicationInfo,
677                 userId));
678         mAppInfoView.setImageDrawable(mTask.useLightOnPrimaryColor
679                 ? mLightInfoIcon
680                 : mDarkInfoIcon);
681         mAppOverlayView.setVisibility(View.VISIBLE);
682 
683         int x = mIconView.getLeft() + mIconView.getWidth() / 2;
684         int y = mIconView.getTop() + mIconView.getHeight() / 2;
685         Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y, 0,
686                 getWidth());
687         revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
688         revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
689         revealAnim.start();
690     }
691 
692     /**
693      * Hide the application overlay.
694      */
hideAppOverlay(boolean immediate)695     private void hideAppOverlay(boolean immediate) {
696         // Skip if we haven't even loaded the overlay yet
697         if (mAppOverlayView == null) {
698             return;
699         }
700 
701         if (immediate) {
702             mAppOverlayView.setVisibility(View.GONE);
703         } else {
704             int x = mIconView.getLeft() + mIconView.getWidth() / 2;
705             int y = mIconView.getTop() + mIconView.getHeight() / 2;
706             Animator revealAnim = ViewAnimationUtils.createCircularReveal(mAppOverlayView, x, y,
707                     getWidth(), 0);
708             revealAnim.setDuration(OVERLAY_REVEAL_DURATION);
709             revealAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
710             revealAnim.addListener(new AnimatorListenerAdapter() {
711                 @Override
712                 public void onAnimationEnd(Animator animation) {
713                     mAppOverlayView.setVisibility(View.GONE);
714                 }
715             });
716             revealAnim.start();
717         }
718     }
719 }
720