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