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 /** 71 * Callback when reaches the end of animation on the internal {@link #mLeash}. 72 * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly 73 * call apply on this transaction, it should be applied on the caller side. 74 * @param destinationBounds {@link Rect} of the final bounds. 75 */ onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)76 public abstract void onAnimationEnd(SurfaceControl.Transaction atomicTx, 77 Rect destinationBounds); 78 79 /** A {@link PipContentOverlay} uses solid color. */ 80 public static final class PipColorOverlay extends PipContentOverlay { 81 private static final String TAG = PipColorOverlay.class.getSimpleName(); 82 83 private final Context mContext; 84 PipColorOverlay(Context context)85 public PipColorOverlay(Context context) { 86 mContext = context; 87 mLeash = new SurfaceControl.Builder(new SurfaceSession()) 88 .setCallsite(TAG) 89 .setName(LAYER_NAME) 90 .setColorLayer() 91 .build(); 92 } 93 94 @Override attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)95 public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { 96 tx.show(mLeash); 97 tx.setLayer(mLeash, Integer.MAX_VALUE); 98 tx.setColor(mLeash, getContentOverlayColor(mContext)); 99 tx.setAlpha(mLeash, 0f); 100 tx.reparent(mLeash, parentLeash); 101 tx.apply(); 102 } 103 104 @Override onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)105 public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, 106 Rect currentBounds, float fraction) { 107 atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); 108 } 109 110 @Override onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)111 public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { 112 // Do nothing. Color overlay should be fully opaque by now. 113 } 114 getContentOverlayColor(Context context)115 private float[] getContentOverlayColor(Context context) { 116 final TypedArray ta = context.obtainStyledAttributes(new int[] { 117 android.R.attr.colorBackground }); 118 try { 119 int colorAccent = ta.getColor(0, 0); 120 return new float[] { 121 Color.red(colorAccent) / 255f, 122 Color.green(colorAccent) / 255f, 123 Color.blue(colorAccent) / 255f }; 124 } finally { 125 ta.recycle(); 126 } 127 } 128 } 129 130 /** A {@link PipContentOverlay} uses {@link TaskSnapshot}. */ 131 public static final class PipSnapshotOverlay extends PipContentOverlay { 132 private static final String TAG = PipSnapshotOverlay.class.getSimpleName(); 133 134 private final TaskSnapshot mSnapshot; 135 private final Rect mSourceRectHint; 136 PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint)137 public PipSnapshotOverlay(TaskSnapshot snapshot, Rect sourceRectHint) { 138 mSnapshot = snapshot; 139 mSourceRectHint = new Rect(sourceRectHint); 140 mLeash = new SurfaceControl.Builder(new SurfaceSession()) 141 .setCallsite(TAG) 142 .setName(LAYER_NAME) 143 .build(); 144 } 145 146 @Override attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)147 public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { 148 final float taskSnapshotScaleX = (float) mSnapshot.getTaskSize().x 149 / mSnapshot.getHardwareBuffer().getWidth(); 150 final float taskSnapshotScaleY = (float) mSnapshot.getTaskSize().y 151 / mSnapshot.getHardwareBuffer().getHeight(); 152 tx.show(mLeash); 153 tx.setLayer(mLeash, Integer.MAX_VALUE); 154 tx.setBuffer(mLeash, mSnapshot.getHardwareBuffer()); 155 // Relocate the content to parentLeash's coordinates. 156 tx.setPosition(mLeash, -mSourceRectHint.left, -mSourceRectHint.top); 157 tx.setScale(mLeash, taskSnapshotScaleX, taskSnapshotScaleY); 158 tx.reparent(mLeash, parentLeash); 159 tx.apply(); 160 } 161 162 @Override onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)163 public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, 164 Rect currentBounds, float fraction) { 165 // Do nothing. Keep the snapshot till animation ends. 166 } 167 168 @Override onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)169 public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { 170 atomicTx.remove(mLeash); 171 } 172 } 173 174 /** A {@link PipContentOverlay} shows app icon on solid color background. */ 175 public static final class PipAppIconOverlay extends PipContentOverlay { 176 private static final String TAG = PipAppIconOverlay.class.getSimpleName(); 177 // The maximum size for app icon in pixel. 178 private static final int MAX_APP_ICON_SIZE_DP = 72; 179 180 private final Context mContext; 181 private final int mAppIconSizePx; 182 private final Rect mAppBounds; 183 private final Matrix mTmpTransform = new Matrix(); 184 private final float[] mTmpFloat9 = new float[9]; 185 186 private Bitmap mBitmap; 187 PipAppIconOverlay(Context context, Rect appBounds, Drawable appIcon, int appIconSizePx)188 public PipAppIconOverlay(Context context, Rect appBounds, 189 Drawable appIcon, int appIconSizePx) { 190 mContext = context; 191 final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, 192 MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics()); 193 mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx); 194 mAppBounds = new Rect(appBounds); 195 mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(), 196 Bitmap.Config.ARGB_8888); 197 prepareAppIconOverlay(appIcon); 198 mLeash = new SurfaceControl.Builder(new SurfaceSession()) 199 .setCallsite(TAG) 200 .setName(LAYER_NAME) 201 .build(); 202 } 203 204 @Override attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash)205 public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { 206 tx.show(mLeash); 207 tx.setLayer(mLeash, Integer.MAX_VALUE); 208 tx.setBuffer(mLeash, mBitmap.getHardwareBuffer()); 209 tx.setAlpha(mLeash, 0f); 210 tx.reparent(mLeash, parentLeash); 211 tx.apply(); 212 } 213 214 @Override onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)215 public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, 216 Rect currentBounds, float fraction) { 217 mTmpTransform.reset(); 218 // Scale back the bitmap with the pivot point at center. 219 mTmpTransform.postScale( 220 (float) mAppBounds.width() / currentBounds.width(), 221 (float) mAppBounds.height() / currentBounds.height(), 222 mAppBounds.centerX(), 223 mAppBounds.centerY()); 224 atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9) 225 .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); 226 } 227 228 @Override onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)229 public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { 230 atomicTx.remove(mLeash); 231 } 232 233 @Override detach(SurfaceControl.Transaction tx)234 public void detach(SurfaceControl.Transaction tx) { 235 super.detach(tx); 236 if (mBitmap != null && !mBitmap.isRecycled()) { 237 mBitmap.recycle(); 238 } 239 } 240 prepareAppIconOverlay(Drawable appIcon)241 private void prepareAppIconOverlay(Drawable appIcon) { 242 final Canvas canvas = new Canvas(); 243 canvas.setBitmap(mBitmap); 244 final TypedArray ta = mContext.obtainStyledAttributes(new int[] { 245 android.R.attr.colorBackground }); 246 try { 247 int colorAccent = ta.getColor(0, 0); 248 canvas.drawRGB( 249 Color.red(colorAccent), 250 Color.green(colorAccent), 251 Color.blue(colorAccent)); 252 } finally { 253 ta.recycle(); 254 } 255 final Rect appIconBounds = new Rect( 256 mAppBounds.centerX() - mAppIconSizePx / 2, 257 mAppBounds.centerY() - mAppIconSizePx / 2, 258 mAppBounds.centerX() + mAppIconSizePx / 2, 259 mAppBounds.centerY() + mAppIconSizePx / 2); 260 appIcon.setBounds(appIconBounds); 261 appIcon.draw(canvas); 262 mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */); 263 } 264 } 265 } 266