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