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.reparent(mLeash, parentLeash); 210 tx.apply(); 211 } 212 213 @Override onAnimationUpdate(SurfaceControl.Transaction atomicTx, Rect currentBounds, float fraction)214 public void onAnimationUpdate(SurfaceControl.Transaction atomicTx, 215 Rect currentBounds, float fraction) { 216 mTmpTransform.reset(); 217 // Scale back the bitmap with the pivot point at center. 218 mTmpTransform.postScale( 219 (float) mAppBounds.width() / currentBounds.width(), 220 (float) mAppBounds.height() / currentBounds.height(), 221 mAppBounds.centerX(), 222 mAppBounds.centerY()); 223 atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9) 224 .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); 225 } 226 227 @Override onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds)228 public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { 229 atomicTx.remove(mLeash); 230 } 231 232 @Override detach(SurfaceControl.Transaction tx)233 public void detach(SurfaceControl.Transaction tx) { 234 super.detach(tx); 235 if (mBitmap != null && !mBitmap.isRecycled()) { 236 mBitmap.recycle(); 237 } 238 } 239 prepareAppIconOverlay(Drawable appIcon)240 private void prepareAppIconOverlay(Drawable appIcon) { 241 final Canvas canvas = new Canvas(); 242 canvas.setBitmap(mBitmap); 243 final TypedArray ta = mContext.obtainStyledAttributes(new int[] { 244 android.R.attr.colorBackground }); 245 try { 246 int colorAccent = ta.getColor(0, 0); 247 canvas.drawRGB( 248 Color.red(colorAccent), 249 Color.green(colorAccent), 250 Color.blue(colorAccent)); 251 } finally { 252 ta.recycle(); 253 } 254 final Rect appIconBounds = new Rect( 255 mAppBounds.centerX() - mAppIconSizePx / 2, 256 mAppBounds.centerY() - mAppIconSizePx / 2, 257 mAppBounds.centerX() + mAppIconSizePx / 2, 258 mAppBounds.centerY() + mAppIconSizePx / 2); 259 appIcon.setBounds(appIconBounds); 260 appIcon.draw(canvas); 261 mBitmap = mBitmap.copy(Bitmap.Config.HARDWARE, false /* mutable */); 262 } 263 } 264 } 265