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 android.app.ActivityManager.RunningTaskInfo; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.content.res.Resources; 23 import android.graphics.Color; 24 import android.graphics.PixelFormat; 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.view.Display; 28 import android.view.InsetsState; 29 import android.view.LayoutInflater; 30 import android.view.SurfaceControl; 31 import android.view.SurfaceControlViewHost; 32 import android.view.View; 33 import android.view.ViewRootImpl; 34 import android.view.WindowManager; 35 import android.view.WindowlessWindowManager; 36 import android.window.TaskConstants; 37 import android.window.WindowContainerTransaction; 38 39 import com.android.wm.shell.ShellTaskOrganizer; 40 import com.android.wm.shell.common.DisplayController; 41 42 import java.util.function.Supplier; 43 44 /** 45 * Manages a container surface and a windowless window to show window decoration. Responsible to 46 * update window decoration window state and layout parameters on task info changes and so that 47 * window decoration is in correct state and bounds. 48 * 49 * The container surface is a child of the task display area in the same display, so that window 50 * decorations can be drawn out of the task bounds and receive input events from out of the task 51 * bounds to support drag resizing. 52 * 53 * The windowless window that hosts window decoration is positioned in front of all activities, to 54 * allow the foreground activity to draw its own background behind window decorations, such as 55 * the window captions. 56 * 57 * @param <T> The type of the root view 58 */ 59 public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> 60 implements AutoCloseable { 61 private static final int[] CAPTION_INSETS_TYPES = { InsetsState.ITYPE_CAPTION_BAR }; 62 63 /** 64 * System-wide context. Only used to create context with overridden configurations. 65 */ 66 final Context mContext; 67 final DisplayController mDisplayController; 68 final ShellTaskOrganizer mTaskOrganizer; 69 final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier; 70 final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; 71 final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier; 72 final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory; 73 private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = 74 new DisplayController.OnDisplaysChangedListener() { 75 @Override 76 public void onDisplayAdded(int displayId) { 77 if (mTaskInfo.displayId != displayId) { 78 return; 79 } 80 81 mDisplayController.removeDisplayWindowListener(this); 82 relayout(mTaskInfo); 83 } 84 }; 85 86 RunningTaskInfo mTaskInfo; 87 final SurfaceControl mTaskSurface; 88 89 Display mDisplay; 90 Context mDecorWindowContext; 91 SurfaceControl mDecorationContainerSurface; 92 SurfaceControl mTaskBackgroundSurface; 93 94 SurfaceControl mCaptionContainerSurface; 95 private WindowlessWindowManager mCaptionWindowManager; 96 private SurfaceControlViewHost mViewHost; 97 98 private final Rect mCaptionInsetsRect = new Rect(); 99 private final Rect mTaskSurfaceCrop = new Rect(); 100 private final float[] mTmpColor = new float[3]; 101 WindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, SurfaceControl taskSurface)102 WindowDecoration( 103 Context context, 104 DisplayController displayController, 105 ShellTaskOrganizer taskOrganizer, 106 RunningTaskInfo taskInfo, 107 SurfaceControl taskSurface) { 108 this(context, displayController, taskOrganizer, taskInfo, taskSurface, 109 SurfaceControl.Builder::new, SurfaceControl.Transaction::new, 110 WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {}); 111 } 112 WindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, SurfaceControl taskSurface, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory)113 WindowDecoration( 114 Context context, 115 DisplayController displayController, 116 ShellTaskOrganizer taskOrganizer, 117 RunningTaskInfo taskInfo, 118 SurfaceControl taskSurface, 119 Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, 120 Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, 121 Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, 122 SurfaceControlViewHostFactory surfaceControlViewHostFactory) { 123 mContext = context; 124 mDisplayController = displayController; 125 mTaskOrganizer = taskOrganizer; 126 mTaskInfo = taskInfo; 127 mTaskSurface = taskSurface; 128 mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier; 129 mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; 130 mWindowContainerTransactionSupplier = windowContainerTransactionSupplier; 131 mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; 132 133 mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); 134 mDecorWindowContext = mContext.createConfigurationContext( 135 getConfigurationWithOverrides(mTaskInfo)); 136 } 137 138 /** 139 * Get {@link Configuration} from supplied {@link RunningTaskInfo}. 140 * 141 * Allows values to be overridden before returning the configuration. 142 */ getConfigurationWithOverrides(RunningTaskInfo taskInfo)143 protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) { 144 return taskInfo.getConfiguration(); 145 } 146 147 /** 148 * Used by {@link WindowDecoration} to trigger a new relayout because the requirements for a 149 * relayout weren't satisfied are satisfied now. 150 * 151 * @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the 152 * constructor. 153 */ relayout(RunningTaskInfo taskInfo)154 abstract void relayout(RunningTaskInfo taskInfo); 155 relayout(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult)156 void relayout(RelayoutParams params, SurfaceControl.Transaction startT, 157 SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, 158 RelayoutResult<T> outResult) { 159 outResult.reset(); 160 161 final Configuration oldTaskConfig = mTaskInfo.getConfiguration(); 162 if (params.mRunningTaskInfo != null) { 163 mTaskInfo = params.mRunningTaskInfo; 164 } 165 166 if (!mTaskInfo.isVisible) { 167 releaseViews(); 168 finishT.hide(mTaskSurface); 169 return; 170 } 171 172 if (rootView == null && params.mLayoutResId == 0) { 173 throw new IllegalArgumentException("layoutResId and rootView can't both be invalid."); 174 } 175 176 outResult.mRootView = rootView; 177 rootView = null; // Clear it just in case we use it accidentally 178 final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo); 179 if (oldTaskConfig.densityDpi != taskConfig.densityDpi 180 || mDisplay == null 181 || mDisplay.getDisplayId() != mTaskInfo.displayId) { 182 releaseViews(); 183 184 if (!obtainDisplayOrRegisterListener()) { 185 outResult.mRootView = null; 186 return; 187 } 188 mDecorWindowContext = mContext.createConfigurationContext(taskConfig); 189 if (params.mLayoutResId != 0) { 190 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) 191 .inflate(params.mLayoutResId, null); 192 } 193 } 194 195 if (outResult.mRootView == null) { 196 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) 197 .inflate(params.mLayoutResId , null); 198 } 199 200 // DecorationContainerSurface 201 if (mDecorationContainerSurface == null) { 202 final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); 203 mDecorationContainerSurface = builder 204 .setName("Decor container of Task=" + mTaskInfo.taskId) 205 .setContainerLayer() 206 .setParent(mTaskSurface) 207 .build(); 208 209 startT.setTrustedOverlay(mDecorationContainerSurface, true) 210 .setLayer(mDecorationContainerSurface, 211 TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS); 212 } 213 214 final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); 215 final Resources resources = mDecorWindowContext.getResources(); 216 outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId); 217 outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId); 218 outResult.mWidth = taskBounds.width() 219 + loadDimensionPixelSize(resources, params.mOutsetRightId) 220 - outResult.mDecorContainerOffsetX; 221 outResult.mHeight = taskBounds.height() 222 + loadDimensionPixelSize(resources, params.mOutsetBottomId) 223 - outResult.mDecorContainerOffsetY; 224 startT.setPosition( 225 mDecorationContainerSurface, 226 outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY) 227 .setWindowCrop(mDecorationContainerSurface, 228 outResult.mWidth, outResult.mHeight) 229 .show(mDecorationContainerSurface); 230 231 // TaskBackgroundSurface 232 if (mTaskBackgroundSurface == null) { 233 final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); 234 mTaskBackgroundSurface = builder 235 .setName("Background of Task=" + mTaskInfo.taskId) 236 .setEffectLayer() 237 .setParent(mTaskSurface) 238 .build(); 239 240 startT.setLayer(mTaskBackgroundSurface, TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND); 241 } 242 243 float shadowRadius = loadDimension(resources, params.mShadowRadiusId); 244 int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); 245 mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; 246 mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; 247 mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; 248 startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), 249 taskBounds.height()) 250 .setShadowRadius(mTaskBackgroundSurface, shadowRadius) 251 .setColor(mTaskBackgroundSurface, mTmpColor) 252 .show(mTaskBackgroundSurface); 253 254 // CaptionContainerSurface, CaptionWindowManager 255 if (mCaptionContainerSurface == null) { 256 final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); 257 mCaptionContainerSurface = builder 258 .setName("Caption container of Task=" + mTaskInfo.taskId) 259 .setContainerLayer() 260 .setParent(mDecorationContainerSurface) 261 .build(); 262 } 263 264 final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); 265 final int captionWidth = taskBounds.width(); 266 267 startT.setPosition( 268 mCaptionContainerSurface, 269 -outResult.mDecorContainerOffsetX + params.mCaptionX, 270 -outResult.mDecorContainerOffsetY + params.mCaptionY) 271 .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) 272 .show(mCaptionContainerSurface); 273 274 if (mCaptionWindowManager == null) { 275 // Put caption under a container surface because ViewRootImpl sets the destination frame 276 // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. 277 mCaptionWindowManager = new WindowlessWindowManager( 278 mTaskInfo.getConfiguration(), mCaptionContainerSurface, 279 null /* hostInputToken */); 280 } 281 282 // Caption view 283 mCaptionWindowManager.setConfiguration(taskConfig); 284 final WindowManager.LayoutParams lp = 285 new WindowManager.LayoutParams(captionWidth, captionHeight, 286 WindowManager.LayoutParams.TYPE_APPLICATION, 287 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); 288 lp.setTitle("Caption of Task=" + mTaskInfo.taskId); 289 lp.setTrustedOverlay(); 290 if (mViewHost == null) { 291 mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, 292 mCaptionWindowManager); 293 mViewHost.setView(outResult.mRootView, lp); 294 } else { 295 mViewHost.relayout(lp); 296 } 297 298 if (ViewRootImpl.CAPTION_ON_SHELL) { 299 outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused); 300 301 // Caption insets 302 mCaptionInsetsRect.set(taskBounds); 303 mCaptionInsetsRect.bottom = 304 mCaptionInsetsRect.top + captionHeight + params.mCaptionY; 305 wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, 306 CAPTION_INSETS_TYPES); 307 } else { 308 startT.hide(mCaptionContainerSurface); 309 } 310 311 // Task surface itself 312 Point taskPosition = mTaskInfo.positionInParent; 313 mTaskSurfaceCrop.set( 314 outResult.mDecorContainerOffsetX, 315 outResult.mDecorContainerOffsetY, 316 outResult.mWidth + outResult.mDecorContainerOffsetX, 317 outResult.mHeight + outResult.mDecorContainerOffsetY); 318 startT.show(mTaskSurface); 319 finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) 320 .setCrop(mTaskSurface, mTaskSurfaceCrop); 321 } 322 323 /** 324 * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or 325 * registers {@link #mOnDisplaysChangedListener} if it doesn't. 326 * 327 * @return {@code true} if the {@link Display} instance exists; or {@code false} otherwise 328 */ obtainDisplayOrRegisterListener()329 private boolean obtainDisplayOrRegisterListener() { 330 mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); 331 if (mDisplay == null) { 332 mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener); 333 return false; 334 } 335 return true; 336 } 337 releaseViews()338 void releaseViews() { 339 if (mViewHost != null) { 340 mViewHost.release(); 341 mViewHost = null; 342 } 343 344 mCaptionWindowManager = null; 345 346 final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); 347 boolean released = false; 348 if (mCaptionContainerSurface != null) { 349 t.remove(mCaptionContainerSurface); 350 mCaptionContainerSurface = null; 351 released = true; 352 } 353 354 if (mDecorationContainerSurface != null) { 355 t.remove(mDecorationContainerSurface); 356 mDecorationContainerSurface = null; 357 released = true; 358 } 359 360 if (mTaskBackgroundSurface != null) { 361 t.remove(mTaskBackgroundSurface); 362 mTaskBackgroundSurface = null; 363 released = true; 364 } 365 366 if (released) { 367 t.apply(); 368 } 369 370 final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get(); 371 wct.removeInsetsProvider(mTaskInfo.token, CAPTION_INSETS_TYPES); 372 mTaskOrganizer.applyTransaction(wct); 373 } 374 375 @Override close()376 public void close() { 377 mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener); 378 releaseViews(); 379 } 380 loadDimensionPixelSize(Resources resources, int resourceId)381 static int loadDimensionPixelSize(Resources resources, int resourceId) { 382 if (resourceId == Resources.ID_NULL) { 383 return 0; 384 } 385 return resources.getDimensionPixelSize(resourceId); 386 } 387 loadDimension(Resources resources, int resourceId)388 static float loadDimension(Resources resources, int resourceId) { 389 if (resourceId == Resources.ID_NULL) { 390 return 0; 391 } 392 return resources.getDimension(resourceId); 393 } 394 395 /** 396 * Create a window associated with this WindowDecoration. 397 * Note that subclass must dispose of this when the task is hidden/closed. 398 * @param layoutId layout to make the window from 399 * @param t the transaction to apply 400 * @param xPos x position of new window 401 * @param yPos y position of new window 402 * @param width width of new window 403 * @param height height of new window 404 * @param shadowRadius radius of the shadow of the new window 405 * @param cornerRadius radius of the corners of the new window 406 * @return the {@link AdditionalWindow} that was added. 407 */ addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t, int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius)408 AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t, 409 int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) { 410 final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); 411 SurfaceControl windowSurfaceControl = builder 412 .setName(namePrefix + " of Task=" + mTaskInfo.taskId) 413 .setContainerLayer() 414 .setParent(mDecorationContainerSurface) 415 .build(); 416 View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null); 417 418 t.setPosition(windowSurfaceControl, xPos, yPos) 419 .setWindowCrop(windowSurfaceControl, width, height) 420 .setShadowRadius(windowSurfaceControl, shadowRadius) 421 .setCornerRadius(windowSurfaceControl, cornerRadius) 422 .show(windowSurfaceControl); 423 final WindowManager.LayoutParams lp = 424 new WindowManager.LayoutParams(width, height, 425 WindowManager.LayoutParams.TYPE_APPLICATION, 426 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); 427 lp.setTitle("Additional window of Task=" + mTaskInfo.taskId); 428 lp.setTrustedOverlay(); 429 WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration, 430 windowSurfaceControl, null /* hostInputToken */); 431 SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory 432 .create(mDecorWindowContext, mDisplay, windowManager); 433 viewHost.setView(v, lp); 434 return new AdditionalWindow(windowSurfaceControl, viewHost, 435 mSurfaceControlTransactionSupplier); 436 } 437 438 static class RelayoutParams{ 439 RunningTaskInfo mRunningTaskInfo; 440 int mLayoutResId; 441 int mCaptionHeightId; 442 int mCaptionWidthId; 443 int mShadowRadiusId; 444 445 int mOutsetTopId; 446 int mOutsetBottomId; 447 int mOutsetLeftId; 448 int mOutsetRightId; 449 450 int mCaptionX; 451 int mCaptionY; 452 setOutsets(int leftId, int topId, int rightId, int bottomId)453 void setOutsets(int leftId, int topId, int rightId, int bottomId) { 454 mOutsetLeftId = leftId; 455 mOutsetTopId = topId; 456 mOutsetRightId = rightId; 457 mOutsetBottomId = bottomId; 458 } 459 setCaptionPosition(int left, int top)460 void setCaptionPosition(int left, int top) { 461 mCaptionX = left; 462 mCaptionY = top; 463 } 464 reset()465 void reset() { 466 mLayoutResId = Resources.ID_NULL; 467 mCaptionHeightId = Resources.ID_NULL; 468 mCaptionWidthId = Resources.ID_NULL; 469 mShadowRadiusId = Resources.ID_NULL; 470 471 mOutsetTopId = Resources.ID_NULL; 472 mOutsetBottomId = Resources.ID_NULL; 473 mOutsetLeftId = Resources.ID_NULL; 474 mOutsetRightId = Resources.ID_NULL; 475 476 mCaptionX = 0; 477 mCaptionY = 0; 478 } 479 } 480 481 static class RelayoutResult<T extends View & TaskFocusStateConsumer> { 482 int mWidth; 483 int mHeight; 484 T mRootView; 485 int mDecorContainerOffsetX; 486 int mDecorContainerOffsetY; 487 reset()488 void reset() { 489 mWidth = 0; 490 mHeight = 0; 491 mDecorContainerOffsetX = 0; 492 mDecorContainerOffsetY = 0; 493 mRootView = null; 494 } 495 } 496 497 interface SurfaceControlViewHostFactory { create(Context c, Display d, WindowlessWindowManager wmm)498 default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) { 499 return new SurfaceControlViewHost(c, d, wmm); 500 } 501 } 502 503 /** 504 * Subclass for additional windows associated with this WindowDecoration 505 */ 506 static class AdditionalWindow { 507 SurfaceControl mWindowSurface; 508 SurfaceControlViewHost mWindowViewHost; 509 Supplier<SurfaceControl.Transaction> mTransactionSupplier; 510 AdditionalWindow(SurfaceControl surfaceControl, SurfaceControlViewHost surfaceControlViewHost, Supplier<SurfaceControl.Transaction> transactionSupplier)511 private AdditionalWindow(SurfaceControl surfaceControl, 512 SurfaceControlViewHost surfaceControlViewHost, 513 Supplier<SurfaceControl.Transaction> transactionSupplier) { 514 mWindowSurface = surfaceControl; 515 mWindowViewHost = surfaceControlViewHost; 516 mTransactionSupplier = transactionSupplier; 517 } 518 releaseView()519 void releaseView() { 520 WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM(); 521 522 if (mWindowViewHost != null) { 523 mWindowViewHost.release(); 524 mWindowViewHost = null; 525 } 526 windowManager = null; 527 final SurfaceControl.Transaction t = mTransactionSupplier.get(); 528 boolean released = false; 529 if (mWindowSurface != null) { 530 t.remove(mWindowSurface); 531 mWindowSurface = null; 532 released = true; 533 } 534 if (released) { 535 t.apply(); 536 } 537 } 538 } 539 } 540