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 android.window; 18 19 import static android.graphics.Color.WHITE; 20 import static android.graphics.Color.alpha; 21 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; 22 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; 23 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 24 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; 25 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 26 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 27 import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE; 28 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 29 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 30 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 31 import static android.view.WindowManager.LayoutParams.FLAG_SCALED; 32 import static android.view.WindowManager.LayoutParams.FLAG_SECURE; 33 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; 34 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 35 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; 36 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; 37 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 38 import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; 39 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED; 40 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; 41 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 42 43 import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; 44 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; 45 import static com.android.internal.policy.DecorView.getNavigationBarRect; 46 47 import android.annotation.Nullable; 48 import android.app.ActivityManager; 49 import android.app.ActivityThread; 50 import android.content.Context; 51 import android.graphics.Canvas; 52 import android.graphics.Paint; 53 import android.graphics.Rect; 54 import android.hardware.HardwareBuffer; 55 import android.os.IBinder; 56 import android.util.Log; 57 import android.view.SurfaceControl; 58 import android.view.ViewGroup; 59 import android.view.WindowInsets; 60 import android.view.WindowManager; 61 62 import com.android.internal.R; 63 import com.android.internal.annotations.VisibleForTesting; 64 import com.android.internal.policy.DecorView; 65 66 /** 67 * Utils class to help draw a snapshot on a surface. 68 * @hide 69 */ 70 public class SnapshotDrawerUtils { 71 private static final String TAG = "SnapshotDrawerUtils"; 72 73 /** 74 * Used to check if toolkitSetFrameRateReadOnly flag is enabled 75 * 76 * @hide 77 */ 78 private static boolean sToolkitSetFrameRateReadOnlyFlagValue = 79 android.view.flags.Flags.toolkitSetFrameRateReadOnly(); 80 81 /** 82 * When creating the starting window, we use the exact same layout flags such that we end up 83 * with a window with the exact same dimensions etc. However, these flags are not used in layout 84 * and might cause other side effects so we exclude them. 85 */ 86 static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE 87 | FLAG_NOT_TOUCHABLE 88 | FLAG_NOT_TOUCH_MODAL 89 | FLAG_ALT_FOCUSABLE_IM 90 | FLAG_NOT_FOCUSABLE 91 | FLAG_HARDWARE_ACCELERATED 92 | FLAG_IGNORE_CHEEK_PRESSES 93 | FLAG_LOCAL_FOCUS_MODE 94 | FLAG_SLIPPERY 95 | FLAG_WATCH_OUTSIDE_TOUCH 96 | FLAG_SPLIT_TOUCH 97 | FLAG_SCALED 98 | FLAG_SECURE 99 | FLAG_DIM_BEHIND; 100 101 /** 102 * The internal object to hold the surface and drawing on it. 103 */ 104 @VisibleForTesting 105 public static class SnapshotSurface { 106 private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); 107 private final SurfaceControl mRootSurface; 108 private final TaskSnapshot mSnapshot; 109 private final CharSequence mTitle; 110 111 private final int mSnapshotW; 112 private final int mSnapshotH; 113 private final int mContainerW; 114 private final int mContainerH; 115 SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot, Rect windowBounds, CharSequence title)116 public SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot, 117 Rect windowBounds, CharSequence title) { 118 mRootSurface = rootSurface; 119 mSnapshot = snapshot; 120 mTitle = title; 121 final HardwareBuffer hwBuffer = snapshot.getHardwareBuffer(); 122 mSnapshotW = hwBuffer.getWidth(); 123 mSnapshotH = hwBuffer.getHeight(); 124 mContainerW = windowBounds.width(); 125 mContainerH = windowBounds.height(); 126 } 127 drawSnapshot(boolean releaseAfterDraw)128 private void drawSnapshot(boolean releaseAfterDraw) { 129 final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); 130 final boolean sizeMismatch = mContainerW != mSnapshotW || mContainerH != mSnapshotH 131 || letterboxInsets.left != 0 || letterboxInsets.top != 0; 132 Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + sizeMismatch); 133 if (sizeMismatch) { 134 // The dimensions of the buffer and the window don't match, so attaching the buffer 135 // will fail. Better create a child window with the exact dimensions and fill the 136 // parent window with the background color! 137 drawSizeMismatchSnapshot(); 138 } else { 139 drawSizeMatchSnapshot(); 140 } 141 142 // In case window manager leaks us, make sure we don't retain the snapshot. 143 if (mSnapshot.getHardwareBuffer() != null) { 144 mSnapshot.getHardwareBuffer().close(); 145 } 146 if (releaseAfterDraw) { 147 mRootSurface.release(); 148 } 149 } 150 drawSizeMatchSnapshot()151 private void drawSizeMatchSnapshot() { 152 mTransaction.setBuffer(mRootSurface, mSnapshot.getHardwareBuffer()) 153 .setColorSpace(mRootSurface, mSnapshot.getColorSpace()) 154 .apply(); 155 } 156 drawSizeMismatchSnapshot()157 private void drawSizeMismatchSnapshot() { 158 final HardwareBuffer buffer = mSnapshot.getHardwareBuffer(); 159 160 // Keep a reference to it such that it doesn't get destroyed when finalized. 161 SurfaceControl childSurfaceControl = new SurfaceControl.Builder() 162 .setName(mTitle + " - task-snapshot-surface") 163 .setBLASTLayer() 164 .setFormat(buffer.getFormat()) 165 .setParent(mRootSurface) 166 .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot") 167 .build(); 168 169 final Rect letterboxInsets = mSnapshot.getLetterboxInsets(); 170 float offsetX = letterboxInsets.left; 171 float offsetY = letterboxInsets.top; 172 // We can just show the surface here as it will still be hidden as the parent is 173 // still hidden. 174 mTransaction.show(childSurfaceControl); 175 176 // Align the snapshot with content area. 177 if (offsetX != 0f || offsetY != 0f) { 178 mTransaction.setPosition(childSurfaceControl, 179 -offsetX * mContainerW / mSnapshot.getTaskSize().x, 180 -offsetY * mContainerH / mSnapshot.getTaskSize().y); 181 } 182 // Scale the mismatch dimensions to fill the target frame. 183 final float scaleX = (float) mContainerW / mSnapshotW; 184 final float scaleY = (float) mContainerH / mSnapshotH; 185 mTransaction.setScale(childSurfaceControl, scaleX, scaleY); 186 mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace()); 187 mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer()); 188 mTransaction.apply(); 189 childSurfaceControl.release(); 190 } 191 } 192 193 /** 194 * Get or create a TaskDescription from a RunningTaskInfo. 195 */ getOrCreateTaskDescription( ActivityManager.RunningTaskInfo runningTaskInfo)196 public static ActivityManager.TaskDescription getOrCreateTaskDescription( 197 ActivityManager.RunningTaskInfo runningTaskInfo) { 198 final ActivityManager.TaskDescription taskDescription; 199 if (runningTaskInfo.taskDescription != null) { 200 taskDescription = runningTaskInfo.taskDescription; 201 } else { 202 taskDescription = new ActivityManager.TaskDescription(); 203 taskDescription.setBackgroundColor(WHITE); 204 } 205 return taskDescription; 206 } 207 208 /** 209 * Help method to draw the snapshot on a surface. 210 */ drawSnapshotOnSurface(WindowManager.LayoutParams lp, SurfaceControl rootSurface, TaskSnapshot snapshot, Rect windowBounds, boolean releaseAfterDraw)211 public static void drawSnapshotOnSurface(WindowManager.LayoutParams lp, 212 SurfaceControl rootSurface, TaskSnapshot snapshot, 213 Rect windowBounds, boolean releaseAfterDraw) { 214 if (windowBounds.isEmpty()) { 215 Log.e(TAG, "Unable to draw snapshot on an empty windowBounds"); 216 return; 217 } 218 final SnapshotSurface drawSurface = new SnapshotSurface( 219 rootSurface, snapshot, windowBounds, lp.getTitle()); 220 drawSurface.drawSnapshot(releaseAfterDraw); 221 } 222 223 /** 224 * Help method to create a layout parameters for a window. 225 */ createLayoutParameters(StartingWindowInfo info, CharSequence title, @WindowManager.LayoutParams.WindowType int windowType, int pixelFormat, IBinder token)226 public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info, 227 CharSequence title, @WindowManager.LayoutParams.WindowType int windowType, 228 int pixelFormat, IBinder token) { 229 final WindowManager.LayoutParams attrs = info.mainWindowLayoutParams; 230 if (attrs == null) { 231 Log.w(TAG, "unable to create taskSnapshot surface "); 232 return null; 233 } 234 final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); 235 236 final int appearance = attrs.insetsFlags.appearance; 237 final int windowFlags = attrs.flags; 238 final int windowPrivateFlags = attrs.privateFlags; 239 240 layoutParams.packageName = attrs.packageName; 241 layoutParams.windowAnimations = attrs.windowAnimations; 242 layoutParams.dimAmount = attrs.dimAmount; 243 layoutParams.type = windowType; 244 layoutParams.format = pixelFormat; 245 layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) 246 | FLAG_NOT_FOCUSABLE 247 | FLAG_NOT_TOUCHABLE; 248 layoutParams.privateFlags = 249 (windowPrivateFlags 250 & (PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS 251 | PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED)) 252 // Setting as trusted overlay to let touches pass through. This is safe because this 253 // window is controlled by the system. 254 | PRIVATE_FLAG_TRUSTED_OVERLAY; 255 layoutParams.token = token; 256 layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 257 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 258 layoutParams.insetsFlags.appearance = appearance; 259 layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior; 260 layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode; 261 layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes()); 262 layoutParams.setFitInsetsSides(attrs.getFitInsetsSides()); 263 layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility()); 264 if (sToolkitSetFrameRateReadOnlyFlagValue) { 265 layoutParams.setFrameRatePowerSavingsBalanced(false); 266 } 267 268 layoutParams.setTitle(title); 269 layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL; 270 return layoutParams; 271 } 272 273 /** 274 * Helper class to draw the background of the system bars in regions the task snapshot isn't 275 * filling the window. 276 */ 277 public static class SystemBarBackgroundPainter { 278 private final Paint mStatusBarPaint = new Paint(); 279 private final Paint mNavigationBarPaint = new Paint(); 280 private final int mStatusBarColor; 281 private final int mNavigationBarColor; 282 private final int mWindowFlags; 283 private final int mWindowPrivateFlags; 284 private final float mScale; 285 private final @WindowInsets.Type.InsetsType int mRequestedVisibleTypes; 286 private final Rect mSystemBarInsets = new Rect(); 287 SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, ActivityManager.TaskDescription taskDescription, float scale, @WindowInsets.Type.InsetsType int requestedVisibleTypes)288 public SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, 289 ActivityManager.TaskDescription taskDescription, float scale, 290 @WindowInsets.Type.InsetsType int requestedVisibleTypes) { 291 mWindowFlags = windowFlags; 292 mWindowPrivateFlags = windowPrivateFlags; 293 mScale = scale; 294 final Context context = ActivityThread.currentActivityThread().getSystemUiContext(); 295 final int semiTransparent = context.getColor( 296 R.color.system_bar_background_semi_transparent); 297 mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS, 298 semiTransparent, taskDescription.getStatusBarColor(), appearance, 299 APPEARANCE_LIGHT_STATUS_BARS, 300 taskDescription.getEnsureStatusBarContrastWhenTransparent(), 301 false /* movesBarColorToScrim */); 302 mNavigationBarColor = DecorView.calculateBarColor(windowFlags, 303 FLAG_TRANSLUCENT_NAVIGATION, semiTransparent, 304 taskDescription.getNavigationBarColor(), appearance, 305 APPEARANCE_LIGHT_NAVIGATION_BARS, 306 taskDescription.getEnsureNavigationBarContrastWhenTransparent() 307 && context.getResources().getBoolean( 308 R.bool.config_navBarNeedsScrim), 309 (windowPrivateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0); 310 mStatusBarPaint.setColor(mStatusBarColor); 311 mNavigationBarPaint.setColor(mNavigationBarColor); 312 mRequestedVisibleTypes = requestedVisibleTypes; 313 } 314 315 /** 316 * Set system bar insets. 317 */ setInsets(Rect systemBarInsets)318 public void setInsets(Rect systemBarInsets) { 319 mSystemBarInsets.set(systemBarInsets); 320 } 321 getStatusBarColorViewHeight()322 int getStatusBarColorViewHeight() { 323 final boolean forceBarBackground = 324 (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; 325 if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( 326 mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, 327 forceBarBackground)) { 328 return (int) (mSystemBarInsets.top * mScale); 329 } else { 330 return 0; 331 } 332 } 333 isNavigationBarColorViewVisible()334 private boolean isNavigationBarColorViewVisible() { 335 final boolean forceBarBackground = 336 (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; 337 return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( 338 mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, 339 forceBarBackground); 340 } 341 342 /** 343 * Draw bar colors to a canvas. 344 */ drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame)345 public void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) { 346 drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight()); 347 drawNavigationBarBackground(c); 348 } 349 drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, int statusBarHeight)350 void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, 351 int statusBarHeight) { 352 if (statusBarHeight > 0 && alpha(mStatusBarColor) != 0 353 && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { 354 final int rightInset = (int) (mSystemBarInsets.right * mScale); 355 final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; 356 c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, 357 mStatusBarPaint); 358 } 359 } 360 drawNavigationBarBackground(Canvas c)361 void drawNavigationBarBackground(Canvas c) { 362 final Rect navigationBarRect = new Rect(); 363 getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, 364 mScale); 365 final boolean visible = isNavigationBarColorViewVisible(); 366 if (visible && alpha(mNavigationBarColor) != 0 367 && !navigationBarRect.isEmpty()) { 368 c.drawRect(navigationBarRect, mNavigationBarPaint); 369 } 370 } 371 } 372 } 373