TaskViewHeader.java revision 37fc513d01503f15ee611595c248374696056bd8
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.ObjectAnimator;
20import android.content.Context;
21import android.content.res.ColorStateList;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Outline;
25import android.graphics.Paint;
26import android.graphics.PorterDuff;
27import android.graphics.PorterDuffColorFilter;
28import android.graphics.PorterDuffXfermode;
29import android.graphics.Rect;
30import android.graphics.drawable.ColorDrawable;
31import android.graphics.drawable.Drawable;
32import android.graphics.drawable.GradientDrawable;
33import android.graphics.drawable.RippleDrawable;
34import android.util.AttributeSet;
35import android.view.View;
36import android.view.ViewOutlineProvider;
37import android.view.animation.AnimationUtils;
38import android.view.animation.Interpolator;
39import android.widget.FrameLayout;
40import android.widget.ImageView;
41import android.widget.TextView;
42import com.android.internal.logging.MetricsLogger;
43import com.android.systemui.R;
44import com.android.systemui.recents.Constants;
45import com.android.systemui.recents.Recents;
46import com.android.systemui.recents.events.EventBus;
47import com.android.systemui.recents.events.ui.ResizeTaskEvent;
48import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
49import com.android.systemui.recents.misc.SystemServicesProxy;
50import com.android.systemui.recents.misc.Utilities;
51import com.android.systemui.recents.model.Task;
52
53
54/* The task bar view */
55public class TaskViewHeader extends FrameLayout
56        implements View.OnClickListener, View.OnLongClickListener {
57
58    Task mTask;
59
60    // Header views
61    ImageView mMoveTaskButton;
62    ImageView mDismissButton;
63    ImageView mApplicationIcon;
64    TextView mActivityDescription;
65
66    // Header drawables
67    int mCornerRadius;
68    int mHighlightHeight;
69    Drawable mLightDismissDrawable;
70    Drawable mDarkDismissDrawable;
71    RippleDrawable mBackground;
72    GradientDrawable mBackgroundColorDrawable;
73    ObjectAnimator 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    Interpolator mFastOutSlowInInterpolator;
84    Interpolator mFastOutLinearInInterpolator;
85
86    boolean mLayersDisabled;
87
88    public TaskViewHeader(Context context) {
89        this(context, null);
90    }
91
92    public TaskViewHeader(Context context, AttributeSet attrs) {
93        this(context, attrs, 0);
94    }
95
96    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr) {
97        this(context, attrs, defStyleAttr, 0);
98    }
99
100    public TaskViewHeader(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
101        super(context, attrs, defStyleAttr, defStyleRes);
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        mCornerRadius = getResources().getDimensionPixelSize(
117                R.dimen.recents_task_view_rounded_corners_radius);
118        mHighlightHeight = getResources().getDimensionPixelSize(
119                R.dimen.recents_task_view_highlight);
120        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
121                com.android.internal.R.interpolator.fast_out_slow_in);
122        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
123                com.android.internal.R.interpolator.fast_out_linear_in);
124
125        // Configure the highlight paint
126        if (sHighlightPaint == null) {
127            sHighlightPaint = new Paint();
128            sHighlightPaint.setStyle(Paint.Style.STROKE);
129            sHighlightPaint.setStrokeWidth(mHighlightHeight);
130            sHighlightPaint.setColor(context.getColor(R.color.recents_task_bar_highlight_color));
131            sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
132            sHighlightPaint.setAntiAlias(true);
133        }
134    }
135
136    @Override
137    protected void onFinishInflate() {
138        // Initialize the icon and description views
139        mApplicationIcon = (ImageView) findViewById(R.id.application_icon);
140        mApplicationIcon.setOnLongClickListener(this);
141        mActivityDescription = (TextView) findViewById(R.id.activity_description);
142        mDismissButton = (ImageView) findViewById(R.id.dismiss_task);
143        mDismissButton.setOnClickListener(this);
144        mMoveTaskButton = (ImageView) findViewById(R.id.move_task);
145
146        // Hide the backgrounds if they are ripple drawables
147        if (mApplicationIcon.getBackground() instanceof RippleDrawable) {
148            mApplicationIcon.setBackground(null);
149        }
150
151        mBackgroundColorDrawable = (GradientDrawable) getContext().getDrawable(R.drawable
152                .recents_task_view_header_bg_color);
153        // Copy the ripple drawable since we are going to be manipulating it
154        mBackground = (RippleDrawable)
155                getContext().getDrawable(R.drawable.recents_task_view_header_bg);
156        mBackground = (RippleDrawable) mBackground.mutate().getConstantState().newDrawable();
157        mBackground.setColor(ColorStateList.valueOf(0));
158        mBackground.setDrawableByLayerId(mBackground.getId(0), mBackgroundColorDrawable);
159        setBackground(mBackground);
160    }
161
162    @Override
163    protected void onDraw(Canvas canvas) {
164        // Draw the highlight at the top edge (but put the bottom edge just out of view)
165        float offset = (float) Math.ceil(mHighlightHeight / 2f);
166        float radius = mCornerRadius;
167        int count = canvas.save(Canvas.CLIP_SAVE_FLAG);
168        canvas.clipRect(0, 0, getMeasuredWidth(), getMeasuredHeight());
169        canvas.drawRoundRect(-offset, 0f, (float) getMeasuredWidth() + offset,
170                getMeasuredHeight() + radius, radius, radius, sHighlightPaint);
171        canvas.restoreToCount(count);
172    }
173
174    @Override
175    public boolean hasOverlappingRendering() {
176        return false;
177    }
178
179    /**
180     * Sets the dim alpha, only used when we are not using hardware layers.
181     * (see RecentsConfiguration.useHardwareLayers)
182     */
183    void setDimAlpha(int alpha) {
184        mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0));
185        mDimLayerPaint.setColorFilter(mDimColorFilter);
186        if (!mLayersDisabled) {
187            setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
188        }
189    }
190
191    /** Returns the secondary color for a primary color. */
192    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
193        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
194        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
195    }
196
197    /** Binds the bar view to the task */
198    public void rebindToTask(Task t) {
199        mTask = t;
200
201        // If an activity icon is defined, then we use that as the primary icon to show in the bar,
202        // otherwise, we fall back to the application icon
203        if (t.activityIcon != null) {
204            mApplicationIcon.setImageDrawable(t.activityIcon);
205        } else if (t.applicationIcon != null) {
206            mApplicationIcon.setImageDrawable(t.applicationIcon);
207        }
208        if (!mActivityDescription.getText().toString().equals(t.activityLabel)) {
209            mActivityDescription.setText(t.activityLabel);
210        }
211        mActivityDescription.setContentDescription(t.contentDescription);
212
213        // Try and apply the system ui tint
214        int existingBgColor = (getBackground() instanceof ColorDrawable) ?
215                ((ColorDrawable) getBackground()).getColor() : 0;
216        if (existingBgColor != t.colorPrimary) {
217            mBackgroundColorDrawable.setColor(t.colorPrimary);
218        }
219
220        int taskBarViewLightTextColor = getResources().getColor(
221                R.color.recents_task_bar_light_text_color);
222        int taskBarViewDarkTextColor = getResources().getColor(
223                R.color.recents_task_bar_dark_text_color);
224        mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
225                taskBarViewLightTextColor : taskBarViewDarkTextColor);
226        mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
227                mLightDismissDrawable : mDarkDismissDrawable);
228        mDismissButton.setContentDescription(String.format(mDismissContentDescription,
229                t.contentDescription));
230        updateResizeTaskBarIcon(t);
231        mMoveTaskButton.setVisibility(View.VISIBLE);
232        mMoveTaskButton.setOnClickListener(this);
233
234        // In accessibility, a single click on the focused app info button will show it
235        SystemServicesProxy ssp = Recents.getSystemServices();
236        if (ssp.isTouchExplorationEnabled()) {
237            mApplicationIcon.setOnClickListener(this);
238        }
239    }
240
241    /** Unbinds the bar view from the task */
242    void unbindFromTask() {
243        mTask = null;
244        mApplicationIcon.setImageDrawable(null);
245        mApplicationIcon.setOnClickListener(null);
246        mMoveTaskButton.setOnClickListener(null);
247
248        // Stop any focus animations
249        Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
250    }
251
252    /** Updates the resize task bar button. */
253    void updateResizeTaskBarIcon(Task t) {
254        SystemServicesProxy ssp = Recents.getSystemServices();
255        Rect display = ssp.getWindowRect();
256        Rect taskRect = ssp.getTaskBounds(t.key.id);
257        int resId = R.drawable.star;
258        if (display.equals(taskRect) || taskRect.isEmpty()) {
259            resId = R.drawable.vector_drawable_place_fullscreen;
260        } else {
261            boolean top = display.top == taskRect.top;
262            boolean bottom = display.bottom == taskRect.bottom;
263            boolean left = display.left == taskRect.left;
264            boolean right = display.right == taskRect.right;
265            if (top && bottom && left) {
266                resId = R.drawable.vector_drawable_place_left;
267            } else if (top && bottom && right) {
268                resId = R.drawable.vector_drawable_place_right;
269            } else if (top && left && right) {
270                resId = R.drawable.vector_drawable_place_top;
271            } else if (bottom && left && right) {
272                resId = R.drawable.vector_drawable_place_bottom;
273            } else if (top && right) {
274                resId = R.drawable.vector_drawable_place_top_right;
275            } else if (top && left) {
276                resId = R.drawable.vector_drawable_place_top_left;
277            } else if (bottom && right) {
278                resId = R.drawable.vector_drawable_place_bottom_right;
279            } else if (bottom && left) {
280                resId = R.drawable.vector_drawable_place_bottom_left;
281            }
282        }
283        mMoveTaskButton.setImageResource(resId);
284    }
285
286    /** Animates this task bar dismiss button when launching a task. */
287    void startLaunchTaskDismissAnimation(final Runnable postAnimationRunanble) {
288        if (mDismissButton.getVisibility() == View.VISIBLE) {
289            int taskViewExitToAppDuration = mContext.getResources().getInteger(
290                    R.integer.recents_task_exit_to_app_duration);
291            mDismissButton.animate().cancel();
292            mDismissButton.animate()
293                    .alpha(0f)
294                    .setStartDelay(0)
295                    .setInterpolator(mFastOutSlowInInterpolator)
296                    .setDuration(taskViewExitToAppDuration)
297                    .withEndAction(postAnimationRunanble)
298                    .start();
299        }
300    }
301
302    /** Animates this task bar if the user does not interact with the stack after a certain time. */
303    void startNoUserInteractionAnimation() {
304        if (mDismissButton.getVisibility() != View.VISIBLE) {
305            mDismissButton.setVisibility(View.VISIBLE);
306            mDismissButton.setAlpha(0f);
307            mDismissButton.animate()
308                    .alpha(1f)
309                    .setStartDelay(0)
310                    .setInterpolator(mFastOutLinearInInterpolator)
311                    .setDuration(getResources().getInteger(
312                            R.integer.recents_task_enter_from_app_duration))
313                    .start();
314        }
315    }
316
317    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
318    void setNoUserInteractionState() {
319        if (mDismissButton.getVisibility() != View.VISIBLE) {
320            mDismissButton.animate().cancel();
321            mDismissButton.setVisibility(View.VISIBLE);
322            mDismissButton.setAlpha(1f);
323        }
324    }
325
326    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
327    void resetNoUserInteractionState() {
328        mDismissButton.setVisibility(View.INVISIBLE);
329    }
330
331    @Override
332    protected int[] onCreateDrawableState(int extraSpace) {
333
334        // Don't forward our state to the drawable - we do it manually in onTaskViewFocusChanged.
335        // This is to prevent layer trashing when the view is pressed.
336        return new int[] {};
337    }
338
339    @Override
340    protected void dispatchDraw(Canvas canvas) {
341        super.dispatchDraw(canvas);
342        if (mLayersDisabled) {
343            mLayersDisabled = false;
344            postOnAnimation(new Runnable() {
345                @Override
346                public void run() {
347                    mLayersDisabled = false;
348                    setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
349                }
350            });
351        }
352    }
353
354    public void disableLayersForOneFrame() {
355        mLayersDisabled = true;
356
357        // Disable layer for a frame so we can draw our first frame faster.
358        setLayerType(LAYER_TYPE_NONE, null);
359    }
360
361    /** Notifies the associated TaskView has been focused. */
362    void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
363        boolean isRunning = false;
364        if (mFocusAnimator != null) {
365            isRunning = mFocusAnimator.isRunning();
366        }
367        Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
368
369        if (focused) {
370            // If we are not animating the visible state, just return
371            if (!animateFocusedState) return;
372
373            // Bump up the translation
374            mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 8f);
375            mFocusAnimator.setDuration(200);
376            mFocusAnimator.start();
377        } else {
378            if (isRunning) {
379                // Restore the translation
380                mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 0f);
381                mFocusAnimator.setDuration(150);
382                mFocusAnimator.start();
383            } else {
384                setTranslationZ(0f);
385            }
386        }
387    }
388
389    @Override
390    public void onClick(View v) {
391        if (v == mApplicationIcon) {
392            // In accessibility, a single click on the focused app info button will show it
393            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
394        } else if (v == mDismissButton) {
395            TaskView tv = Utilities.findParent(this, TaskView.class);
396            tv.dismissTask();
397
398            // Keep track of deletions by the dismiss button
399            MetricsLogger.histogram(getContext(), "overview_task_dismissed_source",
400                    Constants.Metrics.DismissSourceHeaderButton);
401        } else if (v == mMoveTaskButton) {
402            EventBus.getDefault().send(new ResizeTaskEvent(mTask));
403        }
404    }
405
406    @Override
407    public boolean onLongClick(View v) {
408        if (v == mApplicationIcon) {
409            EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
410            return true;
411        }
412        return false;
413    }
414}
415