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.ValueAnimator;
22import android.content.Context;
23import android.graphics.Bitmap;
24import android.graphics.BitmapShader;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.LightingColorFilter;
28import android.graphics.Matrix;
29import android.graphics.Paint;
30import android.graphics.Rect;
31import android.graphics.RectF;
32import android.graphics.Shader;
33import android.util.AttributeSet;
34import android.view.View;
35import com.android.systemui.recents.RecentsConfiguration;
36import com.android.systemui.recents.misc.Utilities;
37import com.android.systemui.recents.model.Task;
38
39
40/**
41 * The task thumbnail view.  It implements an image view that allows for animating the dim and
42 * alpha of the thumbnail image.
43 */
44public class TaskViewThumbnail extends View {
45
46    RecentsConfiguration mConfig;
47
48    // Drawing
49    float mDimAlpha;
50    Matrix mScaleMatrix = new Matrix();
51    Paint mDrawPaint = new Paint();
52    RectF mBitmapRect = new RectF();
53    RectF mLayoutRect = new RectF();
54    BitmapShader mBitmapShader;
55    LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
56
57    // Thumbnail alpha
58    float mThumbnailAlpha;
59    ValueAnimator mThumbnailAlphaAnimator;
60    ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
61            = new ValueAnimator.AnimatorUpdateListener() {
62        @Override
63        public void onAnimationUpdate(ValueAnimator animation) {
64            mThumbnailAlpha = (float) animation.getAnimatedValue();
65            updateThumbnailPaintFilter();
66        }
67    };
68
69    // Task bar clipping, the top of this thumbnail can be clipped against the opaque header
70    // bar that overlaps this thumbnail
71    View mTaskBar;
72    Rect mClipRect = new Rect();
73
74    // Visibility optimization, if the thumbnail height is less than the height of the header
75    // bar for the task view, then just mark this thumbnail view as invisible
76    boolean mInvisible;
77
78    public TaskViewThumbnail(Context context) {
79        this(context, null);
80    }
81
82    public TaskViewThumbnail(Context context, AttributeSet attrs) {
83        this(context, attrs, 0);
84    }
85
86    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
87        this(context, attrs, defStyleAttr, 0);
88    }
89
90    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
91        super(context, attrs, defStyleAttr, defStyleRes);
92        mConfig = RecentsConfiguration.getInstance();
93        mDrawPaint.setColorFilter(mLightingColorFilter);
94        mDrawPaint.setFilterBitmap(true);
95        mDrawPaint.setAntiAlias(true);
96    }
97
98    @Override
99    protected void onFinishInflate() {
100        mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;
101        updateThumbnailPaintFilter();
102    }
103
104    @Override
105    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
106        super.onLayout(changed, left, top, right, bottom);
107        if (changed) {
108            mLayoutRect.set(0, 0, getWidth(), getHeight());
109            updateThumbnailScale();
110        }
111    }
112
113    @Override
114    protected void onDraw(Canvas canvas) {
115        if (mInvisible) {
116            return;
117        }
118        // Draw the thumbnail with the rounded corners
119        canvas.drawRoundRect(0, 0, getWidth(), getHeight(),
120                mConfig.taskViewRoundedCornerRadiusPx,
121                mConfig.taskViewRoundedCornerRadiusPx, mDrawPaint);
122    }
123
124    /** Sets the thumbnail to a given bitmap. */
125    void setThumbnail(Bitmap bm) {
126        if (bm != null) {
127            mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP,
128                    Shader.TileMode.CLAMP);
129            mDrawPaint.setShader(mBitmapShader);
130            mBitmapRect.set(0, 0, bm.getWidth(), bm.getHeight());
131            updateThumbnailScale();
132        } else {
133            mBitmapShader = null;
134            mDrawPaint.setShader(null);
135        }
136        updateThumbnailPaintFilter();
137    }
138
139    /** Updates the paint to draw the thumbnail. */
140    void updateThumbnailPaintFilter() {
141        if (mInvisible) {
142            return;
143        }
144        int mul = (int) ((1.0f - mDimAlpha) * mThumbnailAlpha * 255);
145        int add = (int) ((1.0f - mDimAlpha) * (1 - mThumbnailAlpha) * 255);
146        if (mBitmapShader != null) {
147            mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
148            mLightingColorFilter.setColorAdd(Color.argb(0, add, add, add));
149            mDrawPaint.setColorFilter(mLightingColorFilter);
150            mDrawPaint.setColor(0xffffffff);
151        } else {
152            int grey = mul + add;
153            mDrawPaint.setColorFilter(null);
154            mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
155        }
156        invalidate();
157    }
158
159    /** Updates the thumbnail shader's scale transform. */
160    void updateThumbnailScale() {
161        if (mBitmapShader != null) {
162            mScaleMatrix.setRectToRect(mBitmapRect, mLayoutRect, Matrix.ScaleToFit.FILL);
163            mBitmapShader.setLocalMatrix(mScaleMatrix);
164        }
165    }
166
167    /** Updates the clip rect based on the given task bar. */
168    void updateClipToTaskBar(View taskBar) {
169        mTaskBar = taskBar;
170        int top = (int) Math.max(0, taskBar.getTranslationY() +
171                taskBar.getMeasuredHeight() - 1);
172        mClipRect.set(0, top, getMeasuredWidth(), getMeasuredHeight());
173        setClipBounds(mClipRect);
174    }
175
176    /** Updates the visibility of the the thumbnail. */
177    void updateThumbnailVisibility(int clipBottom) {
178        boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();
179        if (invisible != mInvisible) {
180            mInvisible = invisible;
181            if (!mInvisible) {
182                updateThumbnailPaintFilter();
183            }
184            invalidate();
185        }
186    }
187
188    /**
189     * Sets the dim alpha, only used when we are not using hardware layers.
190     * (see RecentsConfiguration.useHardwareLayers)
191     */
192    public void setDimAlpha(float dimAlpha) {
193        mDimAlpha = dimAlpha;
194        updateThumbnailPaintFilter();
195    }
196
197    /** Binds the thumbnail view to the task */
198    void rebindToTask(Task t) {
199        if (t.thumbnail != null) {
200            setThumbnail(t.thumbnail);
201        } else {
202            setThumbnail(null);
203        }
204    }
205
206    /** Unbinds the thumbnail view from the task */
207    void unbindFromTask() {
208        setThumbnail(null);
209    }
210
211    /** Handles focus changes. */
212    void onFocusChanged(boolean focused) {
213        if (focused) {
214            if (Float.compare(getAlpha(), 1f) != 0) {
215                startFadeAnimation(1f, 0, 150, null);
216            }
217        } else {
218            if (Float.compare(getAlpha(), mConfig.taskViewThumbnailAlpha) != 0) {
219                startFadeAnimation(mConfig.taskViewThumbnailAlpha, 0, 150, null);
220            }
221        }
222    }
223
224    /**
225     * Prepares for the enter recents animation, this gets called before the the view
226     * is first visible and will be followed by a startEnterRecentsAnimation() call.
227     */
228    void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask) {
229        if (isTaskViewLaunchTargetTask) {
230            mThumbnailAlpha = 1f;
231        } else {
232            mThumbnailAlpha = mConfig.taskViewThumbnailAlpha;
233        }
234        updateThumbnailPaintFilter();
235    }
236
237    /** Animates this task thumbnail as it enters Recents. */
238    void startEnterRecentsAnimation(int delay, Runnable postAnimRunnable) {
239        startFadeAnimation(mConfig.taskViewThumbnailAlpha, delay,
240                mConfig.taskViewEnterFromAppDuration, postAnimRunnable);
241    }
242
243    /** Animates this task thumbnail as it exits Recents. */
244    void startLaunchTaskAnimation(Runnable postAnimRunnable) {
245        startFadeAnimation(1f, 0, mConfig.taskViewExitToAppDuration, postAnimRunnable);
246    }
247
248    /** Starts a new thumbnail alpha animation. */
249    void startFadeAnimation(float finalAlpha, int delay, int duration, final Runnable postAnimRunnable) {
250        Utilities.cancelAnimationWithoutCallbacks(mThumbnailAlphaAnimator);
251        mThumbnailAlphaAnimator = ValueAnimator.ofFloat(mThumbnailAlpha, finalAlpha);
252        mThumbnailAlphaAnimator.setStartDelay(delay);
253        mThumbnailAlphaAnimator.setDuration(duration);
254        mThumbnailAlphaAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
255        mThumbnailAlphaAnimator.addUpdateListener(mThumbnailAlphaUpdateListener);
256        if (postAnimRunnable != null) {
257            mThumbnailAlphaAnimator.addListener(new AnimatorListenerAdapter() {
258                @Override
259                public void onAnimationEnd(Animator animation) {
260                    postAnimRunnable.run();
261                }
262            });
263        }
264        mThumbnailAlphaAnimator.start();
265    }
266}
267