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