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 
17 package com.android.systemui.recents.views;
18 
19 import android.app.ActivityManager;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapShader;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.ColorMatrix;
27 import android.graphics.ColorMatrixColorFilter;
28 import android.graphics.LightingColorFilter;
29 import android.graphics.Matrix;
30 import android.graphics.Paint;
31 import android.graphics.Rect;
32 import android.graphics.Shader;
33 import android.util.AttributeSet;
34 import android.view.View;
35 import android.view.ViewDebug;
36 
37 import com.android.systemui.R;
38 import com.android.systemui.recents.model.Task;
39 
40 
41 /**
42  * The task thumbnail view.  It implements an image view that allows for animating the dim and
43  * alpha of the thumbnail image.
44  */
45 public class TaskViewThumbnail extends View {
46 
47     private static final ColorMatrix TMP_FILTER_COLOR_MATRIX = new ColorMatrix();
48     private static final ColorMatrix TMP_BRIGHTNESS_COLOR_MATRIX = new ColorMatrix();
49 
50     private Task mTask;
51 
52     private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
53     private Rect mDisplayRect = new Rect();
54 
55     // Drawing
56     @ViewDebug.ExportedProperty(category="recents")
57     private Rect mTaskViewRect = new Rect();
58     @ViewDebug.ExportedProperty(category="recents")
59     private Rect mThumbnailRect = new Rect();
60     @ViewDebug.ExportedProperty(category="recents")
61     private float mThumbnailScale;
62     private float mFullscreenThumbnailScale;
63     private ActivityManager.TaskThumbnailInfo mThumbnailInfo;
64 
65     private int mCornerRadius;
66     @ViewDebug.ExportedProperty(category="recents")
67     private float mDimAlpha;
68     private Matrix mScaleMatrix = new Matrix();
69     private Paint mDrawPaint = new Paint();
70     private Paint mBgFillPaint = new Paint();
71     private BitmapShader mBitmapShader;
72     private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
73 
74     // Clip the top of the thumbnail against the opaque header bar that overlaps this view
75     private View mTaskBar;
76 
77     // Visibility optimization, if the thumbnail height is less than the height of the header
78     // bar for the task view, then just mark this thumbnail view as invisible
79     @ViewDebug.ExportedProperty(category="recents")
80     private boolean mInvisible;
81 
82     @ViewDebug.ExportedProperty(category="recents")
83     private boolean mDisabledInSafeMode;
84 
TaskViewThumbnail(Context context)85     public TaskViewThumbnail(Context context) {
86         this(context, null);
87     }
88 
TaskViewThumbnail(Context context, AttributeSet attrs)89     public TaskViewThumbnail(Context context, AttributeSet attrs) {
90         this(context, attrs, 0);
91     }
92 
TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr)93     public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr) {
94         this(context, attrs, defStyleAttr, 0);
95     }
96 
TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)97     public TaskViewThumbnail(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
98         super(context, attrs, defStyleAttr, defStyleRes);
99         mDrawPaint.setColorFilter(mLightingColorFilter);
100         mDrawPaint.setFilterBitmap(true);
101         mDrawPaint.setAntiAlias(true);
102         mCornerRadius = getResources().getDimensionPixelSize(
103                 R.dimen.recents_task_view_rounded_corners_radius);
104         mBgFillPaint.setColor(Color.WHITE);
105         mFullscreenThumbnailScale = context.getResources().getFraction(
106                 com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
107     }
108 
109     /**
110      * Called when the task view frame changes, allowing us to move the contents of the header
111      * to match the frame changes.
112      */
onTaskViewSizeChanged(int width, int height)113     public void onTaskViewSizeChanged(int width, int height) {
114         // Return early if the bounds have not changed
115         if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) {
116             return;
117         }
118 
119         mTaskViewRect.set(0, 0, width, height);
120         setLeftTopRightBottom(0, 0, width, height);
121         updateThumbnailScale();
122     }
123 
124     @Override
onDraw(Canvas canvas)125     protected void onDraw(Canvas canvas) {
126         if (mInvisible) {
127             return;
128         }
129 
130         int viewWidth = mTaskViewRect.width();
131         int viewHeight = mTaskViewRect.height();
132         int thumbnailWidth = Math.min(viewWidth,
133                 (int) (mThumbnailRect.width() * mThumbnailScale));
134         int thumbnailHeight = Math.min(viewHeight,
135                 (int) (mThumbnailRect.height() * mThumbnailScale));
136         if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
137             int topOffset = mTaskBar != null
138                     ? mTaskBar.getHeight() - mCornerRadius
139                     : 0;
140 
141             // Draw the background, there will be some small overdraw with the thumbnail
142             if (thumbnailWidth < viewWidth) {
143                 // Portrait thumbnail on a landscape task view
144                 canvas.drawRoundRect(Math.max(0, thumbnailWidth - mCornerRadius), topOffset,
145                         viewWidth, viewHeight,
146                         mCornerRadius, mCornerRadius, mBgFillPaint);
147             }
148             if (thumbnailHeight < viewHeight) {
149                 // Landscape thumbnail on a portrait task view
150                 canvas.drawRoundRect(0, Math.max(topOffset, thumbnailHeight - mCornerRadius),
151                         viewWidth, viewHeight,
152                         mCornerRadius, mCornerRadius, mBgFillPaint);
153             }
154 
155             // Draw the thumbnail
156             canvas.drawRoundRect(0, topOffset, thumbnailWidth, thumbnailHeight,
157                     mCornerRadius, mCornerRadius, mDrawPaint);
158         } else {
159             canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
160                     mBgFillPaint);
161         }
162     }
163 
164     /** Sets the thumbnail to a given bitmap. */
setThumbnail(Bitmap bm, ActivityManager.TaskThumbnailInfo thumbnailInfo)165     void setThumbnail(Bitmap bm, ActivityManager.TaskThumbnailInfo thumbnailInfo) {
166         if (bm != null) {
167             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
168             mDrawPaint.setShader(mBitmapShader);
169             mThumbnailRect.set(0, 0, bm.getWidth(), bm.getHeight());
170             mThumbnailInfo = thumbnailInfo;
171             updateThumbnailScale();
172         } else {
173             mBitmapShader = null;
174             mDrawPaint.setShader(null);
175             mThumbnailRect.setEmpty();
176             mThumbnailInfo = null;
177         }
178     }
179 
180     /** Updates the paint to draw the thumbnail. */
updateThumbnailPaintFilter()181     void updateThumbnailPaintFilter() {
182         if (mInvisible) {
183             return;
184         }
185         int mul = (int) ((1.0f - mDimAlpha) * 255);
186         if (mBitmapShader != null) {
187             if (mDisabledInSafeMode) {
188                 // Brightness: C-new = C-old*(1-amount) + amount
189                 TMP_FILTER_COLOR_MATRIX.setSaturation(0);
190                 float scale = 1f - mDimAlpha;
191                 float[] mat = TMP_BRIGHTNESS_COLOR_MATRIX.getArray();
192                 mat[0] = scale;
193                 mat[6] = scale;
194                 mat[12] = scale;
195                 mat[4] = mDimAlpha * 255f;
196                 mat[9] = mDimAlpha * 255f;
197                 mat[14] = mDimAlpha * 255f;
198                 TMP_FILTER_COLOR_MATRIX.preConcat(TMP_BRIGHTNESS_COLOR_MATRIX);
199                 ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX);
200                 mDrawPaint.setColorFilter(filter);
201                 mBgFillPaint.setColorFilter(filter);
202             } else {
203                 mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
204                 mDrawPaint.setColorFilter(mLightingColorFilter);
205                 mDrawPaint.setColor(0xFFffffff);
206                 mBgFillPaint.setColorFilter(mLightingColorFilter);
207             }
208         } else {
209             int grey = mul;
210             mDrawPaint.setColorFilter(null);
211             mDrawPaint.setColor(Color.argb(255, grey, grey, grey));
212         }
213         if (!mInvisible) {
214             invalidate();
215         }
216     }
217 
218     /**
219      * Updates the scale of the bitmap relative to this view.
220      */
updateThumbnailScale()221     public void updateThumbnailScale() {
222         mThumbnailScale = 1f;
223         if (mBitmapShader != null) {
224             // We consider this a stack task if it is not freeform (ie. has no bounds) or has been
225             // dragged into the stack from the freeform workspace
226             boolean isStackTask = !mTask.isFreeformTask() || mTask.bounds == null;
227             if (mTaskViewRect.isEmpty() || mThumbnailInfo == null ||
228                     mThumbnailInfo.taskWidth == 0 || mThumbnailInfo.taskHeight == 0) {
229                 // If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing
230                 // and only draw the background color
231                 mThumbnailScale = 0f;
232             } else if (isStackTask) {
233                 float invThumbnailScale = 1f / mFullscreenThumbnailScale;
234                 if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
235                     if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) {
236                         // If we are in the same orientation as the screenshot, just scale it to the
237                         // width of the task view
238                         mThumbnailScale = (float) mTaskViewRect.width() / mThumbnailRect.width();
239                     } else {
240                         // Scale the landscape thumbnail up to app size, then scale that to the task
241                         // view size to match other portrait screenshots
242                         mThumbnailScale = invThumbnailScale *
243                                 ((float) mTaskViewRect.width() / mDisplayRect.width());
244                     }
245                 } else {
246                     // Otherwise, scale the screenshot to fit 1:1 in the current orientation
247                     mThumbnailScale = invThumbnailScale;
248                 }
249             } else {
250                 // Otherwise, if this is a freeform task with task bounds, then scale the thumbnail
251                 // to fit the entire bitmap into the task bounds
252                 mThumbnailScale = Math.min(
253                         (float) mTaskViewRect.width() / mThumbnailRect.width(),
254                         (float) mTaskViewRect.height() / mThumbnailRect.height());
255             }
256             mScaleMatrix.setScale(mThumbnailScale, mThumbnailScale);
257             mBitmapShader.setLocalMatrix(mScaleMatrix);
258         }
259         if (!mInvisible) {
260             invalidate();
261         }
262     }
263 
264     /** Updates the clip rect based on the given task bar. */
updateClipToTaskBar(View taskBar)265     void updateClipToTaskBar(View taskBar) {
266         mTaskBar = taskBar;
267         invalidate();
268     }
269 
270     /** Updates the visibility of the the thumbnail. */
updateThumbnailVisibility(int clipBottom)271     void updateThumbnailVisibility(int clipBottom) {
272         boolean invisible = mTaskBar != null && (getHeight() - clipBottom) <= mTaskBar.getHeight();
273         if (invisible != mInvisible) {
274             mInvisible = invisible;
275             if (!mInvisible) {
276                 updateThumbnailPaintFilter();
277             }
278         }
279     }
280 
281     /**
282      * Sets the dim alpha, only used when we are not using hardware layers.
283      * (see RecentsConfiguration.useHardwareLayers)
284      */
setDimAlpha(float dimAlpha)285     public void setDimAlpha(float dimAlpha) {
286         mDimAlpha = dimAlpha;
287         updateThumbnailPaintFilter();
288     }
289 
290     /**
291      * Binds the thumbnail view to the task.
292      */
bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect)293     void bindToTask(Task t, boolean disabledInSafeMode, int displayOrientation, Rect displayRect) {
294         mTask = t;
295         mDisabledInSafeMode = disabledInSafeMode;
296         mDisplayOrientation = displayOrientation;
297         mDisplayRect.set(displayRect);
298         if (t.colorBackground != 0) {
299             mBgFillPaint.setColor(t.colorBackground);
300         }
301     }
302 
303     /**
304      * Called when the bound task's data has loaded and this view should update to reflect the
305      * changes.
306      */
onTaskDataLoaded(ActivityManager.TaskThumbnailInfo thumbnailInfo)307     void onTaskDataLoaded(ActivityManager.TaskThumbnailInfo thumbnailInfo) {
308         if (mTask.thumbnail != null) {
309             setThumbnail(mTask.thumbnail, thumbnailInfo);
310         } else {
311             setThumbnail(null, null);
312         }
313     }
314 
315     /** Unbinds the thumbnail view from the task */
unbindFromTask()316     void unbindFromTask() {
317         mTask = null;
318         setThumbnail(null, null);
319     }
320 }
321