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