TaskViewHeader.java revision b9026ff592ac781bc12a042cf6451dd927be6dd5
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    public TaskViewHeader(Context context) {
84        this(context, null);
85    }
86
87    public TaskViewHeader(Context context, AttributeSet attrs) {
88        this(context, attrs, 0);
89    }
90
91    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
92        this(context, attrs, defStyleAttr, 0);
93    }
94
95    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
96        super(context, attrs, defStyleAttr, defStyleRes);
97        mConfig = RecentsConfiguration.getInstance();
98        mSsp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
99        setWillNotDraw(false);
100        setClipToOutline(true);
101        setOutlineProvider(new ViewOutlineProvider() {
102            @Override
103            public void getOutline(View view, Outline outline) {
104                outline.setRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
105            }
106        });
107
108        // Load the dismiss resources
109        mLightDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_light);
110        mDarkDismissDrawable = context.getDrawable(R.drawable.recents_dismiss_dark);
111        mDismissContentDescription =
112                context.getString(R.string.accessibility_recents_item_will_be_dismissed);
113
114        // Configure the highlight paint
115        if (sHighlightPaint == null) {
116            sHighlightPaint = new Paint();
117            sHighlightPaint.setStyle(Paint.Style.STROKE);
118            sHighlightPaint.setStrokeWidth(mConfig.taskViewHighlightPx);
119            sHighlightPaint.setColor(mConfig.taskBarViewHighlightColor);
120            sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
121            sHighlightPaint.setAntiAlias(true);
122        }
123    }
124
125    @Override
126    protected void onFinishInflate() {
127        // Initialize the icon and description views
128        mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
129        mActivityDescription = (TextView) findViewById(R.id.activity_description);
130        mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
131        mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
132
133        // Hide the backgrounds if they are ripple drawables
134        if (!Constants.DebugFlags.App.EnableTaskFiltering) {
135            if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
136                mApplicationIcon.setBackground(null);
137            }
138        }
139
140        mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable
141                .recents_task_view_header_bg_color);
142        // Copy the ripple drawable since we are going to be manipulating it
143        mBackground = (RippleDrawable)
144                getContext().getDrawable(R.drawable.recents_task_view_header_bg);
145        mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
146        mBackground.setColor(ColorStateList.valueOf(0));
147        mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable);
148        setBackground(mBackground);
149    }
150
151    @Override
152    protected void onDraw(Canvas canvas) {
153        // Draw the highlight at the top edge (but put the bottom edge just out of view)
154        float offset = (float) Math.ceil(mConfig.taskViewHighlightPx / 2f);
155        float radius = mConfig.taskViewRoundedCornerRadiusPx;
156        int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
157        canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
158        canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
159                getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
160        canvas.restoreToCount(count);
161    }
162
163    @Override
164    public boolean hasOverlappingRendering() {
165        return false;
166    }
167
168    /**
169     * Sets the dim alpha, only used when we are not using hardware layers.
170     * (see RecentsConfiguration.useHardwareLayers)
171     */
172    void setDimAlpha(int alpha) {
173        mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0));
174        mDimLayerPaint.setColorFilter(mDimColorFilter);
175        setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
176    }
177
178    /** Returns the secondary color for a primary color. */
179    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
180        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
181        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
182    }
183
184    /** Binds the bar view to the task */
185    public void rebindToTask(Task t) {
186        // If an activity icon is defined, then we use that as the primary icon to show in the bar,
187        // otherwise, we fall back to the application icon
188        if (t.activityIcon != null) {
189            mApplicationIcon.setImageDrawable(t.activityIcon);
190        } else if (t.applicationIcon != null) {
191            mApplicationIcon.setImageDrawable(t.applicationIcon);
192        }
193        mApplicationIcon.setContentDescription(t.activityLabel);
194        if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
195            mActivityDescription.setText(t.activityLabel);
196        }
197        // Try and apply the system ui tint
198        int existingBgColor = getBackgroundColor();
199        if (existingBgColor != t.colorPrimary) {
200            mBackgroundColorDrawable.setColor(t.colorPrimary);
201            mBackgroundColor = t.colorPrimary;
202        }
203        mCurrentPrimaryColor = t.colorPrimary;
204        mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
205        mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
206                mConfig.taskBarViewLightTextColor : mConfig.taskBarViewDarkTextColor);
207        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
208                mLightDismissDrawable : mDarkDismissDrawable);
209        mDismissButton.setContentDescription(String.format(mDismissContentDescription,
210                t.activityLabel));
211        mMoveTaskButton.setVisibility((mConfig.multiStackEnabled) ? View.VISIBLE : View.INVISIBLE);
212        if (mConfig.multiStackEnabled) {
213            updateResizeTaskBarIcon(t);
214        }
215    }
216
217    /** Updates the resize task bar button. */
218    void updateResizeTaskBarIcon(Task t) {
219        Rect display = mSsp.getWindowRect();
220        Rect taskRect = mSsp.getTaskBounds(t.key.stackId);
221        int resId = R.drawable.star;
222        if (display.equals(taskRect)) {
223            resId = R.drawable.vector_drawable_place_fullscreen;
224        } else {
225            boolean top = display.top == taskRect.top;
226            boolean bottom = display.bottom == taskRect.bottom;
227            boolean left = display.left == taskRect.left;
228            boolean right = display.right == taskRect.right;
229            if (top && bottom && left) {
230                resId = R.drawable.vector_drawable_place_left;
231            } else if (top && bottom && right) {
232                resId = R.drawable.vector_drawable_place_right;
233            } else if (top && left && right) {
234                resId = R.drawable.vector_drawable_place_top;
235            } else if (bottom && left && right) {
236                resId = R.drawable.vector_drawable_place_bottom;
237            } else if (top && right) {
238                resId = R.drawable.vector_drawable_place_top_right;
239            } else if (top && left) {
240                resId = R.drawable.vector_drawable_place_top_left;
241            } else if (bottom && right) {
242                resId = R.drawable.vector_drawable_place_bottom_right;
243            } else if (bottom && left) {
244                resId = R.drawable.vector_drawable_place_bottom_left;
245            }
246        }
247        mMoveTaskButton.setImageResource(resId);
248    }
249
250    /** Unbinds the bar view from the task */
251    void unbindFromTask() {
252        mApplicationIcon.setImageDrawable(null);
253    }
254
255    /** Animates this task bar dismiss button when launching a task. */
256    void startLaunchTaskDismissAnimation() {
257        if (mDismissButton.getVisibility() == View.VISIBLE) {
258            mDismissButton.animate().cancel();
259            mDismissButton.animate()
260                    .alpha(0f)
261                    .setStartDelay(0)
262                    .setInterpolator(mConfig.fastOutSlowInInterpolator)
263                    .setDuration(mConfig.taskViewExitToAppDuration)
264                    .withLayer()
265                    .start();
266        }
267    }
268
269    /** Animates this task bar if the user does not interact with the stack after a certain time. */
270    void startNoUserInteractionAnimation() {
271        if (mDismissButton.getVisibility() != View.VISIBLE) {
272            mDismissButton.setVisibility(View.VISIBLE);
273            mDismissButton.setAlpha(0f);
274            mDismissButton.animate()
275                    .alpha(1f)
276                    .setStartDelay(0)
277                    .setInterpolator(mConfig.fastOutLinearInInterpolator)
278                    .setDuration(mConfig.taskViewEnterFromAppDuration)
279                    .withLayer()
280                    .start();
281        }
282    }
283
284    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
285    void setNoUserInteractionState() {
286        if (mDismissButton.getVisibility() != View.VISIBLE) {
287            mDismissButton.animate().cancel();
288            mDismissButton.setVisibility(View.VISIBLE);
289            mDismissButton.setAlpha(1f);
290        }
291    }
292
293    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
294    void resetNoUserInteractionState() {
295        mDismissButton.setVisibility(View.INVISIBLE);
296    }
297
298    @Override
299    protected int[] onCreateDrawableState(int extraSpace) {
300
301        // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
302        // This is to prevent layer trashing when the view is pressed.
303        return new int[] {};
304    }
305
306    /** Notifies the associated TaskView has been focused. */
307    void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
308        // If we are not animating the visible state, just return
309        if (!animateFocusedState) return;
310
311        boolean isRunning = false;
312        if (mFocusAnimator != null) {
313            isRunning = mFocusAnimator.isRunning();
314            Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
315        }
316
317        if (focused) {
318            int currentColor = mBackgroundColor;
319            int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
320            int[][] states = new int[][] {
321                    new int[] {},
322                    new int[] { android.R.attr.state_enabled },
323                    new int[] { android.R.attr.state_pressed }
324            };
325            int[] newStates = new int[]{
326                    0,
327                    android.R.attr.state_enabled,
328                    android.R.attr.state_pressed
329            };
330            int[] colors = new int[] {
331                    currentColor,
332                    secondaryColor,
333                    secondaryColor
334            };
335            mBackground.setColor(new ColorStateList(states, colors));
336            mBackground.setState(newStates);
337            // Pulse the background color
338            int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
339            ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
340                    currentColor, lightPrimaryColor);
341            backgroundColor.addListener(new AnimatorListenerAdapter() {
342                @Override
343                public void onAnimationStart(Animator animation) {
344                    mBackground.setState(new int[]{});
345                }
346            });
347            backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
348                @Override
349                public void onAnimationUpdate(ValueAnimator animation) {
350                    int color = (int) animation.getAnimatedValue();
351                    mBackgroundColorDrawable.setColor(color);
352                    mBackgroundColor = color;
353                }
354            });
355            backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
356            backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
357            // Pulse the translation
358            ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
359            translation.setRepeatCount(ValueAnimator.INFINITE);
360            translation.setRepeatMode(ValueAnimator.REVERSE);
361
362            mFocusAnimator = new AnimatorSet();
363            mFocusAnimator.playTogether(backgroundColor, translation);
364            mFocusAnimator.setStartDelay(150);
365            mFocusAnimator.setDuration(750);
366            mFocusAnimator.start();
367        } else {
368            if (isRunning) {
369                // Restore the background color
370                int currentColor = mBackgroundColor;
371                ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
372                        currentColor, mCurrentPrimaryColor);
373                backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
374                    @Override
375                    public void onAnimationUpdate(ValueAnimator animation) {
376                        int color = (int) animation.getAnimatedValue();
377                        mBackgroundColorDrawable.setColor(color);
378                        mBackgroundColor = color;
379                    }
380                });
381                // Restore the translation
382                ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
383
384                mFocusAnimator = new AnimatorSet();
385                mFocusAnimator.playTogether(backgroundColor, translation);
386                mFocusAnimator.setDuration(150);
387                mFocusAnimator.start();
388            } else {
389                mBackground.setState(new int[] {});
390                setTranslationZ(0f);
391            }
392        }
393    }
394}
395