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