• 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.launcher3.Utilities.comp;
23 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
24 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
25 
26 import android.content.Context;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapShader;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.ColorFilter;
32 import android.graphics.Insets;
33 import android.graphics.Matrix;
34 import android.graphics.Paint;
35 import android.graphics.PorterDuff;
36 import android.graphics.PorterDuffXfermode;
37 import android.graphics.Rect;
38 import android.graphics.RectF;
39 import android.graphics.Shader;
40 import android.os.Build;
41 import android.util.AttributeSet;
42 import android.util.FloatProperty;
43 import android.util.Property;
44 import android.view.Surface;
45 import android.view.View;
46 
47 import androidx.annotation.RequiresApi;
48 import androidx.core.graphics.ColorUtils;
49 
50 import com.android.launcher3.BaseActivity;
51 import com.android.launcher3.DeviceProfile;
52 import com.android.launcher3.Utilities;
53 import com.android.launcher3.config.FeatureFlags;
54 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
55 import com.android.launcher3.util.MainThreadInitializedObject;
56 import com.android.launcher3.util.SystemUiController;
57 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
58 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
59 import com.android.systemui.plugins.OverviewScreenshotActions;
60 import com.android.systemui.plugins.PluginListener;
61 import com.android.systemui.shared.recents.model.Task;
62 import com.android.systemui.shared.recents.model.ThumbnailData;
63 
64 /**
65  * A task in the Recents view.
66  */
67 public class TaskThumbnailView extends View implements PluginListener<OverviewScreenshotActions> {
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     private final BaseActivity mActivity;
85     private TaskOverlay mOverlay;
86     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
87     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
88     private final Paint mClearPaint = new Paint();
89     private final Paint mDimmingPaintAfterClearing = new Paint();
90     private final int mDimColor;
91 
92     // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
93     private final Rect mPreviewRect = new Rect();
94     private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper();
95     private TaskView.FullscreenDrawParams mFullscreenParams;
96 
97     private Task mTask;
98     private ThumbnailData mThumbnailData;
99     protected BitmapShader mBitmapShader;
100 
101     /** How much this thumbnail is dimmed, 0 not dimmed at all, 1 totally dimmed. */
102     private float mDimAlpha = 0f;
103 
104     private boolean mOverlayEnabled;
105     private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
106 
TaskThumbnailView(Context context)107     public TaskThumbnailView(Context context) {
108         this(context, null);
109     }
110 
TaskThumbnailView(Context context, AttributeSet attrs)111     public TaskThumbnailView(Context context, AttributeSet attrs) {
112         this(context, attrs, 0);
113     }
114 
TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr)115     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
116         super(context, attrs, defStyleAttr);
117         mPaint.setFilterBitmap(true);
118         mBackgroundPaint.setColor(Color.WHITE);
119         mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
120         mActivity = BaseActivity.fromContext(context);
121         // Initialize with placeholder value. It is overridden later by TaskView
122         mFullscreenParams = TEMP_PARAMS.get(context);
123 
124         mDimColor = RecentsView.getForegroundScrimDimColor(context);
125         mDimmingPaintAfterClearing.setColor(mDimColor);
126     }
127 
128     /**
129      * Updates the thumbnail to draw the provided task
130      * @param task
131      */
bind(Task task)132     public void bind(Task task) {
133         getTaskOverlay().reset();
134         mTask = task;
135         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
136         mPaint.setColor(color);
137         mBackgroundPaint.setColor(color);
138     }
139 
140     /**
141      * Updates the thumbnail.
142      * @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
143      *                   In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)}
144      *                   version with {@code refreshNow} is true. The only exception is
145      *                   in the live tile case that we grab a screenshot when user enters Overview
146      *                   upon swipe up so that a usable screenshot is accessible immediately when
147      *                   recents animation needs to be finished / cancelled.
148      */
setThumbnail(Task task, ThumbnailData thumbnailData, boolean refreshNow)149     public void setThumbnail(Task task, ThumbnailData thumbnailData, boolean refreshNow) {
150         mTask = task;
151         mThumbnailData =
152                 (thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
153         if (refreshNow) {
154             refresh();
155         }
156     }
157 
158     /** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */
setThumbnail(Task task, ThumbnailData thumbnailData)159     public void setThumbnail(Task task, ThumbnailData thumbnailData) {
160         setThumbnail(task, thumbnailData, true /* refreshNow */);
161     }
162 
163     /** Updates the shader, paint, matrix to redraw. */
refresh()164     public void refresh() {
165         if (mThumbnailData != null && mThumbnailData.thumbnail != null) {
166             Bitmap bm = mThumbnailData.thumbnail;
167             bm.prepareToDraw();
168             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
169             mPaint.setShader(mBitmapShader);
170             updateThumbnailMatrix();
171         } else {
172             mBitmapShader = null;
173             mThumbnailData = null;
174             mPaint.setShader(null);
175             getTaskOverlay().reset();
176         }
177         if (mOverviewScreenshotActionsPlugin != null) {
178             mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
179         }
180         updateThumbnailPaintFilter();
181     }
182 
183     /**
184      * Sets the alpha of the dim layer on top of this view.
185      * <p>
186      * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be the
187      * extracted background color.
188      *
189      */
setDimAlpha(float dimAlpha)190     public void setDimAlpha(float dimAlpha) {
191         mDimAlpha = dimAlpha;
192         updateThumbnailPaintFilter();
193     }
194 
getTaskOverlay()195     public TaskOverlay getTaskOverlay() {
196         if (mOverlay == null) {
197             mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this);
198         }
199         return mOverlay;
200     }
201 
getDimAlpha()202     public float getDimAlpha() {
203         return mDimAlpha;
204     }
205 
206     /**
207      * Get the scaled insets that are being used to draw the task view. This is a subsection of
208      * the full snapshot.
209      * @return the insets in snapshot bitmap coordinates.
210      */
211     @RequiresApi(api = Build.VERSION_CODES.Q)
getScaledInsets()212     public Insets getScaledInsets() {
213         if (mThumbnailData == null) {
214             return Insets.NONE;
215         }
216 
217         if (!TaskView.CLIP_STATUS_AND_NAV_BARS) {
218             return Insets.NONE;
219         }
220 
221         RectF bitmapRect = new RectF(
222                 0, 0,
223                 mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
224         RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
225 
226         // The position helper matrix tells us how to transform the bitmap to fit the view, the
227         // inverse tells us where the view would be in the bitmaps coordinates. The insets are the
228         // difference between the bitmap bounds and the projected view bounds.
229         Matrix boundsToBitmapSpace = new Matrix();
230         mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace);
231         RectF boundsInBitmapSpace = new RectF();
232         boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
233 
234         return Insets.of(
235             Math.round(boundsInBitmapSpace.left),
236             Math.round(boundsInBitmapSpace.top),
237             Math.round(bitmapRect.right - boundsInBitmapSpace.right),
238             Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom));
239     }
240 
241 
getSysUiStatusNavFlags()242     public int getSysUiStatusNavFlags() {
243         if (mThumbnailData != null) {
244             int flags = 0;
245             flags |= (mThumbnailData.appearance & APPEARANCE_LIGHT_STATUS_BARS) != 0
246                     ? SystemUiController.FLAG_LIGHT_STATUS
247                     : SystemUiController.FLAG_DARK_STATUS;
248             flags |= (mThumbnailData.appearance & APPEARANCE_LIGHT_NAVIGATION_BARS) != 0
249                     ? SystemUiController.FLAG_LIGHT_NAV
250                     : SystemUiController.FLAG_DARK_NAV;
251             return flags;
252         }
253         return 0;
254     }
255 
256     @Override
onDraw(Canvas canvas)257     protected void onDraw(Canvas canvas) {
258         RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets;
259         canvas.save();
260         canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale);
261         canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top);
262         // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
263         drawOnCanvas(canvas,
264                 -currentDrawnInsets.left,
265                 -currentDrawnInsets.top,
266                 getMeasuredWidth() + currentDrawnInsets.right,
267                 getMeasuredHeight() + currentDrawnInsets.bottom,
268                 mFullscreenParams.mCurrentDrawnCornerRadius);
269         canvas.restore();
270     }
271 
272     @Override
onPluginConnected(OverviewScreenshotActions overviewScreenshotActions, Context context)273     public void onPluginConnected(OverviewScreenshotActions overviewScreenshotActions,
274             Context context) {
275         mOverviewScreenshotActionsPlugin = overviewScreenshotActions;
276         mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
277     }
278 
279     @Override
onPluginDisconnected(OverviewScreenshotActions plugin)280     public void onPluginDisconnected(OverviewScreenshotActions plugin) {
281         if (mOverviewScreenshotActionsPlugin != null) {
282             mOverviewScreenshotActionsPlugin = null;
283         }
284     }
285 
286     @Override
onAttachedToWindow()287     protected void onAttachedToWindow() {
288         super.onAttachedToWindow();
289         PluginManagerWrapper.INSTANCE.get(getContext())
290             .addPluginListener(this, OverviewScreenshotActions.class);
291     }
292 
293     @Override
onDetachedFromWindow()294     protected void onDetachedFromWindow() {
295         super.onDetachedFromWindow();
296         PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
297     }
298 
getPreviewPositionHelper()299     public PreviewPositionHelper getPreviewPositionHelper() {
300         return mPreviewPositionHelper;
301     }
302 
setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams)303     public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
304         mFullscreenParams = fullscreenParams;
305         invalidate();
306     }
307 
drawOnCanvas(Canvas canvas, float x, float y, float width, float height, float cornerRadius)308     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
309             float cornerRadius) {
310         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
311             if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
312                 // TODO(b/189265196): Temporary fix to align the surface with the cutout perfectly.
313                 // Round up only when the live tile task is displayed in Overview.
314                 float rounding = comp(mFullscreenParams.mFullscreenProgress);
315                 float left = x + rounding / 2;
316                 float top = y + rounding / 2;
317                 float right = width - rounding;
318                 float bottom = height - rounding;
319 
320                 canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius,
321                         mClearPaint);
322                 canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius,
323                         mDimmingPaintAfterClearing);
324                 return;
325             }
326         }
327 
328         // Draw the background in all cases, except when the thumbnail data is opaque
329         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
330                 || mThumbnailData == null;
331         if (drawBackgroundOnly || mThumbnailData.isTranslucent) {
332             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
333             if (drawBackgroundOnly) {
334                 return;
335             }
336         }
337 
338         canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
339     }
340 
getTaskView()341     public TaskView getTaskView() {
342         return (TaskView) getParent();
343     }
344 
setOverlayEnabled(boolean overlayEnabled)345     public void setOverlayEnabled(boolean overlayEnabled) {
346         if (mOverlayEnabled != overlayEnabled) {
347             mOverlayEnabled = overlayEnabled;
348 
349             refreshOverlay();
350         }
351     }
352 
353     /**
354      * Potentially re-init the task overlay. Be cautious when calling this as the overlay may
355      * do processing on initialization.
356      */
refreshOverlay()357     private void refreshOverlay() {
358         if (mOverlayEnabled) {
359             getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
360                     mPreviewPositionHelper.mIsOrientationChanged);
361         } else {
362             getTaskOverlay().reset();
363         }
364     }
365 
updateThumbnailPaintFilter()366     private void updateThumbnailPaintFilter() {
367         ColorFilter filter = getColorFilter(mDimAlpha);
368         mBackgroundPaint.setColorFilter(filter);
369         int alpha = (int) (mDimAlpha * 255);
370         mDimmingPaintAfterClearing.setAlpha(alpha);
371         if (mBitmapShader != null) {
372             mPaint.setColorFilter(filter);
373         } else {
374             mPaint.setColorFilter(null);
375             mPaint.setColor(ColorUtils.blendARGB(Color.BLACK, mDimColor, alpha));
376         }
377         invalidate();
378     }
379 
updateThumbnailMatrix()380     private void updateThumbnailMatrix() {
381         mPreviewPositionHelper.mIsOrientationChanged = false;
382         if (mBitmapShader != null && mThumbnailData != null) {
383             mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
384                     mThumbnailData.thumbnail.getHeight());
385             int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
386                     .getRecentsActivityRotation();
387             boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
388             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
389                     getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
390                     currentRotation, isRtl);
391 
392             mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
393             mPaint.setShader(mBitmapShader);
394         }
395         getTaskView().updateCurrentFullscreenParams(mPreviewPositionHelper);
396         invalidate();
397     }
398 
399     @Override
onSizeChanged(int w, int h, int oldw, int oldh)400     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
401         super.onSizeChanged(w, h, oldw, oldh);
402         updateThumbnailMatrix();
403 
404         refreshOverlay();
405     }
406 
getColorFilter(float dimAmount)407     private ColorFilter getColorFilter(float dimAmount) {
408         return Utilities.makeColorTintingColorFilter(mDimColor, dimAmount);
409     }
410 
getThumbnail()411     public Bitmap getThumbnail() {
412         if (mThumbnailData == null) {
413             return null;
414         }
415         return mThumbnailData.thumbnail;
416     }
417 
418     /**
419      * Returns whether the snapshot is real. If the device is locked for the user of the task,
420      * the snapshot used will be an app-theme generated snapshot instead of a real snapshot.
421      */
isRealSnapshot()422     public boolean isRealSnapshot() {
423         if (mThumbnailData == null) {
424             return false;
425         }
426         return mThumbnailData.isRealSnapshot && !mTask.isLocked;
427     }
428 
429     /**
430      * Utility class to position the thumbnail in the TaskView
431      */
432     public static class PreviewPositionHelper {
433 
434         // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
435         private final RectF mClippedInsets = new RectF();
436         private final Matrix mMatrix = new Matrix();
437         private boolean mIsOrientationChanged;
438 
getMatrix()439         public Matrix getMatrix() {
440             return mMatrix;
441         }
442 
443         /**
444          * Updates the matrix based on the provided parameters
445          */
updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData, int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation, boolean isRtl)446         public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
447                 int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation,
448                 boolean isRtl) {
449             boolean isRotated = false;
450             boolean isOrientationDifferent;
451 
452             int thumbnailRotation = thumbnailData.rotation;
453             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
454             RectF thumbnailClipHint = TaskView.CLIP_STATUS_AND_NAV_BARS
455                     ? new RectF(thumbnailData.insets) : new RectF();
456 
457             float scale = thumbnailData.scale;
458             final float thumbnailScale;
459 
460             // Landscape vs portrait change.
461             // Note: Disable rotation in grid layout.
462             boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
463                     && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN
464                     && !(dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
465             isOrientationDifferent = isOrientationChange(deltaRotate)
466                     && windowingModeSupportsRotation;
467             if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
468                 // If we haven't measured , skip the thumbnail drawing and only draw the background
469                 // color
470                 thumbnailScale = 0f;
471             } else {
472                 // Rotate the screenshot if not in multi-window mode
473                 isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
474 
475                 float surfaceWidth = thumbnailBounds.width() / scale;
476                 float surfaceHeight = thumbnailBounds.height() / scale;
477                 float availableWidth = surfaceWidth
478                         - (thumbnailClipHint.left + thumbnailClipHint.right);
479                 float availableHeight = surfaceHeight
480                         - (thumbnailClipHint.top + thumbnailClipHint.bottom);
481 
482                 if (isRotated) {
483                     float canvasAspect = canvasWidth / (float) canvasHeight;
484                     float availableAspect = availableHeight / availableWidth;
485                     // Do not rotate thumbnail if it would not improve fit
486                     if (Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
487                             availableAspect, 0.1f)) {
488                         isRotated = false;
489                         isOrientationDifferent = false;
490                     }
491                 }
492 
493                 final float targetW, targetH;
494                 if (isOrientationDifferent) {
495                     targetW = canvasHeight;
496                     targetH = canvasWidth;
497                 } else {
498                     targetW = canvasWidth;
499                     targetH = canvasHeight;
500                 }
501                 float canvasAspect = targetW / targetH;
502 
503                 // Update the clipHint such that
504                 //   > the final clipped position has same aspect ratio as requested by canvas
505                 //   > the clipped region is within the task insets if possible
506                 //   > the clipped region is not scaled up when drawing. If that is not possible
507                 //     while staying within the taskInsets, move outside the insets.
508                 float croppedWidth = availableWidth;
509                 if (croppedWidth < targetW) {
510                     croppedWidth = Math.min(targetW, surfaceWidth);
511                 }
512 
513                 float croppedHeight = croppedWidth / canvasAspect;
514                 if (croppedHeight > availableHeight) {
515                     croppedHeight = availableHeight;
516                     if (croppedHeight < targetH) {
517                         croppedHeight = Math.min(targetH, surfaceHeight);
518                     }
519                     croppedWidth = croppedHeight * canvasAspect;
520 
521                     // One last check in case the task aspect radio messed up something
522                     if (croppedWidth > surfaceWidth) {
523                         croppedWidth = surfaceWidth;
524                         croppedHeight = croppedWidth / canvasAspect;
525                     }
526                 }
527 
528                 // Update the clip hints. Align to 0,0, crop the remaining.
529                 if (isRtl) {
530                     thumbnailClipHint.left += availableWidth - croppedWidth;
531                     if (thumbnailClipHint.right < 0) {
532                         thumbnailClipHint.left += thumbnailClipHint.right;
533                         thumbnailClipHint.right = 0;
534                     }
535                 } else {
536                     thumbnailClipHint.right += availableWidth - croppedWidth;
537                     if (thumbnailClipHint.left < 0) {
538                         thumbnailClipHint.right += thumbnailClipHint.left;
539                         thumbnailClipHint.left = 0;
540                     }
541                 }
542                 thumbnailClipHint.bottom += availableHeight - croppedHeight;
543                 if (thumbnailClipHint.top < 0) {
544                     thumbnailClipHint.bottom += thumbnailClipHint.top;
545                     thumbnailClipHint.top = 0;
546                 } else if (thumbnailClipHint.bottom < 0) {
547                     thumbnailClipHint.top += thumbnailClipHint.bottom;
548                     thumbnailClipHint.bottom = 0;
549                 }
550 
551                 thumbnailScale = targetW / (croppedWidth * scale);
552             }
553 
554             Rect splitScreenInsets = dp.getInsets();
555             if (!isRotated) {
556                 // No Rotation
557                 if (dp.isMultiWindowMode) {
558                     mClippedInsets.offsetTo(splitScreenInsets.left * scale,
559                             splitScreenInsets.top * scale);
560                 } else {
561                     mClippedInsets.offsetTo(thumbnailClipHint.left * scale,
562                             thumbnailClipHint.top * scale);
563                 }
564                 mMatrix.setTranslate(
565                         -thumbnailClipHint.left * scale,
566                         -thumbnailClipHint.top * scale);
567             } else {
568                 setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds);
569             }
570 
571             final float widthWithInsets;
572             final float heightWithInsets;
573             if (isOrientationDifferent) {
574                 widthWithInsets = thumbnailBounds.height() * thumbnailScale;
575                 heightWithInsets = thumbnailBounds.width() * thumbnailScale;
576             } else {
577                 widthWithInsets = thumbnailBounds.width() * thumbnailScale;
578                 heightWithInsets = thumbnailBounds.height() * thumbnailScale;
579             }
580             mClippedInsets.left *= thumbnailScale;
581             mClippedInsets.top *= thumbnailScale;
582 
583             if (dp.isMultiWindowMode) {
584                 mClippedInsets.right = splitScreenInsets.right * scale * thumbnailScale;
585                 mClippedInsets.bottom = splitScreenInsets.bottom * scale * thumbnailScale;
586             } else {
587                 mClippedInsets.right = Math.max(0,
588                         widthWithInsets - mClippedInsets.left - canvasWidth);
589                 mClippedInsets.bottom = Math.max(0,
590                         heightWithInsets - mClippedInsets.top - canvasHeight);
591             }
592 
593             mMatrix.postScale(thumbnailScale, thumbnailScale);
594             mIsOrientationChanged = isOrientationDifferent;
595         }
596 
getRotationDelta(int oldRotation, int newRotation)597         private int getRotationDelta(int oldRotation, int newRotation) {
598             int delta = newRotation - oldRotation;
599             if (delta < 0) delta += 4;
600             return delta;
601         }
602 
603         /**
604          * @param deltaRotation the number of 90 degree turns from the current orientation
605          * @return {@code true} if the change in rotation results in a shift from landscape to
606          * portrait or vice versa, {@code false} otherwise
607          */
isOrientationChange(int deltaRotation)608         private boolean isOrientationChange(int deltaRotation) {
609             return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
610         }
611 
setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale, Rect thumbnailPosition)612         private void setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale,
613                 Rect thumbnailPosition) {
614             float newLeftInset = 0;
615             float newTopInset = 0;
616             float translateX = 0;
617             float translateY = 0;
618 
619             mMatrix.setRotate(90 * deltaRotate);
620             switch (deltaRotate) { /* Counter-clockwise */
621                 case Surface.ROTATION_90:
622                     newLeftInset = thumbnailInsets.bottom;
623                     newTopInset = thumbnailInsets.left;
624                     translateX = thumbnailPosition.height();
625                     break;
626                 case Surface.ROTATION_270:
627                     newLeftInset = thumbnailInsets.top;
628                     newTopInset = thumbnailInsets.right;
629                     translateY = thumbnailPosition.width();
630                     break;
631                 case Surface.ROTATION_180:
632                     newLeftInset = -thumbnailInsets.top;
633                     newTopInset = -thumbnailInsets.left;
634                     translateX = thumbnailPosition.width();
635                     translateY = thumbnailPosition.height();
636                     break;
637             }
638             mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
639             mMatrix.postTranslate(translateX - mClippedInsets.left,
640                     translateY - mClippedInsets.top);
641         }
642 
643         /**
644          * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
645          */
getInsetsToDrawInFullscreen()646         public RectF getInsetsToDrawInFullscreen() {
647             return mClippedInsets;
648         }
649     }
650 }
651