1 /* 2 * Copyright (C) 2022 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.wm.shell.pip; 18 19 import static android.util.TypedValue.COMPLEX_UNIT_DIP; 20 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.content.res.TypedArray; 24 import android.graphics.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.Color; 27 import android.graphics.Matrix; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.util.TypedValue; 31 import android.view.SurfaceControl; 32 import android.view.SurfaceSession; 33 import android.window.TaskSnapshot; 34 35 /** 36 * Represents the content overlay used during the entering PiP animation. 37 */ 38 public abstract class PipContentOverlay { 39 // Fixed string used in WMShellFlickerTests 40 protected static final String LAYER_NAME = "PipContentOverlay"; 41 42 protected SurfaceControl mLeash; 43 44 /** Attaches the internal {@link #mLeash} to the given parent leash. */ attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)45 public abstract void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash); 46 47 /** Detaches the internal {@link #mLeash} from its parent by removing itself. */ detach(SurfaceControl.Transaction tx)48 public void detach(SurfaceControl.Transaction tx) { 49 if (mLeash != null && mLeash.isValid()) { 50 tx.remove(mLeash); 51 tx.apply(); 52 } 53 } 54 55 @Nullable getLeash()56 public SurfaceControl getLeash() { 57 return mLeash; 58 } 59 60 /** 61 * Animates the internal {@link #mLeash} by a given fraction. 62 * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly 63 * call apply on this transaction, it should be applied on the caller side. 64 * @param currentBounds {@link Rect} of the current animation bounds. 65 * @param fraction progress of the animation ranged from 0f to 1f. 66 */ onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)67 public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx, 68 Rect currentBounds, float fraction); 69 70 /** A {@link PipContentOverlay} uses solid color. */ 71 public static final class PipColorOverlay extends PipContentOverlay { 72 private static final String TAG = PipColorOverlay.class.getSimpleName(); 73 74 private final Context mContext; 75 PipColorOverlay(Context context)76 public PipColorOverlay(Context context) { 77 mContext = context; 78 mLeash = new SurfaceControl.Builder(new SurfaceSession()) 79 .setCallsite(TAG) 80 .setName(LAYER_NAME) 81 .setColorLayer() 82 .build(); 83 } 84 85 @Override attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)86 public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { 87 tx.show(mLeash); 88 tx.setLayer(mLeash, Integer.MAX_VALUE); 89 tx.setColor(mLeash, getContentOverlayColor(mContext)); 90 tx.setAlpha(mLeash, 0f); 91 tx.reparent(mLeash, parentLeash); 92 tx.apply(); 93 } 94 95 @Override onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)96 public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, 97 Rect currentBounds, float fraction) { 98 atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); 99 } 100 getContentOverlayColor(Context context)101 private float[] getContentOverlayColor(Context context) { 102 final TypedArray ta = context.obtainStyledAttributes(new int[] { 103 android.R.attr.colorBackground }); 104 try { 105 int colorAccent = ta.getColor(0, 0); 106 return new float[] { 107 Color.red(colorAccent) / 255f, 108 Color.green(colorAccent) / 255f, 109 Color.blue(colorAccent) / 255f }; 110 } finally { 111 ta.recycle(); 112 } 113 } 114 } 115 116 /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */ 117 public static final class PipSnapshotOverlay extends PipContentOverlay { 118 private static final String TAG = PipSnapshotOverlay.class.getSimpleName(); 119 120 private final TaskSnapshot mSnapshot; 121 private final Rect mSourceRectHint; 122 PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint)123 public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) { 124 mSnapshot = snapshot; 125 mSourceRectHint = new Rect(sourceRectHint); 126 mLeash = new SurfaceControl.Builder(new SurfaceSession()) 127 .setCallsite(TAG) 128 .setName(LAYER_NAME) 129 .build(); 130 } 131 132 @Override attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)133 public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { 134 final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x 135 / mSnapshot.getHardwareBuffer().getWidth(); 136 final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y 137 / mSnapshot.getHardwareBuffer().getHeight(); 138 tx.show(mLeash); 139 tx.setLayer(mLeash, Integer.MAX_VALUE); 140 tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer()); 141 // Relocate the content to parentLeash's coordinates. 142 tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top); 143 tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY); 144 tx.reparent(mLeash, parentLeash); 145 tx.apply(); 146 } 147 148 @Override onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)149 public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, 150 Rect currentBounds, float fraction) { 151 // Do nothing. Keep the snapshot till animation ends. 152 } 153 } 154 155 /** A {@link PipContentOverlay} shows app icon on solid color background. */ 156 public static final class PipAppIconOverlay extends PipContentOverlay { 157 private static final String TAG = PipAppIconOverlay.class.getSimpleName(); 158 // The maximum size for app icon in pixel. 159 private static final int MAX_APP_ICON_SIZE_DP = 72; 160 161 private final Context mContext; 162 private final int mAppIconSizePx; 163 private final Rect mAppBounds; 164 private final int mOverlayHalfSize; 165 private final Matrix mTmpTransform = new Matrix(); 166 private final float[] mTmpFloat9 = new float[9]; 167 168 private Bitmap mBitmap; 169 PipAppIconOverlay(Context context, Rect appBounds, Rect destinationBounds, Drawable appIcon, int appIconSizePx)170 public PipAppIconOverlay(Context context, Rect appBounds, Rect destinationBounds, 171 Drawable appIcon, int appIconSizePx) { 172 mContext = context; 173 final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, 174 MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics()); 175 mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx); 176 177 final int overlaySize = getOverlaySize(appBounds, destinationBounds); 178 mOverlayHalfSize = overlaySize >> 1; 179 180 // When the activity is in the secondary split, make sure the scaling center is not 181 // offset. 182 mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height()); 183 184 mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888); 185 prepareAppIconOverlay(appIcon); 186 mLeash = new SurfaceControl.Builder(new SurfaceSession()) 187 .setCallsite(TAG) 188 .setName(LAYER_NAME) 189 .build(); 190 } 191 192 /** 193 * Returns the size of the app icon overlay. 194 * 195 * In order to have the overlay always cover the pip window during the transition, 196 * the overlay will be drawn with the max size of the start and end bounds in different 197 * rotation. 198 */ getOverlaySize(Rect appBounds, Rect destinationBounds)199 public static int getOverlaySize(Rect appBounds, Rect destinationBounds) { 200 final int appWidth = appBounds.width(); 201 final int appHeight = appBounds.height(); 202 203 return Math.max(Math.max(appWidth, appHeight), 204 Math.max(destinationBounds.width(), destinationBounds.height())) + 1; 205 } 206 207 @Override attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)208 public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { 209 tx.show(mLeash); 210 tx.setLayer(mLeash, Integer.MAX_VALUE); 211 tx.setBuffer(mLeash, mBitmap.getHardwareBuffer()); 212 tx.setAlpha(mLeash, 0f); 213 tx.reparent(mLeash, parentLeash); 214 tx.apply(); 215 } 216 217 @Override onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)218 public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, 219 Rect currentBounds, float fraction) { 220 mTmpTransform.reset(); 221 // In order for the overlay to always cover the pip window, the overlay may have a 222 // size larger than the pip window. Make sure that app icon is at the center. 223 final int appBoundsCenterX = mAppBounds.centerX(); 224 final int appBoundsCenterY = mAppBounds.centerY(); 225 mTmpTransform.setTranslate( 226 appBoundsCenterX - mOverlayHalfSize, 227 appBoundsCenterY - mOverlayHalfSize); 228 // Scale back the bitmap with the pivot point at center. 229 final float scale = Math.min( 230 (float) mAppBounds.width() / currentBounds.width(), 231 (float) mAppBounds.height() / currentBounds.height()); 232 mTmpTransform.postScale(scale, scale, appBoundsCenterX, appBoundsCenterY); 233 atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9) 234 .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); 235 } 236 237 @Override detach(SurfaceControl.Transaction tx)238 public void detach(SurfaceControl.Transaction tx) { 239 super.detach(tx); 240 if (mBitmap != null && !mBitmap.isRecycled()) { 241 mBitmap.recycle(); 242 } 243 } 244 prepareAppIconOverlay(Drawable appIcon)245 private void prepareAppIconOverlay(Drawable appIcon) { 246 final Canvas canvas = new Canvas(); 247 canvas.setBitmap(mBitmap); 248 final TypedArray ta = mContext.obtainStyledAttributes(new int[] { 249 android.R.attr.colorBackground }); 250 try { 251 int colorAccent = ta.getColor(0, 0); 252 canvas.drawRGB( 253 Color.red(colorAccent), 254 Color.green(colorAccent), 255 Color.blue(colorAccent)); 256 } finally { 257 ta.recycle(); 258 } 259 final Rect appIconBounds = new Rect( 260 mOverlayHalfSize - mAppIconSizePx / 2, 261 mOverlayHalfSize - mAppIconSizePx / 2, 262 mOverlayHalfSize + mAppIconSizePx / 2, 263 mOverlayHalfSize + mAppIconSizePx / 2); 264 appIcon.setBounds(appIconBounds); 265 appIcon.draw(canvas); 266 mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */); 267 } 268 } 269 } 270