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 android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
20 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
21 
22 import static com.android.systemui.shared.recents.utilities.PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT;
23 import static com.android.systemui.shared.recents.utilities.Utilities.isRelativePercentDifferenceGreaterThan;
24 
25 import android.content.Context;
26 import android.graphics.Bitmap;
27 import android.graphics.BitmapShader;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.ColorFilter;
31 import android.graphics.Insets;
32 import android.graphics.Matrix;
33 import android.graphics.Paint;
34 import android.graphics.PorterDuff;
35 import android.graphics.PorterDuffXfermode;
36 import android.graphics.Rect;
37 import android.graphics.RectF;
38 import android.graphics.Shader;
39 import android.graphics.drawable.Drawable;
40 import android.os.Build;
41 import android.util.AttributeSet;
42 import android.util.FloatProperty;
43 import android.util.Property;
44 import android.view.View;
45 import android.widget.ImageView;
46 
47 import androidx.annotation.Nullable;
48 import androidx.annotation.RequiresApi;
49 import androidx.core.graphics.ColorUtils;
50 
51 import com.android.launcher3.DeviceProfile;
52 import com.android.launcher3.Utilities;
53 import com.android.launcher3.util.MainThreadInitializedObject;
54 import com.android.launcher3.util.SystemUiController;
55 import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
56 import com.android.launcher3.util.ViewPool;
57 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
58 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
59 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
60 import com.android.systemui.shared.recents.model.Task;
61 import com.android.systemui.shared.recents.model.ThumbnailData;
62 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
63 
64 import java.util.Objects;
65 
66 /**
67  * A task in the Recents view.
68  *
69  * @deprecated This class will be replaced by the new [TaskThumbnailView].
70  */
71 @Deprecated
72 public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable {
73     private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
74             new MainThreadInitializedObject<>(FullscreenDrawParams::new);
75 
76     public static final Property<TaskThumbnailViewDeprecated, Float> DIM_ALPHA =
77             new FloatProperty<TaskThumbnailViewDeprecated>("dimAlpha") {
78                 @Override
79                 public void setValue(TaskThumbnailViewDeprecated thumbnail, float dimAlpha) {
80                     thumbnail.setDimAlpha(dimAlpha);
81                 }
82 
83                 @Override
84                 public Float get(TaskThumbnailViewDeprecated thumbnailView) {
85                     return thumbnailView.mDimAlpha;
86                 }
87             };
88 
89     public static final Property<TaskThumbnailViewDeprecated, Float> SPLASH_ALPHA =
90             new FloatProperty<TaskThumbnailViewDeprecated>("splashAlpha") {
91                 @Override
92                 public void setValue(TaskThumbnailViewDeprecated thumbnail, float splashAlpha) {
93                     thumbnail.setSplashAlpha(splashAlpha);
94                 }
95 
96                 @Override
97                 public Float get(TaskThumbnailViewDeprecated thumbnailView) {
98                     return thumbnailView.mSplashAlpha / 255f;
99                 }
100             };
101 
102     /** Use to animate thumbnail translationX while first app in split selection is initiated */
103     public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_X =
104             new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateX") {
105                 @Override
106                 public void setValue(TaskThumbnailViewDeprecated thumbnail,
107                         float splitSelectTranslateX) {
108                     thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
109                 }
110 
111                 @Override
112                 public Float get(TaskThumbnailViewDeprecated thumbnailView) {
113                     return thumbnailView.mSplitSelectTranslateX;
114                 }
115             };
116 
117     /** Use to animate thumbnail translationY while first app in split selection is initiated */
118     public static final Property<TaskThumbnailViewDeprecated, Float> SPLIT_SELECT_TRANSLATE_Y =
119             new FloatProperty<TaskThumbnailViewDeprecated>("splitSelectTranslateY") {
120                 @Override
121                 public void setValue(TaskThumbnailViewDeprecated thumbnail,
122                         float splitSelectTranslateY) {
123                     thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
124                 }
125 
126                 @Override
127                 public Float get(TaskThumbnailViewDeprecated thumbnailView) {
128                     return thumbnailView.mSplitSelectTranslateY;
129                 }
130             };
131 
132     private final RecentsViewContainer mContainer;
133     private TaskOverlay<?> mOverlay;
134     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
135     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
136     private final Paint mSplashBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
137     private final Paint mClearPaint = new Paint();
138     private final Paint mDimmingPaintAfterClearing = new Paint();
139     private final int mDimColor;
140 
141     // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
142     private final Rect mPreviewRect = new Rect();
143     private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper();
144     private TaskView.FullscreenDrawParams mFullscreenParams;
145     private ImageView mSplashView;
146     private Drawable mSplashViewDrawable;
147 
148     @Nullable
149     private Task mTask;
150     @Nullable
151     private ThumbnailData mThumbnailData;
152     @Nullable
153     protected BitmapShader mBitmapShader;
154 
155     /** How much this thumbnail is dimmed, 0 not dimmed at all, 1 totally dimmed. */
156     private float mDimAlpha = 0f;
157     /** Controls visibility of the splash view, 0 is transparent, 255 fully opaque. */
158     private int mSplashAlpha = 0;
159 
160     private boolean mOverlayEnabled;
161     /** Used as a placeholder when the original thumbnail animates out to. */
162     private boolean mShowSplashForSplitSelection;
163     private float mSplitSelectTranslateX;
164     private float mSplitSelectTranslateY;
165 
TaskThumbnailViewDeprecated(Context context)166     public TaskThumbnailViewDeprecated(Context context) {
167         this(context, null);
168     }
169 
TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs)170     public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs) {
171         this(context, attrs, 0);
172     }
173 
TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs, int defStyleAttr)174     public TaskThumbnailViewDeprecated(Context context, @Nullable AttributeSet attrs,
175             int defStyleAttr) {
176         super(context, attrs, defStyleAttr);
177         mPaint.setFilterBitmap(true);
178         mBackgroundPaint.setColor(Color.WHITE);
179         mSplashBackgroundPaint.setColor(Color.WHITE);
180         mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
181         mContainer = RecentsViewContainer.containerFromContext(context);
182         // Initialize with placeholder value. It is overridden later by TaskView
183         mFullscreenParams = TEMP_PARAMS.get(context);
184 
185         mDimColor = RecentsView.getForegroundScrimDimColor(context);
186         mDimmingPaintAfterClearing.setColor(mDimColor);
187     }
188 
189     /**
190      * Updates the thumbnail to draw the provided task
191      */
bind(Task task, TaskOverlay<?> overlay)192     public void bind(Task task, TaskOverlay<?> overlay) {
193         mOverlay = overlay;
194         mOverlay.reset();
195         mTask = task;
196         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
197         mPaint.setColor(color);
198         mBackgroundPaint.setColor(color);
199         mSplashBackgroundPaint.setColor(color);
200         updateSplashView(mTask.icon);
201     }
202 
203     /**
204      * Sets TaskOverlay without binding a task.
205      *
206      * @deprecated Should only be used when using new
207      * {@link com.android.quickstep.task.thumbnail.TaskThumbnailView}.
208      */
209     @Deprecated
setTaskOverlay(TaskOverlay<?> overlay)210     public void setTaskOverlay(TaskOverlay<?> overlay) {
211         mOverlay = overlay;
212     }
213 
214     /**
215      * Updates the thumbnail.
216      *
217      * @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
218      *                   In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)}
219      *                   version with {@code refreshNow} is true. The only exception is
220      *                   in the live tile case that we grab a screenshot when user enters Overview
221      *                   upon swipe up so that a usable screenshot is accessible immediately when
222      *                   recents animation needs to be finished / cancelled.
223      */
setThumbnail(@ullable Task task, @Nullable ThumbnailData thumbnailData, boolean refreshNow)224     public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData,
225             boolean refreshNow) {
226         mTask = task;
227         ThumbnailData oldThumbnailData = mThumbnailData;
228         mThumbnailData = (thumbnailData != null && thumbnailData.getThumbnail() != null)
229                 ? thumbnailData : null;
230         if (mTask != null) {
231             updateSplashView(mTask.icon);
232         }
233         if (refreshNow) {
234             Long oldSnapshotId = oldThumbnailData != null ? oldThumbnailData.getSnapshotId() : null;
235             Long snapshotId = mThumbnailData != null ? mThumbnailData.getSnapshotId() : null;
236             refresh(snapshotId != null && !Objects.equals(oldSnapshotId, snapshotId));
237         }
238     }
239 
240     /** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */
setThumbnail(@ullable Task task, @Nullable ThumbnailData thumbnailData)241     public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData) {
242         setThumbnail(task, thumbnailData, true /* refreshNow */);
243     }
244 
245     /** Updates the shader, paint, matrix to redraw. */
refresh()246     public void refresh() {
247         refresh(false);
248     }
249 
250     /**
251      * Updates the shader, paint, matrix to redraw.
252      *
253      * @param shouldRefreshOverlay whether to re-initialize overlay
254      */
refresh(boolean shouldRefreshOverlay)255     private void refresh(boolean shouldRefreshOverlay) {
256         if (mThumbnailData != null && mThumbnailData.getThumbnail() != null) {
257             Bitmap bm = mThumbnailData.getThumbnail();
258             bm.prepareToDraw();
259             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
260             mPaint.setShader(mBitmapShader);
261             updateThumbnailMatrix();
262             if (shouldRefreshOverlay) {
263                 refreshOverlay();
264             }
265         } else {
266             mBitmapShader = null;
267             mThumbnailData = null;
268             mPaint.setShader(null);
269             mOverlay.reset();
270         }
271         updateThumbnailPaintFilter();
272     }
273 
274     /**
275      * Sets the alpha of the dim layer on top of this view.
276      * <p>
277      * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be the
278      * extracted background color.
279      */
setDimAlpha(float dimAlpha)280     public void setDimAlpha(float dimAlpha) {
281         mDimAlpha = dimAlpha;
282         updateThumbnailPaintFilter();
283     }
284 
285     /**
286      * Sets the alpha of the splash view.
287      */
setSplashAlpha(float splashAlpha)288     public void setSplashAlpha(float splashAlpha) {
289         mSplashAlpha = (int) (Utilities.boundToRange(splashAlpha, 0f, 1f) * 255);
290         if (mSplashViewDrawable != null) {
291             mSplashViewDrawable.setAlpha(mSplashAlpha);
292         }
293         mSplashBackgroundPaint.setAlpha(mSplashAlpha);
294         invalidate();
295     }
296 
getDimAlpha()297     public float getDimAlpha() {
298         return mDimAlpha;
299     }
300 
301     /**
302      * Get the scaled insets that are being used to draw the task view. This is a subsection of
303      * the full snapshot.
304      *
305      * @return the insets in snapshot bitmap coordinates.
306      */
307     @RequiresApi(api = Build.VERSION_CODES.Q)
getScaledInsets()308     public Insets getScaledInsets() {
309         if (mThumbnailData == null) {
310             return Insets.NONE;
311         }
312 
313         RectF bitmapRect = new RectF(
314                 0,
315                 0,
316                 mThumbnailData.getThumbnail().getWidth(),
317                 mThumbnailData.getThumbnail().getHeight());
318         RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
319 
320         // The position helper matrix tells us how to transform the bitmap to fit the view, the
321         // inverse tells us where the view would be in the bitmaps coordinates. The insets are the
322         // difference between the bitmap bounds and the projected view bounds.
323         Matrix boundsToBitmapSpace = new Matrix();
324         mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace);
325         RectF boundsInBitmapSpace = new RectF();
326         boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
327 
328         DeviceProfile dp = mContainer.getDeviceProfile();
329         int bottomInset = dp.isTablet
330                 ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0;
331         return Insets.of(0, 0, 0, bottomInset);
332     }
333 
334 
335     @SystemUiControllerFlags
getSysUiStatusNavFlags()336     public int getSysUiStatusNavFlags() {
337         if (mThumbnailData != null) {
338             int flags = 0;
339             flags |= (mThumbnailData.appearance & APPEARANCE_LIGHT_STATUS_BARS) != 0
340                     ? SystemUiController.FLAG_LIGHT_STATUS
341                     : SystemUiController.FLAG_DARK_STATUS;
342             flags |= (mThumbnailData.appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0
343                     ? SystemUiController.FLAG_LIGHT_NAV
344                     : SystemUiController.FLAG_DARK_NAV;
345             return flags;
346         }
347         return 0;
348     }
349 
350     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)351     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
352         super.onLayout(changed, left, top, right, bottom);
353         updateSplashView(mSplashViewDrawable);
354     }
355 
356     @Override
onDraw(Canvas canvas)357     protected void onDraw(Canvas canvas) {
358         canvas.save();
359         // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
360         drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(),
361                 mFullscreenParams.getCurrentDrawnCornerRadius());
362         canvas.restore();
363     }
364 
getPreviewPositionHelper()365     public PreviewPositionHelper getPreviewPositionHelper() {
366         return mPreviewPositionHelper;
367     }
368 
setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams)369     public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
370         mFullscreenParams = fullscreenParams;
371         invalidate();
372     }
373 
drawOnCanvas(Canvas canvas, float x, float y, float width, float height, float cornerRadius)374     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
375             float cornerRadius) {
376         if (mTask != null && getTaskView().isRunningTask()
377                 && !getTaskView().getShouldShowScreenshot()) {
378             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
379             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
380                     mDimmingPaintAfterClearing);
381             return;
382         }
383 
384         // Always draw the background since the snapshots might be translucent or partially empty
385         // (For example, tasks been reparented out of dismissing split root when drag-to-dismiss
386         // split screen).
387         canvas.drawRoundRect(x, y + 1, width, height - 1, cornerRadius,
388                 cornerRadius, mBackgroundPaint);
389 
390         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
391                 || mThumbnailData == null;
392         if (drawBackgroundOnly) {
393             return;
394         }
395 
396         canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
397 
398         // Draw splash above thumbnail to hide inconsistencies in rotation and aspect ratios.
399         if (shouldShowSplashView()) {
400             float cornerRadiusX = cornerRadius;
401             float cornerRadiusY = cornerRadius;
402             if (mShowSplashForSplitSelection) {
403                 cornerRadiusX = cornerRadius / getScaleX();
404                 cornerRadiusY = cornerRadius / getScaleY();
405             }
406 
407             // Always draw background for hiding inconsistencies, even if splash view is not yet
408             // loaded (which can happen as task icons are loaded asynchronously in the background)
409             canvas.drawRoundRect(x, y, width + 1, height + 1, cornerRadiusX,
410                     cornerRadiusY, mSplashBackgroundPaint);
411             if (mSplashView != null) {
412                 mSplashView.layout((int) x, (int) (y + 1), (int) width, (int) height - 1);
413                 mSplashView.draw(canvas);
414             }
415         }
416     }
417 
418     /** See {@link #SPLIT_SELECT_TRANSLATE_X} */
applySplitSelectTranslateX(float splitSelectTranslateX)419     protected void applySplitSelectTranslateX(float splitSelectTranslateX) {
420         mSplitSelectTranslateX = splitSelectTranslateX;
421         applyTranslateX();
422     }
423 
424     /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */
applySplitSelectTranslateY(float splitSelectTranslateY)425     protected void applySplitSelectTranslateY(float splitSelectTranslateY) {
426         mSplitSelectTranslateY = splitSelectTranslateY;
427         applyTranslateY();
428     }
429 
applyTranslateX()430     private void applyTranslateX() {
431         setTranslationX(mSplitSelectTranslateX);
432     }
433 
applyTranslateY()434     private void applyTranslateY() {
435         setTranslationY(mSplitSelectTranslateY);
436     }
437 
resetViewTransforms()438     protected void resetViewTransforms() {
439         mSplitSelectTranslateX = 0;
440         mSplitSelectTranslateY = 0;
441     }
442 
getTaskView()443     public TaskView getTaskView() {
444         return (TaskView) getParent();
445     }
446 
setOverlayEnabled(boolean overlayEnabled)447     public void setOverlayEnabled(boolean overlayEnabled) {
448         if (mOverlayEnabled != overlayEnabled) {
449             mOverlayEnabled = overlayEnabled;
450 
451             refreshOverlay();
452         }
453     }
454 
455     /**
456      * Determine if the splash should be shown over top of the thumbnail.
457      *
458      * <p>We want to show the splash if the aspect ratio or rotation of the thumbnail would be
459      * different from the task.
460      */
shouldShowSplashView()461     public boolean shouldShowSplashView() {
462         return isThumbnailAspectRatioDifferentFromThumbnailData()
463                 || isThumbnailRotationDifferentFromTask()
464                 || mShowSplashForSplitSelection;
465     }
466 
setShowSplashForSplitSelection(boolean showSplashForSplitSelection)467     public void setShowSplashForSplitSelection(boolean showSplashForSplitSelection) {
468         mShowSplashForSplitSelection = showSplashForSplitSelection;
469     }
470 
refreshSplashView()471     protected void refreshSplashView() {
472         if (mTask != null) {
473             updateSplashView(mTask.icon);
474             invalidate();
475         }
476     }
477 
updateSplashView(Drawable icon)478     private void updateSplashView(Drawable icon) {
479         if (icon == null || icon.getConstantState() == null) {
480             mSplashViewDrawable = null;
481             mSplashView = null;
482             return;
483         }
484         mSplashViewDrawable = icon.getConstantState().newDrawable().mutate();
485         mSplashViewDrawable.setAlpha(mSplashAlpha);
486         ImageView imageView = mSplashView == null ? new ImageView(getContext()) : mSplashView;
487         imageView.setImageDrawable(mSplashViewDrawable);
488 
489         imageView.setScaleType(ImageView.ScaleType.MATRIX);
490         Matrix matrix = new Matrix();
491         float drawableWidth = mSplashViewDrawable.getIntrinsicWidth();
492         float drawableHeight = mSplashViewDrawable.getIntrinsicHeight();
493         float viewWidth = getMeasuredWidth();
494         float viewCenterX = viewWidth / 2f;
495         float viewHeight = getMeasuredHeight();
496         float viewCenterY = viewHeight / 2f;
497         float centeredDrawableLeft = (viewWidth - drawableWidth) / 2f;
498         float centeredDrawableTop = (viewHeight - drawableHeight) / 2f;
499         float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale();
500         float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null
501                 ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen();
502         float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX());
503         float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY());
504 
505         // Center the image in the view.
506         matrix.setTranslate(centeredDrawableLeft, centeredDrawableTop);
507         // Apply scale transformation after translation, pivoting around center of view.
508         matrix.postScale(scaleX, scaleY, viewCenterX, viewCenterY);
509 
510         imageView.setImageMatrix(matrix);
511         mSplashView = imageView;
512     }
513 
isThumbnailAspectRatioDifferentFromThumbnailData()514     private boolean isThumbnailAspectRatioDifferentFromThumbnailData() {
515         if (mThumbnailData == null || mThumbnailData.getThumbnail() == null) {
516             return false;
517         }
518 
519         float thumbnailViewAspect = getWidth() / (float) getHeight();
520         float thumbnailDataAspect = mThumbnailData.getThumbnail().getWidth()
521                 / (float) mThumbnailData.getThumbnail().getHeight();
522 
523         return isRelativePercentDifferenceGreaterThan(thumbnailViewAspect,
524                 thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
525     }
526 
isThumbnailRotationDifferentFromTask()527     private boolean isThumbnailRotationDifferentFromTask() {
528         RecentsView recents = getTaskView().getRecentsView();
529         if (recents == null || mThumbnailData == null) {
530             return false;
531         }
532 
533         if (recents.getPagedOrientationHandler() == RecentsPagedOrientationHandler.PORTRAIT) {
534             int currentRotation = recents.getPagedViewOrientedState().getRecentsActivityRotation();
535             return (currentRotation - mThumbnailData.rotation) % 2 != 0;
536         } else {
537             return recents.getPagedOrientationHandler().getRotation() != mThumbnailData.rotation;
538         }
539     }
540 
541     /**
542      * Potentially re-init the task overlay. Be cautious when calling this as the overlay may
543      * do processing on initialization.
544      */
refreshOverlay()545     private void refreshOverlay() {
546         if (mOverlayEnabled) {
547             mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
548                     mPreviewPositionHelper.isOrientationChanged());
549         } else {
550             mOverlay.reset();
551         }
552     }
553 
updateThumbnailPaintFilter()554     private void updateThumbnailPaintFilter() {
555         ColorFilter filter = getColorFilter(mDimAlpha);
556         mBackgroundPaint.setColorFilter(filter);
557         int alpha = (int) (mDimAlpha * 255);
558         mDimmingPaintAfterClearing.setAlpha(alpha);
559         if (mBitmapShader != null) {
560             mPaint.setColorFilter(filter);
561         } else {
562             mPaint.setColorFilter(null);
563             mPaint.setColor(ColorUtils.blendARGB(Color.BLACK, mDimColor, alpha));
564         }
565         invalidate();
566     }
567 
updateThumbnailMatrix()568     private void updateThumbnailMatrix() {
569         DeviceProfile dp = mContainer.getDeviceProfile();
570         mPreviewPositionHelper.setOrientationChanged(false);
571         if (mBitmapShader != null && mThumbnailData != null) {
572             mPreviewRect.set(0, 0, mThumbnailData.getThumbnail().getWidth(),
573                     mThumbnailData.getThumbnail().getHeight());
574             int currentRotation = getTaskView().getOrientedState().getRecentsActivityRotation();
575             boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
576             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
577                     getMeasuredWidth(), getMeasuredHeight(), dp.isTablet, currentRotation, isRtl);
578 
579             mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix());
580             mPaint.setShader(mBitmapShader);
581         }
582         getTaskView().updateCurrentFullscreenParams();
583         invalidate();
584     }
585 
586     @Override
onSizeChanged(int w, int h, int oldw, int oldh)587     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
588         super.onSizeChanged(w, h, oldw, oldh);
589         updateThumbnailMatrix();
590 
591         refreshOverlay();
592     }
593 
getColorFilter(float dimAmount)594     private ColorFilter getColorFilter(float dimAmount) {
595         return Utilities.makeColorTintingColorFilter(mDimColor, dimAmount);
596     }
597 
598     /**
599      * Returns current thumbnail or null if none is set.
600      */
601     @Nullable
getThumbnail()602     public Bitmap getThumbnail() {
603         if (mThumbnailData == null) {
604             return null;
605         }
606         return mThumbnailData.getThumbnail();
607     }
608 
609     /**
610      * Returns whether the snapshot is real. If the device is locked for the user of the task,
611      * the snapshot used will be an app-theme generated snapshot instead of a real snapshot.
612      */
isRealSnapshot()613     public boolean isRealSnapshot() {
614         if (mThumbnailData == null) {
615             return false;
616         }
617         return mThumbnailData.isRealSnapshot && !mTask.isLocked;
618     }
619 
620     @Override
onRecycle()621     public void onRecycle() {
622         // Do nothing
623     }
624 }
625