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.content.Context;
20import android.content.res.Configuration;
21import android.content.res.Resources;
22import android.graphics.Bitmap;
23import android.graphics.BitmapShader;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.ColorMatrix;
27import android.graphics.ColorMatrixColorFilter;
28import android.graphics.LightingColorFilter;
29import android.graphics.Matrix;
30import android.graphics.Paint;
31import android.graphics.Rect;
32import android.graphics.Shader;
33import android.util.AttributeSet;
34import android.view.View;
35import android.view.ViewDebug;
36
37import com.android.systemui.R;
38import com.android.systemui.recents.events.EventBus;
39import com.android.systemui.recents.events.ui.TaskSnapshotChangedEvent;
40import com.android.systemui.shared.recents.utilities.Utilities;
41import com.android.systemui.shared.recents.model.Task;
42import com.android.systemui.shared.recents.model.ThumbnailData;
43import java.io.PrintWriter;
44
45
46/**
47 * The task thumbnail view.  It implements an image view that allows for animating the dim and
48 * alpha of the thumbnail image.
49 */
50public class TaskViewThumbnail extends View {
51
52    private static final ColorMatrix TMP_FILTER_COLOR_MATRIX = new ColorMatrix();
53    private static final ColorMatrix TMP_BRIGHTNESS_COLOR_MATRIX = new ColorMatrix();
54
55    private Task mTask;
56
57    private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
58    private Rect mDisplayRect = new Rect();
59
60    // Drawing
61    @ViewDebug.ExportedProperty(category="recents")
62    protected Rect mTaskViewRect = new Rect();
63    @ViewDebug.ExportedProperty(category="recents")
64    protected Rect mThumbnailRect = new Rect();
65    @ViewDebug.ExportedProperty(category="recents")
66    protected float mThumbnailScale;
67    private float mFullscreenThumbnailScale = 1f;
68    /** The height, in pixels, of the task view's title bar. */
69    private int mTitleBarHeight;
70    private boolean mSizeToFit = false;
71    private boolean mOverlayHeaderOnThumbnailActionBar = true;
72    private ThumbnailData mThumbnailData;
73
74    protected int mCornerRadius;
75    @ViewDebug.ExportedProperty(category="recents")
76    private float mDimAlpha;
77    private Matrix mMatrix = new Matrix();
78    private Paint mDrawPaint = new Paint();
79    protected Paint mLockedPaint = new Paint();
80    protected Paint mBgFillPaint = new Paint();
81    protected BitmapShader mBitmapShader;
82    protected boolean mUserLocked = false;
83    private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
84
85    // Clip the top of the thumbnail against the opaque header bar that overlaps this view
86    private View mTaskBar;
87
88    // Visibility optimization, if the thumbnail height is less than the height of the header
89    // bar for the task view, then just mark this thumbnail view as invisible
90    @ViewDebug.ExportedProperty(category="recents")
91    private boolean mInvisible;
92
93    @ViewDebug.ExportedProperty(category="recents")
94    private boolean mDisabledInSafeMode;
95
96    public TaskViewThumbnail(Context context) {
97        this(context, null);
98    }
99
100    public TaskViewThumbnail(Context context, AttributeSet attrs) {
101        this(context, attrs, 0);
102    }
103
104    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
105        this(context, attrs, defStyleAttr, 0);
106    }
107
108    public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
109        super(context, attrs, defStyleAttr, defStyleRes);
110        mDrawPaint.setColorFilter(mLightingColorFilter);
111        mDrawPaint.setFilterBitmap(true);
112        mDrawPaint.setAntiAlias(true);
113        Resources res = getResources();
114        mCornerRadius = res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius);
115        mBgFillPaint.setColor(Color.WHITE);
116        mLockedPaint.setColor(Color.WHITE);
117        mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
118    }
119
120    /**
121     * Called when the task view frame changes, allowing us to move the contents of the header
122     * to match the frame changes.
123     */
124    public void onTaskViewSizeChanged(int width, int height) {
125        // Return early if the bounds have not changed
126        if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) {
127            return;
128        }
129
130        mTaskViewRect.set(0, 0, width, height);
131        setLeftTopRightBottom(0, 0, width, height);
132        updateThumbnailMatrix();
133    }
134
135    @Override
136    protected void onDraw(Canvas canvas) {
137        if (mInvisible) {
138            return;
139        }
140
141        int viewWidth = mTaskViewRect.width();
142        int viewHeight = mTaskViewRect.height();
143        int thumbnailWidth = Math.min(viewWidth,
144                (int) (mThumbnailRect.width() * mThumbnailScale));
145        int thumbnailHeight = Math.min(viewHeight,
146                (int) (mThumbnailRect.height() * mThumbnailScale));
147
148        if (mUserLocked) {
149            canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
150                    mLockedPaint);
151        } else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
152            int topOffset = 0;
153            if (mTaskBar != null && mOverlayHeaderOnThumbnailActionBar) {
154                topOffset = mTaskBar.getHeight() - mCornerRadius;
155            }
156
157            // Draw the background, there will be some small overdraw with the thumbnail
158            if (thumbnailWidth < viewWidth) {
159                // Portrait thumbnail on a landscape task view
160                canvas.drawRoundRect(Math.max(0, thumbnailWidth - mCornerRadius), topOffset,
161                        viewWidth, viewHeight,
162                        mCornerRadius, mCornerRadius, mBgFillPaint);
163            }
164            if (thumbnailHeight < viewHeight) {
165                // Landscape thumbnail on a portrait task view
166                canvas.drawRoundRect(0, Math.max(topOffset, thumbnailHeight - mCornerRadius),
167                        viewWidth, viewHeight,
168                        mCornerRadius, mCornerRadius, mBgFillPaint);
169            }
170
171            // Draw the thumbnail
172            canvas.drawRoundRect(0, topOffset, thumbnailWidth, thumbnailHeight,
173                    mCornerRadius, mCornerRadius, mDrawPaint);
174        } else {
175            canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
176                    mBgFillPaint);
177        }
178    }
179
180    /** Sets the thumbnail to a given bitmap. */
181    void setThumbnail(ThumbnailData thumbnailData) {
182        if (thumbnailData != null && thumbnailData.thumbnail != null) {
183            Bitmap bm = thumbnailData.thumbnail;
184            bm.prepareToDraw();
185            mFullscreenThumbnailScale = thumbnailData.scale;
186            mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
187            mDrawPaint.setShader(mBitmapShader);
188            mThumbnailRect.set(0, 0,
189                    bm.getWidth() - thumbnailData.insets.left - thumbnailData.insets.right,
190                    bm.getHeight() - thumbnailData.insets.top - thumbnailData.insets.bottom);
191            mThumbnailData = thumbnailData;
192            updateThumbnailMatrix();
193            updateThumbnailPaintFilter();
194        } else {
195            mBitmapShader = null;
196            mDrawPaint.setShader(null);
197            mThumbnailRect.setEmpty();
198            mThumbnailData = null;
199        }
200    }
201
202    /** Updates the paint to draw the thumbnail. */
203    void updateThumbnailPaintFilter() {
204        if (mInvisible) {
205            return;
206        }
207        int mul = (int) ((1.0f - mDimAlpha) * 255);
208        if (mBitmapShader != null) {
209            if (mDisabledInSafeMode) {
210                // Brightness: C-new = C-old*(1-amount) + amount
211                TMP_FILTER_COLOR_MATRIX.setSaturation(0);
212                float scale = 1f - mDimAlpha;
213                float[] mat = TMP_BRIGHTNESS_COLOR_MATRIX.getArray();
214                mat[0] = scale;
215                mat[6] = scale;
216                mat[12] = scale;
217                mat[4] = mDimAlpha * 255f;
218                mat[9] = mDimAlpha * 255f;
219                mat[14] = mDimAlpha * 255f;
220                TMP_FILTER_COLOR_MATRIX.preConcat(TMP_BRIGHTNESS_COLOR_MATRIX);
221                ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX);
222                mDrawPaint.setColorFilter(filter);
223                mBgFillPaint.setColorFilter(filter);
224                mLockedPaint.setColorFilter(filter);
225            } else {
226                mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
227                mDrawPaint.setColorFilter(mLightingColorFilter);
228                mDrawPaint.setColor(0xFFffffff);
229                mBgFillPaint.setColorFilter(mLightingColorFilter);
230                mLockedPaint.setColorFilter(mLightingColorFilter);
231            }
232        } else {
233            int grey = mul;
234            mDrawPaint.setColorFilter(null);
235            mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
236        }
237        if (!mInvisible) {
238            invalidate();
239        }
240    }
241
242    /**
243     * Updates the scale of the bitmap relative to this view.
244     */
245    public void updateThumbnailMatrix() {
246        mThumbnailScale = 1f;
247        if (mBitmapShader != null && mThumbnailData != null) {
248            if (mTaskViewRect.isEmpty()) {
249                // If we haven't measured , skip the thumbnail drawing and only draw the background
250                // color
251                mThumbnailScale = 0f;
252            } else if (mSizeToFit) {
253                // Make sure we fill the entire space regardless of the orientation.
254                float viewAspectRatio = (float) mTaskViewRect.width() /
255                        (float) (mTaskViewRect.height() - mTitleBarHeight);
256                float thumbnailAspectRatio =
257                        (float) mThumbnailRect.width() / (float) mThumbnailRect.height();
258                if (viewAspectRatio > thumbnailAspectRatio) {
259                    mThumbnailScale =
260                            (float) mTaskViewRect.width() / (float) mThumbnailRect.width();
261                } else {
262                    mThumbnailScale = (float) (mTaskViewRect.height() - mTitleBarHeight)
263                            / (float) mThumbnailRect.height();
264                }
265            } else {
266                float invThumbnailScale = 1f / mFullscreenThumbnailScale;
267                if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
268                    if (mThumbnailData.orientation == Configuration.ORIENTATION_PORTRAIT) {
269                        // If we are in the same orientation as the screenshot, just scale it to the
270                        // width of the task view
271                        mThumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width();
272                    } else {
273                        // Scale the landscape thumbnail up to app size, then scale that to the task
274                        // view size to match other portrait screenshots
275                        mThumbnailScale = invThumbnailScale *
276                                ((float) mTaskViewRect.width() / mDisplayRect.width());
277                    }
278                } else {
279                    // Otherwise, scale the screenshot to fit 1:1 in the current orientation
280                    mThumbnailScale = invThumbnailScale;
281                }
282            }
283            mMatrix.setTranslate(-mThumbnailData.insets.left * mFullscreenThumbnailScale,
284                    -mThumbnailData.insets.top * mFullscreenThumbnailScale);
285            mMatrix.postScale(mThumbnailScale, mThumbnailScale);
286            mBitmapShader.setLocalMatrix(mMatrix);
287        }
288        if (!mInvisible) {
289            invalidate();
290        }
291    }
292
293    /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */
294    public void setSizeToFit(boolean flag) {
295        mSizeToFit = flag;
296    }
297
298    /**
299     * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or
300     * be stacked just above it.
301     */
302    public void setOverlayHeaderOnThumbnailActionBar(boolean flag) {
303        mOverlayHeaderOnThumbnailActionBar = flag;
304    }
305
306    /** Updates the clip rect based on the given task bar. */
307    void updateClipToTaskBar(View taskBar) {
308        mTaskBar = taskBar;
309        invalidate();
310    }
311
312    /** Updates the visibility of the the thumbnail. */
313    void updateThumbnailVisibility(int clipBottom) {
314        boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();
315        if (invisible != mInvisible) {
316            mInvisible = invisible;
317            if (!mInvisible) {
318                updateThumbnailPaintFilter();
319            }
320        }
321    }
322
323    /**
324     * Sets the dim alpha, only used when we are not using hardware layers.
325     * (see RecentsConfiguration.useHardwareLayers)
326     */
327    public void setDimAlpha(float dimAlpha) {
328        mDimAlpha = dimAlpha;
329        updateThumbnailPaintFilter();
330    }
331
332    /**
333     * Returns the {@link Paint} used to draw a task screenshot, or {@link #mLockedPaint} if the
334     * thumbnail shouldn't be drawn because it belongs to a locked user.
335     */
336    protected Paint getDrawPaint() {
337        if (mUserLocked) {
338            return mLockedPaint;
339        }
340        return mDrawPaint;
341    }
342
343    /**
344     * Binds the thumbnail view to the task.
345     */
346    void bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect) {
347        mTask = t;
348        mDisabledInSafeMode = disabledInSafeMode;
349        mDisplayOrientation = displayOrientation;
350        mDisplayRect.set(displayRect);
351        if (t.colorBackground != 0) {
352            mBgFillPaint.setColor(t.colorBackground);
353        }
354        if (t.colorPrimary != 0) {
355            mLockedPaint.setColor(t.colorPrimary);
356        }
357        mUserLocked = t.isLocked;
358        EventBus.getDefault().register(this);
359    }
360
361    /**
362     * Called when the bound task's data has loaded and this view should update to reflect the
363     * changes.
364     */
365    void onTaskDataLoaded(ThumbnailData thumbnailData) {
366        setThumbnail(thumbnailData);
367    }
368
369    /** Unbinds the thumbnail view from the task */
370    void unbindFromTask() {
371        mTask = null;
372        setThumbnail(null);
373        EventBus.getDefault().unregister(this);
374    }
375
376    public final void onBusEvent(TaskSnapshotChangedEvent event) {
377        if (mTask == null || event.taskId != mTask.key.id || event.thumbnailData == null
378                || event.thumbnailData.thumbnail == null) {
379            return;
380        }
381        setThumbnail(event.thumbnailData);
382    }
383
384    public void dump(String prefix, PrintWriter writer) {
385        writer.print(prefix); writer.print("TaskViewThumbnail");
386        writer.print(" mTaskViewRect="); writer.print(Utilities.dumpRect(mTaskViewRect));
387        writer.print(" mThumbnailRect="); writer.print(Utilities.dumpRect(mThumbnailRect));
388        writer.print(" mThumbnailScale="); writer.print(mThumbnailScale);
389        writer.print(" mDimAlpha="); writer.print(mDimAlpha);
390        writer.println();
391    }
392}
393