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