1 /* 2 * Copyright (C) 2017 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.server.wm; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 24 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 25 import static android.app.WindowConfiguration.ROTATION_UNDEFINED; 26 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 27 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 28 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 29 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 30 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 31 import static android.app.WindowConfiguration.activityTypeToString; 32 import static android.app.WindowConfiguration.isFloating; 33 import static android.app.WindowConfiguration.windowingModeToString; 34 import static android.app.WindowConfigurationProto.WINDOWING_MODE; 35 import static android.content.ConfigurationProto.WINDOW_CONFIGURATION; 36 import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; 37 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; 38 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 39 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 40 import static android.content.res.Configuration.ORIENTATION_UNDEFINED; 41 import static android.view.Surface.ROTATION_270; 42 import static android.view.Surface.ROTATION_90; 43 44 import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION; 45 import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION; 46 import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGURATION; 47 48 import android.annotation.CallSuper; 49 import android.annotation.NonNull; 50 import android.app.WindowConfiguration; 51 import android.content.pm.ApplicationInfo; 52 import android.content.res.Configuration; 53 import android.graphics.Point; 54 import android.graphics.Rect; 55 import android.os.LocaleList; 56 import android.util.DisplayMetrics; 57 import android.util.proto.ProtoOutputStream; 58 import android.view.DisplayInfo; 59 60 import com.android.internal.annotations.VisibleForTesting; 61 62 import java.io.PrintWriter; 63 import java.util.ArrayList; 64 65 /** 66 * Contains common logic for classes that have override configurations and are organized in a 67 * hierarchy. 68 */ 69 public abstract class ConfigurationContainer<E extends ConfigurationContainer> { 70 /** 71 * {@link #Rect} returned from {@link #getRequestedOverrideBounds()} to prevent original value 72 * from being set directly. 73 */ 74 private Rect mReturnBounds = new Rect(); 75 76 /** 77 * Contains requested override configuration settings applied to this configuration container. 78 */ 79 private Configuration mRequestedOverrideConfiguration = new Configuration(); 80 81 /** 82 * Contains the requested override configuration with parent and policy constraints applied. 83 * This is the set of overrides that gets applied to the full and merged configurations. 84 */ 85 private Configuration mResolvedOverrideConfiguration = new Configuration(); 86 87 /** True if mRequestedOverrideConfiguration is not empty */ 88 private boolean mHasOverrideConfiguration; 89 90 /** 91 * Contains full configuration applied to this configuration container. Corresponds to full 92 * parent's config with applied {@link #mResolvedOverrideConfiguration}. 93 */ 94 private Configuration mFullConfiguration = new Configuration(); 95 96 /** 97 * Contains merged override configuration settings from the top of the hierarchy down to this 98 * particular instance. It is different from {@link #mFullConfiguration} because it starts from 99 * topmost container's override config instead of global config. 100 */ 101 private Configuration mMergedOverrideConfiguration = new Configuration(); 102 103 private ArrayList<ConfigurationContainerListener> mChangeListeners = new ArrayList<>(); 104 105 // TODO: Can't have ag/2592611 soon enough! 106 private final Configuration mRequestsTmpConfig = new Configuration(); 107 private final Configuration mResolvedTmpConfig = new Configuration(); 108 109 // Used for setting bounds 110 private final Rect mTmpRect = new Rect(); 111 112 static final int BOUNDS_CHANGE_NONE = 0; 113 114 /** 115 * Return value from {@link #setBounds(Rect)} indicating the position of the override bounds 116 * changed. 117 */ 118 static final int BOUNDS_CHANGE_POSITION = 1; 119 120 /** 121 * Return value from {@link #setBounds(Rect)} indicating the size of the override bounds 122 * changed. 123 */ 124 static final int BOUNDS_CHANGE_SIZE = 1 << 1; 125 126 /** 127 * Returns full configuration applied to this configuration container. 128 * This method should be used for getting settings applied in each particular level of the 129 * hierarchy. 130 */ 131 @NonNull getConfiguration()132 public Configuration getConfiguration() { 133 return mFullConfiguration; 134 } 135 136 /** 137 * Notify that parent config changed and we need to update full configuration. 138 * @see #mFullConfiguration 139 */ onConfigurationChanged(Configuration newParentConfig)140 public void onConfigurationChanged(Configuration newParentConfig) { 141 mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration); 142 resolveOverrideConfiguration(newParentConfig); 143 mFullConfiguration.setTo(newParentConfig); 144 // Do not inherit always-on-top property from parent, otherwise the always-on-top 145 // property is propagated to all children. In that case, newly added child is 146 // always being positioned at bottom (behind the always-on-top siblings). 147 mFullConfiguration.windowConfiguration.unsetAlwaysOnTop(); 148 mFullConfiguration.updateFrom(mResolvedOverrideConfiguration); 149 onMergedOverrideConfigurationChanged(); 150 if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) { 151 // This depends on the assumption that change-listeners don't do 152 // their own override resolution. This way, dependent hierarchies 153 // can stay properly synced-up with a primary hierarchy's constraints. 154 // Since the hierarchies will be merged, this whole thing will go away 155 // before the assumption will be broken. 156 // Inform listeners of the change. 157 for (int i = mChangeListeners.size() - 1; i >= 0; --i) { 158 mChangeListeners.get(i).onRequestedOverrideConfigurationChanged( 159 mResolvedOverrideConfiguration); 160 } 161 } 162 for (int i = mChangeListeners.size() - 1; i >= 0; --i) { 163 mChangeListeners.get(i).onMergedOverrideConfigurationChanged( 164 mMergedOverrideConfiguration); 165 } 166 for (int i = getChildCount() - 1; i >= 0; --i) { 167 dispatchConfigurationToChild(getChildAt(i), mFullConfiguration); 168 } 169 } 170 171 /** 172 * Dispatches the configuration to child when {@link #onConfigurationChanged(Configuration)} is 173 * called. This allows the derived classes to override how to dispatch the configuration. 174 */ dispatchConfigurationToChild(E child, Configuration config)175 void dispatchConfigurationToChild(E child, Configuration config) { 176 child.onConfigurationChanged(config); 177 } 178 179 /** 180 * Resolves the current requested override configuration into 181 * {@link #mResolvedOverrideConfiguration} 182 * 183 * @param newParentConfig The new parent configuration to resolve overrides against. 184 */ resolveOverrideConfiguration(Configuration newParentConfig)185 void resolveOverrideConfiguration(Configuration newParentConfig) { 186 mResolvedOverrideConfiguration.setTo(mRequestedOverrideConfiguration); 187 } 188 189 /** 190 * If necessary, override configuration fields related to app bounds. 191 * This will happen when the app is targeting SDK earlier than 35. 192 * The insets and configuration has decoupled since SDK level 35, to make the system 193 * compatible to existing apps, override the configuration with legacy metrics. In legacy 194 * metrics, fields such as appBounds will exclude some of the system bar areas. 195 * The override contains all potentially affected fields in Configuration, including 196 * screenWidthDp, screenHeightDp, smallestScreenWidthDp, and orientation. 197 * All overrides to those fields should be in this method. 198 * 199 * Task is only needed for split-screen to apply an offset special handling. 200 * 201 * TODO: Consider integrate this with computeConfigByResolveHint() 202 */ applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo, Configuration newParentConfiguration, Configuration inOutConfig, boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform, boolean hasCompatDisplayInsets, Task task)203 static void applySizeOverrideIfNeeded(DisplayContent displayContent, ApplicationInfo appInfo, 204 Configuration newParentConfiguration, Configuration inOutConfig, 205 boolean optsOutEdgeToEdge, boolean hasFixedRotationTransform, 206 boolean hasCompatDisplayInsets, Task task) { 207 if (displayContent == null) { 208 return; 209 } 210 final boolean useOverrideInsetsForConfig = 211 displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration 212 ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED) 213 && !appInfo.isChangeEnabled( 214 OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION) 215 : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION); 216 final int parentWindowingMode = 217 newParentConfiguration.windowConfiguration.getWindowingMode(); 218 final boolean isFloating = isFloating(parentWindowingMode) 219 // Check the requested windowing mode of activity as well in case it is 220 // switching between PiP and fullscreen. 221 && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED 222 || isFloating(inOutConfig.windowConfiguration.getWindowingMode())); 223 int rotation = newParentConfiguration.windowConfiguration.getRotation(); 224 if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) { 225 rotation = displayContent.getRotation(); 226 } 227 if (!optsOutEdgeToEdge && (!useOverrideInsetsForConfig 228 || hasCompatDisplayInsets 229 || rotation == ROTATION_UNDEFINED)) { 230 // If the insets configuration decoupled logic is not enabled for the app, or the app 231 // already has a compat override, or the context doesn't contain enough info to 232 // calculate the override, skip the override. 233 return; 234 } 235 if (isFloating) { 236 // Floating window won't have any insets affect configuration. Skip the override. 237 return; 238 } 239 // Make sure the orientation related fields will be updated by the override insets, because 240 // fixed rotation has assigned the fields from display's configuration. 241 if (hasFixedRotationTransform) { 242 inOutConfig.windowConfiguration.setAppBounds(null); 243 inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED; 244 inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; 245 inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; 246 inOutConfig.orientation = ORIENTATION_UNDEFINED; 247 } 248 249 // Override starts here. 250 final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); 251 final int dw = rotated 252 ? displayContent.mBaseDisplayHeight 253 : displayContent.mBaseDisplayWidth; 254 final int dh = rotated 255 ? displayContent.mBaseDisplayWidth 256 : displayContent.mBaseDisplayHeight; 257 // This should be the only place override the configuration for ActivityRecord. Override 258 // the value if not calculated yet. 259 Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); 260 Rect outConfigBounds = new Rect(outAppBounds); 261 if (outAppBounds == null || outAppBounds.isEmpty()) { 262 inOutConfig.windowConfiguration.setAppBounds( 263 newParentConfiguration.windowConfiguration.getBounds()); 264 outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); 265 outConfigBounds.set(outAppBounds); 266 if (task != null) { 267 task = task.getCreatedByOrganizerTask(); 268 if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) { 269 outAppBounds.offset(task.mOffsetXForInsets, task.mOffsetYForInsets); 270 } 271 } 272 final DisplayPolicy.DecorInsets.Info decor = 273 displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh); 274 if (!outAppBounds.intersect(decor.mOverrideNonDecorFrame)) { 275 // TODO (b/364883053): When a split screen is requested from an app intent for a new 276 // task, the bounds is not the final bounds, and this is also not a bounds change 277 // event handled correctly with the offset. Revert back to legacy method for this 278 // case. 279 if (inOutConfig.windowConfiguration.getWindowingMode() 280 == WINDOWING_MODE_MULTI_WINDOW) { 281 outAppBounds.inset(decor.mOverrideNonDecorInsets); 282 } 283 } 284 if (!outConfigBounds.intersect(decor.mOverrideConfigFrame)) { 285 if (inOutConfig.windowConfiguration.getWindowingMode() 286 == WINDOWING_MODE_MULTI_WINDOW) { 287 outAppBounds.inset(decor.mOverrideConfigInsets); 288 } 289 } 290 if (task != null && (task.mOffsetYForInsets != 0 || task.mOffsetXForInsets != 0)) { 291 outAppBounds.offset(-task.mOffsetXForInsets, -task.mOffsetYForInsets); 292 } 293 } 294 float density = inOutConfig.densityDpi; 295 if (density == Configuration.DENSITY_DPI_UNDEFINED) { 296 density = newParentConfiguration.densityDpi; 297 } 298 density *= DisplayMetrics.DENSITY_DEFAULT_SCALE; 299 if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { 300 inOutConfig.screenWidthDp = (int) (outConfigBounds.width() / density + 0.5f); 301 } 302 if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) { 303 inOutConfig.screenHeightDp = (int) (outConfigBounds.height() / density + 0.5f); 304 } 305 if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED 306 && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { 307 // For the case of PIP transition and multi-window environment, the 308 // smallestScreenWidthDp is handled already. Override only if the app is in 309 // fullscreen. 310 final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo()); 311 displayContent.computeSizeRanges(info, rotated, dw, dh, 312 displayContent.getDisplayMetrics().density, 313 inOutConfig, true /* overrideConfig */); 314 } 315 316 // It's possible that screen size will be considered in different orientation with or 317 // without considering the system bar insets. Override orientation as well. 318 if (inOutConfig.orientation == ORIENTATION_UNDEFINED) { 319 inOutConfig.orientation = (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp) 320 ? ORIENTATION_PORTRAIT 321 : ORIENTATION_LANDSCAPE; 322 } 323 } 324 325 /** Returns {@code true} if requested override override configuration is not empty. */ hasRequestedOverrideConfiguration()326 boolean hasRequestedOverrideConfiguration() { 327 return mHasOverrideConfiguration; 328 } 329 330 /** Returns requested override configuration applied to this configuration container. */ 331 @NonNull getRequestedOverrideConfiguration()332 public Configuration getRequestedOverrideConfiguration() { 333 return mRequestedOverrideConfiguration; 334 } 335 336 /** Returns the resolved override configuration. */ 337 @NonNull getResolvedOverrideConfiguration()338 Configuration getResolvedOverrideConfiguration() { 339 return mResolvedOverrideConfiguration; 340 } 341 342 /** 343 * Update override configuration and recalculate full config. 344 * @see #mRequestedOverrideConfiguration 345 * @see #mFullConfiguration 346 */ onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration)347 public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) { 348 updateRequestedOverrideConfiguration(overrideConfiguration); 349 // Update full configuration of this container and all its children. 350 final ConfigurationContainer parent = getParent(); 351 onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY); 352 } 353 354 /** Updates override configuration without recalculate full config. */ updateRequestedOverrideConfiguration(Configuration overrideConfiguration)355 void updateRequestedOverrideConfiguration(Configuration overrideConfiguration) { 356 // Pre-compute this here, so we don't need to go through the entire Configuration when 357 // writing to proto (which has significant cost if we write a lot of empty configurations). 358 mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration); 359 mRequestedOverrideConfiguration.setTo(overrideConfiguration); 360 final Rect newBounds = mRequestedOverrideConfiguration.windowConfiguration.getBounds(); 361 if (mHasOverrideConfiguration && providesMaxBounds() 362 && diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) { 363 mRequestedOverrideConfiguration.windowConfiguration.setMaxBounds(newBounds); 364 } 365 } 366 367 /** 368 * Get merged override configuration from the top of the hierarchy down to this particular 369 * instance. This should be reported to client as override config. 370 */ 371 @NonNull getMergedOverrideConfiguration()372 public Configuration getMergedOverrideConfiguration() { 373 return mMergedOverrideConfiguration; 374 } 375 376 /** 377 * Update merged override configuration based on corresponding parent's config. If there is no 378 * parent, merged override configuration will set equal to current override config. This 379 * doesn't cascade on its own since it's called by {@link #onConfigurationChanged}. 380 * @see #mMergedOverrideConfiguration 381 */ onMergedOverrideConfigurationChanged()382 void onMergedOverrideConfigurationChanged() { 383 final ConfigurationContainer parent = getParent(); 384 if (parent != null) { 385 mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration()); 386 // Do not inherit always-on-top property from parent, otherwise the always-on-top 387 // property is propagated to all children. In that case, newly added child is 388 // always being positioned at bottom (behind the always-on-top siblings). 389 mMergedOverrideConfiguration.windowConfiguration.unsetAlwaysOnTop(); 390 mMergedOverrideConfiguration.updateFrom(mResolvedOverrideConfiguration); 391 } else { 392 mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration); 393 } 394 } 395 396 /** 397 * Indicates whether this container chooses not to override any bounds from its parent, either 398 * because it doesn't request to override them or the request is dropped during configuration 399 * resolution. In this case, it will inherit the bounds of the first ancestor which specifies a 400 * bounds subject to policy constraints. 401 * 402 * @return {@code true} if this container level uses bounds from parent level. {@code false} 403 * otherwise. 404 */ matchParentBounds()405 public boolean matchParentBounds() { 406 return getResolvedOverrideBounds().isEmpty(); 407 } 408 409 /** 410 * Returns whether the bounds specified are considered the same as the existing requested 411 * override bounds. This is either when the two bounds are equal or the requested override 412 * bounds are empty and the specified bounds is null. 413 * 414 * @return {@code true} if the bounds are equivalent, {@code false} otherwise 415 */ equivalentRequestedOverrideBounds(Rect bounds)416 public boolean equivalentRequestedOverrideBounds(Rect bounds) { 417 return equivalentBounds(getRequestedOverrideBounds(), bounds); 418 } 419 420 /** Similar to {@link #equivalentRequestedOverrideBounds(Rect)}, but compares max bounds. */ equivalentRequestedOverrideMaxBounds(Rect bounds)421 public boolean equivalentRequestedOverrideMaxBounds(Rect bounds) { 422 return equivalentBounds(getRequestedOverrideMaxBounds(), bounds); 423 } 424 425 /** 426 * Returns whether the two bounds are equal to each other or are a combination of null or empty. 427 */ equivalentBounds(Rect bounds, Rect other)428 public static boolean equivalentBounds(Rect bounds, Rect other) { 429 return bounds == other 430 || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null))) 431 || (other != null && other.isEmpty() && bounds == null); 432 } 433 434 /** 435 * Returns the effective bounds of this container, inheriting the first non-empty bounds set in 436 * its ancestral hierarchy, including itself. 437 */ getBounds()438 public Rect getBounds() { 439 mReturnBounds.set(getConfiguration().windowConfiguration.getBounds()); 440 return mReturnBounds; 441 } 442 getBounds(Rect outBounds)443 public void getBounds(Rect outBounds) { 444 outBounds.set(getBounds()); 445 } 446 447 /** Similar to {@link #getBounds()}, but reports the max bounds. */ getMaxBounds()448 public Rect getMaxBounds() { 449 mReturnBounds.set(getConfiguration().windowConfiguration.getMaxBounds()); 450 return mReturnBounds; 451 } 452 453 /** 454 * Sets {@code out} to the top-left corner of the bounds as returned by {@link #getBounds()}. 455 */ getPosition(Point out)456 public void getPosition(Point out) { 457 Rect bounds = getBounds(); 458 out.set(bounds.left, bounds.top); 459 } 460 getResolvedOverrideBounds()461 Rect getResolvedOverrideBounds() { 462 mReturnBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds()); 463 return mReturnBounds; 464 } 465 466 /** 467 * Returns the bounds requested on this container. These may not be the actual bounds the 468 * container ends up with due to policy constraints. The {@link Rect} handed back is 469 * shared for all calls to this method and should not be modified. 470 */ getRequestedOverrideBounds()471 public Rect getRequestedOverrideBounds() { 472 mReturnBounds.set(getRequestedOverrideConfiguration().windowConfiguration.getBounds()); 473 474 return mReturnBounds; 475 } 476 477 /** Similar to {@link #getRequestedOverrideBounds()}, but returns the max bounds. */ getRequestedOverrideMaxBounds()478 public Rect getRequestedOverrideMaxBounds() { 479 mReturnBounds.set(getRequestedOverrideConfiguration().windowConfiguration.getMaxBounds()); 480 481 return mReturnBounds; 482 } 483 484 /** 485 * Returns {@code true} if the {@link WindowConfiguration} in the requested override 486 * {@link Configuration} specifies bounds. 487 */ hasOverrideBounds()488 public boolean hasOverrideBounds() { 489 return !getRequestedOverrideBounds().isEmpty(); 490 } 491 492 /** 493 * Sets the passed in {@link Rect} to the current bounds. 494 * @see #getRequestedOverrideBounds() 495 */ getRequestedOverrideBounds(Rect outBounds)496 public void getRequestedOverrideBounds(Rect outBounds) { 497 outBounds.set(getRequestedOverrideBounds()); 498 } 499 500 /** 501 * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor. 502 * This value will be reported when {@link #getBounds()} and 503 * {@link #getRequestedOverrideBounds()}. If 504 * an empty {@link Rect} or null is specified, this container will be considered to match its 505 * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent. 506 * 507 * @param bounds The bounds defining the container size. 508 * 509 * @return a bitmask representing the types of changes made to the bounds. 510 */ setBounds(Rect bounds)511 public int setBounds(Rect bounds) { 512 int boundsChange = diffRequestedOverrideBounds(bounds); 513 final boolean overrideMaxBounds = providesMaxBounds() 514 && diffRequestedOverrideMaxBounds(bounds) != BOUNDS_CHANGE_NONE; 515 516 if (boundsChange == BOUNDS_CHANGE_NONE && !overrideMaxBounds) { 517 return boundsChange; 518 } 519 520 mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); 521 mRequestsTmpConfig.windowConfiguration.setBounds(bounds); 522 onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); 523 524 return boundsChange; 525 } 526 setBounds(int left, int top, int right, int bottom)527 public int setBounds(int left, int top, int right, int bottom) { 528 mTmpRect.set(left, top, right, bottom); 529 return setBounds(mTmpRect); 530 } 531 532 /** 533 * Returns {@code true} if this {@link ConfigurationContainer} provides the maximum bounds to 534 * its child {@link ConfigurationContainer}s. Returns {@code false}, otherwise. 535 * <p> 536 * The maximum bounds is how large a window can be expanded. 537 * </p> 538 */ providesMaxBounds()539 protected boolean providesMaxBounds() { 540 return false; 541 } 542 diffRequestedOverrideMaxBounds(Rect bounds)543 int diffRequestedOverrideMaxBounds(Rect bounds) { 544 if (equivalentRequestedOverrideMaxBounds(bounds)) { 545 return BOUNDS_CHANGE_NONE; 546 } 547 548 int boundsChange = BOUNDS_CHANGE_NONE; 549 550 final Rect existingBounds = getRequestedOverrideMaxBounds(); 551 552 if (bounds == null || existingBounds.left != bounds.left 553 || existingBounds.top != bounds.top) { 554 boundsChange |= BOUNDS_CHANGE_POSITION; 555 } 556 557 if (bounds == null || existingBounds.width() != bounds.width() 558 || existingBounds.height() != bounds.height()) { 559 boundsChange |= BOUNDS_CHANGE_SIZE; 560 } 561 562 return boundsChange; 563 } 564 diffRequestedOverrideBounds(Rect bounds)565 int diffRequestedOverrideBounds(Rect bounds) { 566 if (equivalentRequestedOverrideBounds(bounds)) { 567 return BOUNDS_CHANGE_NONE; 568 } 569 570 int boundsChange = BOUNDS_CHANGE_NONE; 571 572 final Rect existingBounds = getRequestedOverrideBounds(); 573 574 if (bounds == null || existingBounds.left != bounds.left 575 || existingBounds.top != bounds.top) { 576 boundsChange |= BOUNDS_CHANGE_POSITION; 577 } 578 579 if (bounds == null || existingBounds.width() != bounds.width() 580 || existingBounds.height() != bounds.height()) { 581 boundsChange |= BOUNDS_CHANGE_SIZE; 582 } 583 584 return boundsChange; 585 } 586 getWindowConfiguration()587 public WindowConfiguration getWindowConfiguration() { 588 return mFullConfiguration.windowConfiguration; 589 } 590 591 /** Returns the windowing mode the configuration container is currently in. */ getWindowingMode()592 public int getWindowingMode() { 593 return mFullConfiguration.windowConfiguration.getWindowingMode(); 594 } 595 596 /** Returns the windowing mode override that is requested by this container. */ getRequestedOverrideWindowingMode()597 public int getRequestedOverrideWindowingMode() { 598 return mRequestedOverrideConfiguration.windowConfiguration.getWindowingMode(); 599 } 600 601 /** Sets the requested windowing mode override for the configuration container. */ setWindowingMode( int windowingMode)602 public void setWindowingMode(/*@WindowConfiguration.WindowingMode*/ int windowingMode) { 603 mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); 604 mRequestsTmpConfig.windowConfiguration.setWindowingMode(windowingMode); 605 onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); 606 } 607 608 /** Sets the always on top flag for this configuration container. 609 * When you call this function, make sure that the following functions are called as well to 610 * keep proper z-order. 611 * - {@link TaskDisplayArea#positionChildAt(int POSITION_TOP, Task, boolean)}; 612 * */ setAlwaysOnTop(boolean alwaysOnTop)613 public void setAlwaysOnTop(boolean alwaysOnTop) { 614 mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); 615 mRequestsTmpConfig.windowConfiguration.setAlwaysOnTop(alwaysOnTop); 616 onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); 617 } 618 619 /** 620 * Returns true if this container is currently in multi-window mode. I.e. sharing the screen 621 * with another activity. 622 */ inMultiWindowMode()623 public boolean inMultiWindowMode() { 624 /*@WindowConfiguration.WindowingMode*/ int windowingMode = 625 mFullConfiguration.windowConfiguration.getWindowingMode(); 626 return WindowConfiguration.inMultiWindowMode(windowingMode); 627 } 628 inPinnedWindowingMode()629 public boolean inPinnedWindowingMode() { 630 return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; 631 } 632 inFreeformWindowingMode()633 public boolean inFreeformWindowingMode() { 634 return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM; 635 } 636 637 /** Returns the activity type associated with the configuration container. */ 638 /*@WindowConfiguration.ActivityType*/ getActivityType()639 public int getActivityType() { 640 return mFullConfiguration.windowConfiguration.getActivityType(); 641 } 642 643 /** Sets the activity type to associate with the configuration container. */ setActivityType( int activityType)644 public void setActivityType(/*@WindowConfiguration.ActivityType*/ int activityType) { 645 int currentActivityType = getActivityType(); 646 if (currentActivityType == activityType) { 647 return; 648 } 649 if (currentActivityType != ACTIVITY_TYPE_UNDEFINED) { 650 throw new IllegalStateException("Can't change activity type once set: " + this 651 + " activityType=" + activityTypeToString(activityType)); 652 } 653 mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); 654 mRequestsTmpConfig.windowConfiguration.setActivityType(activityType); 655 onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); 656 } 657 isActivityTypeHome()658 public boolean isActivityTypeHome() { 659 return getActivityType() == ACTIVITY_TYPE_HOME; 660 } 661 isActivityTypeRecents()662 public boolean isActivityTypeRecents() { 663 return getActivityType() == ACTIVITY_TYPE_RECENTS; 664 } 665 isActivityTypeHomeOrRecents()666 final boolean isActivityTypeHomeOrRecents() { 667 final int activityType = getActivityType(); 668 return activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS; 669 } 670 isActivityTypeAssistant()671 public boolean isActivityTypeAssistant() { 672 return getActivityType() == ACTIVITY_TYPE_ASSISTANT; 673 } 674 675 /** 676 * Applies app-specific nightMode and {@link LocaleList} on requested configuration. 677 * @return true if any of the requested configuration has been updated. 678 */ applyAppSpecificConfig(Integer nightMode, LocaleList locales, @Configuration.GrammaticalGender Integer gender)679 public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales, 680 @Configuration.GrammaticalGender Integer gender) { 681 mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration()); 682 boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig, 683 nightMode); 684 boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig, 685 locales); 686 boolean newGenderSet = setOverrideGender(mRequestsTmpConfig, 687 gender == null ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : gender); 688 if (newNightModeSet || newLocalesSet || newGenderSet) { 689 onRequestedOverrideConfigurationChanged(mRequestsTmpConfig); 690 } 691 return newNightModeSet || newLocalesSet || newGenderSet; 692 } 693 694 /** 695 * Overrides the night mode applied to this ConfigurationContainer. 696 * @return true if the nightMode has been changed. 697 */ setOverrideNightMode(Configuration requestsTmpConfig, int nightMode)698 private boolean setOverrideNightMode(Configuration requestsTmpConfig, int nightMode) { 699 final int currentUiMode = mRequestedOverrideConfiguration.uiMode; 700 final int currentNightMode = currentUiMode & Configuration.UI_MODE_NIGHT_MASK; 701 final int validNightMode = nightMode & Configuration.UI_MODE_NIGHT_MASK; 702 if (currentNightMode == validNightMode) { 703 return false; 704 } 705 requestsTmpConfig.uiMode = validNightMode 706 | (currentUiMode & ~Configuration.UI_MODE_NIGHT_MASK); 707 return true; 708 } 709 710 /** 711 * Overrides the locales applied to this ConfigurationContainer. 712 * @return true if the LocaleList has been changed. 713 */ setOverrideLocales(Configuration requestsTmpConfig, @NonNull LocaleList overrideLocales)714 private boolean setOverrideLocales(Configuration requestsTmpConfig, 715 @NonNull LocaleList overrideLocales) { 716 if (mRequestedOverrideConfiguration.getLocales().equals(overrideLocales)) { 717 return false; 718 } 719 requestsTmpConfig.setLocales(overrideLocales); 720 requestsTmpConfig.userSetLocale = true; 721 return true; 722 } 723 724 /** 725 * Overrides the gender to this ConfigurationContainer. 726 * 727 * @return true if the grammatical gender has been changed. 728 */ setOverrideGender(Configuration requestsTmpConfig, @Configuration.GrammaticalGender int gender)729 protected boolean setOverrideGender(Configuration requestsTmpConfig, 730 @Configuration.GrammaticalGender int gender) { 731 // Noop, only ActivityRecord and WindowProcessController have enough knowledge about the 732 // app to apply gender correctly. 733 return false; 734 } 735 isActivityTypeDream()736 public boolean isActivityTypeDream() { 737 return getActivityType() == ACTIVITY_TYPE_DREAM; 738 } 739 isActivityTypeStandard()740 public boolean isActivityTypeStandard() { 741 return getActivityType() == ACTIVITY_TYPE_STANDARD; 742 } 743 isActivityTypeStandardOrUndefined()744 public boolean isActivityTypeStandardOrUndefined() { 745 /*@WindowConfiguration.ActivityType*/ final int activityType = getActivityType(); 746 return activityType == ACTIVITY_TYPE_STANDARD || activityType == ACTIVITY_TYPE_UNDEFINED; 747 } 748 isCompatibleActivityType(int currentType, int otherType)749 public static boolean isCompatibleActivityType(int currentType, int otherType) { 750 if (currentType == otherType) { 751 return true; 752 } 753 if (currentType == ACTIVITY_TYPE_ASSISTANT) { 754 // Assistant activities are only compatible with themselves... 755 return false; 756 } 757 // Otherwise we are compatible if us or other is not currently defined. 758 return currentType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED; 759 } 760 761 /** 762 * Returns true if this container is compatible with the input windowing mode and activity type. 763 * The container is compatible: 764 * - If {@param activityType} and {@param windowingMode} match this container activity type and 765 * windowing mode. 766 * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or 767 * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also 768 * standard or undefined and its windowing mode matches {@param windowingMode}. 769 * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or 770 * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't 771 * also standard or undefined and its activity type matches {@param activityType} regardless of 772 * if {@param windowingMode} matches the containers windowing mode. 773 */ isCompatible(int windowingMode, int activityType)774 public boolean isCompatible(int windowingMode, int activityType) { 775 final int thisActivityType = getActivityType(); 776 final int thisWindowingMode = getWindowingMode(); 777 final boolean sameActivityType = thisActivityType == activityType; 778 final boolean sameWindowingMode = thisWindowingMode == windowingMode; 779 780 if (sameActivityType && sameWindowingMode) { 781 return true; 782 } 783 784 if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD) 785 || !isActivityTypeStandardOrUndefined()) { 786 // Only activity type need to match for non-standard activity types that are defined. 787 return sameActivityType; 788 } 789 790 // Otherwise we are compatible if the windowing mode is the same. 791 return sameWindowingMode; 792 } 793 registerConfigurationChangeListener(ConfigurationContainerListener listener)794 void registerConfigurationChangeListener(ConfigurationContainerListener listener) { 795 registerConfigurationChangeListener(listener, true /* shouldDispatchConfig */); 796 } 797 registerConfigurationChangeListener(ConfigurationContainerListener listener, boolean shouldDispatchConfig)798 void registerConfigurationChangeListener(ConfigurationContainerListener listener, 799 boolean shouldDispatchConfig) { 800 if (mChangeListeners.contains(listener)) { 801 return; 802 } 803 mChangeListeners.add(listener); 804 if (shouldDispatchConfig) { 805 listener.onRequestedOverrideConfigurationChanged(mResolvedOverrideConfiguration); 806 listener.onMergedOverrideConfigurationChanged(mMergedOverrideConfiguration); 807 } 808 } 809 unregisterConfigurationChangeListener(ConfigurationContainerListener listener)810 void unregisterConfigurationChangeListener(ConfigurationContainerListener listener) { 811 mChangeListeners.remove(listener); 812 } 813 814 @VisibleForTesting containsListener(ConfigurationContainerListener listener)815 boolean containsListener(ConfigurationContainerListener listener) { 816 return mChangeListeners.contains(listener); 817 } 818 819 /** 820 * Must be called when new parent for the container was set. 821 */ onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent)822 void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { 823 // Removing parent usually means that we've detached this entity to destroy it or to attach 824 // to another parent. In both cases we don't need to update the configuration now. 825 if (newParent != null) { 826 // Update full configuration of this container and all its children. 827 onConfigurationChanged(newParent.mFullConfiguration); 828 } 829 } 830 831 /** 832 * Write to a protocol buffer output stream. Protocol buffer message definition is at 833 * {@link com.android.server.wm.ConfigurationContainerProto}. 834 * 835 * @param proto Stream to write the ConfigurationContainer object to. 836 * @param fieldId Field Id of the ConfigurationContainer as defined in the parent 837 * message. 838 * @param logLevel Determines the amount of data to be written to the Protobuf. 839 * @hide 840 */ 841 @CallSuper dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTracingLogLevel int logLevel)842 protected void dumpDebug(ProtoOutputStream proto, long fieldId, 843 @WindowTracingLogLevel int logLevel) { 844 final long token = proto.start(fieldId); 845 846 if (logLevel == WindowTracingLogLevel.ALL || mHasOverrideConfiguration) { 847 mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION, 848 logLevel == WindowTracingLogLevel.CRITICAL); 849 } 850 851 // Unless trace level is set to `WindowTraceLogLevel.ALL` don't dump anything that isn't 852 // required to mitigate performance overhead 853 if (logLevel == WindowTracingLogLevel.ALL) { 854 mFullConfiguration.dumpDebug(proto, FULL_CONFIGURATION, false /* critical */); 855 mMergedOverrideConfiguration.dumpDebug(proto, MERGED_OVERRIDE_CONFIGURATION, 856 false /* critical */); 857 } 858 859 if (logLevel == WindowTracingLogLevel.TRIM) { 860 // Required for Fass to automatically detect pip transitions in Winscope traces 861 dumpDebugWindowingMode(proto); 862 } 863 864 proto.end(token); 865 } 866 dumpDebugWindowingMode(ProtoOutputStream proto)867 private void dumpDebugWindowingMode(ProtoOutputStream proto) { 868 final long fullConfigToken = proto.start(FULL_CONFIGURATION); 869 final long windowConfigToken = proto.start(WINDOW_CONFIGURATION); 870 871 int windowingMode = mFullConfiguration.windowConfiguration.getWindowingMode(); 872 proto.write(WINDOWING_MODE, windowingMode); 873 874 proto.end(windowConfigToken); 875 proto.end(fullConfigToken); 876 } 877 878 /** 879 * Dumps the names of this container children in the input print writer indenting each 880 * level with the input prefix. 881 */ dumpChildrenNames(PrintWriter pw, String prefix)882 public void dumpChildrenNames(PrintWriter pw, String prefix) { 883 dumpChildrenNames(pw, prefix, true /* isLastChild */); 884 } 885 886 /** 887 * Dumps the names of this container children in the input print writer indenting each 888 * level with the input prefix. 889 */ dumpChildrenNames(PrintWriter pw, String prefix, boolean isLastChild)890 public void dumpChildrenNames(PrintWriter pw, String prefix, boolean isLastChild) { 891 int curWinMode = getWindowingMode(); 892 String winMode = windowingModeToString(curWinMode); 893 if (curWinMode != WINDOWING_MODE_UNDEFINED && 894 curWinMode != WINDOWING_MODE_FULLSCREEN) { 895 winMode = winMode.toUpperCase(); 896 } 897 int requestedWinMode = getRequestedOverrideWindowingMode(); 898 String overrideWinMode = windowingModeToString(requestedWinMode); 899 if (requestedWinMode != WINDOWING_MODE_UNDEFINED && 900 requestedWinMode != WINDOWING_MODE_FULLSCREEN) { 901 overrideWinMode = overrideWinMode.toUpperCase(); 902 } 903 String actType = activityTypeToString(getActivityType()); 904 if (getActivityType() != ACTIVITY_TYPE_UNDEFINED 905 && getActivityType() != ACTIVITY_TYPE_STANDARD) { 906 actType = actType.toUpperCase(); 907 } 908 pw.print(prefix + (isLastChild ? "└─ " : "├─ ")); 909 pw.println(getName() 910 + " type=" + actType 911 + " mode=" + winMode 912 + " override-mode=" + overrideWinMode 913 + " requested-bounds=" + getRequestedOverrideBounds().toShortString() 914 + " bounds=" + getBounds().toShortString()); 915 916 String childPrefix = prefix + (isLastChild ? " " : "│ "); 917 for (int i = getChildCount() - 1; i >= 0; --i) { 918 final E cc = getChildAt(i); 919 cc.dumpChildrenNames(pw, childPrefix, i == 0 /* isLastChild */); 920 } 921 } 922 getName()923 String getName() { 924 return toString(); 925 } 926 isAlwaysOnTop()927 public boolean isAlwaysOnTop() { 928 return mFullConfiguration.windowConfiguration.isAlwaysOnTop(); 929 } 930 hasChild()931 boolean hasChild() { 932 return getChildCount() > 0; 933 } 934 getChildCount()935 abstract protected int getChildCount(); 936 getChildAt(int index)937 abstract protected E getChildAt(int index); 938 getParent()939 abstract protected ConfigurationContainer getParent(); 940 941 } 942