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