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