TaskViewHeader.java revision d213a1e53c7b31e9d7c072b5f0332127ed781d5a
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    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
79    public TaskViewHeader(Context context) {
80        this(context, null);
81    }
82
83    public TaskViewHeader(Context context, AttributeSet attrs) {
84        this(context, attrs, 0);
85    }
86
87    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
88        this(context, attrs, defStyleAttr, 0);
89    }
90
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
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
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
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. */
175    void setIsFullscreen(boolean isFullscreen) {
176        mIsFullscreen = isFullscreen;
177    }
178
179    @Override
180    public boolean hasOverlappingRendering() {
181        return false;
182    }
183
184    /** Returns the secondary color for a primary color. */
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 */
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 */
222    void unbindFromTask() {
223        mApplicationIcon.setImageDrawable(null);
224    }
225
226    /** Animates this task bar dismiss button when launching a task. */
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. */
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. */
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
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. */
271    void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
272        // If we are not animating the visible state, just return
273        if (!animateFocusedState) return;
274
275        boolean isRunning = false;
276        if (mFocusAnimator != null) {
277            isRunning = mFocusAnimator.isRunning();
278            mFocusAnimator.removeAllListeners();
279            mFocusAnimator.cancel();
280        }
281
282        if (focused) {
283            int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
284            int[][] states = new int[][] {
285                    new int[] { android.R.attr.state_enabled },
286                    new int[] { android.R.attr.state_pressed }
287            };
288            int[] newStates = new int[]{
289                    android.R.attr.state_enabled,
290                    android.R.attr.state_pressed
291            };
292            int[] colors = new int[] {
293                    secondaryColor,
294                    secondaryColor
295            };
296            mBackground.setColor(new ColorStateList(states, colors));
297            mBackground.setState(newStates);
298            // Pulse the background color
299            int currentColor = mBackgroundColor;
300            int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
301            ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
302                    lightPrimaryColor, currentColor);
303            backgroundColor.addListener(new AnimatorListenerAdapter() {
304                @Override
305                public void onAnimationStart(Animator animation) {
306                    mBackground.setState(new int[]{});
307                }
308            });
309            backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
310                @Override
311                public void onAnimationUpdate(ValueAnimator animation) {
312                    int color = (int) animation.getAnimatedValue();
313                    mBackgroundColorDrawable.setColor(color);
314                    mBackgroundColor = color;
315                }
316            });
317            backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
318            backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
319            // Pulse the translation
320            ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
321            translation.setRepeatCount(ValueAnimator.INFINITE);
322            translation.setRepeatMode(ValueAnimator.REVERSE);
323
324            mFocusAnimator = new AnimatorSet();
325            mFocusAnimator.playTogether(backgroundColor, translation);
326            mFocusAnimator.setStartDelay(750);
327            mFocusAnimator.setDuration(750);
328            mFocusAnimator.start();
329        } else {
330            if (isRunning) {
331                // Restore the background color
332                int currentColor = mBackgroundColor;
333                ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
334                        currentColor, mCurrentPrimaryColor);
335                backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
336                    @Override
337                    public void onAnimationUpdate(ValueAnimator animation) {
338                        int color = (int) animation.getAnimatedValue();
339                        mBackgroundColorDrawable.setColor(color);
340                        mBackgroundColor = color;
341                    }
342                });
343                // Restore the translation
344                ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
345
346                mFocusAnimator = new AnimatorSet();
347                mFocusAnimator.playTogether(backgroundColor, translation);
348                mFocusAnimator.setDuration(150);
349                mFocusAnimator.start();
350            } else {
351                mBackground.setState(new int[] {});
352                setTranslationZ(0f);
353            }
354        }
355    }
356
357    public void setDimAlpha(int alpha) {
358        int color = Color.argb(alpha, 0, 0, 0);
359        mDimFilter.setColor(color);
360        mDimPaint.setColorFilter(mDimFilter);
361        setLayerType(LAYER_TYPE_HARDWARE, mDimPaint);
362    }
363}
364