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