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