1 /* 2 * Copyright (C) 2024 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.ROTATION_UNDEFINED; 20 import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_METADATA; 21 import static android.content.pm.ActivityInfo.SIZE_CHANGES_SUPPORTED_OVERRIDE; 22 import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_METADATA; 23 import static android.content.pm.ActivityInfo.SIZE_CHANGES_UNSUPPORTED_OVERRIDE; 24 import static android.content.res.Configuration.ORIENTATION_UNDEFINED; 25 26 import static com.android.window.flags.Flags.enableSizeCompatModeImprovementsForConnectedDisplays; 27 import static com.android.server.wm.AppCompatUtils.isInDesktopMode; 28 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.content.pm.ActivityInfo; 32 import android.content.res.Configuration; 33 import android.graphics.Rect; 34 35 import java.io.PrintWriter; 36 import java.util.function.DoubleSupplier; 37 38 /** 39 * Encapsulate logic related to the SizeCompatMode. 40 */ 41 class AppCompatSizeCompatModePolicy { 42 43 @NonNull 44 private final ActivityRecord mActivityRecord; 45 @NonNull 46 private final AppCompatOverrides mAppCompatOverrides; 47 48 // Whether this activity is in size compatibility mode because its bounds don't fit in parent 49 // naturally. 50 private boolean mInSizeCompatModeForBounds = false; 51 /** 52 * The scale to fit at least one side of the activity to its parent. If the activity uses 53 * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5. 54 */ 55 private float mSizeCompatScale = 1f; 56 57 /** 58 * The bounds in global coordinates for activity in size compatibility mode. 59 * @see #hasSizeCompatBounds() 60 */ 61 private Rect mSizeCompatBounds; 62 63 /** 64 * The precomputed display insets for resolving configuration. It will be non-null if 65 * {@link ActivityRecord#shouldCreateAppCompatDisplayInsets} returns {@code true}. 66 */ 67 @Nullable 68 private AppCompatDisplayInsets mAppCompatDisplayInsets; 69 AppCompatSizeCompatModePolicy(@onNull ActivityRecord activityRecord, @NonNull AppCompatOverrides appCompatOverrides)70 AppCompatSizeCompatModePolicy(@NonNull ActivityRecord activityRecord, 71 @NonNull AppCompatOverrides appCompatOverrides) { 72 mActivityRecord = activityRecord; 73 mAppCompatOverrides = appCompatOverrides; 74 } 75 isInSizeCompatModeForBounds()76 boolean isInSizeCompatModeForBounds() { 77 return mInSizeCompatModeForBounds; 78 } 79 setInSizeCompatModeForBounds(boolean inSizeCompatModeForBounds)80 void setInSizeCompatModeForBounds(boolean inSizeCompatModeForBounds) { 81 mInSizeCompatModeForBounds = inSizeCompatModeForBounds; 82 } 83 hasSizeCompatBounds()84 boolean hasSizeCompatBounds() { 85 return mSizeCompatBounds != null; 86 } 87 88 /** 89 * @return The {@code true} if the current instance has 90 * {@link AppCompatSizeCompatModePolicy#mAppCompatDisplayInsets} without 91 * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()} 92 */ hasAppCompatDisplayInsetsWithoutInheritance()93 boolean hasAppCompatDisplayInsetsWithoutInheritance() { 94 return mAppCompatDisplayInsets != null; 95 } 96 97 @Nullable getAppCompatDisplayInsets()98 AppCompatDisplayInsets getAppCompatDisplayInsets() { 99 final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController 100 .getTransparentPolicy(); 101 if (transparentPolicy.isRunning()) { 102 return transparentPolicy.getInheritedAppCompatDisplayInsets(); 103 } 104 return mAppCompatDisplayInsets; 105 } 106 getCompatScaleIfAvailable(@onNull DoubleSupplier scaleWhenNotAvailable)107 float getCompatScaleIfAvailable(@NonNull DoubleSupplier scaleWhenNotAvailable) { 108 return hasSizeCompatBounds() ? mSizeCompatScale 109 : (float) scaleWhenNotAvailable.getAsDouble(); 110 } 111 112 @NonNull getAppSizeCompatBoundsIfAvailable(@onNull Rect boundsWhenNotAvailable)113 Rect getAppSizeCompatBoundsIfAvailable(@NonNull Rect boundsWhenNotAvailable) { 114 return hasSizeCompatBounds() ? mSizeCompatBounds : boundsWhenNotAvailable; 115 } 116 117 @NonNull replaceResolvedBoundsIfNeeded(@onNull Rect resolvedBounds)118 Rect replaceResolvedBoundsIfNeeded(@NonNull Rect resolvedBounds) { 119 return hasSizeCompatBounds() ? mSizeCompatBounds : resolvedBounds; 120 } 121 applyOffsetIfNeeded(@onNull Rect resolvedBounds, @NonNull Configuration resolvedConfig, int offsetX, int offsetY)122 boolean applyOffsetIfNeeded(@NonNull Rect resolvedBounds, 123 @NonNull Configuration resolvedConfig, int offsetX, int offsetY) { 124 if (hasSizeCompatBounds()) { 125 mSizeCompatBounds.offset(offsetX , offsetY); 126 final int dy = mSizeCompatBounds.top - resolvedBounds.top; 127 final int dx = mSizeCompatBounds.left - resolvedBounds.left; 128 AppCompatUtils.offsetBounds(resolvedConfig, dx, dy); 129 return true; 130 } 131 return false; 132 } 133 alignToTopIfNeeded(@onNull Rect parentBounds)134 void alignToTopIfNeeded(@NonNull Rect parentBounds) { 135 if (hasSizeCompatBounds()) { 136 mSizeCompatBounds.top = parentBounds.top; 137 } 138 } 139 applySizeCompatScaleIfNeeded(@onNull Rect resolvedBounds, @NonNull Configuration resolvedConfig)140 void applySizeCompatScaleIfNeeded(@NonNull Rect resolvedBounds, 141 @NonNull Configuration resolvedConfig) { 142 if (mSizeCompatScale != 1f) { 143 final int screenPosX = resolvedBounds.left; 144 final int screenPosY = resolvedBounds.top; 145 final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX; 146 final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY; 147 AppCompatUtils.offsetBounds(resolvedConfig, dx, dy); 148 } 149 } 150 updateSizeCompatScale(@onNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig)151 void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds, 152 @NonNull Configuration newParentConfig) { 153 mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy() 154 .findOpaqueNotFinishingActivityBelow() 155 .map(ar -> Math.min(1.0f, ar.getCompatScale())) 156 .orElseGet(() -> calculateSizeCompatScale( 157 resolvedAppBounds, containerAppBounds, newParentConfig)); 158 } 159 clearSizeCompatModeAttributes()160 void clearSizeCompatModeAttributes() { 161 mInSizeCompatModeForBounds = false; 162 final float lastSizeCompatScale = mSizeCompatScale; 163 mSizeCompatScale = 1f; 164 if (mSizeCompatScale != lastSizeCompatScale) { 165 mActivityRecord.forAllWindows(WindowState::updateGlobalScale, 166 false /* traverseTopToBottom */); 167 } 168 mSizeCompatBounds = null; 169 mAppCompatDisplayInsets = null; 170 mActivityRecord.mAppCompatController.getTransparentPolicy() 171 .clearInheritedAppCompatDisplayInsets(); 172 } 173 clearOverrideConfiguration()174 private Configuration clearOverrideConfiguration() { 175 // Clear config override in #updateAppCompatDisplayInsets(). 176 final int activityType = mActivityRecord.getActivityType(); 177 final Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration(); 178 overrideConfig.unset(); 179 // Keep the activity type which was set when attaching to a task to prevent leaving it 180 // undefined. 181 overrideConfig.windowConfiguration.setActivityType(activityType); 182 return overrideConfig; 183 } 184 clearSizeCompatMode()185 void clearSizeCompatMode() { 186 clearSizeCompatModeAttributes(); 187 final Configuration overrideConfig = clearOverrideConfiguration(); 188 mActivityRecord.onRequestedOverrideConfigurationChanged(overrideConfig); 189 } 190 clearSizeCompatModeIfNeededOnResolveOverrideConfiguration()191 void clearSizeCompatModeIfNeededOnResolveOverrideConfiguration() { 192 if (mAppCompatDisplayInsets == null || !mActivityRecord.isUniversalResizeable()) { 193 return; 194 } 195 clearSizeCompatModeAttributes(); 196 clearOverrideConfiguration(); 197 } 198 dump(@onNull PrintWriter pw, @NonNull String prefix)199 void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 200 if (mSizeCompatScale != 1f || hasSizeCompatBounds()) { 201 pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds=" 202 + mSizeCompatBounds); 203 } 204 } 205 206 /** 207 * Resolves consistent screen configuration for orientation and rotation changes without 208 * inheriting the parent bounds. 209 */ resolveSizeCompatModeConfiguration(@onNull Configuration newParentConfiguration, @NonNull AppCompatDisplayInsets appCompatDisplayInsets, @NonNull Rect tmpBounds)210 void resolveSizeCompatModeConfiguration(@NonNull Configuration newParentConfiguration, 211 @NonNull AppCompatDisplayInsets appCompatDisplayInsets, @NonNull Rect tmpBounds) { 212 final Configuration resolvedConfig = mActivityRecord.getResolvedOverrideConfiguration(); 213 final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); 214 215 // When an activity needs to be letterboxed because of fixed orientation, use fixed 216 // orientation bounds (stored in resolved bounds) instead of parent bounds since the 217 // activity will be displayed within them even if it is in size compat mode. They should be 218 // saved here before resolved bounds are overridden below. 219 final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController 220 .getAspectRatioPolicy(); 221 final boolean useResolvedBounds = aspectRatioPolicy.isAspectRatioApplied(); 222 final Rect containerBounds = useResolvedBounds 223 ? new Rect(resolvedBounds) 224 : newParentConfiguration.windowConfiguration.getBounds(); 225 final Rect containerAppBounds = useResolvedBounds 226 ? new Rect(resolvedConfig.windowConfiguration.getAppBounds()) 227 : mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride; 228 229 final int requestedOrientation = mActivityRecord.getRequestedConfigurationOrientation(); 230 final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; 231 final int parentOrientation = mActivityRecord.mResolveConfigHint.mUseOverrideInsetsForConfig 232 ? mActivityRecord.mResolveConfigHint.mTmpOverrideConfigOrientation 233 : newParentConfiguration.orientation; 234 final int orientation = orientationRequested 235 ? requestedOrientation 236 // We should use the original orientation of the activity when possible to avoid 237 // forcing the activity in the opposite orientation. 238 : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED 239 ? appCompatDisplayInsets.mOriginalRequestedOrientation 240 : parentOrientation; 241 int rotation = newParentConfiguration.windowConfiguration.getRotation(); 242 final boolean isFixedToUserRotation = mActivityRecord.mDisplayContent == null 243 || mActivityRecord.mDisplayContent.getDisplayRotation().isFixedToUserRotation(); 244 final boolean tasksAreFloating = 245 newParentConfiguration.windowConfiguration.tasksAreFloating(); 246 // Ignore parent rotation for floating tasks as window rotation is independent of its parent 247 // and thus will remain, and so should be reconfigured, in its original rotation. 248 if (!isFixedToUserRotation && !tasksAreFloating) { 249 // Use parent rotation because the original display can be rotated. 250 resolvedConfig.windowConfiguration.setRotation(rotation); 251 } else { 252 final int overrideRotation = resolvedConfig.windowConfiguration.getRotation(); 253 if (overrideRotation != ROTATION_UNDEFINED) { 254 rotation = overrideRotation; 255 } 256 } 257 258 // Use compat insets to lock width and height. We should not use the parent width and height 259 // because apps in compat mode should have a constant width and height. The compat insets 260 // are locked when the app is first launched and are never changed after that, so we can 261 // rely on them to contain the original and unchanging width and height of the app. 262 final Rect containingAppBounds = new Rect(); 263 final Rect containingBounds = tmpBounds; 264 appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation, 265 orientation, orientationRequested, isFixedToUserRotation); 266 resolvedBounds.set(containingBounds); 267 // The size of floating task is fixed (only swap), so the aspect ratio is already correct. 268 if (!appCompatDisplayInsets.mIsFloating) { 269 aspectRatioPolicy.applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds, 270 containingBounds); 271 } 272 273 // Use resolvedBounds to compute other override configurations such as appBounds. The bounds 274 // are calculated in compat container space. The actual position on screen will be applied 275 // later, so the calculation is simpler that doesn't need to involve offset from parent. 276 mActivityRecord.mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets; 277 mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration); 278 // Use current screen layout as source because the size of app is independent to parent. 279 resolvedConfig.screenLayout = ActivityRecord.computeScreenLayout( 280 mActivityRecord.getConfiguration().screenLayout, resolvedConfig.screenWidthDp, 281 resolvedConfig.screenHeightDp); 282 283 // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside 284 // the parent bounds appropriately. 285 if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) { 286 resolvedConfig.orientation = parentOrientation; 287 } 288 289 // Below figure is an example that puts an activity which was launched in a larger container 290 // into a smaller container. 291 // The outermost rectangle is the real display bounds. 292 // "@" is the container app bounds (parent bounds or fixed orientation bounds) 293 // "#" is the {@code resolvedBounds} that applies to application. 294 // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled. 295 // ------------------------------ 296 // | | 297 // | @@@@*********@@@@### | 298 // | @ * * @ # | 299 // | @ * * @ # | 300 // | @ * * @ # | 301 // | @@@@*********@@@@ # | 302 // ---------#--------------#----- 303 // # # 304 // ################ 305 // The application is still layouted in "#" since it was launched, and it will be visually 306 // scaled and positioned to "*". 307 308 final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); 309 // Calculates the scale the size compatibility bounds into the region which is available 310 // to application. 311 final float lastSizeCompatScale = mSizeCompatScale; 312 updateSizeCompatScale(resolvedAppBounds, containerAppBounds, newParentConfiguration); 313 314 // Container top inset when floating is not included in height of bounds. 315 final int containerTopInset = tasksAreFloating ? 0 316 : containerAppBounds.top - containerBounds.top; 317 final boolean topNotAligned = 318 containerTopInset != resolvedAppBounds.top - resolvedBounds.top; 319 if (mSizeCompatScale != 1f || topNotAligned) { 320 if (mSizeCompatBounds == null) { 321 mSizeCompatBounds = new Rect(); 322 } 323 mSizeCompatBounds.set(resolvedAppBounds); 324 mSizeCompatBounds.offsetTo(0, 0); 325 mSizeCompatBounds.scale(mSizeCompatScale); 326 // The insets are included in height, e.g. the area of real cutout shouldn't be scaled. 327 mSizeCompatBounds.bottom += containerTopInset; 328 } else { 329 mSizeCompatBounds = null; 330 } 331 if (mSizeCompatScale != lastSizeCompatScale) { 332 mActivityRecord.forAllWindows(WindowState::updateGlobalScale, 333 false /* traverseTopToBottom */); 334 } 335 336 // The position will be later adjusted in updateResolvedBoundsPosition. 337 // Above coordinates are in "@" space, now place "*" and "#" to screen space. 338 final boolean fillContainer = resolvedBounds.equals(containingBounds); 339 final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; 340 final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; 341 342 if (screenPosX != 0 || screenPosY != 0) { 343 if (hasSizeCompatBounds()) { 344 mSizeCompatBounds.offset(screenPosX, screenPosY); 345 } 346 // Add the global coordinates and remove the local coordinates. 347 final int dx = screenPosX - resolvedBounds.left; 348 final int dy = screenPosY - resolvedBounds.top; 349 AppCompatUtils.offsetBounds(resolvedConfig, dx, dy); 350 } 351 352 mInSizeCompatModeForBounds = isInSizeCompatModeForBounds(resolvedAppBounds, 353 containerAppBounds); 354 } 355 356 // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. updateAppCompatDisplayInsets()357 void updateAppCompatDisplayInsets() { 358 if (getAppCompatDisplayInsets() != null 359 || !mActivityRecord.shouldCreateAppCompatDisplayInsets()) { 360 // The override configuration is set only once in size compatibility mode. 361 return; 362 } 363 364 Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration(); 365 final Configuration fullConfig = mActivityRecord.getConfiguration(); 366 367 // Ensure the screen related fields are set. It is used to prevent activity relaunch 368 // when moving between displays. For screenWidthDp and screenWidthDp, because they 369 // are relative to bounds and density, they will be calculated in 370 // {@link Task#computeConfigResourceOverrides} and the result will also be 371 // relatively fixed. 372 overrideConfig.colorMode = fullConfig.colorMode; 373 overrideConfig.densityDpi = fullConfig.densityDpi; 374 if (enableSizeCompatModeImprovementsForConnectedDisplays()) { 375 overrideConfig.touchscreen = fullConfig.touchscreen; 376 overrideConfig.navigation = fullConfig.navigation; 377 overrideConfig.keyboard = fullConfig.keyboard; 378 overrideConfig.keyboardHidden = fullConfig.keyboardHidden; 379 overrideConfig.hardKeyboardHidden = fullConfig.hardKeyboardHidden; 380 overrideConfig.navigationHidden = fullConfig.navigationHidden; 381 } 382 // The smallest screen width is the short side of screen bounds. Because the bounds 383 // and density won't be changed, smallestScreenWidthDp is also fixed. 384 overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp; 385 if (ActivityInfo.isFixedOrientation(mActivityRecord.getOverrideOrientation())) { 386 // lock rotation too. When in size-compat, onConfigurationChanged will watch for and 387 // apply runtime rotation changes. 388 overrideConfig.windowConfiguration.setRotation( 389 fullConfig.windowConfiguration.getRotation()); 390 } 391 392 final Rect letterboxedContainerBounds = mActivityRecord.mAppCompatController 393 .getAspectRatioPolicy().getLetterboxedContainerBounds(); 394 395 // The role of AppCompatDisplayInsets is like the override bounds. 396 mAppCompatDisplayInsets = 397 new AppCompatDisplayInsets(mActivityRecord.mDisplayContent, mActivityRecord, 398 letterboxedContainerBounds, mActivityRecord.mResolveConfigHint 399 .mUseOverrideInsetsForConfig); 400 } 401 402 /** 403 * @return {@code true} if this activity is in size compatibility mode that uses the different 404 * density than its parent or its bounds don't fit in parent naturally. 405 */ inSizeCompatMode()406 boolean inSizeCompatMode() { 407 if (isInSizeCompatModeForBounds()) { 408 return true; 409 } 410 if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets() 411 // The orientation is different from parent when transforming. 412 || mActivityRecord.isFixedRotationTransforming()) { 413 return false; 414 } 415 final Rect appBounds = mActivityRecord.getConfiguration().windowConfiguration 416 .getAppBounds(); 417 if (appBounds == null) { 418 // The app bounds hasn't been computed yet. 419 return false; 420 } 421 final WindowContainer<?> parent = mActivityRecord.getParent(); 422 if (parent == null) { 423 // The parent of detached Activity can be null. 424 return false; 425 } 426 final Configuration parentConfig = parent.getConfiguration(); 427 // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these 428 // fields should be changed with density and bounds, so here only compares the most 429 // significant field. 430 return parentConfig.densityDpi != mActivityRecord.getConfiguration().densityDpi; 431 } 432 433 /** 434 * Indicates the activity will keep the bounds and screen configuration when it was first 435 * launched, no matter how its parent changes. 436 * 437 * <p>If {@true}, then {@link AppCompatDisplayInsets} will be created in {@link 438 * ActivityRecord#resolveOverrideConfiguration} to "freeze" activity bounds and insets. 439 * 440 * @return {@code true} if this activity is declared as non-resizable and fixed orientation or 441 * aspect ratio. 442 */ shouldCreateAppCompatDisplayInsets()443 boolean shouldCreateAppCompatDisplayInsets() { 444 if (mActivityRecord.mAppCompatController.getAspectRatioOverrides() 445 .hasFullscreenOverride()) { 446 // If the user has forced the applications aspect ratio to be fullscreen, don't use size 447 // compatibility mode in any situation. The user has been warned and therefore accepts 448 // the risk of the application misbehaving. 449 return false; 450 } 451 switch (supportsSizeChanges()) { 452 case SIZE_CHANGES_SUPPORTED_METADATA: 453 case SIZE_CHANGES_SUPPORTED_OVERRIDE: 454 return false; 455 case SIZE_CHANGES_UNSUPPORTED_OVERRIDE: 456 return true; 457 default: 458 // Fall through 459 } 460 // Use root activity's info for tasks in multi-window mode, or fullscreen tasks in freeform 461 // task display areas, to ensure visual consistency across activity launches and exits in 462 // the same task. 463 final TaskDisplayArea tda = mActivityRecord.getTaskDisplayArea(); 464 if (mActivityRecord.inMultiWindowMode() || (tda != null && tda.inFreeformWindowingMode())) { 465 final Task task = mActivityRecord.getTask(); 466 final ActivityRecord root = task != null ? task.getRootActivity() : null; 467 if (root != null && root != mActivityRecord 468 && !root.shouldCreateAppCompatDisplayInsets()) { 469 // If the root activity doesn't use size compatibility mode, the activities above 470 // are forced to be the same for consistent visual appearance. 471 return false; 472 } 473 } 474 final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController 475 .getAspectRatioPolicy(); 476 return !mActivityRecord.isResizeable() && (mActivityRecord.info.isFixedOrientation() 477 || aspectRatioPolicy.hasFixedAspectRatio()) 478 // The configuration of non-standard type should be enforced by system. 479 // {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} is set when this activity is 480 // added to a task, but this function is called when resolving the launch params, at 481 // which point, the activity type is still undefined if it will be standard. 482 // For other non-standard types, the type is set in the constructor, so this should 483 // not be a problem. 484 && mActivityRecord.isActivityTypeStandardOrUndefined(); 485 } 486 487 /** 488 * Returns whether the activity supports size changes. 489 */ 490 @ActivityInfo.SizeChangesSupportMode supportsSizeChanges()491 int supportsSizeChanges() { 492 final AppCompatResizeOverrides resizeOverrides = mAppCompatOverrides.getResizeOverrides(); 493 if (resizeOverrides.shouldOverrideForceNonResizeApp()) { 494 return SIZE_CHANGES_UNSUPPORTED_OVERRIDE; 495 } 496 497 if (mActivityRecord.info.supportsSizeChanges) { 498 return SIZE_CHANGES_SUPPORTED_METADATA; 499 } 500 501 if (resizeOverrides.shouldOverrideForceResizeApp()) { 502 return SIZE_CHANGES_SUPPORTED_OVERRIDE; 503 } 504 505 return SIZE_CHANGES_UNSUPPORTED_METADATA; 506 } 507 508 isInSizeCompatModeForBounds(final @NonNull Rect appBounds, final @NonNull Rect containerBounds)509 private boolean isInSizeCompatModeForBounds(final @NonNull Rect appBounds, 510 final @NonNull Rect containerBounds) { 511 if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()) { 512 // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity 513 // is letterboxed. 514 return false; 515 } 516 final int appWidth = appBounds.width(); 517 final int appHeight = appBounds.height(); 518 final int containerAppWidth = containerBounds.width(); 519 final int containerAppHeight = containerBounds.height(); 520 521 if (containerAppWidth == appWidth && containerAppHeight == appHeight) { 522 // Matched the container bounds. 523 return false; 524 } 525 if (containerAppWidth > appWidth && containerAppHeight > appHeight) { 526 // Both sides are smaller than the container. 527 return true; 528 } 529 if (containerAppWidth < appWidth || containerAppHeight < appHeight) { 530 // One side is larger than the container. 531 return true; 532 } 533 534 // The rest of the condition is that only one side is smaller than the container, but it 535 // still needs to exclude the cases where the size is limited by the fixed aspect ratio. 536 final float maxAspectRatio = mActivityRecord.getMaxAspectRatio(); 537 if (maxAspectRatio > 0) { 538 final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) 539 / Math.min(appWidth, appHeight); 540 if (aspectRatio >= maxAspectRatio) { 541 // The current size has reached the max aspect ratio. 542 return false; 543 } 544 } 545 final float minAspectRatio = mActivityRecord.getMinAspectRatio(); 546 if (minAspectRatio > 0) { 547 // The activity should have at least the min aspect ratio, so this checks if the 548 // container still has available space to provide larger aspect ratio. 549 final float containerAspectRatio = 550 (0.5f + Math.max(containerAppWidth, containerAppHeight)) 551 / Math.min(containerAppWidth, containerAppHeight); 552 if (containerAspectRatio <= minAspectRatio) { 553 // The long side has reached the parent. 554 return false; 555 } 556 } 557 return true; 558 } 559 calculateSizeCompatScale(@onNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig)560 private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds, 561 @NonNull Rect containerAppBounds, @NonNull Configuration newParentConfig) { 562 final int contentW = resolvedAppBounds.width(); 563 final int contentH = resolvedAppBounds.height(); 564 final int viewportW = containerAppBounds.width(); 565 final int viewportH = containerAppBounds.height(); 566 // Allow an application to be up-scaled if its window is smaller than its 567 // original container or if it's a freeform window in desktop mode. 568 boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH) 569 || isInDesktopMode(mActivityRecord.mAtmService.mContext, 570 newParentConfig.windowConfiguration.getWindowingMode()); 571 return shouldAllowUpscaling ? Math.min( 572 (float) viewportW / contentW, (float) viewportH / contentH) : 1f; 573 } 574 } 575