TaskViewHeader.java revision 752896648e9c15ea5884b1683ee14daae5147ad3
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.graphics.Canvas;
28import android.graphics.Color;
29import android.graphics.drawable.Drawable;
30import android.graphics.drawable.GradientDrawable;
31import android.graphics.drawable.RippleDrawable;
32import android.graphics.Outline;
33import android.graphics.Paint;
34import android.graphics.PorterDuff;
35import android.graphics.PorterDuffColorFilter;
36import android.graphics.PorterDuffXfermode;
37import android.graphics.Rect;
38import android.util.AttributeSet;
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.SystemServicesProxy;
48import com.android.systemui.recents.misc.Utilities;
49import com.android.systemui.recents.model.RecentsTaskLoader;
50import com.android.systemui.recents.model.Task;
51
52
53/* The task bar view */
54public class TaskViewHeader extends FrameLayout {
55
56    RecentsConfiguration mConfig;
57    private SystemServicesProxy mSsp;
58
59    // Header views
60    ImageView mMoveTaskButton;
61    ImageView mDismissButton;
62    ImageView mApplicationIcon;
63    TextView mActivityDescription;
64
65    // Header drawables
66    boolean mCurrentPrimaryColorIsDark;
67    int mCurrentPrimaryColor;
68    int mBackgroundColor;
69    Drawable mLightDismissDrawable;
70    Drawable mDarkDismissDrawable;
71    RippleDrawable mBackground;
72    GradientDrawable mBackgroundColorDrawable;
73    AnimatorSet mFocusAnimator;
74    String mDismissContentDescription;
75
76    // Static highlight that we draw at the top of each view
77    static Paint sHighlightPaint;
78
79    // Header dim, which is only used when task view hardware layers are not used
80    Paint mDimLayerPaint = new Paint();
81    PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
82
83    boolean mLayersDisabled;
84
85    public TaskViewHeader(Context context) {
86        this(context, null);
87    }
88
89    public TaskViewHeader(Context context, AttributeSet attrs) {
90        this(context, attrs, 0);
91    }
92
93    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
94        this(context, attrs, defStyleAttr, 0);
95    }
96
97    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
98        super(context, attrs, defStyleAttr, defStyleRes);
99        mConfig = RecentsConfiguration.getInstance();
100        mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
101        setWillNotDraw(false);
102        setClipToOutline(true);
103        setOutlineProvider(new ViewOutlineProvider() {
104            @Override
105            public void getOutline(View view, Outline outline) {
106                outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
107            }
108        });
109
110        // Load the dismiss resources
111        mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
112        mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
113        mDismissContentDescription =
114                context.getString(R.string.accessibility_recents_item_will_be_dismissed);
115
116        // Configure the highlight paint
117        if (sHighlightPaint == null) {
118            sHighlightPaint = new Paint();
119            sHighlightPaint.setStyle(Paint.Style.STROKE);
120            sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
121            sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
122            sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
123            sHighlightPaint.setAntiAlias(true);
124        }
125    }
126
127    @Override
128    protected void onFinishInflate() {
129        // Initialize the icon and description views
130        mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
131        mActivityDescription = (TextView) findViewById(R.id.activity_description);
132        mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
133        mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
134
135        // Hide the backgrounds if they are ripple drawables
136        if (!Constants.DebugFlags.App.EnableTaskFiltering) {
137            if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
138                mApplicationIcon.setBackground(null);
139            }
140        }
141
142        mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable
143                .recents_task_view_header_bg_color);
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), mBackgroundColorDrawable);
150        setBackground(mBackground);
151    }
152
153    @Override
154    protected void onDraw(Canvas canvas) {
155        // Draw the highlight at the top edge (but put the bottom edge just out of view)
156        float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
157        float radius = mConfig.taskViewRoundedCornerRadiusPx;
158        int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
159        canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
160        canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
161                getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
162        canvas.restoreToCount(count);
163    }
164
165    @Override
166    public boolean hasOverlappingRendering() {
167        return false;
168    }
169
170    /**
171     * Sets the dim alpha, only used when we are not using hardware layers.
172     * (see RecentsConfiguration.useHardwareLayers)
173     */
174    void setDimAlpha(int alpha) {
175        mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0));
176        mDimLayerPaint.setColorFilter(mDimColorFilter);
177        if (!mLayersDisabled) {
178            setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
179        }
180    }
181
182    /** Returns the secondary color for a primary color. */
183    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
184        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
185        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
186    }
187
188    /** Binds the bar view to the task */
189    public void rebindToTask(Task t) {
190        // If an activity icon is defined, then we use that as the primary icon to show in the bar,
191        // otherwise, we fall back to the application icon
192        if (t.activityIcon != null) {
193            mApplicationIcon.setImageDrawable(t.activityIcon);
194        } else if (t.applicationIcon != null) {
195            mApplicationIcon.setImageDrawable(t.applicationIcon);
196        }
197        if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
198            mActivityDescription.setText(t.activityLabel);
199        }
200        mActivityDescription.setContentDescription(t.contentDescription);
201
202        // Try and apply the system ui tint
203        int existingBgColor = getBackgroundColor();
204        if (existingBgColor != t.colorPrimary) {
205            mBackgroundColorDrawable.setColor(t.colorPrimary);
206            mBackgroundColor = t.colorPrimary;
207        }
208        mCurrentPrimaryColor = t.colorPrimary;
209        mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
210        mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
211                mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
212        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
213                mLightDismissDrawable : mDarkDismissDrawable);
214        mDismissButton.setContentDescription(String.format(mDismissContentDescription,
215                t.contentDescription));
216        mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE);
217        if (mConfig.multiStackEnabled) {
218            updateResizeTaskBarIcon(t);
219        }
220    }
221
222    /** Updates the resize task bar button. */
223    void updateResizeTaskBarIcon(Task t) {
224        Rect display = mSsp.getWindowRect();
225        Rect taskRect = mSsp.getTaskBounds(t.key.stackId);
226        int resId = R.drawable.star;
227        if (display.equals(taskRect) || taskRect.isEmpty()) {
228            resId = R.drawable.vector_drawable_place_fullscreen;
229        } else {
230            boolean top = display.top == taskRect.top;
231            boolean bottom = display.bottom == taskRect.bottom;
232            boolean left = display.left == taskRect.left;
233            boolean right = display.right == taskRect.right;
234            if (top && bottom && left) {
235                resId = R.drawable.vector_drawable_place_left;
236            } else if (top && bottom && right) {
237                resId = R.drawable.vector_drawable_place_right;
238            } else if (top && left && right) {
239                resId = R.drawable.vector_drawable_place_top;
240            } else if (bottom && left && right) {
241                resId = R.drawable.vector_drawable_place_bottom;
242            } else if (top && right) {
243                resId = R.drawable.vector_drawable_place_top_right;
244            } else if (top && left) {
245                resId = R.drawable.vector_drawable_place_top_left;
246            } else if (bottom && right) {
247                resId = R.drawable.vector_drawable_place_bottom_right;
248            } else if (bottom && left) {
249                resId = R.drawable.vector_drawable_place_bottom_left;
250            }
251        }
252        mMoveTaskButton.setImageResource(resId);
253    }
254
255    /** Unbinds the bar view from the task */
256    void unbindFromTask() {
257        mApplicationIcon.setImageDrawable(null);
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.taskViewExitToAppDuration)
269                    .start();
270        }
271    }
272
273    /** Animates this task bar if the user does not interact with the stack after a certain time. */
274    void startNoUserInteractionAnimation() {
275        if (mDismissButton.getVisibility() != View.VISIBLE) {
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.taskViewEnterFromAppDuration)
283                    .start();
284        }
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    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
297    void resetNoUserInteractionState() {
298        mDismissButton.setVisibility(View.INVISIBLE);
299    }
300
301    @Override
302    protected int[] onCreateDrawableState(int extraSpace) {
303
304        // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
305        // This is to prevent layer trashing when the view is pressed.
306        return new int[] {};
307    }
308
309    @Override
310    protected void dispatchDraw(Canvas canvas) {
311        super.dispatchDraw(canvas);
312        if (mLayersDisabled) {
313            mLayersDisabled = false;
314            postOnAnimation(new Runnable() {
315                @Override
316                public void run() {
317                    mLayersDisabled = false;
318                    setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
319                }
320            });
321        }
322    }
323
324    public void disableLayersForOneFrame() {
325        mLayersDisabled = true;
326
327        // Disable layer for a frame so we can draw our first frame faster.
328        setLayerType(LAYER_TYPE_NONE, null);
329    }
330
331    /** Notifies the associated TaskView has been focused. */
332    void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
333        // If we are not animating the visible state, just return
334        if (!animateFocusedState) return;
335
336        boolean isRunning = false;
337        if (mFocusAnimator != null) {
338            isRunning = mFocusAnimator.isRunning();
339            Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
340        }
341
342        if (focused) {
343            int currentColor = mBackgroundColor;
344            int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
345            int[][] states = new int[][] {
346                    new int[] {},
347                    new int[] { android.R.attr.state_enabled },
348                    new int[] { android.R.attr.state_pressed }
349            };
350            int[] newStates = new int[]{
351                    0,
352                    android.R.attr.state_enabled,
353                    android.R.attr.state_pressed
354            };
355            int[] colors = new int[] {
356                    currentColor,
357                    secondaryColor,
358                    secondaryColor
359            };
360            mBackground.setColor(new ColorStateList(states, colors));
361            mBackground.setState(newStates);
362            // Pulse the background color
363            int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
364            ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
365                    currentColor, lightPrimaryColor);
366            backgroundColor.addListener(new AnimatorListenerAdapter() {
367                @Override
368                public void onAnimationStart(Animator animation) {
369                    mBackground.setState(new int[]{});
370                }
371            });
372            backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
373                @Override
374                public void onAnimationUpdate(ValueAnimator animation) {
375                    int color = (int) animation.getAnimatedValue();
376                    mBackgroundColorDrawable.setColor(color);
377                    mBackgroundColor = color;
378                }
379            });
380            backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
381            backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
382            // Pulse the translation
383            ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
384            translation.setRepeatCount(ValueAnimator.INFINITE);
385            translation.setRepeatMode(ValueAnimator.REVERSE);
386
387            mFocusAnimator = new AnimatorSet();
388            mFocusAnimator.playTogether(backgroundColor, translation);
389            mFocusAnimator.setStartDelay(150);
390            mFocusAnimator.setDuration(750);
391            mFocusAnimator.start();
392        } else {
393            if (isRunning) {
394                // Restore the background color
395                int currentColor = mBackgroundColor;
396                ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
397                        currentColor, mCurrentPrimaryColor);
398                backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
399                    @Override
400                    public void onAnimationUpdate(ValueAnimator animation) {
401                        int color = (int) animation.getAnimatedValue();
402                        mBackgroundColorDrawable.setColor(color);
403                        mBackgroundColor = color;
404                    }
405                });
406                // Restore the translation
407                ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
408
409                mFocusAnimator = new AnimatorSet();
410                mFocusAnimator.playTogether(backgroundColor, translation);
411                mFocusAnimator.setDuration(150);
412                mFocusAnimator.start();
413            } else {
414                mBackground.setState(new int[] {});
415                setTranslationZ(0f);
416            }
417        }
418    }
419}
420