• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.quickstep.views;
18 
19 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
20 
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapShader;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.LightingColorFilter;
28 import android.graphics.Matrix;
29 import android.graphics.Paint;
30 import android.graphics.Rect;
31 import android.graphics.Shader;
32 import android.support.v4.graphics.ColorUtils;
33 import android.util.AttributeSet;
34 import android.util.FloatProperty;
35 import android.util.Property;
36 import android.view.View;
37 
38 import com.android.launcher3.BaseActivity;
39 import com.android.launcher3.DeviceProfile;
40 import com.android.launcher3.R;
41 import com.android.launcher3.Utilities;
42 import com.android.launcher3.config.FeatureFlags;
43 import com.android.launcher3.util.SystemUiController;
44 import com.android.launcher3.util.Themes;
45 import com.android.quickstep.TaskOverlayFactory;
46 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
47 import com.android.systemui.shared.recents.model.Task;
48 import com.android.systemui.shared.recents.model.ThumbnailData;
49 
50 /**
51  * A task in the Recents view.
52  */
53 public class TaskThumbnailView extends View {
54 
55     private static final LightingColorFilter[] sDimFilterCache = new LightingColorFilter[256];
56     private static final LightingColorFilter[] sHighlightFilterCache = new LightingColorFilter[256];
57 
58     public static final Property<TaskThumbnailView, Float> DIM_ALPHA_MULTIPLIER =
59             new FloatProperty<TaskThumbnailView>("dimAlphaMultiplier") {
60                 @Override
61                 public void setValue(TaskThumbnailView thumbnail, float dimAlphaMultiplier) {
62                     thumbnail.setDimAlphaMultipler(dimAlphaMultiplier);
63                 }
64 
65                 @Override
66                 public Float get(TaskThumbnailView thumbnailView) {
67                     return thumbnailView.mDimAlphaMultiplier;
68                 }
69             };
70 
71     private final float mCornerRadius;
72 
73     private final BaseActivity mActivity;
74     private final TaskOverlay mOverlay;
75     private final boolean mIsDarkTextTheme;
76     private final Paint mPaint = new Paint();
77     private final Paint mBackgroundPaint = new Paint();
78 
79     private final Matrix mMatrix = new Matrix();
80 
81     private float mClipBottom = -1;
82 
83     private Task mTask;
84     private ThumbnailData mThumbnailData;
85     protected BitmapShader mBitmapShader;
86 
87     private float mDimAlpha = 1f;
88     private float mDimAlphaMultiplier = 1f;
89 
TaskThumbnailView(Context context)90     public TaskThumbnailView(Context context) {
91         this(context, null);
92     }
93 
TaskThumbnailView(Context context, AttributeSet attrs)94     public TaskThumbnailView(Context context, AttributeSet attrs) {
95         this(context, attrs, 0);
96     }
97 
TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr)98     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
99         super(context, attrs, defStyleAttr);
100         mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
101         mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
102         mPaint.setFilterBitmap(true);
103         mBackgroundPaint.setColor(Color.WHITE);
104         mActivity = BaseActivity.fromContext(context);
105         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
106     }
107 
bind()108     public void bind() {
109         mOverlay.reset();
110     }
111 
112     /**
113      * Updates this thumbnail.
114      */
setThumbnail(Task task, ThumbnailData thumbnailData)115     public void setThumbnail(Task task, ThumbnailData thumbnailData) {
116         mTask = task;
117         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
118         mPaint.setColor(color);
119         mBackgroundPaint.setColor(color);
120 
121         if (thumbnailData != null && thumbnailData.thumbnail != null) {
122             Bitmap bm = thumbnailData.thumbnail;
123             bm.prepareToDraw();
124             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
125             mPaint.setShader(mBitmapShader);
126             mThumbnailData = thumbnailData;
127             updateThumbnailMatrix();
128         } else {
129             mBitmapShader = null;
130             mThumbnailData = null;
131             mPaint.setShader(null);
132             mOverlay.reset();
133         }
134         updateThumbnailPaintFilter();
135     }
136 
setDimAlphaMultipler(float dimAlphaMultipler)137     public void setDimAlphaMultipler(float dimAlphaMultipler) {
138         mDimAlphaMultiplier = dimAlphaMultipler;
139         setDimAlpha(mDimAlpha);
140     }
141 
142     /**
143      * Sets the alpha of the dim layer on top of this view.
144      *
145      * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be black.
146      */
setDimAlpha(float dimAlpha)147     public void setDimAlpha(float dimAlpha) {
148         mDimAlpha = dimAlpha;
149         updateThumbnailPaintFilter();
150     }
151 
getInsets()152     public Rect getInsets() {
153         if (mThumbnailData != null) {
154             return mThumbnailData.insets;
155         }
156         return new Rect();
157     }
158 
getSysUiStatusNavFlags()159     public int getSysUiStatusNavFlags() {
160         if (mThumbnailData != null) {
161             int flags = 0;
162             flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
163                     ? SystemUiController.FLAG_LIGHT_STATUS
164                     : SystemUiController.FLAG_DARK_STATUS;
165             flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0
166                     ? SystemUiController.FLAG_LIGHT_NAV
167                     : SystemUiController.FLAG_DARK_NAV;
168             return flags;
169         }
170         return 0;
171     }
172 
173     @Override
onDraw(Canvas canvas)174     protected void onDraw(Canvas canvas) {
175         drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
176     }
177 
getCornerRadius()178     public float getCornerRadius() {
179         return mCornerRadius;
180     }
181 
drawOnCanvas(Canvas canvas, float x, float y, float width, float height, float cornerRadius)182     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
183             float cornerRadius) {
184         // Draw the background in all cases, except when the thumbnail data is opaque
185         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
186                 || mThumbnailData == null;
187         if (drawBackgroundOnly || mClipBottom > 0 || mThumbnailData.isTranslucent) {
188             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
189             if (drawBackgroundOnly) {
190                 return;
191             }
192         }
193 
194         if (mClipBottom > 0) {
195             canvas.save();
196             canvas.clipRect(x, y, width, mClipBottom);
197             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
198             canvas.restore();
199         } else {
200             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
201         }
202     }
203 
updateThumbnailPaintFilter()204     private void updateThumbnailPaintFilter() {
205         int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255);
206         if (mBitmapShader != null) {
207             LightingColorFilter filter = getDimmingColorFilter(mul, mIsDarkTextTheme);
208             mPaint.setColorFilter(filter);
209             mBackgroundPaint.setColorFilter(filter);
210         } else {
211             mPaint.setColorFilter(null);
212             mPaint.setColor(Color.argb(255, mul, mul, mul));
213         }
214         invalidate();
215     }
216 
updateThumbnailMatrix()217     private void updateThumbnailMatrix() {
218         boolean rotate = false;
219         mClipBottom = -1;
220         if (mBitmapShader != null && mThumbnailData != null) {
221             float scale = mThumbnailData.scale;
222             Rect thumbnailInsets  = mThumbnailData.insets;
223             final float thumbnailWidth = mThumbnailData.thumbnail.getWidth() -
224                     (thumbnailInsets.left + thumbnailInsets.right) * scale;
225             final float thumbnailHeight = mThumbnailData.thumbnail.getHeight() -
226                     (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
227 
228             final float thumbnailScale;
229             final DeviceProfile profile = mActivity.getDeviceProfile();
230 
231             if (getMeasuredWidth() == 0) {
232                 // If we haven't measured , skip the thumbnail drawing and only draw the background
233                 // color
234                 thumbnailScale = 0f;
235             } else {
236                 final Configuration configuration =
237                         getContext().getResources().getConfiguration();
238                 // Rotate the screenshot if not in multi-window mode
239                 rotate = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
240                         configuration.orientation != mThumbnailData.orientation &&
241                         !mActivity.isInMultiWindowModeCompat() &&
242                         mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
243                 // Scale the screenshot to always fit the width of the card.
244                 thumbnailScale = rotate
245                         ? getMeasuredWidth() / thumbnailHeight
246                         : getMeasuredWidth() / thumbnailWidth;
247             }
248 
249             if (rotate) {
250                 int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1;
251                 mMatrix.setRotate(90 * rotationDir);
252                 int newLeftInset = rotationDir == 1 ? thumbnailInsets.bottom : thumbnailInsets.top;
253                 int newTopInset = rotationDir == 1 ? thumbnailInsets.left : thumbnailInsets.right;
254                 mMatrix.postTranslate(-newLeftInset * scale, -newTopInset * scale);
255                 if (rotationDir == -1) {
256                     // Crop the right/bottom side of the screenshot rather than left/top
257                     float excessHeight = thumbnailWidth * thumbnailScale - getMeasuredHeight();
258                     mMatrix.postTranslate(0, -excessHeight);
259                 }
260                 // Move the screenshot to the thumbnail window (rotation moved it out).
261                 if (rotationDir == 1) {
262                     mMatrix.postTranslate(mThumbnailData.thumbnail.getHeight(), 0);
263                 } else {
264                     mMatrix.postTranslate(0, mThumbnailData.thumbnail.getWidth());
265                 }
266             } else {
267                 mMatrix.setTranslate(-mThumbnailData.insets.left * scale,
268                         -mThumbnailData.insets.top * scale);
269             }
270             mMatrix.postScale(thumbnailScale, thumbnailScale);
271             mBitmapShader.setLocalMatrix(mMatrix);
272 
273             float bitmapHeight = Math.max((rotate ? thumbnailWidth : thumbnailHeight)
274                     * thumbnailScale, 0);
275             if (Math.round(bitmapHeight) < getMeasuredHeight()) {
276                 mClipBottom = bitmapHeight;
277             }
278             mPaint.setShader(mBitmapShader);
279         }
280 
281         if (rotate) {
282             // The overlay doesn't really work when the screenshot is rotated, so don't add it.
283             mOverlay.reset();
284         } else {
285             mOverlay.setTaskInfo(mTask, mThumbnailData, mMatrix);
286         }
287         invalidate();
288     }
289 
290     @Override
onSizeChanged(int w, int h, int oldw, int oldh)291     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
292         super.onSizeChanged(w, h, oldw, oldh);
293         updateThumbnailMatrix();
294     }
295 
getDimmingColorFilter(int intensity, boolean shouldLighten)296     private static LightingColorFilter getDimmingColorFilter(int intensity, boolean shouldLighten) {
297         intensity = Utilities.boundToRange(intensity, 0, 255);
298         if (intensity == 255) {
299             return null;
300         }
301         if (shouldLighten) {
302             if (sHighlightFilterCache[intensity] == null) {
303                 int colorAdd = 255 - intensity;
304                 sHighlightFilterCache[intensity] = new LightingColorFilter(
305                         Color.argb(255, intensity, intensity, intensity),
306                         Color.argb(255, colorAdd, colorAdd, colorAdd));
307             }
308             return sHighlightFilterCache[intensity];
309         } else {
310             if (sDimFilterCache[intensity] == null) {
311                 sDimFilterCache[intensity] = new LightingColorFilter(
312                         Color.argb(255, intensity, intensity, intensity), 0);
313             }
314             return sDimFilterCache[intensity];
315         }
316     }
317 }
318