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