• 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.animation.AnimatorSet;
22 import android.animation.ArgbEvaluator;
23 import android.animation.ObjectAnimator;
24 import android.animation.ValueAnimator;
25 import android.content.Context;
26 import android.content.res.ColorStateList;
27 import android.content.res.Resources;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.Outline;
31 import android.graphics.Paint;
32 import android.graphics.PorterDuff;
33 import android.graphics.PorterDuffColorFilter;
34 import android.graphics.PorterDuffXfermode;
35 import android.graphics.drawable.ColorDrawable;
36 import android.graphics.drawable.Drawable;
37 import android.graphics.drawable.GradientDrawable;
38 import android.graphics.drawable.RippleDrawable;
39 import android.graphics.drawable.ShapeDrawable;
40 import android.util.AttributeSet;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewOutlineProvider;
44 import android.widget.FrameLayout;
45 import android.widget.ImageView;
46 import android.widget.TextView;
47 import com.android.systemui.R;
48 import com.android.systemui.recents.Constants;
49 import com.android.systemui.recents.RecentsConfiguration;
50 import com.android.systemui.recents.misc.Utilities;
51 import com.android.systemui.recents.model.Task;
52 
53 
54 /* The task bar view */
55 public class TaskViewHeader extends FrameLayout {
56 
57     RecentsConfiguration mConfig;
58 
59     ImageView mDismissButton;
60     ImageView mApplicationIcon;
61     TextView mActivityDescription;
62 
63     RippleDrawable mBackground;
64     GradientDrawable mBackgroundColorDrawable;
65     int mBackgroundColor;
66     Drawable mLightDismissDrawable;
67     Drawable mDarkDismissDrawable;
68     AnimatorSet mFocusAnimator;
69     ValueAnimator backgroundColorAnimator;
70     PorterDuffColorFilter mDimFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
71 
72     boolean mIsFullscreen;
73     boolean mCurrentPrimaryColorIsDark;
74     int mCurrentPrimaryColor;
75 
76     static Paint sHighlightPaint;
77     private Paint mDimPaint = new Paint();
78 
TaskViewHeader(Context context)79     public TaskViewHeader(Context context) {
80         this(context, null);
81     }
82 
TaskViewHeader(Context context, AttributeSet attrs)83     public TaskViewHeader(Context context, AttributeSet attrs) {
84         this(context, attrs, 0);
85     }
86 
TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr)87     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
88         this(context, attrs, defStyleAttr, 0);
89     }
90 
TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)91     public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
92         super(context, attrs, defStyleAttr, defStyleRes);
93         mConfig = RecentsConfiguration.getInstance();
94         setWillNotDraw(false);
95         setClipToOutline(true);
96         setOutlineProvider(new ViewOutlineProvider() {
97             @Override
98             public void getOutline(View view, Outline outline) {
99                 outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
100             }
101         });
102 
103         // Load the dismiss resources
104         Resources res = context.getResources();
105         mLightDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_light);
106         mDarkDismissDrawable = res.getDrawable(R.drawable.recents_dismiss_dark);
107 
108         // Configure the highlight paint
109         if (sHighlightPaint == null) {
110             sHighlightPaint = new Paint();
111             sHighlightPaint.setStyle(Paint.Style.STROKE);
112             sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
113             sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
114             sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
115             sHighlightPaint.setAntiAlias(true);
116         }
117     }
118 
119     @Override
onTouchEvent(MotionEvent event)120     public boolean onTouchEvent(MotionEvent event) {
121         // We ignore taps on the task bar except on the filter and dismiss buttons
122         if (!Constants.DebugFlags.App.EnableTaskBarTouchEvents) return true;
123 
124         return super.onTouchEvent(event);
125     }
126 
127     @Override
onFinishInflate()128     protected void onFinishInflate() {
129         // Set the outline provider
130         setOutlineProvider(new ViewOutlineProvider() {
131             @Override
132             public void getOutline(View view, Outline outline) {
133                 outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
134             }
135         });
136 
137         // Initialize the icon and description views
138         mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
139         mActivityDescription = (TextView) findViewById(R.id.activity_description);
140         mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
141 
142         // Hide the backgrounds if they are ripple drawables
143         if (!Constants.DebugFlags.App.EnableTaskFiltering) {
144             if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
145                 mApplicationIcon.setBackground(null);
146             }
147         }
148 
149         mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable
150                 .recents_task_view_header_bg_color);
151         // Copy the ripple drawable since we are going to be manipulating it
152         mBackground = (RippleDrawable)
153                 getContext().getDrawable(R.drawable.recents_task_view_header_bg);
154         mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
155         mBackground.setColor(ColorStateList.valueOf(0));
156         mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable);
157         setBackground(mBackground);
158     }
159 
160     @Override
onDraw(Canvas canvas)161     protected void onDraw(Canvas canvas) {
162         if (!mIsFullscreen) {
163             // Draw the highlight at the top edge (but put the bottom edge just out of view)
164             float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
165             float radius = mConfig.taskViewRoundedCornerRadiusPx;
166             int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
167             canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
168             canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
169                     getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
170             canvas.restoreToCount(count);
171         }
172     }
173 
174     /** Sets whether the current task is full screen or not. */
setIsFullscreen(boolean isFullscreen)175     void setIsFullscreen(boolean isFullscreen) {
176         mIsFullscreen = isFullscreen;
177     }
178 
179     @Override
hasOverlappingRendering()180     public boolean hasOverlappingRendering() {
181         return false;
182     }
183 
184     /** Returns the secondary color for a primary color. */
getSecondaryColor(int primaryColor, boolean useLightOverlayColor)185     int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
186         int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
187         return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
188     }
189 
190     /** Binds the bar view to the task */
rebindToTask(Task t)191     public void rebindToTask(Task t) {
192         // If an activity icon is defined, then we use that as the primary icon to show in the bar,
193         // otherwise, we fall back to the application icon
194         if (t.activityIcon != null) {
195             mApplicationIcon.setImageDrawable(t.activityIcon);
196         } else if (t.applicationIcon != null) {
197             mApplicationIcon.setImageDrawable(t.applicationIcon);
198         }
199         mApplicationIcon.setContentDescription(t.activityLabel);
200         if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
201             mActivityDescription.setText(t.activityLabel);
202         }
203         // Try and apply the system ui tint
204         int existingBgColor = (getBackground() instanceof ColorDrawable) ?
205                 ((ColorDrawable) getBackground()).getColor() : 0;
206         if (existingBgColor != t.colorPrimary) {
207             mBackgroundColorDrawable.setColor(t.colorPrimary);
208             mBackgroundColor = t.colorPrimary;
209         }
210         mCurrentPrimaryColor = t.colorPrimary;
211         mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
212         mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
213                 mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
214         mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
215                 mLightDismissDrawable : mDarkDismissDrawable);
216         mDismissButton.setContentDescription(
217                 getContext().getString(R.string.accessibility_recents_item_will_be_dismissed,
218                         t.activityLabel));
219     }
220 
221     /** Unbinds the bar view from the task */
unbindFromTask()222     void unbindFromTask() {
223         mApplicationIcon.setImageDrawable(null);
224     }
225 
226     /** Animates this task bar dismiss button when launching a task. */
startLaunchTaskDismissAnimation()227     void startLaunchTaskDismissAnimation() {
228         if (mDismissButton.getVisibility() == View.VISIBLE) {
229             mDismissButton.animate().cancel();
230             mDismissButton.animate()
231                     .alpha(0f)
232                     .setStartDelay(0)
233                     .setInterpolator(mConfig.fastOutSlowInInterpolator)
234                     .setDuration(mConfig.taskBarExitAnimDuration)
235                     .withLayer()
236                     .start();
237         }
238     }
239 
240     /** Animates this task bar if the user does not interact with the stack after a certain time. */
startNoUserInteractionAnimation()241     void startNoUserInteractionAnimation() {
242         mDismissButton.setVisibility(View.VISIBLE);
243         mDismissButton.setAlpha(0f);
244         mDismissButton.animate()
245                 .alpha(1f)
246                 .setStartDelay(0)
247                 .setInterpolator(mConfig.fastOutLinearInInterpolator)
248                 .setDuration(mConfig.taskBarEnterAnimDuration)
249                 .withLayer()
250                 .start();
251     }
252 
253     /** Mark this task view that the user does has not interacted with the stack after a certain time. */
setNoUserInteractionState()254     void setNoUserInteractionState() {
255         if (mDismissButton.getVisibility() != View.VISIBLE) {
256             mDismissButton.animate().cancel();
257             mDismissButton.setVisibility(View.VISIBLE);
258             mDismissButton.setAlpha(1f);
259         }
260     }
261 
262     @Override
onCreateDrawableState(int extraSpace)263     protected int[] onCreateDrawableState(int extraSpace) {
264 
265         // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
266         // This is to prevent layer trashing when the view is pressed.
267         return new int[] {};
268     }
269 
270     /** Notifies the associated TaskView has been focused. */
onTaskViewFocusChanged(boolean focused)271     void onTaskViewFocusChanged(boolean focused) {
272         boolean isRunning = false;
273         if (mFocusAnimator != null) {
274             isRunning = mFocusAnimator.isRunning();
275             mFocusAnimator.removeAllListeners();
276             mFocusAnimator.cancel();
277         }
278         if (focused) {
279             int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
280             int[][] states = new int[][] {
281                     new int[] { android.R.attr.state_enabled },
282                     new int[] { android.R.attr.state_pressed }
283             };
284             int[] newStates = new int[]{
285                     android.R.attr.state_enabled,
286                     android.R.attr.state_pressed
287             };
288             int[] colors = new int[] {
289                     secondaryColor,
290                     secondaryColor
291             };
292             mBackground.setColor(new ColorStateList(states, colors));
293             mBackground.setState(newStates);
294             // Pulse the background color
295             int currentColor = mBackgroundColor;
296             int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
297             ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
298                     lightPrimaryColor, currentColor);
299             backgroundColor.addListener(new AnimatorListenerAdapter() {
300                 @Override
301                 public void onAnimationStart(Animator animation) {
302                     mBackground.setState(new int[]{});
303                 }
304             });
305             backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
306                 @Override
307                 public void onAnimationUpdate(ValueAnimator animation) {
308                     int color = (int) animation.getAnimatedValue();
309                     mBackgroundColorDrawable.setColor(color);
310                     mBackgroundColor = color;
311                 }
312             });
313             backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
314             backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
315             // Pulse the translation
316             ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
317             translation.setRepeatCount(ValueAnimator.INFINITE);
318             translation.setRepeatMode(ValueAnimator.REVERSE);
319 
320             mFocusAnimator = new AnimatorSet();
321             mFocusAnimator.playTogether(backgroundColor, translation);
322             mFocusAnimator.setStartDelay(750);
323             mFocusAnimator.setDuration(750);
324             mFocusAnimator.start();
325         } else {
326             if (isRunning) {
327                 // Restore the background color
328                 int currentColor = mBackgroundColor;
329                 ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
330                         currentColor, mCurrentPrimaryColor);
331                 backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
332                     @Override
333                     public void onAnimationUpdate(ValueAnimator animation) {
334                         int color = (int) animation.getAnimatedValue();
335                         mBackgroundColorDrawable.setColor(color);
336                         mBackgroundColor = color;
337                     }
338                 });
339                 // Restore the translation
340                 ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
341 
342                 mFocusAnimator = new AnimatorSet();
343                 mFocusAnimator.playTogether(backgroundColor, translation);
344                 mFocusAnimator.setDuration(150);
345                 mFocusAnimator.start();
346             } else {
347                 mBackground.setState(new int[] {});
348                 setTranslationZ(0f);
349             }
350         }
351     }
352 
setDimAlpha(int alpha)353     public void setDimAlpha(int alpha) {
354         int color = Color.argb(alpha, 0, 0, 0);
355         mDimFilter.setColor(color);
356         mDimPaint.setColorFilter(mDimFilter);
357         setLayerType(LAYER_TYPE_HARDWARE, mDimPaint);
358     }
359 }
360