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.windowdecor; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 21 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED; 22 import static android.view.WindowInsets.Type.captionBar; 23 import static android.view.WindowInsets.Type.mandatorySystemGestures; 24 import static android.view.WindowInsets.Type.statusBars; 25 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 26 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.app.ActivityManager.RunningTaskInfo; 30 import android.app.WindowConfiguration.WindowingMode; 31 import android.content.Context; 32 import android.content.res.Configuration; 33 import android.content.res.Resources; 34 import android.graphics.Color; 35 import android.graphics.PixelFormat; 36 import android.graphics.Point; 37 import android.graphics.Rect; 38 import android.graphics.Region; 39 import android.os.Binder; 40 import android.os.Trace; 41 import android.view.Display; 42 import android.view.InsetsSource; 43 import android.view.InsetsState; 44 import android.view.LayoutInflater; 45 import android.view.SurfaceControl; 46 import android.view.SurfaceControlViewHost; 47 import android.view.View; 48 import android.view.WindowManager; 49 import android.view.WindowlessWindowManager; 50 import android.window.SurfaceSyncGroup; 51 import android.window.TaskConstants; 52 import android.window.WindowContainerToken; 53 import android.window.WindowContainerTransaction; 54 55 import com.android.internal.annotations.VisibleForTesting; 56 import com.android.wm.shell.ShellTaskOrganizer; 57 import com.android.wm.shell.common.DisplayController; 58 import com.android.wm.shell.shared.DesktopModeStatus; 59 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement; 60 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer; 61 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.List; 65 import java.util.Objects; 66 import java.util.function.Supplier; 67 68 /** 69 * Manages a container surface and a windowless window to show window decoration. Responsible to 70 * update window decoration window state and layout parameters on task info changes and so that 71 * window decoration is in correct state and bounds. 72 * 73 * The container surface is a child of the task display area in the same display, so that window 74 * decorations can be drawn out of the task bounds and receive input events from out of the task 75 * bounds to support drag resizing. 76 * 77 * The windowless window that hosts window decoration is positioned in front of all activities, to 78 * allow the foreground activity to draw its own background behind window decorations, such as 79 * the window captions. 80 * 81 * @param <T> The type of the root view 82 */ 83 public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> 84 implements AutoCloseable { 85 86 /** 87 * The Z-order of {@link #mCaptionContainerSurface}. 88 * <p> 89 * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by 90 * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input 91 * prior to caption view itself, treating corner inputs as resize events rather than 92 * repositioning. 93 */ 94 static final int CAPTION_LAYER_Z_ORDER = -1; 95 /** 96 * The Z-order of the task input sink in {@link DragPositioningCallback}. 97 * <p> 98 * This task input sink is used to prevent undesired dispatching of motion events out of task 99 * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle 100 * input events first. 101 */ 102 static final int INPUT_SINK_Z_ORDER = -2; 103 104 /** 105 * System-wide context. Only used to create context with overridden configurations. 106 */ 107 final Context mContext; 108 final DisplayController mDisplayController; 109 final ShellTaskOrganizer mTaskOrganizer; 110 final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier; 111 final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; 112 final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier; 113 final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory; 114 private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = 115 new DisplayController.OnDisplaysChangedListener() { 116 @Override 117 public void onDisplayAdded(int displayId) { 118 if (mTaskInfo.displayId != displayId) { 119 return; 120 } 121 122 mDisplayController.removeDisplayWindowListener(this); 123 relayout(mTaskInfo); 124 } 125 }; 126 127 RunningTaskInfo mTaskInfo; 128 int mLayoutResId; 129 final SurfaceControl mTaskSurface; 130 131 Display mDisplay; 132 Context mDecorWindowContext; 133 SurfaceControl mDecorationContainerSurface; 134 135 SurfaceControl mCaptionContainerSurface; 136 private WindowlessWindowManager mCaptionWindowManager; 137 private SurfaceControlViewHost mViewHost; 138 private Configuration mWindowDecorConfig; 139 TaskDragResizer mTaskDragResizer; 140 private boolean mIsCaptionVisible; 141 142 /** The most recent set of insets applied to this window decoration. */ 143 private WindowDecorationInsets mWindowDecorationInsets; 144 private final Binder mOwner = new Binder(); 145 private final float[] mTmpColor = new float[3]; 146 WindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, SurfaceControl taskSurface)147 WindowDecoration( 148 Context context, 149 DisplayController displayController, 150 ShellTaskOrganizer taskOrganizer, 151 RunningTaskInfo taskInfo, 152 SurfaceControl taskSurface) { 153 this(context, displayController, taskOrganizer, taskInfo, taskSurface, 154 SurfaceControl.Builder::new, SurfaceControl.Transaction::new, 155 WindowContainerTransaction::new, SurfaceControl::new, 156 new SurfaceControlViewHostFactory() {}); 157 } 158 WindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, @NonNull SurfaceControl taskSurface, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory)159 WindowDecoration( 160 Context context, 161 DisplayController displayController, 162 ShellTaskOrganizer taskOrganizer, 163 RunningTaskInfo taskInfo, 164 @NonNull SurfaceControl taskSurface, 165 Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, 166 Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, 167 Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, 168 Supplier<SurfaceControl> surfaceControlSupplier, 169 SurfaceControlViewHostFactory surfaceControlViewHostFactory) { 170 mContext = context; 171 mDisplayController = displayController; 172 mTaskOrganizer = taskOrganizer; 173 mTaskInfo = taskInfo; 174 mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier); 175 mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier; 176 mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; 177 mWindowContainerTransactionSupplier = windowContainerTransactionSupplier; 178 mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; 179 180 mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); 181 } 182 183 /** 184 * Used by {@link WindowDecoration} to trigger a new relayout because the requirements for a 185 * relayout weren't satisfied are satisfied now. 186 * 187 * @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the 188 * constructor. 189 */ relayout(RunningTaskInfo taskInfo)190 abstract void relayout(RunningTaskInfo taskInfo); 191 192 /** 193 * Used by the {@link DragPositioningCallback} associated with the implementing class to 194 * enforce drags ending in a valid position. A null result means no restriction. 195 */ 196 @Nullable calculateValidDragArea()197 abstract Rect calculateValidDragArea(); 198 relayout(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult)199 void relayout(RelayoutParams params, SurfaceControl.Transaction startT, 200 SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, 201 RelayoutResult<T> outResult) { 202 updateViewsAndSurfaces(params, startT, finishT, wct, rootView, outResult); 203 if (outResult.mRootView != null) { 204 updateViewHost(params, startT, outResult); 205 } 206 } 207 updateViewsAndSurfaces(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult)208 protected void updateViewsAndSurfaces(RelayoutParams params, 209 SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, 210 WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) { 211 outResult.reset(); 212 if (params.mRunningTaskInfo != null) { 213 mTaskInfo = params.mRunningTaskInfo; 214 } 215 final int oldLayoutResId = mLayoutResId; 216 mLayoutResId = params.mLayoutResId; 217 218 if (!mTaskInfo.isVisible) { 219 releaseViews(wct); 220 finishT.hide(mTaskSurface); 221 return; 222 } 223 224 inflateIfNeeded(params, wct, rootView, oldLayoutResId, outResult); 225 if (outResult.mRootView == null) { 226 // Didn't manage to create a root view, early out. 227 return; 228 } 229 rootView = null; // Clear it just in case we use it accidentally 230 231 updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId); 232 233 final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds(); 234 outResult.mWidth = taskBounds.width(); 235 outResult.mHeight = taskBounds.height(); 236 outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused); 237 final Resources resources = mDecorWindowContext.getResources(); 238 outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); 239 outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL 240 ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width(); 241 outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2; 242 243 updateDecorationContainerSurface(startT, outResult); 244 updateCaptionContainerSurface(startT, outResult); 245 updateCaptionInsets(params, wct, outResult, taskBounds); 246 updateTaskSurface(params, startT, finishT, outResult); 247 } 248 inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct, T rootView, int oldLayoutResId, RelayoutResult<T> outResult)249 private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct, 250 T rootView, int oldLayoutResId, RelayoutResult<T> outResult) { 251 if (rootView == null && params.mLayoutResId == 0) { 252 throw new IllegalArgumentException("layoutResId and rootView can't both be invalid."); 253 } 254 255 outResult.mRootView = rootView; 256 final int oldDensityDpi = mWindowDecorConfig != null 257 ? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED; 258 final int oldNightMode = mWindowDecorConfig != null 259 ? (mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) 260 : Configuration.UI_MODE_NIGHT_UNDEFINED; 261 mWindowDecorConfig = params.mWindowDecorConfig != null ? params.mWindowDecorConfig 262 : mTaskInfo.getConfiguration(); 263 final int newDensityDpi = mWindowDecorConfig.densityDpi; 264 final int newNightMode = mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; 265 if (oldDensityDpi != newDensityDpi 266 || mDisplay == null 267 || mDisplay.getDisplayId() != mTaskInfo.displayId 268 || oldLayoutResId != mLayoutResId 269 || oldNightMode != newNightMode 270 || mDecorWindowContext == null) { 271 releaseViews(wct); 272 273 if (!obtainDisplayOrRegisterListener()) { 274 outResult.mRootView = null; 275 return; 276 } 277 mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig); 278 mDecorWindowContext.setTheme(mContext.getThemeResId()); 279 if (params.mLayoutResId != 0) { 280 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) 281 .inflate(params.mLayoutResId, null); 282 } 283 } 284 285 if (outResult.mRootView == null) { 286 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) 287 .inflate(params.mLayoutResId, null); 288 } 289 } 290 updateDecorationContainerSurface( SurfaceControl.Transaction startT, RelayoutResult<T> outResult)291 private void updateDecorationContainerSurface( 292 SurfaceControl.Transaction startT, RelayoutResult<T> outResult) { 293 if (mDecorationContainerSurface == null) { 294 final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); 295 mDecorationContainerSurface = builder 296 .setName("Decor container of Task=" + mTaskInfo.taskId) 297 .setContainerLayer() 298 .setParent(mTaskSurface) 299 .setCallsite("WindowDecoration.updateDecorationContainerSurface") 300 .build(); 301 302 startT.setTrustedOverlay(mDecorationContainerSurface, true) 303 .setLayer(mDecorationContainerSurface, 304 TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS); 305 } 306 307 startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) 308 .show(mDecorationContainerSurface); 309 } 310 updateCaptionContainerSurface( SurfaceControl.Transaction startT, RelayoutResult<T> outResult)311 private void updateCaptionContainerSurface( 312 SurfaceControl.Transaction startT, RelayoutResult<T> outResult) { 313 if (mCaptionContainerSurface == null) { 314 final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); 315 mCaptionContainerSurface = builder 316 .setName("Caption container of Task=" + mTaskInfo.taskId) 317 .setContainerLayer() 318 .setParent(mDecorationContainerSurface) 319 .setCallsite("WindowDecoration.updateCaptionContainerSurface") 320 .build(); 321 } 322 323 startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth, 324 outResult.mCaptionHeight) 325 .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */) 326 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER) 327 .show(mCaptionContainerSurface); 328 } 329 updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct, RelayoutResult<T> outResult, Rect taskBounds)330 private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct, 331 RelayoutResult<T> outResult, Rect taskBounds) { 332 if (!mIsCaptionVisible) { 333 if (mWindowDecorationInsets != null) { 334 mWindowDecorationInsets.remove(wct); 335 mWindowDecorationInsets = null; 336 } 337 return; 338 } 339 // Caption inset is the full width of the task with the |captionHeight| and 340 // positioned at the top of the task bounds, also in absolute coordinates. 341 // So just reuse the task bounds and adjust the bottom coordinate. 342 final Rect captionInsetsRect = new Rect(taskBounds); 343 captionInsetsRect.bottom = captionInsetsRect.top + outResult.mCaptionHeight; 344 345 // Caption bounding rectangles: these are optional, and are used to present finer 346 // insets than traditional |Insets| to apps about where their content is occluded. 347 // These are also in absolute coordinates. 348 final Rect[] boundingRects; 349 final int numOfElements = params.mOccludingCaptionElements.size(); 350 if (numOfElements == 0) { 351 boundingRects = null; 352 } else { 353 // The customizable region can at most be equal to the caption bar. 354 if (params.hasInputFeatureSpy()) { 355 outResult.mCustomizableCaptionRegion.set(captionInsetsRect); 356 } 357 final Resources resources = mDecorWindowContext.getResources(); 358 boundingRects = new Rect[numOfElements]; 359 for (int i = 0; i < numOfElements; i++) { 360 final OccludingCaptionElement element = 361 params.mOccludingCaptionElements.get(i); 362 final int elementWidthPx = 363 resources.getDimensionPixelSize(element.mWidthResId); 364 boundingRects[i] = 365 calculateBoundingRect(element, elementWidthPx, captionInsetsRect); 366 // Subtract the regions used by the caption elements, the rest is 367 // customizable. 368 if (params.hasInputFeatureSpy()) { 369 outResult.mCustomizableCaptionRegion.op(boundingRects[i], 370 Region.Op.DIFFERENCE); 371 } 372 } 373 } 374 375 final WindowDecorationInsets newInsets = new WindowDecorationInsets( 376 mTaskInfo.token, mOwner, captionInsetsRect, boundingRects); 377 if (!newInsets.equals(mWindowDecorationInsets)) { 378 // Add or update this caption as an insets source. 379 mWindowDecorationInsets = newInsets; 380 mWindowDecorationInsets.addOrUpdate(wct); 381 } 382 } 383 updateTaskSurface(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, RelayoutResult<T> outResult)384 private void updateTaskSurface(RelayoutParams params, SurfaceControl.Transaction startT, 385 SurfaceControl.Transaction finishT, RelayoutResult<T> outResult) { 386 if (params.mSetTaskPositionAndCrop) { 387 final Point taskPosition = mTaskInfo.positionInParent; 388 startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); 389 finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight) 390 .setPosition(mTaskSurface, taskPosition.x, taskPosition.y); 391 } 392 393 float shadowRadius; 394 if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { 395 // Shadow is not needed for fullscreen tasks 396 shadowRadius = 0; 397 } else { 398 shadowRadius = 399 loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId); 400 } 401 startT.setShadowRadius(mTaskSurface, shadowRadius).show(mTaskSurface); 402 finishT.setShadowRadius(mTaskSurface, shadowRadius); 403 404 if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { 405 if (!DesktopModeStatus.isVeiledResizeEnabled()) { 406 // When fluid resize is enabled, add a background to freeform tasks 407 int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); 408 mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; 409 mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; 410 mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; 411 startT.setColor(mTaskSurface, mTmpColor); 412 } 413 startT.setCornerRadius(mTaskSurface, params.mCornerRadius); 414 finishT.setCornerRadius(mTaskSurface, params.mCornerRadius); 415 } else if (!DesktopModeStatus.isVeiledResizeEnabled()) { 416 startT.unsetColor(mTaskSurface); 417 } 418 } 419 420 /** 421 * Updates a {@link SurfaceControlViewHost} to connect the window decoration surfaces with our 422 * View hierarchy. 423 * 424 * @param params parameters to use from the last relayout 425 * @param onDrawTransaction a transaction to apply in sync with #onDraw 426 * @param outResult results to use from the last relayout 427 * 428 */ updateViewHost(RelayoutParams params, SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult)429 protected void updateViewHost(RelayoutParams params, 430 SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult) { 431 Trace.beginSection("CaptionViewHostLayout"); 432 if (mCaptionWindowManager == null) { 433 // Put caption under a container surface because ViewRootImpl sets the destination frame 434 // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. 435 mCaptionWindowManager = new WindowlessWindowManager( 436 mTaskInfo.getConfiguration(), mCaptionContainerSurface, 437 null /* hostInputToken */); 438 } 439 mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration()); 440 final WindowManager.LayoutParams lp = 441 new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight, 442 TYPE_APPLICATION, 443 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); 444 lp.setTitle("Caption of Task=" + mTaskInfo.taskId); 445 lp.setTrustedOverlay(); 446 lp.inputFeatures = params.mInputFeatures; 447 if (mViewHost == null) { 448 Trace.beginSection("CaptionViewHostLayout-new"); 449 mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, 450 mCaptionWindowManager); 451 if (params.mApplyStartTransactionOnDraw) { 452 if (onDrawTransaction == null) { 453 throw new IllegalArgumentException("Trying to sync a null Transaction"); 454 } 455 mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); 456 } 457 mViewHost.setView(outResult.mRootView, lp); 458 Trace.endSection(); 459 } else { 460 Trace.beginSection("CaptionViewHostLayout-relayout"); 461 if (params.mApplyStartTransactionOnDraw) { 462 if (onDrawTransaction == null) { 463 throw new IllegalArgumentException("Trying to sync a null Transaction"); 464 } 465 mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); 466 } 467 mViewHost.relayout(lp); 468 Trace.endSection(); 469 } 470 Trace.endSection(); // CaptionViewHostLayout 471 } 472 calculateBoundingRect(@onNull OccludingCaptionElement element, int elementWidthPx, @NonNull Rect captionRect)473 private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element, 474 int elementWidthPx, @NonNull Rect captionRect) { 475 switch (element.mAlignment) { 476 case START -> { 477 return new Rect(0, 0, elementWidthPx, captionRect.height()); 478 } 479 case END -> { 480 return new Rect(captionRect.width() - elementWidthPx, 0, 481 captionRect.width(), captionRect.height()); 482 } 483 } 484 throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment); 485 } 486 487 /** 488 * Checks if task has entered/exited immersive mode and requires a change in caption visibility. 489 */ updateCaptionVisibility(View rootView, int displayId)490 private void updateCaptionVisibility(View rootView, int displayId) { 491 final InsetsState insetsState = mDisplayController.getInsetsState(displayId); 492 for (int i = 0; i < insetsState.sourceSize(); i++) { 493 final InsetsSource source = insetsState.sourceAt(i); 494 if (source.getType() != statusBars()) { 495 continue; 496 } 497 498 mIsCaptionVisible = source.isVisible(); 499 setCaptionVisibility(rootView, mIsCaptionVisible); 500 501 return; 502 } 503 } 504 setTaskDragResizer(TaskDragResizer taskDragResizer)505 void setTaskDragResizer(TaskDragResizer taskDragResizer) { 506 mTaskDragResizer = taskDragResizer; 507 } 508 setCaptionVisibility(View rootView, boolean visible)509 private void setCaptionVisibility(View rootView, boolean visible) { 510 if (rootView == null) { 511 return; 512 } 513 final int v = visible ? View.VISIBLE : View.GONE; 514 final View captionView = rootView.findViewById(getCaptionViewId()); 515 captionView.setVisibility(v); 516 } 517 getCaptionHeightId(@indowingMode int windowingMode)518 int getCaptionHeightId(@WindowingMode int windowingMode) { 519 return Resources.ID_NULL; 520 } 521 getCaptionViewId()522 int getCaptionViewId() { 523 return Resources.ID_NULL; 524 } 525 526 /** 527 * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or 528 * registers {@link #mOnDisplaysChangedListener} if it doesn't. 529 * 530 * @return {@code true} if the {@link Display} instance exists; or {@code false} otherwise 531 */ obtainDisplayOrRegisterListener()532 private boolean obtainDisplayOrRegisterListener() { 533 mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); 534 if (mDisplay == null) { 535 mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener); 536 return false; 537 } 538 return true; 539 } 540 releaseViews(WindowContainerTransaction wct)541 void releaseViews(WindowContainerTransaction wct) { 542 if (mViewHost != null) { 543 mViewHost.release(); 544 mViewHost = null; 545 } 546 547 mCaptionWindowManager = null; 548 549 final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); 550 boolean released = false; 551 if (mCaptionContainerSurface != null) { 552 t.remove(mCaptionContainerSurface); 553 mCaptionContainerSurface = null; 554 released = true; 555 } 556 557 if (mDecorationContainerSurface != null) { 558 t.remove(mDecorationContainerSurface); 559 mDecorationContainerSurface = null; 560 released = true; 561 } 562 563 if (released) { 564 t.apply(); 565 } 566 567 if (mWindowDecorationInsets != null) { 568 mWindowDecorationInsets.remove(wct); 569 mWindowDecorationInsets = null; 570 } 571 } 572 573 @Override close()574 public void close() { 575 Trace.beginSection("WindowDecoration#close"); 576 mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener); 577 final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get(); 578 releaseViews(wct); 579 mTaskOrganizer.applyTransaction(wct); 580 mTaskSurface.release(); 581 Trace.endSection(); 582 } 583 loadDimensionPixelSize(Resources resources, int resourceId)584 static int loadDimensionPixelSize(Resources resources, int resourceId) { 585 if (resourceId == Resources.ID_NULL) { 586 return 0; 587 } 588 return resources.getDimensionPixelSize(resourceId); 589 } 590 loadDimension(Resources resources, int resourceId)591 static float loadDimension(Resources resources, int resourceId) { 592 if (resourceId == Resources.ID_NULL) { 593 return 0; 594 } 595 return resources.getDimension(resourceId); 596 } 597 cloneSurfaceControl(SurfaceControl sc, Supplier<SurfaceControl> surfaceControlSupplier)598 private static SurfaceControl cloneSurfaceControl(SurfaceControl sc, 599 Supplier<SurfaceControl> surfaceControlSupplier) { 600 final SurfaceControl copy = surfaceControlSupplier.get(); 601 copy.copyFrom(sc, "WindowDecoration"); 602 return copy; 603 } 604 605 /** 606 * Create a window associated with this WindowDecoration. 607 * Note that subclass must dispose of this when the task is hidden/closed. 608 * 609 * @param layoutId layout to make the window from 610 * @param t the transaction to apply 611 * @param xPos x position of new window 612 * @param yPos y position of new window 613 * @param width width of new window 614 * @param height height of new window 615 * @return the {@link AdditionalViewHostViewContainer} that was added. 616 */ addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height)617 AdditionalViewHostViewContainer addWindow(int layoutId, String namePrefix, 618 SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos, 619 int width, int height) { 620 final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); 621 SurfaceControl windowSurfaceControl = builder 622 .setName(namePrefix + " of Task=" + mTaskInfo.taskId) 623 .setContainerLayer() 624 .setParent(mDecorationContainerSurface) 625 .setCallsite("WindowDecoration.addWindow") 626 .build(); 627 View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null); 628 629 t.setPosition(windowSurfaceControl, xPos, yPos) 630 .setWindowCrop(windowSurfaceControl, width, height) 631 .show(windowSurfaceControl); 632 final WindowManager.LayoutParams lp = 633 new WindowManager.LayoutParams(width, height, TYPE_APPLICATION, 634 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 635 PixelFormat.TRANSPARENT); 636 lp.setTitle("Additional window of Task=" + mTaskInfo.taskId); 637 lp.setTrustedOverlay(); 638 WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration, 639 windowSurfaceControl, null /* hostInputToken */); 640 SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory 641 .create(mDecorWindowContext, mDisplay, windowManager); 642 ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp)); 643 return new AdditionalViewHostViewContainer(windowSurfaceControl, viewHost, 644 mSurfaceControlTransactionSupplier); 645 } 646 647 /** 648 * Adds caption inset source to a WCT 649 */ addCaptionInset(WindowContainerTransaction wct)650 public void addCaptionInset(WindowContainerTransaction wct) { 651 final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode()); 652 if (captionHeightId == Resources.ID_NULL || !mIsCaptionVisible) { 653 return; 654 } 655 656 final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId); 657 final Rect captionInsets = new Rect(0, 0, 0, captionHeight); 658 final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token, 659 mOwner, captionInsets, null /* boundingRets */); 660 if (!newInsets.equals(mWindowDecorationInsets)) { 661 mWindowDecorationInsets = newInsets; 662 mWindowDecorationInsets.addOrUpdate(wct); 663 } 664 } 665 666 static class RelayoutParams { 667 RunningTaskInfo mRunningTaskInfo; 668 int mLayoutResId; 669 int mCaptionHeightId; 670 int mCaptionWidthId; 671 final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>(); 672 int mInputFeatures; 673 674 int mShadowRadiusId; 675 int mCornerRadius; 676 677 Configuration mWindowDecorConfig; 678 679 boolean mApplyStartTransactionOnDraw; 680 boolean mSetTaskPositionAndCrop; 681 reset()682 void reset() { 683 mLayoutResId = Resources.ID_NULL; 684 mCaptionHeightId = Resources.ID_NULL; 685 mCaptionWidthId = Resources.ID_NULL; 686 mOccludingCaptionElements.clear(); 687 mInputFeatures = 0; 688 689 mShadowRadiusId = Resources.ID_NULL; 690 mCornerRadius = 0; 691 692 mApplyStartTransactionOnDraw = false; 693 mSetTaskPositionAndCrop = false; 694 mWindowDecorConfig = null; 695 } 696 hasInputFeatureSpy()697 boolean hasInputFeatureSpy() { 698 return (mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_SPY) != 0; 699 } 700 701 /** 702 * Describes elements within the caption bar that could occlude app content, and should be 703 * sent as bounding rectangles to the insets system. 704 */ 705 static class OccludingCaptionElement { 706 int mWidthResId; 707 Alignment mAlignment; 708 709 enum Alignment { 710 START, END 711 } 712 } 713 } 714 715 static class RelayoutResult<T extends View & TaskFocusStateConsumer> { 716 int mCaptionHeight; 717 int mCaptionWidth; 718 int mCaptionX; 719 final Region mCustomizableCaptionRegion = Region.obtain(); 720 int mWidth; 721 int mHeight; 722 T mRootView; 723 reset()724 void reset() { 725 mWidth = 0; 726 mHeight = 0; 727 mCaptionHeight = 0; 728 mCaptionWidth = 0; 729 mCaptionX = 0; 730 mCustomizableCaptionRegion.setEmpty(); 731 mRootView = null; 732 } 733 } 734 735 @VisibleForTesting 736 public interface SurfaceControlViewHostFactory { create(Context c, Display d, WindowlessWindowManager wmm)737 default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) { 738 return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration"); 739 } create(Context c, Display d, WindowlessWindowManager wmm, String callsite)740 default SurfaceControlViewHost create(Context c, Display d, 741 WindowlessWindowManager wmm, String callsite) { 742 return new SurfaceControlViewHost(c, d, wmm, callsite); 743 } 744 } 745 746 private static class WindowDecorationInsets { 747 private static final int INDEX = 0; 748 private final WindowContainerToken mToken; 749 private final Binder mOwner; 750 private final Rect mFrame; 751 private final Rect[] mBoundingRects; 752 WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame, Rect[] boundingRects)753 private WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame, 754 Rect[] boundingRects) { 755 mToken = token; 756 mOwner = owner; 757 mFrame = frame; 758 mBoundingRects = boundingRects; 759 } 760 addOrUpdate(WindowContainerTransaction wct)761 void addOrUpdate(WindowContainerTransaction wct) { 762 wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects); 763 wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame, 764 mBoundingRects); 765 } 766 remove(WindowContainerTransaction wct)767 void remove(WindowContainerTransaction wct) { 768 wct.removeInsetsSource(mToken, mOwner, INDEX, captionBar()); 769 wct.removeInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures()); 770 } 771 772 @Override equals(Object o)773 public boolean equals(Object o) { 774 if (this == o) return true; 775 if (!(o instanceof WindowDecoration.WindowDecorationInsets that)) return false; 776 return Objects.equals(mToken, that.mToken) && Objects.equals(mOwner, 777 that.mOwner) && Objects.equals(mFrame, that.mFrame) 778 && Objects.deepEquals(mBoundingRects, that.mBoundingRects); 779 } 780 781 @Override hashCode()782 public int hashCode() { 783 return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects)); 784 } 785 } 786 } 787