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 Matrix mTmpTransform = new Matrix(); 34 private final float[] mTmpFloat9 = new float[9]; 35 private final RectF mTmpSourceRectF = new RectF(); 36 private final RectF mTmpDestinationRectF = new RectF(); 37 private final Rect mTmpDestinationRect = new Rect(); 38 PipSurfaceTransactionHelper(int cornerRadius)39 public PipSurfaceTransactionHelper(int cornerRadius) { 40 mCornerRadius = cornerRadius; 41 } 42 scale( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds)43 public PictureInPictureSurfaceTransaction scale( 44 SurfaceControl.Transaction tx, SurfaceControl leash, 45 Rect sourceBounds, Rect destinationBounds) { 46 float positionX = destinationBounds.left; 47 float positionY = destinationBounds.top; 48 mTmpSourceRectF.set(sourceBounds); 49 mTmpDestinationRectF.set(destinationBounds); 50 mTmpDestinationRectF.offsetTo(0, 0); 51 mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); 52 final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds); 53 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 54 .setPosition(leash, positionX, positionY) 55 .setCornerRadius(leash, cornerRadius); 56 return newPipSurfaceTransaction(positionX, positionY, 57 mTmpFloat9, 0 /* rotation */, cornerRadius, sourceBounds); 58 } 59 scale( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, float degree, float positionX, float positionY)60 public PictureInPictureSurfaceTransaction scale( 61 SurfaceControl.Transaction tx, SurfaceControl leash, 62 Rect sourceBounds, Rect destinationBounds, 63 float degree, float positionX, float positionY) { 64 mTmpSourceRectF.set(sourceBounds); 65 mTmpDestinationRectF.set(destinationBounds); 66 mTmpDestinationRectF.offsetTo(0, 0); 67 mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL); 68 mTmpTransform.postRotate(degree, 0, 0); 69 final float cornerRadius = getScaledCornerRadius(sourceBounds, destinationBounds); 70 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 71 .setPosition(leash, positionX, positionY) 72 .setCornerRadius(leash, cornerRadius); 73 return newPipSurfaceTransaction(positionX, positionY, 74 mTmpFloat9, degree, cornerRadius, sourceBounds); 75 } 76 scaleAndCrop( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets)77 public PictureInPictureSurfaceTransaction scaleAndCrop( 78 SurfaceControl.Transaction tx, SurfaceControl leash, 79 Rect sourceBounds, Rect destinationBounds, Rect insets) { 80 mTmpSourceRectF.set(sourceBounds); 81 mTmpDestinationRect.set(sourceBounds); 82 mTmpDestinationRect.inset(insets); 83 // Scale by the shortest edge and offset such that the top/left of the scaled inset 84 // source rect aligns with the top/left of the destination bounds 85 final float scale = sourceBounds.width() <= sourceBounds.height() 86 ? (float) destinationBounds.width() / sourceBounds.width() 87 : (float) destinationBounds.height() / sourceBounds.height(); 88 final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale; 89 final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale; 90 mTmpTransform.setScale(scale, scale); 91 final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds); 92 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 93 .setWindowCrop(leash, mTmpDestinationRect) 94 .setPosition(leash, left, top) 95 .setCornerRadius(leash, cornerRadius); 96 return newPipSurfaceTransaction(left, top, 97 mTmpFloat9, 0 /* rotation */, cornerRadius, mTmpDestinationRect); 98 } 99 scaleAndRotate( SurfaceControl.Transaction tx, SurfaceControl leash, Rect sourceBounds, Rect destinationBounds, Rect insets, float degree, float positionX, float positionY)100 public PictureInPictureSurfaceTransaction scaleAndRotate( 101 SurfaceControl.Transaction tx, SurfaceControl leash, 102 Rect sourceBounds, Rect destinationBounds, Rect insets, 103 float degree, float positionX, float positionY) { 104 mTmpSourceRectF.set(sourceBounds); 105 mTmpDestinationRect.set(sourceBounds); 106 mTmpDestinationRect.inset(insets); 107 // Scale by the shortest edge and offset such that the top/left of the scaled inset 108 // source rect aligns with the top/left of the destination bounds 109 final float scale = sourceBounds.width() <= sourceBounds.height() 110 ? (float) destinationBounds.width() / sourceBounds.width() 111 : (float) destinationBounds.height() / sourceBounds.height(); 112 mTmpTransform.setRotate(degree, 0, 0); 113 mTmpTransform.postScale(scale, scale); 114 final float cornerRadius = getScaledCornerRadius(mTmpDestinationRect, destinationBounds); 115 // adjust the positions, take account also the insets 116 final float adjustedPositionX, adjustedPositionY; 117 if (degree < 0) { 118 adjustedPositionX = positionX + insets.top * scale; 119 adjustedPositionY = positionY + insets.left * scale; 120 } else { 121 adjustedPositionX = positionX - insets.top * scale; 122 adjustedPositionY = positionY - insets.left * scale; 123 } 124 tx.setMatrix(leash, mTmpTransform, mTmpFloat9) 125 .setWindowCrop(leash, mTmpDestinationRect) 126 .setPosition(leash, adjustedPositionX, adjustedPositionY) 127 .setCornerRadius(leash, cornerRadius); 128 return newPipSurfaceTransaction(adjustedPositionX, adjustedPositionY, 129 mTmpFloat9, degree, cornerRadius, mTmpDestinationRect); 130 } 131 132 /** @return the round corner radius scaled by given from and to bounds */ getScaledCornerRadius(Rect fromBounds, Rect toBounds)133 private float getScaledCornerRadius(Rect fromBounds, Rect toBounds) { 134 final float scale = (float) (Math.hypot(fromBounds.width(), fromBounds.height()) 135 / Math.hypot(toBounds.width(), toBounds.height())); 136 return mCornerRadius * scale; 137 } 138 newPipSurfaceTransaction( float posX, float posY, float[] float9, float rotation, float cornerRadius, Rect windowCrop)139 private static PictureInPictureSurfaceTransaction newPipSurfaceTransaction( 140 float posX, float posY, float[] float9, float rotation, float cornerRadius, 141 Rect windowCrop) { 142 return new PictureInPictureSurfaceTransaction.Builder() 143 .setPosition(posX, posY) 144 .setTransform(float9, rotation) 145 .setCornerRadius(cornerRadius) 146 .setWindowCrop(windowCrop) 147 .build(); 148 } 149 150 /** @return {@link SurfaceControl.Transaction} instance with vsync-id */ newSurfaceControlTransaction()151 public static SurfaceControl.Transaction newSurfaceControlTransaction() { 152 final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 153 tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId()); 154 return tx; 155 } 156 } 157