• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quickstep.views;
18 
19 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
20 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
21 
22 import android.content.Context;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapShader;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.ColorFilter;
28 import android.graphics.ColorMatrix;
29 import android.graphics.ColorMatrixColorFilter;
30 import android.graphics.Insets;
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.RectF;
37 import android.graphics.Shader;
38 import android.os.Build;
39 import android.util.AttributeSet;
40 import android.util.FloatProperty;
41 import android.util.Property;
42 import android.view.Surface;
43 import android.view.View;
44 
45 import androidx.annotation.RequiresApi;
46 
47 import com.android.launcher3.BaseActivity;
48 import com.android.launcher3.DeviceProfile;
49 import com.android.launcher3.R;
50 import com.android.launcher3.Utilities;
51 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
52 import com.android.launcher3.util.MainThreadInitializedObject;
53 import com.android.launcher3.util.SystemUiController;
54 import com.android.launcher3.util.Themes;
55 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
56 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
57 import com.android.systemui.plugins.OverviewScreenshotActions;
58 import com.android.systemui.plugins.PluginListener;
59 import com.android.systemui.shared.recents.model.Task;
60 import com.android.systemui.shared.recents.model.ThumbnailData;
61 
62 /**
63  * A task in the Recents view.
64  */
65 public class TaskThumbnailView extends View implements PluginListener<OverviewScreenshotActions> {
66 
67     private static final ColorMatrix COLOR_MATRIX = new ColorMatrix();
68     private static final ColorMatrix SATURATION_COLOR_MATRIX = new ColorMatrix();
69 
70     private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
71             new MainThreadInitializedObject<>(FullscreenDrawParams::new);
72 
73     public static final Property<TaskThumbnailView, Float> DIM_ALPHA =
74             new FloatProperty<TaskThumbnailView>("dimAlpha") {
75                 @Override
76                 public void setValue(TaskThumbnailView thumbnail, float dimAlpha) {
77                     thumbnail.setDimAlpha(dimAlpha);
78                 }
79 
80                 @Override
81                 public Float get(TaskThumbnailView thumbnailView) {
82                     return thumbnailView.mDimAlpha;
83                 }
84             };
85 
86     private final BaseActivity mActivity;
87     private TaskOverlay mOverlay;
88     private final boolean mIsDarkTextTheme;
89     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
90     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
91     private final Paint mClearPaint = new Paint();
92     private final Paint mDimmingPaintAfterClearing = new Paint();
93 
94     // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
95     private final Rect mPreviewRect = new Rect();
96     private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper();
97     private TaskView.FullscreenDrawParams mFullscreenParams;
98 
99     private Task mTask;
100     private ThumbnailData mThumbnailData;
101     protected BitmapShader mBitmapShader;
102 
103     private float mDimAlpha = 1f;
104     private float mDimAlphaMultiplier = 1f;
105     private float mSaturation = 1f;
106 
107     private boolean mOverlayEnabled;
108     private OverviewScreenshotActions mOverviewScreenshotActionsPlugin;
109 
TaskThumbnailView(Context context)110     public TaskThumbnailView(Context context) {
111         this(context, null);
112     }
113 
TaskThumbnailView(Context context, AttributeSet attrs)114     public TaskThumbnailView(Context context, AttributeSet attrs) {
115         this(context, attrs, 0);
116     }
117 
TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr)118     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
119         super(context, attrs, defStyleAttr);
120         mPaint.setFilterBitmap(true);
121         mBackgroundPaint.setColor(Color.WHITE);
122         mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
123         mDimmingPaintAfterClearing.setColor(Color.BLACK);
124         mActivity = BaseActivity.fromContext(context);
125         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
126         // Initialize with dummy value. It is overridden later by TaskView
127         mFullscreenParams = TEMP_PARAMS.get(context);
128     }
129 
130     /**
131      * Updates the thumbnail to draw the provided task
132      * @param task
133      */
bind(Task task)134     public void bind(Task task) {
135         getTaskOverlay().reset();
136         mTask = task;
137         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
138         mPaint.setColor(color);
139         mBackgroundPaint.setColor(color);
140     }
141 
142     /**
143      * Updates the thumbnail.
144      * @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
145      *                   In most cases, we use the {@link #setThumbnail(Task, ThumbnailData)}
146      *                   version with {@code refreshNow} is true. The only exception is
147      *                   in the live tile case that we grab a screenshot when user enters Overview
148      *                   upon swipe up so that a usable screenshot is accessible immediately when
149      *                   recents animation needs to be finished / cancelled.
150      */
setThumbnail(Task task, ThumbnailData thumbnailData, boolean refreshNow)151     public void setThumbnail(Task task, ThumbnailData thumbnailData, boolean refreshNow) {
152         mTask = task;
153         mThumbnailData =
154                 (thumbnailData != null && thumbnailData.thumbnail != null) ? thumbnailData : null;
155         if (refreshNow) {
156             refresh();
157         }
158     }
159 
160     /** See {@link #setThumbnail(Task, ThumbnailData, boolean)} */
setThumbnail(Task task, ThumbnailData thumbnailData)161     public void setThumbnail(Task task, ThumbnailData thumbnailData) {
162         setThumbnail(task, thumbnailData, true /* refreshNow */);
163     }
164 
165     /** Updates the shader, paint, matrix to redraw. */
refresh()166     public void refresh() {
167         if (mThumbnailData != null && mThumbnailData.thumbnail != null) {
168             Bitmap bm = mThumbnailData.thumbnail;
169             bm.prepareToDraw();
170             mBitmapShader = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
171             mPaint.setShader(mBitmapShader);
172             updateThumbnailMatrix();
173         } else {
174             mBitmapShader = null;
175             mThumbnailData = null;
176             mPaint.setShader(null);
177             getTaskOverlay().reset();
178         }
179         if (mOverviewScreenshotActionsPlugin != null) {
180             mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
181         }
182         updateThumbnailPaintFilter();
183     }
184 
setDimAlphaMultipler(float dimAlphaMultipler)185     public void setDimAlphaMultipler(float dimAlphaMultipler) {
186         mDimAlphaMultiplier = dimAlphaMultipler;
187         setDimAlpha(mDimAlpha);
188     }
189 
190     /**
191      * Sets the alpha of the dim layer on top of this view.
192      * <p>
193      * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be black.
194      */
setDimAlpha(float dimAlpha)195     public void setDimAlpha(float dimAlpha) {
196         mDimAlpha = dimAlpha;
197         updateThumbnailPaintFilter();
198     }
199 
getTaskOverlay()200     public TaskOverlay getTaskOverlay() {
201         if (mOverlay == null) {
202             mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this);
203         }
204         return mOverlay;
205     }
206 
getDimAlpha()207     public float getDimAlpha() {
208         return mDimAlpha;
209     }
210 
getInsets(Rect fallback)211     public Rect getInsets(Rect fallback) {
212         if (mThumbnailData != null) {
213             return mThumbnailData.insets;
214         }
215         return fallback;
216     }
217 
218     /**
219      * Get the scaled insets that are being used to draw the task view. This is a subsection of
220      * the full snapshot.
221      * @return the insets in snapshot bitmap coordinates.
222      */
223     @RequiresApi(api = Build.VERSION_CODES.Q)
getScaledInsets()224     public Insets getScaledInsets() {
225         if (mThumbnailData == null) {
226             return Insets.NONE;
227         }
228 
229         RectF bitmapRect = new RectF(
230                 0, 0,
231                 mThumbnailData.thumbnail.getWidth(), mThumbnailData.thumbnail.getHeight());
232         RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
233 
234         // The position helper matrix tells us how to transform the bitmap to fit the view, the
235         // inverse tells us where the view would be in the bitmaps coordinates. The insets are the
236         // difference between the bitmap bounds and the projected view bounds.
237         Matrix boundsToBitmapSpace = new Matrix();
238         mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace);
239         RectF boundsInBitmapSpace = new RectF();
240         boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
241 
242         return Insets.of(
243             Math.round(boundsInBitmapSpace.left),
244             Math.round(boundsInBitmapSpace.top),
245             Math.round(bitmapRect.right - boundsInBitmapSpace.right),
246             Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom));
247     }
248 
249 
getSysUiStatusNavFlags()250     public int getSysUiStatusNavFlags() {
251         if (mThumbnailData != null) {
252             int flags = 0;
253             flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
254                     ? SystemUiController.FLAG_LIGHT_STATUS
255                     : SystemUiController.FLAG_DARK_STATUS;
256             flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0
257                     ? SystemUiController.FLAG_LIGHT_NAV
258                     : SystemUiController.FLAG_DARK_NAV;
259             return flags;
260         }
261         return 0;
262     }
263 
264     @Override
onDraw(Canvas canvas)265     protected void onDraw(Canvas canvas) {
266         RectF currentDrawnInsets = mFullscreenParams.mCurrentDrawnInsets;
267         canvas.save();
268         canvas.scale(mFullscreenParams.mScale, mFullscreenParams.mScale);
269         canvas.translate(currentDrawnInsets.left, currentDrawnInsets.top);
270         // Draw the insets if we're being drawn fullscreen (we do this for quick switch).
271         drawOnCanvas(canvas,
272                 -currentDrawnInsets.left,
273                 -currentDrawnInsets.top,
274                 getMeasuredWidth() + currentDrawnInsets.right,
275                 getMeasuredHeight() + currentDrawnInsets.bottom,
276                 mFullscreenParams.mCurrentDrawnCornerRadius);
277         canvas.restore();
278     }
279 
280     @Override
onPluginConnected(OverviewScreenshotActions overviewScreenshotActions, Context context)281     public void onPluginConnected(OverviewScreenshotActions overviewScreenshotActions,
282             Context context) {
283         mOverviewScreenshotActionsPlugin = overviewScreenshotActions;
284         mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
285     }
286 
287     @Override
onPluginDisconnected(OverviewScreenshotActions plugin)288     public void onPluginDisconnected(OverviewScreenshotActions plugin) {
289         if (mOverviewScreenshotActionsPlugin != null) {
290             mOverviewScreenshotActionsPlugin = null;
291         }
292     }
293 
294     @Override
onAttachedToWindow()295     protected void onAttachedToWindow() {
296         super.onAttachedToWindow();
297         PluginManagerWrapper.INSTANCE.get(getContext())
298             .addPluginListener(this, OverviewScreenshotActions.class);
299     }
300 
301     @Override
onDetachedFromWindow()302     protected void onDetachedFromWindow() {
303         super.onDetachedFromWindow();
304         PluginManagerWrapper.INSTANCE.get(getContext()).removePluginListener(this);
305     }
306 
getPreviewPositionHelper()307     public PreviewPositionHelper getPreviewPositionHelper() {
308         return mPreviewPositionHelper;
309     }
310 
setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams)311     public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
312         mFullscreenParams = fullscreenParams;
313         invalidate();
314     }
315 
drawOnCanvas(Canvas canvas, float x, float y, float width, float height, float cornerRadius)316     public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
317             float cornerRadius) {
318         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
319             if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
320                 canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
321                 canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
322                         mDimmingPaintAfterClearing);
323                 return;
324             }
325         }
326 
327         // Draw the background in all cases, except when the thumbnail data is opaque
328         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
329                 || mThumbnailData == null;
330         if (drawBackgroundOnly || mPreviewPositionHelper.mClipBottom > 0
331                 || mThumbnailData.isTranslucent) {
332             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
333             if (drawBackgroundOnly) {
334                 return;
335             }
336         }
337 
338         if (mPreviewPositionHelper.mClipBottom > 0) {
339             canvas.save();
340             canvas.clipRect(x, y, width, mPreviewPositionHelper.mClipBottom);
341             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
342             canvas.restore();
343         } else {
344             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
345         }
346     }
347 
getTaskView()348     public TaskView getTaskView() {
349         return (TaskView) getParent();
350     }
351 
setOverlayEnabled(boolean overlayEnabled)352     public void setOverlayEnabled(boolean overlayEnabled) {
353         if (mOverlayEnabled != overlayEnabled) {
354             mOverlayEnabled = overlayEnabled;
355             updateOverlay();
356         }
357     }
358 
updateOverlay()359     private void updateOverlay() {
360         if (mOverlayEnabled) {
361             getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
362                     mPreviewPositionHelper.mIsOrientationChanged);
363         } else {
364             getTaskOverlay().reset();
365         }
366     }
367 
updateThumbnailPaintFilter()368     private void updateThumbnailPaintFilter() {
369         int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255);
370         ColorFilter filter = getColorFilter(mul, mIsDarkTextTheme, mSaturation);
371         mBackgroundPaint.setColorFilter(filter);
372         mDimmingPaintAfterClearing.setAlpha(255 - mul);
373         if (mBitmapShader != null) {
374             mPaint.setColorFilter(filter);
375         } else {
376             mPaint.setColorFilter(null);
377             mPaint.setColor(Color.argb(255, mul, mul, mul));
378         }
379         invalidate();
380     }
381 
updateThumbnailMatrix()382     private void updateThumbnailMatrix() {
383         mPreviewPositionHelper.mClipBottom = -1;
384         mPreviewPositionHelper.mIsOrientationChanged = false;
385         if (mBitmapShader != null && mThumbnailData != null) {
386             mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
387                     mThumbnailData.thumbnail.getHeight());
388             int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
389                     .getRecentsActivityRotation();
390             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
391                     getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
392                     currentRotation);
393 
394             mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
395             mPaint.setShader(mBitmapShader);
396         }
397         getTaskView().updateCurrentFullscreenParams(mPreviewPositionHelper);
398         invalidate();
399 
400         // Update can be called from {@link #onSizeChanged} during layout, post handling of overlay
401         // as overlay could modify the views in the overlay as a side effect of its update.
402         post(this::updateOverlay);
403     }
404 
405     @Override
onSizeChanged(int w, int h, int oldw, int oldh)406     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
407         super.onSizeChanged(w, h, oldw, oldh);
408         updateThumbnailMatrix();
409     }
410 
411     /**
412      * @param intensity multiplier for color values. 0 - make black (white if shouldLighten), 255 -
413      *                  leave unchanged.
414      */
getColorFilter(int intensity, boolean shouldLighten, float saturation)415     private static ColorFilter getColorFilter(int intensity, boolean shouldLighten,
416             float saturation) {
417         intensity = Utilities.boundToRange(intensity, 0, 255);
418 
419         if (intensity == 255 && saturation == 1) {
420             return null;
421         }
422 
423         final float intensityScale = intensity / 255f;
424         COLOR_MATRIX.setScale(intensityScale, intensityScale, intensityScale, 1);
425 
426         if (saturation != 1) {
427             SATURATION_COLOR_MATRIX.setSaturation(saturation);
428             COLOR_MATRIX.postConcat(SATURATION_COLOR_MATRIX);
429         }
430 
431         if (shouldLighten) {
432             final float[] colorArray = COLOR_MATRIX.getArray();
433             final int colorAdd = 255 - intensity;
434             colorArray[4] = colorAdd;
435             colorArray[9] = colorAdd;
436             colorArray[14] = colorAdd;
437         }
438 
439         return new ColorMatrixColorFilter(COLOR_MATRIX);
440     }
441 
getThumbnail()442     public Bitmap getThumbnail() {
443         if (mThumbnailData == null) {
444             return null;
445         }
446         return mThumbnailData.thumbnail;
447     }
448 
449     /**
450      * Returns whether the snapshot is real. If the device is locked for the user of the task,
451      * the snapshot used will be an app-theme generated snapshot instead of a real snapshot.
452      */
isRealSnapshot()453     public boolean isRealSnapshot() {
454         if (mThumbnailData == null) {
455             return false;
456         }
457         return mThumbnailData.isRealSnapshot && !mTask.isLocked;
458     }
459 
460     /**
461      * Utility class to position the thumbnail in the TaskView
462      */
463     public static class PreviewPositionHelper {
464 
465         // Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
466         private final RectF mClippedInsets = new RectF();
467         private final Matrix mMatrix = new Matrix();
468         private float mClipBottom = -1;
469         private boolean mIsOrientationChanged;
470 
getMatrix()471         public Matrix getMatrix() {
472             return mMatrix;
473         }
474 
475         /**
476          * Updates the matrix based on the provided parameters
477          */
updateThumbnailMatrix(Rect thumbnailPosition, ThumbnailData thumbnailData, int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation)478         public void updateThumbnailMatrix(Rect thumbnailPosition, ThumbnailData thumbnailData,
479                 int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
480             boolean isRotated = false;
481             boolean isOrientationDifferent;
482             mClipBottom = -1;
483 
484             int thumbnailRotation = thumbnailData.rotation;
485             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
486             Rect thumbnailInsets = getBoundedInsets(
487                     dp.getInsets(), thumbnailData.insets, deltaRotate);
488 
489             float scale = thumbnailData.scale;
490             final float thumbnailWidth = thumbnailPosition.width()
491                     - (thumbnailInsets.left + thumbnailInsets.right) * scale;
492             final float thumbnailHeight = thumbnailPosition.height()
493                     - (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
494 
495             final float thumbnailScale;
496 
497             // Landscape vs portrait change
498             boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
499                     && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
500             isOrientationDifferent = isOrientationChange(deltaRotate)
501                     && windowingModeSupportsRotation;
502             if (canvasWidth == 0) {
503                 // If we haven't measured , skip the thumbnail drawing and only draw the background
504                 // color
505                 thumbnailScale = 0f;
506             } else {
507                 // Rotate the screenshot if not in multi-window mode
508                 isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
509                 // Scale the screenshot to always fit the width of the card.
510                 thumbnailScale = isOrientationDifferent
511                         ? canvasWidth / thumbnailHeight
512                         : canvasWidth / thumbnailWidth;
513             }
514 
515             Rect splitScreenInsets = dp.getInsets();
516             if (!isRotated) {
517                 // No Rotation
518                 if (dp.isMultiWindowMode) {
519                     mClippedInsets.offsetTo(splitScreenInsets.left * scale,
520                             splitScreenInsets.top * scale);
521                 } else {
522                     mClippedInsets.offsetTo(thumbnailInsets.left * scale,
523                             thumbnailInsets.top * scale);
524                 }
525                 mMatrix.setTranslate(
526                         -thumbnailInsets.left * scale,
527                         -thumbnailInsets.top * scale);
528             } else {
529                 setThumbnailRotation(deltaRotate, thumbnailInsets, scale, thumbnailPosition);
530             }
531 
532             final float widthWithInsets;
533             final float heightWithInsets;
534             if (isOrientationDifferent) {
535                 widthWithInsets = thumbnailPosition.height() * thumbnailScale;
536                 heightWithInsets = thumbnailPosition.width() * thumbnailScale;
537             } else {
538                 widthWithInsets = thumbnailPosition.width() * thumbnailScale;
539                 heightWithInsets = thumbnailPosition.height() * thumbnailScale;
540             }
541             mClippedInsets.left *= thumbnailScale;
542             mClippedInsets.top *= thumbnailScale;
543 
544             if (dp.isMultiWindowMode) {
545                 mClippedInsets.right = splitScreenInsets.right * scale * thumbnailScale;
546                 mClippedInsets.bottom = splitScreenInsets.bottom * scale * thumbnailScale;
547             } else {
548                 mClippedInsets.right = Math.max(0,
549                         widthWithInsets - mClippedInsets.left - canvasWidth);
550                 mClippedInsets.bottom = Math.max(0,
551                         heightWithInsets - mClippedInsets.top - canvasHeight);
552             }
553 
554             mMatrix.postScale(thumbnailScale, thumbnailScale);
555 
556             float bitmapHeight = Math.max(0,
557                     (isOrientationDifferent ? thumbnailWidth : thumbnailHeight) * thumbnailScale);
558             if (Math.round(bitmapHeight) < canvasHeight) {
559                 mClipBottom = bitmapHeight;
560             }
561             mIsOrientationChanged = isOrientationDifferent;
562         }
563 
getBoundedInsets(Rect activityInsets, Rect insets, int deltaRotation)564         private Rect getBoundedInsets(Rect activityInsets, Rect insets, int deltaRotation) {
565             if (deltaRotation != 0) {
566                 return insets;
567             }
568             return new Rect(Math.min(insets.left, activityInsets.left),
569                     Math.min(insets.top, activityInsets.top),
570                     Math.min(insets.right, activityInsets.right),
571                     Math.min(insets.bottom, activityInsets.bottom));
572         }
573 
getRotationDelta(int oldRotation, int newRotation)574         private int getRotationDelta(int oldRotation, int newRotation) {
575             int delta = newRotation - oldRotation;
576             if (delta < 0) delta += 4;
577             return delta;
578         }
579 
580         /**
581          * @param deltaRotation the number of 90 degree turns from the current orientation
582          * @return {@code true} if the change in rotation results in a shift from landscape to
583          * portrait or vice versa, {@code false} otherwise
584          */
isOrientationChange(int deltaRotation)585         private boolean isOrientationChange(int deltaRotation) {
586             return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
587         }
588 
setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale, Rect thumbnailPosition)589         private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale,
590                 Rect thumbnailPosition) {
591             int newLeftInset = 0;
592             int newTopInset = 0;
593             int translateX = 0;
594             int translateY = 0;
595 
596             mMatrix.setRotate(90 * deltaRotate);
597             switch (deltaRotate) { /* Counter-clockwise */
598                 case Surface.ROTATION_90:
599                     newLeftInset = thumbnailInsets.bottom;
600                     newTopInset = thumbnailInsets.left;
601                     translateX = thumbnailPosition.height();
602                     break;
603                 case Surface.ROTATION_270:
604                     newLeftInset = thumbnailInsets.top;
605                     newTopInset = thumbnailInsets.right;
606                     translateY = thumbnailPosition.width();
607                     break;
608                 case Surface.ROTATION_180:
609                     newLeftInset = -thumbnailInsets.top;
610                     newTopInset = -thumbnailInsets.left;
611                     translateX = thumbnailPosition.width();
612                     translateY = thumbnailPosition.height();
613                     break;
614             }
615             mClippedInsets.offsetTo(newLeftInset * scale, newTopInset * scale);
616             mMatrix.postTranslate(translateX - mClippedInsets.left,
617                     translateY - mClippedInsets.top);
618         }
619 
620         /**
621          * Insets to used for clipping the thumbnail (in case it is drawing outside its own space)
622          */
getInsetsToDrawInFullscreen()623         public RectF getInsetsToDrawInFullscreen() {
624             return mClippedInsets;
625         }
626     }
627 }
628