TaskViewHeader.java revision f95c7f5727e6838e49519e07629683fe89db9ce1
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                getResources().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        setTranslationY(-getMeasuredHeight());
226        animate()
227                .translationY(0)
228                .setStartDelay(delay)
229                .setInterpolator(mConfig.fastOutSlowInInterpolator)
230                .setDuration(mConfig.taskBarEnterAnimDuration)
231                .withEndAction(postAnimRunnable)
232                .start();
233    }
234
235    /** Animates this task bar as it exits recents */
236    void startLaunchTaskAnimation(Runnable preAnimRunnable, final Runnable postAnimRunnable,
237            boolean isFocused) {
238        if (isFocused) {
239            onTaskViewFocusChanged(false);
240        }
241
242        // Animate the task bar out of the first task view
243        animate()
244                .translationY(-getMeasuredHeight())
245                .setStartDelay(0)
246                .setInterpolator(mConfig.fastOutLinearInInterpolator)
247                .setDuration(mConfig.taskBarExitAnimDuration)
248                .withStartAction(preAnimRunnable)
249                .withEndAction(new Runnable() {
250                    @Override
251                    public void run() {
252                        post(postAnimRunnable);
253                    }
254                })
255                .start();
256    }
257
258    /** Animates this task bar dismiss button when launching a task. */
259    void startLaunchTaskDismissAnimation() {
260        if (mDismissButton.getVisibility() == View.VISIBLE) {
261            mDismissButton.animate().cancel();
262            mDismissButton.animate()
263                    .alpha(0f)
264                    .setStartDelay(0)
265                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
266                    .setDuration(mConfig.taskBarExitAnimDuration)
267                    .withLayer()
268                    .start();
269        }
270    }
271
272    /** Animates this task bar if the user does not interact with the stack after a certain time. */
273    void startNoUserInteractionAnimation() {
274        mDismissButton.setVisibility(View.VISIBLE);
275        mDismissButton.setAlpha(0f);
276        mDismissButton.animate()
277                .alpha(1f)
278                .setStartDelay(0)
279                .setInterpolator(mConfig.fastOutLinearInInterpolator)
280                .setDuration(mConfig.taskBarEnterAnimDuration)
281                .withLayer()
282                .start();
283    }
284
285    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
286    void setNoUserInteractionState() {
287        if (mDismissButton.getVisibility() != View.VISIBLE) {
288            mDismissButton.animate().cancel();
289            mDismissButton.setVisibility(View.VISIBLE);
290            mDismissButton.setAlpha(1f);
291        }
292    }
293
294    /** Notifies the associated TaskView has been focused. */
295    void onTaskViewFocusChanged(boolean focused) {
296        boolean isRunning = false;
297        if (mFocusAnimator != null) {
298            isRunning = mFocusAnimator.isRunning();
299            mFocusAnimator.removeAllListeners();
300            mFocusAnimator.cancel();
301        }
302        if (focused) {
303            int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
304            int[][] states = new int[][] {
305                    new int[] { android.R.attr.state_enabled },
306                    new int[] { android.R.attr.state_pressed }
307            };
308            int[] newStates = new int[]{
309                    android.R.attr.state_enabled,
310                    android.R.attr.state_pressed
311            };
312            int[] colors = new int[] {
313                    secondaryColor,
314                    secondaryColor
315            };
316            mBackground.setColor(new ColorStateList(states, colors));
317            mBackground.setState(newStates);
318            // Pulse the background color
319            int currentColor = mBackgroundColor.getColor();
320            int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
321            ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
322                    lightPrimaryColor, currentColor);
323            backgroundColor.addListener(new AnimatorListenerAdapter() {
324                @Override
325                public void onAnimationStart(Animator animation) {
326                    mBackground.setState(new int[]{});
327                }
328            });
329            backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
330                @Override
331                public void onAnimationUpdate(ValueAnimator animation) {
332                    mBackgroundColor.setColor((Integer) animation.getAnimatedValue());
333                }
334            });
335            backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
336            backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
337            // Pulse the translation
338            ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
339            translation.setRepeatCount(ValueAnimator.INFINITE);
340            translation.setRepeatMode(ValueAnimator.REVERSE);
341
342            mFocusAnimator = new AnimatorSet();
343            mFocusAnimator.playTogether(backgroundColor, translation);
344            mFocusAnimator.setStartDelay(750);
345            mFocusAnimator.setDuration(750);
346            mFocusAnimator.start();
347        } else {
348            if (isRunning) {
349                // Restore the background color
350                int currentColor = mBackgroundColor.getColor();
351                ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
352                        currentColor, mCurrentPrimaryColor);
353                backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
354                    @Override
355                    public void onAnimationUpdate(ValueAnimator animation) {
356                        mBackgroundColor.setColor((Integer) animation.getAnimatedValue());
357                    }
358                });
359                // Restore the translation
360                ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
361
362                mFocusAnimator = new AnimatorSet();
363                mFocusAnimator.playTogether(backgroundColor, translation);
364                mFocusAnimator.setDuration(150);
365                mFocusAnimator.start();
366            } else {
367                mBackground.setState(new int[] {});
368                setTranslationZ(0f);
369            }
370        }
371    }
372}
373