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.wm.shell.startingsurface; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 20 import static android.graphics.Color.WHITE; 21 import static android.graphics.Color.alpha; 22 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 23 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; 24 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; 25 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 26 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 27 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES; 28 import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE; 29 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 30 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 31 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 32 import static android.view.WindowManager.LayoutParams.FLAG_SCALED; 33 import static android.view.WindowManager.LayoutParams.FLAG_SECURE; 34 import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; 35 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 36 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; 37 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS; 38 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; 39 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; 40 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; 41 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; 42 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 43 44 import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; 45 import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; 46 import static com.android.internal.policy.DecorView.getNavigationBarRect; 47 48 import android.annotation.BinderThread; 49 import android.annotation.NonNull; 50 import android.annotation.Nullable; 51 import android.app.ActivityManager; 52 import android.app.ActivityManager.TaskDescription; 53 import android.app.ActivityThread; 54 import android.content.Context; 55 import android.graphics.Canvas; 56 import android.graphics.Color; 57 import android.graphics.GraphicBuffer; 58 import android.graphics.Matrix; 59 import android.graphics.Paint; 60 import android.graphics.PixelFormat; 61 import android.graphics.Point; 62 import android.graphics.Rect; 63 import android.graphics.RectF; 64 import android.hardware.HardwareBuffer; 65 import android.os.Bundle; 66 import android.os.IBinder; 67 import android.os.RemoteException; 68 import android.os.Trace; 69 import android.util.MergedConfiguration; 70 import android.util.Slog; 71 import android.view.IWindowSession; 72 import android.view.InputChannel; 73 import android.view.InsetsSourceControl; 74 import android.view.InsetsState; 75 import android.view.SurfaceControl; 76 import android.view.SurfaceSession; 77 import android.view.View; 78 import android.view.ViewGroup; 79 import android.view.WindowInsets; 80 import android.view.WindowManager; 81 import android.view.WindowManagerGlobal; 82 import android.window.ClientWindowFrames; 83 import android.window.StartingWindowInfo; 84 import android.window.TaskSnapshot; 85 86 import com.android.internal.R; 87 import com.android.internal.annotations.VisibleForTesting; 88 import com.android.internal.policy.DecorView; 89 import com.android.internal.protolog.common.ProtoLog; 90 import com.android.internal.view.BaseIWindow; 91 import com.android.wm.shell.common.ShellExecutor; 92 import com.android.wm.shell.protolog.ShellProtoLogGroup; 93 94 import java.lang.ref.WeakReference; 95 96 /** 97 * This class represents a starting window that shows a snapshot. 98 * 99 * @hide 100 */ 101 public class TaskSnapshotWindow { 102 /** 103 * When creating the starting window, we use the exact same layout flags such that we end up 104 * with a window with the exact same dimensions etc. However, these flags are not used in layout 105 * and might cause other side effects so we exclude them. 106 */ 107 static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE 108 | FLAG_NOT_TOUCHABLE 109 | FLAG_NOT_TOUCH_MODAL 110 | FLAG_ALT_FOCUSABLE_IM 111 | FLAG_NOT_FOCUSABLE 112 | FLAG_HARDWARE_ACCELERATED 113 | FLAG_IGNORE_CHEEK_PRESSES 114 | FLAG_LOCAL_FOCUS_MODE 115 | FLAG_SLIPPERY 116 | FLAG_WATCH_OUTSIDE_TOUCH 117 | FLAG_SPLIT_TOUCH 118 | FLAG_SCALED 119 | FLAG_SECURE; 120 121 private static final String TAG = StartingWindowController.TAG; 122 private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s"; 123 124 private static final long DELAY_REMOVAL_TIME_GENERAL = 100; 125 /** 126 * The max delay time in milliseconds for removing the task snapshot window with IME visible. 127 * Ideally the delay time will be shorter when receiving 128 * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}. 129 */ 130 private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600; 131 132 private final Window mWindow; 133 private final Runnable mClearWindowHandler; 134 private final ShellExecutor mSplashScreenExecutor; 135 private final SurfaceControl mSurfaceControl; 136 private final IWindowSession mSession; 137 private final Rect mTaskBounds; 138 private final Rect mFrame = new Rect(); 139 private final Rect mSystemBarInsets = new Rect(); 140 private TaskSnapshot mSnapshot; 141 private final RectF mTmpSnapshotSize = new RectF(); 142 private final RectF mTmpDstFrame = new RectF(); 143 private final CharSequence mTitle; 144 private boolean mHasDrawn; 145 private boolean mSizeMismatch; 146 private final Paint mBackgroundPaint = new Paint(); 147 private final int mActivityType; 148 private final int mStatusBarColor; 149 private final SystemBarBackgroundPainter mSystemBarBackgroundPainter; 150 private final int mOrientationOnCreation; 151 private final SurfaceControl.Transaction mTransaction; 152 private final Matrix mSnapshotMatrix = new Matrix(); 153 private final float[] mTmpFloat9 = new float[9]; 154 private final Runnable mScheduledRunnable = this::removeImmediately; 155 private final boolean mHasImeSurface; 156 create(StartingWindowInfo info, IBinder appToken, TaskSnapshot snapshot, ShellExecutor splashScreenExecutor, @NonNull Runnable clearWindowHandler)157 static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken, 158 TaskSnapshot snapshot, ShellExecutor splashScreenExecutor, 159 @NonNull Runnable clearWindowHandler) { 160 final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; 161 final int taskId = runningTaskInfo.taskId; 162 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 163 "create taskSnapshot surface for task: %d", taskId); 164 165 final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; 166 final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; 167 final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; 168 if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) { 169 Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId); 170 return null; 171 } 172 final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); 173 174 final int appearance = attrs.insetsFlags.appearance; 175 final int windowFlags = attrs.flags; 176 final int windowPrivateFlags = attrs.privateFlags; 177 178 layoutParams.packageName = mainWindowParams.packageName; 179 layoutParams.windowAnimations = mainWindowParams.windowAnimations; 180 layoutParams.dimAmount = mainWindowParams.dimAmount; 181 layoutParams.type = TYPE_APPLICATION_STARTING; 182 layoutParams.format = snapshot.getHardwareBuffer().getFormat(); 183 layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES) 184 | FLAG_NOT_FOCUSABLE 185 | FLAG_NOT_TOUCHABLE; 186 // Setting as trusted overlay to let touches pass through. This is safe because this 187 // window is controlled by the system. 188 layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) 189 | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST; 190 layoutParams.token = appToken; 191 layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 192 layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 193 layoutParams.insetsFlags.appearance = appearance; 194 layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior; 195 layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode; 196 layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes()); 197 layoutParams.setFitInsetsSides(attrs.getFitInsetsSides()); 198 layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility()); 199 200 layoutParams.setTitle(String.format(TITLE_FORMAT, taskId)); 201 202 final Point taskSize = snapshot.getTaskSize(); 203 final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y); 204 final int orientation = snapshot.getOrientation(); 205 final int activityType = runningTaskInfo.topActivityType; 206 final int displayId = runningTaskInfo.displayId; 207 208 final IWindowSession session = WindowManagerGlobal.getWindowSession(); 209 final SurfaceControl surfaceControl = new SurfaceControl(); 210 final ClientWindowFrames tmpFrames = new ClientWindowFrames(); 211 212 final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0]; 213 final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); 214 215 final TaskDescription taskDescription; 216 if (runningTaskInfo.taskDescription != null) { 217 taskDescription = runningTaskInfo.taskDescription; 218 } else { 219 taskDescription = new TaskDescription(); 220 taskDescription.setBackgroundColor(WHITE); 221 } 222 223 final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow( 224 surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance, 225 windowFlags, windowPrivateFlags, taskBounds, orientation, activityType, 226 topWindowInsetsState, clearWindowHandler, splashScreenExecutor); 227 final Window window = snapshotSurface.mWindow; 228 229 final InsetsState tmpInsetsState = new InsetsState(); 230 final InputChannel tmpInputChannel = new InputChannel(); 231 final float[] sizeCompatScale = { 1f }; 232 233 try { 234 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay"); 235 final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId, 236 info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls, 237 new Rect(), sizeCompatScale); 238 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 239 if (res < 0) { 240 Slog.w(TAG, "Failed to add snapshot starting window res=" + res); 241 return null; 242 } 243 } catch (RemoteException e) { 244 snapshotSurface.clearWindowSynced(); 245 } 246 window.setOuter(snapshotSurface); 247 try { 248 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout"); 249 session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0, 250 tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState, 251 tmpControls, new Bundle()); 252 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 253 } catch (RemoteException e) { 254 snapshotSurface.clearWindowSynced(); 255 } 256 257 final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState); 258 snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets); 259 snapshotSurface.drawSnapshot(); 260 return snapshotSurface; 261 } 262 TaskSnapshotWindow(SurfaceControl surfaceControl, TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription, int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds, int currentOrientation, int activityType, InsetsState topWindowInsetsState, Runnable clearWindowHandler, ShellExecutor splashScreenExecutor)263 public TaskSnapshotWindow(SurfaceControl surfaceControl, 264 TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription, 265 int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds, 266 int currentOrientation, int activityType, InsetsState topWindowInsetsState, 267 Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) { 268 mSplashScreenExecutor = splashScreenExecutor; 269 mSession = WindowManagerGlobal.getWindowSession(); 270 mWindow = new Window(); 271 mWindow.setSession(mSession); 272 mSurfaceControl = surfaceControl; 273 mSnapshot = snapshot; 274 mTitle = title; 275 int backgroundColor = taskDescription.getBackgroundColor(); 276 mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE); 277 mTaskBounds = taskBounds; 278 mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags, 279 windowPrivateFlags, appearance, taskDescription, 1f, topWindowInsetsState); 280 mStatusBarColor = taskDescription.getStatusBarColor(); 281 mOrientationOnCreation = currentOrientation; 282 mActivityType = activityType; 283 mTransaction = new SurfaceControl.Transaction(); 284 mClearWindowHandler = clearWindowHandler; 285 mHasImeSurface = snapshot.hasImeSurface(); 286 } 287 getBackgroundColor()288 int getBackgroundColor() { 289 return mBackgroundPaint.getColor(); 290 } 291 hasImeSurface()292 boolean hasImeSurface() { 293 return mHasImeSurface; 294 } 295 296 /** 297 * Ask system bar background painter to draw status bar background. 298 * @hide 299 */ drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame)300 public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) { 301 mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame, 302 mSystemBarBackgroundPainter.getStatusBarColorViewHeight()); 303 } 304 305 /** 306 * Ask system bar background painter to draw navigation bar background. 307 * @hide 308 */ drawNavigationBarBackground(Canvas c)309 public void drawNavigationBarBackground(Canvas c) { 310 mSystemBarBackgroundPainter.drawNavigationBarBackground(c); 311 } 312 scheduleRemove(boolean deferRemoveForIme)313 void scheduleRemove(boolean deferRemoveForIme) { 314 // Show the latest content as soon as possible for unlocking to home. 315 if (mActivityType == ACTIVITY_TYPE_HOME) { 316 removeImmediately(); 317 return; 318 } 319 mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); 320 final long delayRemovalTime = mHasImeSurface && deferRemoveForIme 321 ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE 322 : DELAY_REMOVAL_TIME_GENERAL; 323 mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime); 324 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 325 "Defer removing snapshot surface in %d", delayRemovalTime); 326 } 327 removeImmediately()328 void removeImmediately() { 329 mSplashScreenExecutor.removeCallbacks(mScheduledRunnable); 330 try { 331 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 332 "Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn); 333 mSession.remove(mWindow); 334 } catch (RemoteException e) { 335 // nothing 336 } 337 } 338 339 /** 340 * Set frame size. 341 * @hide 342 */ setFrames(Rect frame, Rect systemBarInsets)343 public void setFrames(Rect frame, Rect systemBarInsets) { 344 mFrame.set(frame); 345 mSystemBarInsets.set(systemBarInsets); 346 final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); 347 mSizeMismatch = (mFrame.width() != snapshot.getWidth() 348 || mFrame.height() != snapshot.getHeight()); 349 mSystemBarBackgroundPainter.setInsets(systemBarInsets); 350 } 351 getSystemBarInsets(Rect frame, InsetsState state)352 static Rect getSystemBarInsets(Rect frame, InsetsState state) { 353 return state.calculateInsets(frame, WindowInsets.Type.systemBars(), 354 false /* ignoreVisibility */).toRect(); 355 } 356 drawSnapshot()357 private void drawSnapshot() { 358 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, 359 "Drawing snapshot surface sizeMismatch=%b", mSizeMismatch); 360 if (mSizeMismatch) { 361 // The dimensions of the buffer and the window don't match, so attaching the buffer 362 // will fail. Better create a child window with the exact dimensions and fill the parent 363 // window with the background color! 364 drawSizeMismatchSnapshot(); 365 } else { 366 drawSizeMatchSnapshot(); 367 } 368 mHasDrawn = true; 369 reportDrawn(); 370 371 // In case window manager leaks us, make sure we don't retain the snapshot. 372 if (mSnapshot.getHardwareBuffer() != null) { 373 mSnapshot.getHardwareBuffer().close(); 374 } 375 mSnapshot = null; 376 mSurfaceControl.release(); 377 } 378 drawSizeMatchSnapshot()379 private void drawSizeMatchSnapshot() { 380 mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer()) 381 .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace()) 382 .apply(); 383 } 384 drawSizeMismatchSnapshot()385 private void drawSizeMismatchSnapshot() { 386 final HardwareBuffer buffer = mSnapshot.getHardwareBuffer(); 387 final SurfaceSession session = new SurfaceSession(); 388 389 // We consider nearly matched dimensions as there can be rounding errors and the user won't 390 // notice very minute differences from scaling one dimension more than the other 391 final boolean aspectRatioMismatch = Math.abs( 392 ((float) buffer.getWidth() / buffer.getHeight()) 393 - ((float) mFrame.width() / mFrame.height())) > 0.01f; 394 395 // Keep a reference to it such that it doesn't get destroyed when finalized. 396 SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session) 397 .setName(mTitle + " - task-snapshot-surface") 398 .setBLASTLayer() 399 .setFormat(buffer.getFormat()) 400 .setParent(mSurfaceControl) 401 .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot") 402 .build(); 403 404 final Rect frame; 405 // We can just show the surface here as it will still be hidden as the parent is 406 // still hidden. 407 mTransaction.show(childSurfaceControl); 408 if (aspectRatioMismatch) { 409 // Clip off ugly navigation bar. 410 final Rect crop = calculateSnapshotCrop(); 411 frame = calculateSnapshotFrame(crop); 412 mTransaction.setWindowCrop(childSurfaceControl, crop); 413 mTransaction.setPosition(childSurfaceControl, frame.left, frame.top); 414 mTmpSnapshotSize.set(crop); 415 mTmpDstFrame.set(frame); 416 } else { 417 frame = null; 418 mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight()); 419 mTmpDstFrame.set(mFrame); 420 mTmpDstFrame.offsetTo(0, 0); 421 } 422 423 // Scale the mismatch dimensions to fill the task bounds 424 mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL); 425 mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9); 426 mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace()); 427 mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer()); 428 429 if (aspectRatioMismatch) { 430 GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(), 431 PixelFormat.RGBA_8888, 432 GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER 433 | GraphicBuffer.USAGE_SW_WRITE_RARELY); 434 // TODO: Support this on HardwareBuffer 435 final Canvas c = background.lockCanvas(); 436 drawBackgroundAndBars(c, frame); 437 background.unlockCanvasAndPost(c); 438 mTransaction.setBuffer(mSurfaceControl, 439 HardwareBuffer.createFromGraphicBuffer(background)); 440 } 441 mTransaction.apply(); 442 childSurfaceControl.release(); 443 } 444 445 /** 446 * Calculates the snapshot crop in snapshot coordinate space. 447 * 448 * @return crop rect in snapshot coordinate space. 449 */ calculateSnapshotCrop()450 public Rect calculateSnapshotCrop() { 451 final Rect rect = new Rect(); 452 final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); 453 rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight()); 454 final Rect insets = mSnapshot.getContentInsets(); 455 456 final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; 457 final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; 458 459 // Let's remove all system decorations except the status bar, but only if the task is at the 460 // very top of the screen. 461 final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0; 462 rect.inset((int) (insets.left * scaleX), 463 isTop ? 0 : (int) (insets.top * scaleY), 464 (int) (insets.right * scaleX), 465 (int) (insets.bottom * scaleY)); 466 return rect; 467 } 468 469 /** 470 * Calculates the snapshot frame in window coordinate space from crop. 471 * 472 * @param crop rect that is in snapshot coordinate space. 473 */ calculateSnapshotFrame(Rect crop)474 public Rect calculateSnapshotFrame(Rect crop) { 475 final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer(); 476 final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x; 477 final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y; 478 479 // Rescale the frame from snapshot to window coordinate space 480 final Rect frame = new Rect(0, 0, 481 (int) (crop.width() / scaleX + 0.5f), 482 (int) (crop.height() / scaleY + 0.5f) 483 ); 484 485 // However, we also need to make space for the navigation bar on the left side. 486 frame.offset(mSystemBarInsets.left, 0); 487 return frame; 488 } 489 490 /** 491 * Draw status bar and navigation bar background. 492 * @hide 493 */ drawBackgroundAndBars(Canvas c, Rect frame)494 public void drawBackgroundAndBars(Canvas c, Rect frame) { 495 final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight(); 496 final boolean fillHorizontally = c.getWidth() > frame.right; 497 final boolean fillVertically = c.getHeight() > frame.bottom; 498 if (fillHorizontally) { 499 c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0, 500 c.getWidth(), fillVertically 501 ? frame.bottom 502 : c.getHeight(), 503 mBackgroundPaint); 504 } 505 if (fillVertically) { 506 c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint); 507 } 508 mSystemBarBackgroundPainter.drawDecors(c, frame); 509 } 510 511 /** 512 * Clear window from drawer, must be post on main executor. 513 */ clearWindowSynced()514 private void clearWindowSynced() { 515 mSplashScreenExecutor.executeDelayed(mClearWindowHandler, 0); 516 } 517 reportDrawn()518 private void reportDrawn() { 519 try { 520 mSession.finishDrawing(mWindow, null /* postDrawTransaction */, Integer.MAX_VALUE); 521 } catch (RemoteException e) { 522 clearWindowSynced(); 523 } 524 } 525 526 static class Window extends BaseIWindow { 527 private WeakReference<TaskSnapshotWindow> mOuter; 528 setOuter(TaskSnapshotWindow outer)529 public void setOuter(TaskSnapshotWindow outer) { 530 mOuter = new WeakReference<>(outer); 531 } 532 533 @BinderThread 534 @Override resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode)535 public void resized(ClientWindowFrames frames, boolean reportDraw, 536 MergedConfiguration mergedConfiguration, InsetsState insetsState, 537 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId, 538 int resizeMode) { 539 final TaskSnapshotWindow snapshot = mOuter.get(); 540 if (snapshot == null) { 541 return; 542 } 543 snapshot.mSplashScreenExecutor.execute(() -> { 544 if (mergedConfiguration != null 545 && snapshot.mOrientationOnCreation 546 != mergedConfiguration.getMergedConfiguration().orientation) { 547 // The orientation of the screen is changing. We better remove the snapshot 548 // ASAP as we are going to wait on the new window in any case to unfreeze 549 // the screen, and the starting window is not needed anymore. 550 snapshot.clearWindowSynced(); 551 } else if (reportDraw) { 552 if (snapshot.mHasDrawn) { 553 snapshot.reportDrawn(); 554 } 555 } 556 }); 557 } 558 } 559 560 /** 561 * Helper class to draw the background of the system bars in regions the task snapshot isn't 562 * filling the window. 563 */ 564 static class SystemBarBackgroundPainter { 565 private final Paint mStatusBarPaint = new Paint(); 566 private final Paint mNavigationBarPaint = new Paint(); 567 private final int mStatusBarColor; 568 private final int mNavigationBarColor; 569 private final int mWindowFlags; 570 private final int mWindowPrivateFlags; 571 private final float mScale; 572 private final InsetsState mInsetsState; 573 private final Rect mSystemBarInsets = new Rect(); 574 SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, TaskDescription taskDescription, float scale, InsetsState insetsState)575 SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance, 576 TaskDescription taskDescription, float scale, InsetsState insetsState) { 577 mWindowFlags = windowFlags; 578 mWindowPrivateFlags = windowPrivateFlags; 579 mScale = scale; 580 final Context context = ActivityThread.currentActivityThread().getSystemUiContext(); 581 final int semiTransparent = context.getColor( 582 R.color.system_bar_background_semi_transparent); 583 mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS, 584 semiTransparent, taskDescription.getStatusBarColor(), appearance, 585 APPEARANCE_LIGHT_STATUS_BARS, 586 taskDescription.getEnsureStatusBarContrastWhenTransparent()); 587 mNavigationBarColor = DecorView.calculateBarColor(windowFlags, 588 FLAG_TRANSLUCENT_NAVIGATION, semiTransparent, 589 taskDescription.getNavigationBarColor(), appearance, 590 APPEARANCE_LIGHT_NAVIGATION_BARS, 591 taskDescription.getEnsureNavigationBarContrastWhenTransparent() 592 && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim)); 593 mStatusBarPaint.setColor(mStatusBarColor); 594 mNavigationBarPaint.setColor(mNavigationBarColor); 595 mInsetsState = insetsState; 596 } 597 setInsets(Rect systemBarInsets)598 void setInsets(Rect systemBarInsets) { 599 mSystemBarInsets.set(systemBarInsets); 600 } 601 getStatusBarColorViewHeight()602 int getStatusBarColorViewHeight() { 603 final boolean forceBarBackground = 604 (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; 605 if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( 606 mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) { 607 return (int) (mSystemBarInsets.top * mScale); 608 } else { 609 return 0; 610 } 611 } 612 isNavigationBarColorViewVisible()613 private boolean isNavigationBarColorViewVisible() { 614 final boolean forceBarBackground = 615 (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0; 616 return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( 617 mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground); 618 } 619 drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame)620 void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) { 621 drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight()); 622 drawNavigationBarBackground(c); 623 } 624 drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, int statusBarHeight)625 void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, 626 int statusBarHeight) { 627 if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 628 && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { 629 final int rightInset = (int) (mSystemBarInsets.right * mScale); 630 final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; 631 c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint); 632 } 633 } 634 635 @VisibleForTesting drawNavigationBarBackground(Canvas c)636 void drawNavigationBarBackground(Canvas c) { 637 final Rect navigationBarRect = new Rect(); 638 getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, 639 mScale); 640 final boolean visible = isNavigationBarColorViewVisible(); 641 if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { 642 c.drawRect(navigationBarRect, mNavigationBarPaint); 643 } 644 } 645 } 646 } 647