TaskViewHeader.java revision 096f36b8db3e513687b78da3e1dd0197fce148a3
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 */
52class 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            canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
160                    getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
161        }
162    }
163
164    /** Sets whether the current task is full screen or not. */
165    void setIsFullscreen(boolean isFullscreen) {
166        mIsFullscreen = isFullscreen;
167    }
168
169    @Override
170    public boolean hasOverlappingRendering() {
171        return false;
172    }
173
174    /** Returns the secondary color for a primary color. */
175    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
176        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
177        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
178    }
179
180    /** Binds the bar view to the task */
181    void rebindToTask(Task t) {
182        // If an activity icon is defined, then we use that as the primary icon to show in the bar,
183        // otherwise, we fall back to the application icon
184        if (t.activityIcon != null) {
185            mApplicationIcon.setImageDrawable(t.activityIcon);
186        } else if (t.applicationIcon != null) {
187            mApplicationIcon.setImageDrawable(t.applicationIcon);
188        }
189        mApplicationIcon.setContentDescription(t.activityLabel);
190        if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
191            mActivityDescription.setText(t.activityLabel);
192        }
193        // Try and apply the system ui tint
194        int existingBgColor = (getBackground() instanceof ColorDrawable) ?
195                ((ColorDrawable) getBackground()).getColor() : 0;
196        if (existingBgColor != t.colorPrimary) {
197            mBackgroundColor.setColor(t.colorPrimary);
198        }
199        mCurrentPrimaryColor = t.colorPrimary;
200        mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
201        mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
202                mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
203        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
204                mLightDismissDrawable : mDarkDismissDrawable);
205        mDismissButton.setContentDescription(
206                getContext().getString(R.string.accessibility_recents_item_will_be_dismissed,
207                        t.activityLabel));
208    }
209
210    /** Unbinds the bar view from the task */
211    void unbindFromTask() {
212        mApplicationIcon.setImageDrawable(null);
213    }
214
215    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
216     * first layout because the actual animation into recents may take a long time. */
217    void prepareEnterRecentsAnimation() {
218        setVisibility(View.INVISIBLE);
219    }
220
221    /** Animates this task bar as it enters recents */
222    void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
223        // Animate the task bar of the first task view
224        setVisibility(View.VISIBLE);
225        setAlpha(0f);
226        animate()
227                .alpha(1f)
228                .setStartDelay(delay)
229                .setInterpolator(mConfig.linearOutSlowInInterpolator)
230                .setDuration(mConfig.taskBarEnterAnimDuration)
231                .withEndAction(postAnimRunnable)
232                .withLayer()
233                .start();
234    }
235
236    /** Animates this task bar as it exits recents */
237    void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable,
238            boolean isFocused) {
239        if (isFocused) {
240            onTaskViewFocusChanged(false);
241        }
242
243        // Animate the task bar out of the first task view
244        animate()
245                .alpha(0f)
246                .setStartDelay(0)
247                .setInterpolator(mConfig.linearOutSlowInInterpolator)
248                .setDuration(mConfig.taskBarExitAnimDuration)
249                .withStartAction(preAnimRunnable)
250                .withEndAction(new Runnable() {
251                    @Override
252                    public void run() {
253                        post(postAnimRunnable);
254                    }
255                })
256                .withLayer()
257                .start();
258    }
259
260    /** Animates this task bar dismiss button when launching a task. */
261    void startLaunchTaskDismissAnimation() {
262        if (mDismissButton.getVisibility() == View.VISIBLE) {
263            mDismissButton.animate().cancel();
264            mDismissButton.animate()
265                    .alpha(0f)
266                    .setStartDelay(0)
267                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
268                    .setDuration(mConfig.taskBarExitAnimDuration)
269                    .withLayer()
270                    .start();
271        }
272    }
273
274    /** Animates this task bar if the user does not interact with the stack after a certain time. */
275    void startNoUserInteractionAnimation() {
276        mDismissButton.setVisibility(View.VISIBLE);
277        mDismissButton.setAlpha(0f);
278        mDismissButton.animate()
279                .alpha(1f)
280                .setStartDelay(0)
281                .setInterpolator(mConfig.fastOutLinearInInterpolator)
282                .setDuration(mConfig.taskBarEnterAnimDuration)
283                .withLayer()
284                .start();
285    }
286
287    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
288    void setNoUserInteractionState() {
289        if (mDismissButton.getVisibility() != View.VISIBLE) {
290            mDismissButton.animate().cancel();
291            mDismissButton.setVisibility(View.VISIBLE);
292            mDismissButton.setAlpha(1f);
293        }
294    }
295
296    /** Notifies the associated TaskView has been focused. */
297    void onTaskViewFocusChanged(boolean focused) {
298        boolean isRunning = false;
299        if (mFocusAnimator != null) {
300            isRunning = mFocusAnimator.isRunning();
301            mFocusAnimator.removeAllListeners();
302            mFocusAnimator.cancel();
303        }
304        if (focused) {
305            int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
306            int[][] states = new int[][] {
307                    new int[] { android.R.attr.state_enabled },
308                    new int[] { android.R.attr.state_pressed }
309            };
310            int[] newStates = new int[]{
311                    android.R.attr.state_enabled,
312                    android.R.attr.state_pressed
313            };
314            int[] colors = new int[] {
315                    secondaryColor,
316                    secondaryColor
317            };
318            mBackground.setColor(new ColorStateList(states, colors));
319            mBackground.setState(newStates);
320            // Pulse the background color
321            int currentColor = mBackgroundColor.getColor();
322            int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
323            ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
324                    lightPrimaryColor, currentColor);
325            backgroundColor.addListener(new AnimatorListenerAdapter() {
326                @Override
327                public void onAnimationStart(Animator animation) {
328                    mBackground.setState(new int[]{});
329                }
330            });
331            backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
332                @Override
333                public void onAnimationUpdate(ValueAnimator animation) {
334                    mBackgroundColor.setColor((Integer) animation.getAnimatedValue());
335                }
336            });
337            backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
338            backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
339            // Pulse the translation
340            ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
341            translation.setRepeatCount(ValueAnimator.INFINITE);
342            translation.setRepeatMode(ValueAnimator.REVERSE);
343
344            mFocusAnimator = new AnimatorSet();
345            mFocusAnimator.playTogether(backgroundColor, translation);
346            mFocusAnimator.setStartDelay(750);
347            mFocusAnimator.setDuration(750);
348            mFocusAnimator.start();
349        } else {
350            if (isRunning) {
351                // Restore the background color
352                int currentColor = mBackgroundColor.getColor();
353                ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
354                        currentColor, mCurrentPrimaryColor);
355                backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
356                    @Override
357                    public void onAnimationUpdate(ValueAnimator animation) {
358                        mBackgroundColor.setColor((Integer) animation.getAnimatedValue());
359                    }
360                });
361                // Restore the translation
362                ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
363
364                mFocusAnimator = new AnimatorSet();
365                mFocusAnimator.playTogether(backgroundColor, translation);
366                mFocusAnimator.setDuration(150);
367                mFocusAnimator.start();
368            } else {
369                mBackground.setState(new int[] {});
370                setTranslationZ(0f);
371            }
372        }
373    }
374}
375