1 /* 2 * Copyright (C) 2020 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.systemui.shared.pip; 18 19 import android.graphics.Matrix; 20 import android.graphics.Rect; 21 import android.graphics.RectF; 22 import android.view.Choreographer; 23 import android.view.SurfaceControl; 24 import android.window.PictureInPictureSurfaceTransaction; 25 26 /** 27 * TODO(b/171721389): unify this class with 28 * {@link com.android.wm.shell.pip.PipSurfaceTransactionHelper}, for instance, there should be one 29 * source of truth on enabling/disabling and the actual value of corner radius. 30 */ 31 public class PipSurfaceTransactionHelper { 32 private final int mCornerRadius; 33 private final int mShadowRadius; 34 private final Matrix mTmpTransform = new Matrix(); 35 private final float[] mTmpFloat9 = new float[9]; 36 private final RectF mTmpSourceRectF = new RectF(); 37 private final RectF mTmpDestinationRectF = new RectF(); 38 private final Rect mTmpDestinationRect = new Rect(); 39 PipSurfaceTransactionHelper(int cornerRadius, int shadowRadius)40 public PipSurfaceTransactionHelper(int cornerRadius, int shadowRadius) { 41 mCornerRadius = cornerRadius; 42 mShadowRadius = shadowRadius; 43 } 44 scale( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds)45 public PictureInPictureSurfaceTransaction scale( 46 SurfaceControl.Transaction tx, SurfaceControl leash, 47 Rect sourceBounds, Rect destinationBounds) { 48 float positionX = destinationBounds.left; 49 float positionY = destinationBounds.top; 50 mTmpSourceRectF.set(sourceBounds); 51 mTmpDestinationRectF.set(destinationBounds); 52 mTmpDestinationRectF.offsetTo(0, 0); 53 mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); 54 final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds); 55 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 56 .setPosition(leash, positionX, positionY) 57 .setCornerRadius(leash, cornerRadius) 58 .setShadowRadius(leash, mShadowRadius); 59 return newPipSurfaceTransaction(positionX, positionY, 60 mTmpFloat9, 0 /* rotation */, cornerRadius, mShadowRadius, sourceBounds); 61 } 62 scale( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degree, float positionX, float positionY)63 public PictureInPictureSurfaceTransaction scale( 64 SurfaceControl.Transaction tx, SurfaceControl leash, 65 Rect sourceBounds, Rect destinationBounds, 66 float degree, float positionX, float positionY) { 67 mTmpSourceRectF.set(sourceBounds); 68 mTmpDestinationRectF.set(destinationBounds); 69 mTmpDestinationRectF.offsetTo(0, 0); 70 mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); 71 mTmpTransform.postRotate(degree, 0, 0); 72 final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds); 73 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 74 .setPosition(leash, positionX, positionY) 75 .setCornerRadius(leash, cornerRadius) 76 .setShadowRadius(leash, mShadowRadius); 77 return newPipSurfaceTransaction(positionX, positionY, 78 mTmpFloat9, degree, cornerRadius, mShadowRadius, sourceBounds); 79 } 80 scaleAndCrop( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets, float progress)81 public PictureInPictureSurfaceTransaction scaleAndCrop( 82 SurfaceControl.Transaction tx, SurfaceControl leash, 83 Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets, 84 float progress) { 85 mTmpSourceRectF.set(sourceBounds); 86 mTmpDestinationRect.set(sourceBounds); 87 mTmpDestinationRect.inset(insets); 88 // Scale to the bounds no smaller than the destination and offset such that the top/left 89 // of the scaled inset source rect aligns with the top/left of the destination bounds 90 final float scale; 91 if (sourceRectHint.isEmpty() || sourceRectHint.width() == sourceBounds.width()) { 92 scale = Math.max((float) destinationBounds.width() / sourceBounds.width(), 93 (float) destinationBounds.height() / sourceBounds.height()); 94 } else { 95 // scale by sourceRectHint if it's not edge-to-edge 96 final float endScale = sourceRectHint.width() <= sourceRectHint.height() 97 ? (float) destinationBounds.width() / sourceRectHint.width() 98 : (float) destinationBounds.height() / sourceRectHint.height(); 99 final float startScale = sourceRectHint.width() <= sourceRectHint.height() 100 ? (float) destinationBounds.width() / sourceBounds.width() 101 : (float) destinationBounds.height() / sourceBounds.height(); 102 scale = (1 - progress) * startScale + progress * endScale; 103 } 104 final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale; 105 final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale; 106 mTmpTransform.setScale(scale, scale); 107 final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds); 108 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 109 .setCrop(leash, mTmpDestinationRect) 110 .setPosition(leash, left, top) 111 .setCornerRadius(leash, cornerRadius) 112 .setShadowRadius(leash, mShadowRadius); 113 return newPipSurfaceTransaction(left, top, 114 mTmpFloat9, 0 /* rotation */, cornerRadius, mShadowRadius, mTmpDestinationRect); 115 } 116 scaleAndRotate( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets, float degree, float positionX, float positionY)117 public PictureInPictureSurfaceTransaction scaleAndRotate( 118 SurfaceControl.Transaction tx, SurfaceControl leash, 119 Rect sourceBounds, Rect destinationBounds, Rect insets, 120 float degree, float positionX, float positionY) { 121 mTmpSourceRectF.set(sourceBounds); 122 mTmpDestinationRect.set(sourceBounds); 123 mTmpDestinationRect.inset(insets); 124 // Scale by the shortest edge and offset such that the top/left of the scaled inset 125 // source rect aligns with the top/left of the destination bounds 126 final float scale = sourceBounds.width() <= sourceBounds.height() 127 ? (float) destinationBounds.width() / sourceBounds.width() 128 : (float) destinationBounds.height() / sourceBounds.height(); 129 mTmpTransform.setRotate(degree, 0, 0); 130 mTmpTransform.postScale(scale, scale); 131 final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds); 132 // adjust the positions, take account also the insets 133 final float adjustedPositionX, adjustedPositionY; 134 if (degree < 0) { 135 adjustedPositionX = positionX + insets.top * scale; 136 adjustedPositionY = positionY + insets.left * scale; 137 } else { 138 adjustedPositionX = positionX - insets.top * scale; 139 adjustedPositionY = positionY - insets.left * scale; 140 } 141 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 142 .setCrop(leash, mTmpDestinationRect) 143 .setPosition(leash, adjustedPositionX, adjustedPositionY) 144 .setCornerRadius(leash, cornerRadius) 145 .setShadowRadius(leash, mShadowRadius); 146 return newPipSurfaceTransaction(adjustedPositionX, adjustedPositionY, 147 mTmpFloat9, degree, cornerRadius, mShadowRadius, mTmpDestinationRect); 148 } 149 150 /** @return the round corner radius scaled by given from and to bounds */ getScaledCornerRadius(Rect fromBounds, Rect toBounds)151 private float getScaledCornerRadius(Rect fromBounds, Rect toBounds) { 152 final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height()) 153 / Math.hypot(toBounds.width(), toBounds.height())); 154 return mCornerRadius * scale; 155 } 156 newPipSurfaceTransaction( float posX, float posY, float[] float9, float rotation, float cornerRadius, float shadowRadius, Rect windowCrop)157 private static PictureInPictureSurfaceTransaction newPipSurfaceTransaction( 158 float posX, float posY, float[] float9, float rotation, 159 float cornerRadius, float shadowRadius, Rect windowCrop) { 160 return new PictureInPictureSurfaceTransaction.Builder() 161 .setPosition(posX, posY) 162 .setTransform(float9, rotation) 163 .setCornerRadius(cornerRadius) 164 .setShadowRadius(shadowRadius) 165 .setWindowCrop(windowCrop) 166 .build(); 167 } 168 169 /** @return {@link SurfaceControl.Transaction} instance with vsync-id */ newSurfaceControlTransaction()170 public static SurfaceControl.Transaction newSurfaceControlTransaction() { 171 final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 172 tx.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); 173 return tx; 174 } 175 } 176