1 /* 2 * Copyright (C) 2021 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.WINDOWING_MODE_FULLSCREEN; 20 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; 21 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; 22 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; 23 import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; 24 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS; 25 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; 26 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; 27 import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; 28 import static android.content.pm.ActivityInfo.OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA; 29 import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION; 30 import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; 31 import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; 32 import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; 33 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; 34 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 35 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 36 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 37 import static android.content.pm.ActivityInfo.isFixedOrientation; 38 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; 39 import static android.content.pm.ActivityInfo.screenOrientationToString; 40 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; 41 import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 42 import static android.content.res.Configuration.ORIENTATION_UNDEFINED; 43 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED; 44 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED; 45 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; 46 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 47 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; 48 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; 49 import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; 50 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; 51 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED; 52 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; 53 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS; 54 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; 55 56 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; 57 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER; 58 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT; 59 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT; 60 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP; 61 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION; 62 import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; 63 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER; 64 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM; 65 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT; 66 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT; 67 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP; 68 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER; 69 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER; 70 import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER; 71 import static com.android.server.wm.ActivityRecord.computeAspectRatio; 72 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; 73 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; 74 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; 75 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; 76 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR; 77 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER; 78 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; 79 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; 80 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; 81 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER; 82 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; 83 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; 84 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; 85 import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; 86 import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString; 87 88 import static java.lang.Boolean.FALSE; 89 import static java.lang.Boolean.TRUE; 90 91 import android.annotation.NonNull; 92 import android.annotation.Nullable; 93 import android.app.ActivityManager.TaskDescription; 94 import android.content.pm.ActivityInfo.ScreenOrientation; 95 import android.content.pm.PackageManager; 96 import android.content.res.Configuration; 97 import android.content.res.Resources; 98 import android.graphics.Color; 99 import android.graphics.Point; 100 import android.graphics.Rect; 101 import android.util.Slog; 102 import android.view.InsetsSource; 103 import android.view.InsetsState; 104 import android.view.RoundedCorner; 105 import android.view.SurfaceControl; 106 import android.view.SurfaceControl.Transaction; 107 import android.view.WindowManager; 108 109 import com.android.internal.R; 110 import com.android.internal.annotations.VisibleForTesting; 111 import com.android.internal.statusbar.LetterboxDetails; 112 import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType; 113 114 import java.io.PrintWriter; 115 import java.util.Optional; 116 import java.util.function.BooleanSupplier; 117 import java.util.function.Consumer; 118 import java.util.function.Predicate; 119 120 /** Controls behaviour of the letterbox UI for {@link mActivityRecord}. */ 121 // TODO(b/185262487): Improve test coverage of this class. Parts of it are tested in 122 // SizeCompatTests and LetterboxTests but not all. 123 // TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the 124 // hierarchy in addition to ActivityRecord (Task, DisplayArea, ...). 125 // TODO(b/263021211): Consider renaming to more generic CompatUIController. 126 final class LetterboxUiController { 127 128 private static final Predicate<ActivityRecord> FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE = 129 activityRecord -> activityRecord.fillsParent() && !activityRecord.isFinishing() 130 && activityRecord.nowVisible; 131 132 private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; 133 134 private static final float UNDEFINED_ASPECT_RATIO = 0f; 135 136 // Minimum value of mSetOrientationRequestCounter before qualifying as orientation request loop 137 @VisibleForTesting 138 static final int MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP = 2; 139 // Used to determine reset of mSetOrientationRequestCounter if next app requested 140 // orientation is after timeout value 141 @VisibleForTesting 142 static final int SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS = 1000; 143 144 private final Point mTmpPoint = new Point(); 145 146 private final LetterboxConfiguration mLetterboxConfiguration; 147 148 private final ActivityRecord mActivityRecord; 149 150 /** 151 * Taskbar expanded height. Used to determine when to crop an app window to display the 152 * rounded corners above the expanded taskbar. 153 */ 154 private final float mExpandedTaskBarHeight; 155 156 // TODO(b/265576778): Cache other overrides as well. 157 158 // Corresponds to OVERRIDE_ANY_ORIENTATION 159 private final boolean mIsOverrideAnyOrientationEnabled; 160 // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT 161 private final boolean mIsOverrideToPortraitOrientationEnabled; 162 // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR 163 private final boolean mIsOverrideToNosensorOrientationEnabled; 164 // Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE 165 private final boolean mIsOverrideToReverseLandscapeOrientationEnabled; 166 // Corresponds to OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA 167 private final boolean mIsOverrideOrientationOnlyForCameraEnabled; 168 // Corresponds to OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION 169 private final boolean mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled; 170 // Corresponds to OVERRIDE_RESPECT_REQUESTED_ORIENTATION 171 private final boolean mIsOverrideRespectRequestedOrientationEnabled; 172 173 // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION 174 private final boolean mIsOverrideCameraCompatDisableForceRotationEnabled; 175 // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH 176 private final boolean mIsOverrideCameraCompatDisableRefreshEnabled; 177 // Corresponds to OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE 178 private final boolean mIsOverrideCameraCompatEnableRefreshViaPauseEnabled; 179 180 // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION 181 private final boolean mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled; 182 // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED 183 private final boolean mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled; 184 185 // Corresponds to OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS 186 private final boolean mIsOverrideEnableCompatFakeFocusEnabled; 187 188 @Nullable 189 private final Boolean mBooleanPropertyAllowOrientationOverride; 190 @Nullable 191 private final Boolean mBooleanPropertyAllowDisplayOrientationOverride; 192 193 /* 194 * WindowContainerListener responsible to make translucent activities inherit 195 * constraints from the first opaque activity beneath them. It's null for not 196 * translucent activities. 197 */ 198 @Nullable 199 private WindowContainerListener mLetterboxConfigListener; 200 201 private boolean mShowWallpaperForLetterboxBackground; 202 203 // In case of transparent activities we might need to access the aspectRatio of the 204 // first opaque activity beneath. 205 private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; 206 private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; 207 208 // Updated when ActivityRecord#setRequestedOrientation is called 209 private long mTimeMsLastSetOrientationRequest = 0; 210 211 @Configuration.Orientation 212 private int mInheritedOrientation = ORIENTATION_UNDEFINED; 213 214 // The app compat state for the opaque activity if any 215 private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; 216 217 // Counter for ActivityRecord#setRequestedOrientation 218 private int mSetOrientationRequestCounter = 0; 219 220 // The CompatDisplayInsets of the opaque activity beneath the translucent one. 221 private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; 222 223 @Nullable 224 private Letterbox mLetterbox; 225 226 @Nullable 227 private final Boolean mBooleanPropertyCameraCompatAllowForceRotation; 228 229 @Nullable 230 private final Boolean mBooleanPropertyCameraCompatAllowRefresh; 231 232 @Nullable 233 private final Boolean mBooleanPropertyCameraCompatEnableRefreshViaPause; 234 235 // Whether activity "refresh" was requested but not finished in 236 // ActivityRecord#activityResumedLocked following the camera compat force rotation in 237 // DisplayRotationCompatPolicy. 238 private boolean mIsRefreshAfterRotationRequested; 239 240 @Nullable 241 private final Boolean mBooleanPropertyIgnoreRequestedOrientation; 242 243 @Nullable 244 private final Boolean mBooleanPropertyAllowIgnoringOrientationRequestWhenLoopDetected; 245 246 @Nullable 247 private final Boolean mBooleanPropertyFakeFocus; 248 249 private boolean mIsRelaunchingAfterRequestedOrientationChanged; 250 251 private boolean mLastShouldShowLetterboxUi; 252 253 private boolean mDoubleTapEvent; 254 LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord)255 LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) { 256 mLetterboxConfiguration = wmService.mLetterboxConfiguration; 257 // Given activityRecord may not be fully constructed since LetterboxUiController 258 // is created in its constructor. It shouldn't be used in this constructor but it's safe 259 // to use it after since controller is only used in ActivityRecord. 260 mActivityRecord = activityRecord; 261 262 PackageManager packageManager = wmService.mContext.getPackageManager(); 263 mBooleanPropertyIgnoreRequestedOrientation = 264 readComponentProperty(packageManager, mActivityRecord.packageName, 265 mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, 266 PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); 267 mBooleanPropertyAllowIgnoringOrientationRequestWhenLoopDetected = 268 readComponentProperty(packageManager, mActivityRecord.packageName, 269 mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, 270 PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); 271 mBooleanPropertyFakeFocus = 272 readComponentProperty(packageManager, mActivityRecord.packageName, 273 mLetterboxConfiguration::isCompatFakeFocusEnabled, 274 PROPERTY_COMPAT_ENABLE_FAKE_FOCUS); 275 mBooleanPropertyCameraCompatAllowForceRotation = 276 readComponentProperty(packageManager, mActivityRecord.packageName, 277 () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( 278 /* checkDeviceConfig */ true), 279 PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION); 280 mBooleanPropertyCameraCompatAllowRefresh = 281 readComponentProperty(packageManager, mActivityRecord.packageName, 282 () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( 283 /* checkDeviceConfig */ true), 284 PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH); 285 mBooleanPropertyCameraCompatEnableRefreshViaPause = 286 readComponentProperty(packageManager, mActivityRecord.packageName, 287 () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( 288 /* checkDeviceConfig */ true), 289 PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); 290 291 mExpandedTaskBarHeight = 292 getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); 293 294 mBooleanPropertyAllowOrientationOverride = 295 readComponentProperty(packageManager, mActivityRecord.packageName, 296 /* gatingCondition */ null, 297 PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); 298 mBooleanPropertyAllowDisplayOrientationOverride = 299 readComponentProperty(packageManager, mActivityRecord.packageName, 300 /* gatingCondition */ null, 301 PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE); 302 303 mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION); 304 mIsOverrideToPortraitOrientationEnabled = 305 isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT); 306 mIsOverrideToReverseLandscapeOrientationEnabled = 307 isCompatChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE); 308 mIsOverrideToNosensorOrientationEnabled = 309 isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR); 310 mIsOverrideOrientationOnlyForCameraEnabled = 311 isCompatChangeEnabled(OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA); 312 mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled = 313 isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION); 314 mIsOverrideRespectRequestedOrientationEnabled = 315 isCompatChangeEnabled(OVERRIDE_RESPECT_REQUESTED_ORIENTATION); 316 317 mIsOverrideCameraCompatDisableForceRotationEnabled = 318 isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION); 319 mIsOverrideCameraCompatDisableRefreshEnabled = 320 isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH); 321 mIsOverrideCameraCompatEnableRefreshViaPauseEnabled = 322 isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); 323 324 mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled = 325 isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION); 326 mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled = 327 isCompatChangeEnabled( 328 OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED); 329 330 mIsOverrideEnableCompatFakeFocusEnabled = 331 isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS); 332 } 333 334 /** 335 * Reads a {@link Boolean} component property fot a given {@code packageName} and a {@code 336 * propertyName}. Returns {@code null} if {@code gatingCondition} is {@code false} or if the 337 * property isn't specified for the package. 338 * 339 * <p>Return value is {@link Boolean} rather than {@code boolean} so we can know when the 340 * property is unset. Particularly, when this returns {@code null}, {@link 341 * #shouldEnableWithOverrideAndProperty} will check the value of override for the final 342 * decision. 343 */ 344 @Nullable readComponentProperty(PackageManager packageManager, String packageName, @Nullable BooleanSupplier gatingCondition, String propertyName)345 private static Boolean readComponentProperty(PackageManager packageManager, String packageName, 346 @Nullable BooleanSupplier gatingCondition, String propertyName) { 347 if (gatingCondition != null && !gatingCondition.getAsBoolean()) { 348 return null; 349 } 350 try { 351 return packageManager.getProperty(propertyName, packageName).getBoolean(); 352 } catch (PackageManager.NameNotFoundException e) { 353 // No such property name. 354 } 355 return null; 356 } 357 358 /** Cleans up {@link Letterbox} if it exists.*/ destroy()359 void destroy() { 360 if (mLetterbox != null) { 361 mLetterbox.destroy(); 362 mLetterbox = null; 363 } 364 if (mLetterboxConfigListener != null) { 365 mLetterboxConfigListener.onRemoved(); 366 mLetterboxConfigListener = null; 367 } 368 } 369 onMovedToDisplay(int displayId)370 void onMovedToDisplay(int displayId) { 371 if (mLetterbox != null) { 372 mLetterbox.onMovedToDisplay(displayId); 373 } 374 } 375 376 /** 377 * Whether should ignore app requested orientation in response to an app 378 * calling {@link android.app.Activity#setRequestedOrientation}. 379 * 380 * <p>This is needed to avoid getting into {@link android.app.Activity#setRequestedOrientation} 381 * loop when {@link DisplayContent#getIgnoreOrientationRequest} is enabled or device has 382 * landscape natural orientation which app developers don't expect. For example, the loop can 383 * look like this: 384 * <ol> 385 * <li>App sets default orientation to "unspecified" at runtime 386 * <li>App requests to "portrait" after checking some condition (e.g. display rotation). 387 * <li>(2) leads to fullscreen -> letterboxed bounds change and activity relaunch because 388 * app can't handle the corresponding config changes. 389 * <li>Loop goes back to (1) 390 * </ol> 391 * 392 * <p>This treatment is enabled when the following conditions are met: 393 * <ul> 394 * <li>Flag gating the treatment is enabled 395 * <li>Opt-out component property isn't enabled 396 * <li>Opt-in component property or per-app override are enabled 397 * <li>Activity is relaunched after {@link android.app.Activity#setRequestedOrientation} 398 * call from an app or camera compat force rotation treatment is active for the activity. 399 * <li>Orientation request loop detected and is not letterboxed for fixed orientation 400 * </ul> 401 */ shouldIgnoreRequestedOrientation(@creenOrientation int requestedOrientation)402 boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) { 403 if (shouldEnableWithOverrideAndProperty( 404 /* gatingCondition */ mLetterboxConfiguration 405 ::isPolicyForIgnoringRequestedOrientationEnabled, 406 mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled, 407 mBooleanPropertyIgnoreRequestedOrientation)) { 408 if (mIsRelaunchingAfterRequestedOrientationChanged) { 409 Slog.w(TAG, "Ignoring orientation update to " 410 + screenOrientationToString(requestedOrientation) 411 + " due to relaunching after setRequestedOrientation for " 412 + mActivityRecord); 413 return true; 414 } 415 if (isCameraCompatTreatmentActive()) { 416 Slog.w(TAG, "Ignoring orientation update to " 417 + screenOrientationToString(requestedOrientation) 418 + " due to camera compat treatment for " + mActivityRecord); 419 return true; 420 } 421 } 422 423 if (shouldIgnoreOrientationRequestLoop()) { 424 Slog.w(TAG, "Ignoring orientation update to " 425 + screenOrientationToString(requestedOrientation) 426 + " as orientation request loop was detected for " 427 + mActivityRecord); 428 return true; 429 } 430 return false; 431 } 432 433 /** 434 * Whether an app is calling {@link android.app.Activity#setRequestedOrientation} 435 * in a loop and orientation request should be ignored. 436 * 437 * <p>This should only be called once in response to 438 * {@link android.app.Activity#setRequestedOrientation}. See 439 * {@link #shouldIgnoreRequestedOrientation} for more details. 440 * 441 * <p>This treatment is enabled when the following conditions are met: 442 * <ul> 443 * <li>Flag gating the treatment is enabled 444 * <li>Opt-out component property isn't enabled 445 * <li>Per-app override is enabled 446 * <li>App has requested orientation more than 2 times within 1-second 447 * timer and activity is not letterboxed for fixed orientation 448 * </ul> 449 */ 450 @VisibleForTesting shouldIgnoreOrientationRequestLoop()451 boolean shouldIgnoreOrientationRequestLoop() { 452 if (!shouldEnableWithOptInOverrideAndOptOutProperty( 453 /* gatingCondition */ mLetterboxConfiguration 454 ::isPolicyForIgnoringRequestedOrientationEnabled, 455 mIsOverrideEnableCompatIgnoreOrientationRequestWhenLoopDetectedEnabled, 456 mBooleanPropertyAllowIgnoringOrientationRequestWhenLoopDetected)) { 457 return false; 458 } 459 460 final long currTimeMs = System.currentTimeMillis(); 461 if (currTimeMs - mTimeMsLastSetOrientationRequest 462 < SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS) { 463 mSetOrientationRequestCounter += 1; 464 } else { 465 // Resets app setOrientationRequest counter if timed out 466 mSetOrientationRequestCounter = 0; 467 } 468 // Update time last called 469 mTimeMsLastSetOrientationRequest = currTimeMs; 470 471 return mSetOrientationRequestCounter >= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP 472 && !mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio(); 473 } 474 475 @VisibleForTesting getSetOrientationRequestCounter()476 int getSetOrientationRequestCounter() { 477 return mSetOrientationRequestCounter; 478 } 479 480 /** 481 * Whether sending compat fake focus for split screen resumed activities is enabled. Needed 482 * because some game engines wait to get focus before drawing the content of the app which isn't 483 * guaranteed by default in multi-window modes. 484 * 485 * <p>This treatment is enabled when the following conditions are met: 486 * <ul> 487 * <li>Flag gating the treatment is enabled 488 * <li>Component property is NOT set to false 489 * <li>Component property is set to true or per-app override is enabled 490 * </ul> 491 */ shouldSendFakeFocus()492 boolean shouldSendFakeFocus() { 493 return shouldEnableWithOverrideAndProperty( 494 /* gatingCondition */ mLetterboxConfiguration::isCompatFakeFocusEnabled, 495 mIsOverrideEnableCompatFakeFocusEnabled, 496 mBooleanPropertyFakeFocus); 497 } 498 499 /** 500 * Sets whether an activity is relaunching after the app has called {@link 501 * android.app.Activity#setRequestedOrientation}. 502 */ setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching)503 void setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching) { 504 mIsRelaunchingAfterRequestedOrientationChanged = isRelaunching; 505 } 506 507 /** 508 * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked} 509 * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}. 510 */ isRefreshAfterRotationRequested()511 boolean isRefreshAfterRotationRequested() { 512 return mIsRefreshAfterRotationRequested; 513 } 514 setIsRefreshAfterRotationRequested(boolean isRequested)515 void setIsRefreshAfterRotationRequested(boolean isRequested) { 516 mIsRefreshAfterRotationRequested = isRequested; 517 } 518 isOverrideRespectRequestedOrientationEnabled()519 boolean isOverrideRespectRequestedOrientationEnabled() { 520 return mIsOverrideRespectRequestedOrientationEnabled; 521 } 522 523 /** 524 * Whether should fix display orientation to landscape natural orientation when a task is 525 * fullscreen and the display is ignoring orientation requests. 526 * 527 * <p>This treatment is enabled when the following conditions are met: 528 * <ul> 529 * <li>Opt-out component property isn't enabled 530 * <li>Opt-in per-app override is enabled 531 * <li>Task is in fullscreen. 532 * <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled 533 * <li>Natural orientation of the display is landscape. 534 * </ul> 535 */ shouldUseDisplayLandscapeNaturalOrientation()536 boolean shouldUseDisplayLandscapeNaturalOrientation() { 537 return shouldEnableWithOptInOverrideAndOptOutProperty( 538 /* gatingCondition */ () -> mActivityRecord.mDisplayContent != null 539 && mActivityRecord.getTask() != null 540 && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest() 541 && !mActivityRecord.getTask().inMultiWindowMode() 542 && mActivityRecord.mDisplayContent.getNaturalOrientation() 543 == ORIENTATION_LANDSCAPE, 544 mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled, 545 mBooleanPropertyAllowDisplayOrientationOverride); 546 } 547 548 @ScreenOrientation overrideOrientationIfNeeded(@creenOrientation int candidate)549 int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { 550 // In some cases (e.g. Kids app) we need to map the candidate orientation to some other 551 // orientation. 552 candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate); 553 554 if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) { 555 return candidate; 556 } 557 558 DisplayContent displayContent = mActivityRecord.mDisplayContent; 559 if (mIsOverrideOrientationOnlyForCameraEnabled && displayContent != null 560 && (displayContent.mDisplayRotationCompatPolicy == null 561 || !displayContent.mDisplayRotationCompatPolicy 562 .isActivityEligibleForOrientationOverride(mActivityRecord))) { 563 return candidate; 564 } 565 566 if (mIsOverrideToReverseLandscapeOrientationEnabled 567 && (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) { 568 Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " 569 + mActivityRecord + " is overridden to " 570 + screenOrientationToString(SCREEN_ORIENTATION_REVERSE_LANDSCAPE)); 571 return SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 572 } 573 574 if (!mIsOverrideAnyOrientationEnabled && isFixedOrientation(candidate)) { 575 return candidate; 576 } 577 578 if (mIsOverrideToPortraitOrientationEnabled) { 579 Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " 580 + mActivityRecord + " is overridden to " 581 + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT)); 582 return SCREEN_ORIENTATION_PORTRAIT; 583 } 584 585 if (mIsOverrideToNosensorOrientationEnabled) { 586 Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " 587 + mActivityRecord + " is overridden to " 588 + screenOrientationToString(SCREEN_ORIENTATION_NOSENSOR)); 589 return SCREEN_ORIENTATION_NOSENSOR; 590 } 591 592 return candidate; 593 } 594 isOverrideOrientationOnlyForCameraEnabled()595 boolean isOverrideOrientationOnlyForCameraEnabled() { 596 return mIsOverrideOrientationOnlyForCameraEnabled; 597 } 598 599 /** 600 * Whether activity is eligible for activity "refresh" after camera compat force rotation 601 * treatment. See {@link DisplayRotationCompatPolicy} for context. 602 * 603 * <p>This treatment is enabled when the following conditions are met: 604 * <ul> 605 * <li>Flag gating the camera compat treatment is enabled. 606 * <li>Activity isn't opted out by the device manufacturer with override or by the app 607 * developers with the component property. 608 * </ul> 609 */ shouldRefreshActivityForCameraCompat()610 boolean shouldRefreshActivityForCameraCompat() { 611 return shouldEnableWithOptOutOverrideAndProperty( 612 /* gatingCondition */ () -> mLetterboxConfiguration 613 .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), 614 mIsOverrideCameraCompatDisableRefreshEnabled, 615 mBooleanPropertyCameraCompatAllowRefresh); 616 } 617 618 /** 619 * Whether activity should be "refreshed" after the camera compat force rotation treatment 620 * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped 621 * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context. 622 * 623 * <p>This treatment is enabled when the following conditions are met: 624 * <ul> 625 * <li>Flag gating the camera compat treatment is enabled. 626 * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the 627 * component property by the app developers. 628 * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device 629 * manufacturer with override / by the app developers with the component property. 630 * </ul> 631 */ shouldRefreshActivityViaPauseForCameraCompat()632 boolean shouldRefreshActivityViaPauseForCameraCompat() { 633 return shouldEnableWithOverrideAndProperty( 634 /* gatingCondition */ () -> mLetterboxConfiguration 635 .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), 636 mIsOverrideCameraCompatEnableRefreshViaPauseEnabled, 637 mBooleanPropertyCameraCompatEnableRefreshViaPause); 638 } 639 640 /** 641 * Whether activity is eligible for camera compat force rotation treatment. See {@link 642 * DisplayRotationCompatPolicy} for context. 643 * 644 * <p>This treatment is enabled when the following conditions are met: 645 * <ul> 646 * <li>Flag gating the camera compat treatment is enabled. 647 * <li>Activity isn't opted out by the device manufacturer with override or by the app 648 * developers with the component property. 649 * </ul> 650 */ shouldForceRotateForCameraCompat()651 boolean shouldForceRotateForCameraCompat() { 652 return shouldEnableWithOptOutOverrideAndProperty( 653 /* gatingCondition */ () -> mLetterboxConfiguration 654 .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), 655 mIsOverrideCameraCompatDisableForceRotationEnabled, 656 mBooleanPropertyCameraCompatAllowForceRotation); 657 } 658 isCameraCompatTreatmentActive()659 private boolean isCameraCompatTreatmentActive() { 660 DisplayContent displayContent = mActivityRecord.mDisplayContent; 661 if (displayContent == null) { 662 return false; 663 } 664 return displayContent.mDisplayRotationCompatPolicy != null 665 && displayContent.mDisplayRotationCompatPolicy 666 .isTreatmentEnabledForActivity(mActivityRecord); 667 } 668 isCompatChangeEnabled(long overrideChangeId)669 private boolean isCompatChangeEnabled(long overrideChangeId) { 670 return mActivityRecord.info.isChangeEnabled(overrideChangeId); 671 } 672 673 /** 674 * Returns {@code true} when the following conditions are met: 675 * <ul> 676 * <li>{@code gatingCondition} isn't {@code false} 677 * <li>OEM didn't opt out with a per-app override 678 * <li>App developers didn't opt out with a component {@code property} 679 * </ul> 680 * 681 * <p>This is used for the treatments that are enabled based with the heuristic but can be 682 * disabled on per-app basis by OEMs or app developers. 683 */ shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition, boolean isOverrideChangeEnabled, Boolean property)684 private boolean shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition, 685 boolean isOverrideChangeEnabled, Boolean property) { 686 if (!gatingCondition.getAsBoolean()) { 687 return false; 688 } 689 return !FALSE.equals(property) && !isOverrideChangeEnabled; 690 } 691 692 /** 693 * Returns {@code true} when the following conditions are met: 694 * <ul> 695 * <li>{@code gatingCondition} isn't {@code false} 696 * <li>OEM did opt in with a per-app override 697 * <li>App developers didn't opt out with a component {@code property} 698 * </ul> 699 * 700 * <p>This is used for the treatments that are enabled based with the heuristic but can be 701 * disabled on per-app basis by OEMs or app developers. 702 */ shouldEnableWithOptInOverrideAndOptOutProperty(BooleanSupplier gatingCondition, boolean isOverrideChangeEnabled, Boolean property)703 private boolean shouldEnableWithOptInOverrideAndOptOutProperty(BooleanSupplier gatingCondition, 704 boolean isOverrideChangeEnabled, Boolean property) { 705 if (!gatingCondition.getAsBoolean()) { 706 return false; 707 } 708 return !FALSE.equals(property) && isOverrideChangeEnabled; 709 } 710 711 /** 712 * Returns {@code true} when the following conditions are met: 713 * <ul> 714 * <li>{@code gatingCondition} isn't {@code false} 715 * <li>App developers didn't opt out with a component {@code property} 716 * <li>App developers opted in with a component {@code property} or an OEM opted in with a 717 * per-app override 718 * </ul> 719 * 720 * <p>This is used for the treatments that are enabled only on per-app basis. 721 */ shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition, boolean isOverrideChangeEnabled, Boolean property)722 private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition, 723 boolean isOverrideChangeEnabled, Boolean property) { 724 if (!gatingCondition.getAsBoolean()) { 725 return false; 726 } 727 if (FALSE.equals(property)) { 728 return false; 729 } 730 return TRUE.equals(property) || isOverrideChangeEnabled; 731 } 732 hasWallpaperBackgroundForLetterbox()733 boolean hasWallpaperBackgroundForLetterbox() { 734 return mShowWallpaperForLetterboxBackground; 735 } 736 737 /** Gets the letterbox insets. The insets will be empty if there is no letterbox. */ getLetterboxInsets()738 Rect getLetterboxInsets() { 739 if (mLetterbox != null) { 740 return mLetterbox.getInsets(); 741 } else { 742 return new Rect(); 743 } 744 } 745 746 /** Gets the inner bounds of letterbox. The bounds will be empty if there is no letterbox. */ getLetterboxInnerBounds(Rect outBounds)747 void getLetterboxInnerBounds(Rect outBounds) { 748 if (mLetterbox != null) { 749 outBounds.set(mLetterbox.getInnerFrame()); 750 final WindowState w = mActivityRecord.findMainWindow(); 751 if (w != null) { 752 adjustBoundsForTaskbar(w, outBounds); 753 } 754 } else { 755 outBounds.setEmpty(); 756 } 757 } 758 759 /** Gets the outer bounds of letterbox. The bounds will be empty if there is no letterbox. */ getLetterboxOuterBounds(Rect outBounds)760 private void getLetterboxOuterBounds(Rect outBounds) { 761 if (mLetterbox != null) { 762 outBounds.set(mLetterbox.getOuterFrame()); 763 } else { 764 outBounds.setEmpty(); 765 } 766 } 767 768 /** 769 * @return {@code true} if bar shown within a given rectangle is allowed to be fully transparent 770 * when the current activity is displayed. 771 */ isFullyTransparentBarAllowed(Rect rect)772 boolean isFullyTransparentBarAllowed(Rect rect) { 773 return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect); 774 } 775 updateLetterboxSurface(WindowState winHint)776 void updateLetterboxSurface(WindowState winHint) { 777 updateLetterboxSurface(winHint, mActivityRecord.getSyncTransaction()); 778 } 779 updateLetterboxSurface(WindowState winHint, Transaction t)780 void updateLetterboxSurface(WindowState winHint, Transaction t) { 781 final WindowState w = mActivityRecord.findMainWindow(); 782 if (w != winHint && winHint != null && w != null) { 783 return; 784 } 785 layoutLetterbox(winHint); 786 if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { 787 mLetterbox.applySurfaceChanges(t); 788 } 789 } 790 layoutLetterbox(WindowState winHint)791 void layoutLetterbox(WindowState winHint) { 792 final WindowState w = mActivityRecord.findMainWindow(); 793 if (w == null || winHint != null && w != winHint) { 794 return; 795 } 796 updateRoundedCornersIfNeeded(w); 797 // If there is another main window that is not an application-starting window, we should 798 // update rounded corners for it as well, to avoid flickering rounded corners. 799 final WindowState nonStartingAppW = mActivityRecord.findMainWindow( 800 /* includeStartingApp= */ false); 801 if (nonStartingAppW != null && nonStartingAppW != w) { 802 updateRoundedCornersIfNeeded(nonStartingAppW); 803 } 804 805 updateWallpaperForLetterbox(w); 806 if (shouldShowLetterboxUi(w)) { 807 if (mLetterbox == null) { 808 mLetterbox = new Letterbox(() -> mActivityRecord.makeChildSurface(null), 809 mActivityRecord.mWmService.mTransactionFactory, 810 this::shouldLetterboxHaveRoundedCorners, 811 this::getLetterboxBackgroundColor, 812 this::hasWallpaperBackgroundForLetterbox, 813 this::getLetterboxWallpaperBlurRadius, 814 this::getLetterboxWallpaperDarkScrimAlpha, 815 this::handleHorizontalDoubleTap, 816 this::handleVerticalDoubleTap, 817 this::getLetterboxParentSurface); 818 mLetterbox.attachInput(w); 819 } 820 821 if (mActivityRecord.isInLetterboxAnimation()) { 822 // In this case we attach the letterbox to the task instead of the activity. 823 mActivityRecord.getTask().getPosition(mTmpPoint); 824 } else { 825 mActivityRecord.getPosition(mTmpPoint); 826 } 827 828 // Get the bounds of the "space-to-fill". The transformed bounds have the highest 829 // priority because the activity is launched in a rotated environment. In multi-window 830 // mode, the task-level represents this. In fullscreen-mode, the task container does 831 // (since the orientation letterbox is also applied to the task). 832 final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds(); 833 final Rect spaceToFill = transformedBounds != null 834 ? transformedBounds 835 : mActivityRecord.inMultiWindowMode() 836 ? mActivityRecord.getTask().getBounds() 837 : mActivityRecord.getRootTask().getParent().getBounds(); 838 // In case of translucent activities an option is to use the WindowState#getFrame() of 839 // the first opaque activity beneath. In some cases (e.g. an opaque activity is using 840 // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct 841 // information and in particular it might provide a value for a smaller area making 842 // the letterbox overlap with the translucent activity's frame. 843 // If we use WindowState#getFrame() for the translucent activity's letterbox inner 844 // frame, the letterbox will then be overlapped with the translucent activity's frame. 845 // Because the surface layer of letterbox is lower than an activity window, this 846 // won't crop the content, but it may affect other features that rely on values stored 847 // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher 848 // For this reason we use ActivityRecord#getBounds() that the translucent activity 849 // inherits from the first opaque activity beneath and also takes care of the scaling 850 // in case of activities in size compat mode. 851 final Rect innerFrame = hasInheritedLetterboxBehavior() 852 ? mActivityRecord.getBounds() : w.getFrame(); 853 mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); 854 // We need to notify Shell that letterbox position has changed. 855 mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */); 856 } else if (mLetterbox != null) { 857 mLetterbox.hide(); 858 } 859 } 860 isFromDoubleTap()861 boolean isFromDoubleTap() { 862 final boolean isFromDoubleTap = mDoubleTapEvent; 863 mDoubleTapEvent = false; 864 return isFromDoubleTap; 865 } 866 getLetterboxParentSurface()867 SurfaceControl getLetterboxParentSurface() { 868 if (mActivityRecord.isInLetterboxAnimation()) { 869 return mActivityRecord.getTask().getSurfaceControl(); 870 } 871 return mActivityRecord.getSurfaceControl(); 872 } 873 shouldLetterboxHaveRoundedCorners()874 private boolean shouldLetterboxHaveRoundedCorners() { 875 // TODO(b/214030873): remove once background is drawn for transparent activities 876 // Letterbox shouldn't have rounded corners if the activity is transparent 877 return mLetterboxConfiguration.isLetterboxActivityCornersRounded() 878 && mActivityRecord.fillsParent(); 879 } 880 881 // Check if we are in the given pose and in fullscreen mode. 882 // Note that we check the task rather than the parent as with ActivityEmbedding the parent might 883 // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is 884 // actually fullscreen. isDisplayFullScreenAndInPosture(DeviceStateController.DeviceState state, boolean isTabletop)885 private boolean isDisplayFullScreenAndInPosture(DeviceStateController.DeviceState state, 886 boolean isTabletop) { 887 Task task = mActivityRecord.getTask(); 888 return mActivityRecord.mDisplayContent != null 889 && mActivityRecord.mDisplayContent.getDisplayRotation().isDeviceInPosture(state, 890 isTabletop) 891 && task != null 892 && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; 893 } 894 895 // Note that we check the task rather than the parent as with ActivityEmbedding the parent might 896 // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is 897 // actually fullscreen. isDisplayFullScreenAndSeparatingHinge()898 private boolean isDisplayFullScreenAndSeparatingHinge() { 899 Task task = mActivityRecord.getTask(); 900 return mActivityRecord.mDisplayContent != null 901 && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge() 902 && task != null 903 && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; 904 } 905 906 getHorizontalPositionMultiplier(Configuration parentConfiguration)907 float getHorizontalPositionMultiplier(Configuration parentConfiguration) { 908 // Don't check resolved configuration because it may not be updated yet during 909 // configuration change. 910 boolean bookModeEnabled = isFullScreenAndBookModeEnabled(); 911 return isHorizontalReachabilityEnabled(parentConfiguration) 912 // Using the last global dynamic position to avoid "jumps" when moving 913 // between apps or activities. 914 ? mLetterboxConfiguration.getHorizontalMultiplierForReachability(bookModeEnabled) 915 : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(bookModeEnabled); 916 } 917 isFullScreenAndBookModeEnabled()918 private boolean isFullScreenAndBookModeEnabled() { 919 return isDisplayFullScreenAndInPosture( 920 DeviceStateController.DeviceState.HALF_FOLDED, false /* isTabletop */) 921 && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); 922 } 923 getVerticalPositionMultiplier(Configuration parentConfiguration)924 float getVerticalPositionMultiplier(Configuration parentConfiguration) { 925 // Don't check resolved configuration because it may not be updated yet during 926 // configuration change. 927 boolean tabletopMode = isDisplayFullScreenAndInPosture( 928 DeviceStateController.DeviceState.HALF_FOLDED, true /* isTabletop */); 929 return isVerticalReachabilityEnabled(parentConfiguration) 930 // Using the last global dynamic position to avoid "jumps" when moving 931 // between apps or activities. 932 ? mLetterboxConfiguration.getVerticalMultiplierForReachability(tabletopMode) 933 : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode); 934 } 935 getFixedOrientationLetterboxAspectRatio(@onNull Configuration parentConfiguration)936 float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) { 937 return shouldUseSplitScreenAspectRatio(parentConfiguration) 938 ? getSplitScreenAspectRatio() 939 : mActivityRecord.shouldCreateCompatDisplayInsets() 940 ? getDefaultMinAspectRatioForUnresizableApps() 941 : getDefaultMinAspectRatio(); 942 } 943 recomputeConfigurationForCameraCompatIfNeeded()944 void recomputeConfigurationForCameraCompatIfNeeded() { 945 if (isOverrideOrientationOnlyForCameraEnabled() 946 || isCameraCompatSplitScreenAspectRatioAllowed()) { 947 mActivityRecord.recomputeConfiguration(); 948 } 949 } 950 951 /** 952 * Whether we use split screen aspect ratio for the activity when camera compat treatment 953 * is active because the corresponding config is enabled and activity supports resizing. 954 */ isCameraCompatSplitScreenAspectRatioAllowed()955 private boolean isCameraCompatSplitScreenAspectRatioAllowed() { 956 return mLetterboxConfiguration.isCameraCompatSplitScreenAspectRatioEnabled() 957 && !mActivityRecord.shouldCreateCompatDisplayInsets(); 958 } 959 shouldUseSplitScreenAspectRatio(@onNull Configuration parentConfiguration)960 private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) { 961 final boolean isBookMode = isDisplayFullScreenAndInPosture( 962 DeviceStateController.DeviceState.HALF_FOLDED, 963 /* isTabletop */ false); 964 final boolean isNotCenteredHorizontally = getHorizontalPositionMultiplier( 965 parentConfiguration) != LETTERBOX_POSITION_MULTIPLIER_CENTER; 966 final boolean isTabletopMode = isDisplayFullScreenAndInPosture( 967 DeviceStateController.DeviceState.HALF_FOLDED, 968 /* isTabletop */ true); 969 // Don't resize to split screen size when in book mode if letterbox position is centered 970 return ((isBookMode && isNotCenteredHorizontally) || isTabletopMode) 971 || isCameraCompatSplitScreenAspectRatioAllowed() 972 && isCameraCompatTreatmentActive(); 973 } 974 getDefaultMinAspectRatioForUnresizableApps()975 private float getDefaultMinAspectRatioForUnresizableApps() { 976 if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled() 977 || mActivityRecord.getDisplayContent() == null) { 978 return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() 979 > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO 980 ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps() 981 : getDefaultMinAspectRatio(); 982 } 983 984 return getSplitScreenAspectRatio(); 985 } 986 getSplitScreenAspectRatio()987 float getSplitScreenAspectRatio() { 988 // Getting the same aspect ratio that apps get in split screen. 989 final DisplayContent displayContent = mActivityRecord.getDisplayContent(); 990 if (displayContent == null) { 991 return getDefaultMinAspectRatioForUnresizableApps(); 992 } 993 int dividerWindowWidth = 994 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness); 995 int dividerInsets = 996 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); 997 int dividerSize = dividerWindowWidth - dividerInsets * 2; 998 final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds()); 999 if (bounds.width() >= bounds.height()) { 1000 bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); 1001 bounds.right = bounds.centerX(); 1002 } else { 1003 bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2); 1004 bounds.bottom = bounds.centerY(); 1005 } 1006 return computeAspectRatio(bounds); 1007 } 1008 getDefaultMinAspectRatio()1009 private float getDefaultMinAspectRatio() { 1010 final DisplayContent displayContent = mActivityRecord.getDisplayContent(); 1011 if (displayContent == null 1012 || !mLetterboxConfiguration 1013 .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) { 1014 return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio(); 1015 } 1016 return computeAspectRatio(new Rect(displayContent.getBounds())); 1017 } 1018 getResources()1019 Resources getResources() { 1020 return mActivityRecord.mWmService.mContext.getResources(); 1021 } 1022 1023 @LetterboxConfiguration.LetterboxVerticalReachabilityPosition getLetterboxPositionForVerticalReachability()1024 int getLetterboxPositionForVerticalReachability() { 1025 final boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge(); 1026 return mLetterboxConfiguration.getLetterboxPositionForVerticalReachability( 1027 isInFullScreenTabletopMode); 1028 } 1029 1030 @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition getLetterboxPositionForHorizontalReachability()1031 int getLetterboxPositionForHorizontalReachability() { 1032 final boolean isInFullScreenBookMode = isFullScreenAndBookModeEnabled(); 1033 return mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability( 1034 isInFullScreenBookMode); 1035 } 1036 1037 @VisibleForTesting handleHorizontalDoubleTap(int x)1038 void handleHorizontalDoubleTap(int x) { 1039 if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) { 1040 return; 1041 } 1042 1043 if (mLetterbox.getInnerFrame().left <= x && mLetterbox.getInnerFrame().right >= x) { 1044 // Only react to clicks at the sides of the letterboxed app window. 1045 return; 1046 } 1047 1048 boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge() 1049 && mLetterboxConfiguration.getIsAutomaticReachabilityInBookModeEnabled(); 1050 int letterboxPositionForHorizontalReachability = mLetterboxConfiguration 1051 .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode); 1052 if (mLetterbox.getInnerFrame().left > x) { 1053 // Moving to the next stop on the left side of the app window: right > center > left. 1054 mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop( 1055 isInFullScreenBookMode); 1056 int changeToLog = 1057 letterboxPositionForHorizontalReachability 1058 == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER 1059 ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT 1060 : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER; 1061 logLetterboxPositionChange(changeToLog); 1062 } else if (mLetterbox.getInnerFrame().right < x) { 1063 // Moving to the next stop on the right side of the app window: left > center > right. 1064 mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop( 1065 isInFullScreenBookMode); 1066 int changeToLog = 1067 letterboxPositionForHorizontalReachability 1068 == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER 1069 ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT 1070 : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER; 1071 logLetterboxPositionChange(changeToLog); 1072 } 1073 mDoubleTapEvent = true; 1074 // TODO(197549949): Add animation for transition. 1075 mActivityRecord.recomputeConfiguration(); 1076 } 1077 1078 @VisibleForTesting handleVerticalDoubleTap(int y)1079 void handleVerticalDoubleTap(int y) { 1080 if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) { 1081 return; 1082 } 1083 1084 if (mLetterbox.getInnerFrame().top <= y && mLetterbox.getInnerFrame().bottom >= y) { 1085 // Only react to clicks at the top and bottom of the letterboxed app window. 1086 return; 1087 } 1088 boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge(); 1089 int letterboxPositionForVerticalReachability = mLetterboxConfiguration 1090 .getLetterboxPositionForVerticalReachability(isInFullScreenTabletopMode); 1091 if (mLetterbox.getInnerFrame().top > y) { 1092 // Moving to the next stop on the top side of the app window: bottom > center > top. 1093 mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop( 1094 isInFullScreenTabletopMode); 1095 int changeToLog = 1096 letterboxPositionForVerticalReachability 1097 == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER 1098 ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP 1099 : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER; 1100 logLetterboxPositionChange(changeToLog); 1101 } else if (mLetterbox.getInnerFrame().bottom < y) { 1102 // Moving to the next stop on the bottom side of the app window: top > center > bottom. 1103 mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop( 1104 isInFullScreenTabletopMode); 1105 int changeToLog = 1106 letterboxPositionForVerticalReachability 1107 == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER 1108 ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM 1109 : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER; 1110 logLetterboxPositionChange(changeToLog); 1111 } 1112 mDoubleTapEvent = true; 1113 // TODO(197549949): Add animation for transition. 1114 mActivityRecord.recomputeConfiguration(); 1115 } 1116 1117 /** 1118 * Whether horizontal reachability is enabled for an activity in the current configuration. 1119 * 1120 * <p>Conditions that needs to be met: 1121 * <ul> 1122 * <li>Activity is portrait-only. 1123 * <li>Fullscreen window in landscape device orientation. 1124 * <li>Horizontal Reachability is enabled. 1125 * <li>Activity fills parent vertically. 1126 * </ul> 1127 */ isHorizontalReachabilityEnabled(Configuration parentConfiguration)1128 private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) { 1129 // Use screen resolved bounds which uses resolved bounds or size compat bounds 1130 // as activity bounds can sometimes be empty 1131 return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled() 1132 && parentConfiguration.windowConfiguration.getWindowingMode() 1133 == WINDOWING_MODE_FULLSCREEN 1134 && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE 1135 && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT) 1136 // Check whether the activity fills the parent vertically. 1137 && parentConfiguration.windowConfiguration.getAppBounds().height() 1138 <= mActivityRecord.getScreenResolvedBounds().height(); 1139 } 1140 1141 @VisibleForTesting isHorizontalReachabilityEnabled()1142 boolean isHorizontalReachabilityEnabled() { 1143 return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration()); 1144 } 1145 isLetterboxDoubleTapEducationEnabled()1146 boolean isLetterboxDoubleTapEducationEnabled() { 1147 return isHorizontalReachabilityEnabled() || isVerticalReachabilityEnabled(); 1148 } 1149 1150 /** 1151 * Whether vertical reachability is enabled for an activity in the current configuration. 1152 * 1153 * <p>Conditions that needs to be met: 1154 * <ul> 1155 * <li>Activity is landscape-only. 1156 * <li>Fullscreen window in portrait device orientation. 1157 * <li>Vertical Reachability is enabled. 1158 * <li>Activity fills parent horizontally. 1159 * </ul> 1160 */ isVerticalReachabilityEnabled(Configuration parentConfiguration)1161 private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) { 1162 // Use screen resolved bounds which uses resolved bounds or size compat bounds 1163 // as activity bounds can sometimes be empty 1164 return mLetterboxConfiguration.getIsVerticalReachabilityEnabled() 1165 && parentConfiguration.windowConfiguration.getWindowingMode() 1166 == WINDOWING_MODE_FULLSCREEN 1167 && (parentConfiguration.orientation == ORIENTATION_PORTRAIT 1168 && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE) 1169 // Check whether the activity fills the parent horizontally. 1170 && parentConfiguration.windowConfiguration.getBounds().width() 1171 == mActivityRecord.getScreenResolvedBounds().width(); 1172 } 1173 1174 @VisibleForTesting isVerticalReachabilityEnabled()1175 boolean isVerticalReachabilityEnabled() { 1176 return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration()); 1177 } 1178 1179 @VisibleForTesting shouldShowLetterboxUi(WindowState mainWindow)1180 boolean shouldShowLetterboxUi(WindowState mainWindow) { 1181 if (mIsRelaunchingAfterRequestedOrientationChanged || !isSurfaceReadyToShow(mainWindow)) { 1182 return mLastShouldShowLetterboxUi; 1183 } 1184 1185 final boolean shouldShowLetterboxUi = 1186 (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow)) 1187 && mainWindow.areAppWindowBoundsLetterboxed() 1188 // Check for FLAG_SHOW_WALLPAPER explicitly instead of using 1189 // WindowContainer#showWallpaper because the later will return true when this 1190 // activity is using blurred wallpaper for letterbox background. 1191 && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0; 1192 1193 mLastShouldShowLetterboxUi = shouldShowLetterboxUi; 1194 1195 return shouldShowLetterboxUi; 1196 } 1197 1198 @VisibleForTesting isSurfaceReadyToShow(WindowState mainWindow)1199 boolean isSurfaceReadyToShow(WindowState mainWindow) { 1200 return mainWindow.isDrawn() // Regular case 1201 // Waiting for relayoutWindow to call preserveSurface 1202 || mainWindow.isDragResizeChanged(); 1203 } 1204 1205 @VisibleForTesting isSurfaceVisible(WindowState mainWindow)1206 boolean isSurfaceVisible(WindowState mainWindow) { 1207 return mainWindow.isOnScreen() && (mActivityRecord.isVisible() 1208 || mActivityRecord.isVisibleRequested()); 1209 } 1210 getLetterboxBackgroundColor()1211 private Color getLetterboxBackgroundColor() { 1212 final WindowState w = mActivityRecord.findMainWindow(); 1213 if (w == null || w.isLetterboxedForDisplayCutout()) { 1214 return Color.valueOf(Color.BLACK); 1215 } 1216 @LetterboxBackgroundType int letterboxBackgroundType = 1217 mLetterboxConfiguration.getLetterboxBackgroundType(); 1218 TaskDescription taskDescription = mActivityRecord.taskDescription; 1219 switch (letterboxBackgroundType) { 1220 case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING: 1221 if (taskDescription != null && taskDescription.getBackgroundColorFloating() != 0) { 1222 return Color.valueOf(taskDescription.getBackgroundColorFloating()); 1223 } 1224 break; 1225 case LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND: 1226 if (taskDescription != null && taskDescription.getBackgroundColor() != 0) { 1227 return Color.valueOf(taskDescription.getBackgroundColor()); 1228 } 1229 break; 1230 case LETTERBOX_BACKGROUND_WALLPAPER: 1231 if (hasWallpaperBackgroundForLetterbox()) { 1232 // Color is used for translucent scrim that dims wallpaper. 1233 return Color.valueOf(Color.BLACK); 1234 } 1235 Slog.w(TAG, "Wallpaper option is selected for letterbox background but " 1236 + "blur is not supported by a device or not supported in the current " 1237 + "window configuration or both alpha scrim and blur radius aren't " 1238 + "provided so using solid color background"); 1239 break; 1240 case LETTERBOX_BACKGROUND_SOLID_COLOR: 1241 return mLetterboxConfiguration.getLetterboxBackgroundColor(); 1242 default: 1243 throw new AssertionError( 1244 "Unexpected letterbox background type: " + letterboxBackgroundType); 1245 } 1246 // If picked option configured incorrectly or not supported then default to a solid color 1247 // background. 1248 return mLetterboxConfiguration.getLetterboxBackgroundColor(); 1249 } 1250 updateRoundedCornersIfNeeded(final WindowState mainWindow)1251 private void updateRoundedCornersIfNeeded(final WindowState mainWindow) { 1252 final SurfaceControl windowSurface = mainWindow.getSurfaceControl(); 1253 if (windowSurface == null || !windowSurface.isValid()) { 1254 return; 1255 } 1256 1257 // cropBounds must be non-null for the cornerRadius to be ever applied. 1258 mActivityRecord.getSyncTransaction() 1259 .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow)) 1260 .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); 1261 } 1262 1263 @VisibleForTesting 1264 @Nullable getCropBoundsIfNeeded(final WindowState mainWindow)1265 Rect getCropBoundsIfNeeded(final WindowState mainWindow) { 1266 if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) { 1267 // We don't want corner radius on the window. 1268 // In the case the ActivityRecord requires a letterboxed animation we never want 1269 // rounded corners on the window because rounded corners are applied at the 1270 // animation-bounds surface level and rounded corners on the window would interfere 1271 // with that leading to unexpected rounded corner positioning during the animation. 1272 return null; 1273 } 1274 1275 final Rect cropBounds = new Rect(mActivityRecord.getBounds()); 1276 1277 // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo} 1278 // because taskbar bounds used in {@link #adjustBoundsIfNeeded} 1279 // are in screen coordinates 1280 adjustBoundsForTaskbar(mainWindow, cropBounds); 1281 1282 final float scale = mainWindow.mInvGlobalScale; 1283 if (scale != 1f && scale > 0f) { 1284 cropBounds.scale(scale); 1285 } 1286 1287 // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface 1288 // control is in the top left corner of an app window so offsetting bounds 1289 // accordingly. 1290 cropBounds.offsetTo(0, 0); 1291 return cropBounds; 1292 } 1293 requiresRoundedCorners(final WindowState mainWindow)1294 private boolean requiresRoundedCorners(final WindowState mainWindow) { 1295 return isLetterboxedNotForDisplayCutout(mainWindow) 1296 && mLetterboxConfiguration.isLetterboxActivityCornersRounded(); 1297 } 1298 1299 // Returns rounded corners radius the letterboxed activity should have based on override in 1300 // R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii. 1301 // Device corners can be different on the right and left sides, but we use the same radius 1302 // for all corners for consistency and pick a minimal bottom one for consistency with a 1303 // taskbar rounded corners. getRoundedCornersRadius(final WindowState mainWindow)1304 int getRoundedCornersRadius(final WindowState mainWindow) { 1305 if (!requiresRoundedCorners(mainWindow)) { 1306 return 0; 1307 } 1308 1309 final int radius; 1310 if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) { 1311 radius = mLetterboxConfiguration.getLetterboxActivityCornersRadius(); 1312 } else { 1313 final InsetsState insetsState = mainWindow.getInsetsState(); 1314 radius = Math.min( 1315 getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT), 1316 getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT)); 1317 } 1318 1319 final float scale = mainWindow.mInvGlobalScale; 1320 return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius; 1321 } 1322 1323 /** 1324 * Returns the taskbar in case it is visible and expanded in height, otherwise returns null. 1325 */ 1326 @VisibleForTesting 1327 @Nullable getExpandedTaskbarOrNull(final WindowState mainWindow)1328 InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) { 1329 final InsetsSource taskbar = mainWindow.getInsetsState().peekSource( 1330 InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); 1331 if (taskbar != null && taskbar.isVisible() 1332 && taskbar.getFrame().height() >= mExpandedTaskBarHeight) { 1333 return taskbar; 1334 } 1335 return null; 1336 } 1337 getIsRelaunchingAfterRequestedOrientationChanged()1338 boolean getIsRelaunchingAfterRequestedOrientationChanged() { 1339 return mIsRelaunchingAfterRequestedOrientationChanged; 1340 } 1341 adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds)1342 private void adjustBoundsForTaskbar(final WindowState mainWindow, final Rect bounds) { 1343 // Rounded corners should be displayed above the taskbar. When taskbar is hidden, 1344 // an insets frame is equal to a navigation bar which shouldn't affect position of 1345 // rounded corners since apps are expected to handle navigation bar inset. 1346 // This condition checks whether the taskbar is visible. 1347 // Do not crop the taskbar inset if the window is in immersive mode - the user can 1348 // swipe to show/hide the taskbar as an overlay. 1349 // Adjust the bounds only in case there is an expanded taskbar, 1350 // otherwise the rounded corners will be shown behind the navbar. 1351 final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow); 1352 if (expandedTaskbarOrNull != null) { 1353 // Rounded corners should be displayed above the expanded taskbar. 1354 bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top); 1355 } 1356 } 1357 getInsetsStateCornerRadius( InsetsState insetsState, @RoundedCorner.Position int position)1358 private int getInsetsStateCornerRadius( 1359 InsetsState insetsState, @RoundedCorner.Position int position) { 1360 RoundedCorner corner = insetsState.getRoundedCorners().getRoundedCorner(position); 1361 return corner == null ? 0 : corner.getRadius(); 1362 } 1363 isLetterboxedNotForDisplayCutout(WindowState mainWindow)1364 private boolean isLetterboxedNotForDisplayCutout(WindowState mainWindow) { 1365 return shouldShowLetterboxUi(mainWindow) 1366 && !mainWindow.isLetterboxedForDisplayCutout(); 1367 } 1368 updateWallpaperForLetterbox(WindowState mainWindow)1369 private void updateWallpaperForLetterbox(WindowState mainWindow) { 1370 @LetterboxBackgroundType int letterboxBackgroundType = 1371 mLetterboxConfiguration.getLetterboxBackgroundType(); 1372 boolean wallpaperShouldBeShown = 1373 letterboxBackgroundType == LETTERBOX_BACKGROUND_WALLPAPER 1374 // Don't use wallpaper as a background if letterboxed for display cutout. 1375 && isLetterboxedNotForDisplayCutout(mainWindow) 1376 // Check that dark scrim alpha or blur radius are provided 1377 && (getLetterboxWallpaperBlurRadius() > 0 1378 || getLetterboxWallpaperDarkScrimAlpha() > 0) 1379 // Check that blur is supported by a device if blur radius is provided. 1380 && (getLetterboxWallpaperBlurRadius() <= 0 1381 || isLetterboxWallpaperBlurSupported()); 1382 if (mShowWallpaperForLetterboxBackground != wallpaperShouldBeShown) { 1383 mShowWallpaperForLetterboxBackground = wallpaperShouldBeShown; 1384 mActivityRecord.requestUpdateWallpaperIfNeeded(); 1385 } 1386 } 1387 getLetterboxWallpaperBlurRadius()1388 private int getLetterboxWallpaperBlurRadius() { 1389 int blurRadius = mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius(); 1390 return blurRadius < 0 ? 0 : blurRadius; 1391 } 1392 getLetterboxWallpaperDarkScrimAlpha()1393 private float getLetterboxWallpaperDarkScrimAlpha() { 1394 float alpha = mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha(); 1395 // No scrim by default. 1396 return (alpha < 0 || alpha >= 1) ? 0.0f : alpha; 1397 } 1398 isLetterboxWallpaperBlurSupported()1399 private boolean isLetterboxWallpaperBlurSupported() { 1400 return mLetterboxConfiguration.mContext.getSystemService(WindowManager.class) 1401 .isCrossWindowBlurEnabled(); 1402 } 1403 dump(PrintWriter pw, String prefix)1404 void dump(PrintWriter pw, String prefix) { 1405 final WindowState mainWin = mActivityRecord.findMainWindow(); 1406 if (mainWin == null) { 1407 return; 1408 } 1409 1410 boolean areBoundsLetterboxed = mainWin.areAppWindowBoundsLetterboxed(); 1411 pw.println(prefix + "areBoundsLetterboxed=" + areBoundsLetterboxed); 1412 if (!areBoundsLetterboxed) { 1413 return; 1414 } 1415 1416 pw.println(prefix + " letterboxReason=" + getLetterboxReasonString(mainWin)); 1417 pw.println(prefix + " activityAspectRatio=" 1418 + mActivityRecord.computeAspectRatio(mActivityRecord.getBounds())); 1419 1420 boolean shouldShowLetterboxUi = shouldShowLetterboxUi(mainWin); 1421 pw.println(prefix + "shouldShowLetterboxUi=" + shouldShowLetterboxUi); 1422 1423 if (!shouldShowLetterboxUi) { 1424 return; 1425 } 1426 pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( 1427 getLetterboxBackgroundColor().toArgb())); 1428 pw.println(prefix + " letterboxBackgroundType=" 1429 + letterboxBackgroundTypeToString( 1430 mLetterboxConfiguration.getLetterboxBackgroundType())); 1431 pw.println(prefix + " letterboxCornerRadius=" 1432 + getRoundedCornersRadius(mainWin)); 1433 if (mLetterboxConfiguration.getLetterboxBackgroundType() 1434 == LETTERBOX_BACKGROUND_WALLPAPER) { 1435 pw.println(prefix + " isLetterboxWallpaperBlurSupported=" 1436 + isLetterboxWallpaperBlurSupported()); 1437 pw.println(prefix + " letterboxBackgroundWallpaperDarkScrimAlpha=" 1438 + getLetterboxWallpaperDarkScrimAlpha()); 1439 pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius=" 1440 + getLetterboxWallpaperBlurRadius()); 1441 } 1442 1443 pw.println(prefix + " isHorizontalReachabilityEnabled=" 1444 + isHorizontalReachabilityEnabled()); 1445 pw.println(prefix + " isVerticalReachabilityEnabled=" + isVerticalReachabilityEnabled()); 1446 pw.println(prefix + " letterboxHorizontalPositionMultiplier=" 1447 + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration())); 1448 pw.println(prefix + " letterboxVerticalPositionMultiplier=" 1449 + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration())); 1450 pw.println(prefix + " letterboxPositionForHorizontalReachability=" 1451 + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString( 1452 mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(false))); 1453 pw.println(prefix + " letterboxPositionForVerticalReachability=" 1454 + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString( 1455 mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(false))); 1456 pw.println(prefix + " fixedOrientationLetterboxAspectRatio=" 1457 + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio()); 1458 pw.println(prefix + " defaultMinAspectRatioForUnresizableApps=" 1459 + mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()); 1460 pw.println(prefix + " isSplitScreenAspectRatioForUnresizableAppsEnabled=" 1461 + mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); 1462 pw.println(prefix + " isDisplayAspectRatioEnabledForFixedOrientationLetterbox=" 1463 + mLetterboxConfiguration 1464 .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()); 1465 } 1466 1467 /** 1468 * Returns a string representing the reason for letterboxing. This method assumes the activity 1469 * is letterboxed. 1470 */ getLetterboxReasonString(WindowState mainWin)1471 private String getLetterboxReasonString(WindowState mainWin) { 1472 if (mActivityRecord.inSizeCompatMode()) { 1473 return "SIZE_COMPAT_MODE"; 1474 } 1475 if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) { 1476 return "FIXED_ORIENTATION"; 1477 } 1478 if (mainWin.isLetterboxedForDisplayCutout()) { 1479 return "DISPLAY_CUTOUT"; 1480 } 1481 if (mActivityRecord.isAspectRatioApplied()) { 1482 return "ASPECT_RATIO"; 1483 } 1484 return "UNKNOWN_REASON"; 1485 } 1486 letterboxHorizontalReachabilityPositionToLetterboxPosition( @etterboxConfiguration.LetterboxHorizontalReachabilityPosition int position)1487 private int letterboxHorizontalReachabilityPositionToLetterboxPosition( 1488 @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition int position) { 1489 switch (position) { 1490 case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT: 1491 return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT; 1492 case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER: 1493 return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER; 1494 case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT: 1495 return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT; 1496 default: 1497 throw new AssertionError( 1498 "Unexpected letterbox horizontal reachability position type: " 1499 + position); 1500 } 1501 } 1502 letterboxVerticalReachabilityPositionToLetterboxPosition( @etterboxConfiguration.LetterboxVerticalReachabilityPosition int position)1503 private int letterboxVerticalReachabilityPositionToLetterboxPosition( 1504 @LetterboxConfiguration.LetterboxVerticalReachabilityPosition int position) { 1505 switch (position) { 1506 case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP: 1507 return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP; 1508 case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER: 1509 return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER; 1510 case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM: 1511 return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; 1512 default: 1513 throw new AssertionError( 1514 "Unexpected letterbox vertical reachability position type: " 1515 + position); 1516 } 1517 } 1518 getLetterboxPositionForLogging()1519 int getLetterboxPositionForLogging() { 1520 int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION; 1521 if (isHorizontalReachabilityEnabled()) { 1522 int letterboxPositionForHorizontalReachability = getLetterboxConfiguration() 1523 .getLetterboxPositionForHorizontalReachability( 1524 isDisplayFullScreenAndInPosture( 1525 DeviceStateController.DeviceState.HALF_FOLDED, 1526 false /* isTabletop */)); 1527 positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition( 1528 letterboxPositionForHorizontalReachability); 1529 } else if (isVerticalReachabilityEnabled()) { 1530 int letterboxPositionForVerticalReachability = getLetterboxConfiguration() 1531 .getLetterboxPositionForVerticalReachability( 1532 isDisplayFullScreenAndInPosture( 1533 DeviceStateController.DeviceState.HALF_FOLDED, 1534 true /* isTabletop */)); 1535 positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition( 1536 letterboxPositionForVerticalReachability); 1537 } 1538 return positionToLog; 1539 } 1540 getLetterboxConfiguration()1541 private LetterboxConfiguration getLetterboxConfiguration() { 1542 return mLetterboxConfiguration; 1543 } 1544 1545 /** 1546 * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}. 1547 */ logLetterboxPositionChange(int letterboxPositionChange)1548 private void logLetterboxPositionChange(int letterboxPositionChange) { 1549 mActivityRecord.mTaskSupervisor.getActivityMetricsLogger() 1550 .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange); 1551 } 1552 1553 @Nullable getLetterboxDetails()1554 LetterboxDetails getLetterboxDetails() { 1555 final WindowState w = mActivityRecord.findMainWindow(); 1556 if (mLetterbox == null || w == null || w.isLetterboxedForDisplayCutout()) { 1557 return null; 1558 } 1559 Rect letterboxInnerBounds = new Rect(); 1560 Rect letterboxOuterBounds = new Rect(); 1561 getLetterboxInnerBounds(letterboxInnerBounds); 1562 getLetterboxOuterBounds(letterboxOuterBounds); 1563 1564 if (letterboxInnerBounds.isEmpty() || letterboxOuterBounds.isEmpty()) { 1565 return null; 1566 } 1567 1568 return new LetterboxDetails( 1569 letterboxInnerBounds, 1570 letterboxOuterBounds, 1571 w.mAttrs.insetsFlags.appearance 1572 ); 1573 } 1574 1575 /** 1576 * Handles translucent activities letterboxing inheriting constraints from the 1577 * first opaque activity beneath. 1578 * @param parent The parent container. 1579 */ onActivityParentChanged(WindowContainer<?> parent)1580 void onActivityParentChanged(WindowContainer<?> parent) { 1581 if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { 1582 return; 1583 } 1584 if (mLetterboxConfigListener != null) { 1585 mLetterboxConfigListener.onRemoved(); 1586 clearInheritedConfig(); 1587 } 1588 // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the 1589 // opaque activity constraints because we're expecting the activity is already letterboxed. 1590 if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() 1591 || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { 1592 return; 1593 } 1594 final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( 1595 FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, 1596 mActivityRecord /* boundary */, false /* includeBoundary */, 1597 true /* traverseTopToBottom */); 1598 if (firstOpaqueActivityBeneath == null) { 1599 // We skip letterboxing if the translucent activity doesn't have any opaque 1600 // activities beneath 1601 return; 1602 } 1603 inheritConfiguration(firstOpaqueActivityBeneath); 1604 mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( 1605 mActivityRecord, firstOpaqueActivityBeneath, 1606 (opaqueConfig, transparentConfig) -> { 1607 final Configuration mutatedConfiguration = 1608 fromOriginalTranslucentConfig(transparentConfig); 1609 final Rect parentBounds = parent.getWindowConfiguration().getBounds(); 1610 final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds(); 1611 final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); 1612 // We cannot use letterboxBounds directly here because the position relies on 1613 // letterboxing. Using letterboxBounds directly, would produce a double offset. 1614 bounds.set(parentBounds.left, parentBounds.top, 1615 parentBounds.left + letterboxBounds.width(), 1616 parentBounds.top + letterboxBounds.height()); 1617 // We need to initialize appBounds to avoid NPE. The actual value will 1618 // be set ahead when resolving the Configuration for the activity. 1619 mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); 1620 inheritConfiguration(firstOpaqueActivityBeneath); 1621 return mutatedConfiguration; 1622 }); 1623 } 1624 1625 /** 1626 * @return {@code true} if the current activity is translucent with an opaque activity 1627 * beneath. In this case it will inherit bounds, orientation and aspect ratios from 1628 * the first opaque activity beneath. 1629 */ hasInheritedLetterboxBehavior()1630 boolean hasInheritedLetterboxBehavior() { 1631 return mLetterboxConfigListener != null; 1632 } 1633 1634 /** 1635 * @return {@code true} if the current activity is translucent with an opaque activity 1636 * beneath and needs to inherit its orientation. 1637 */ hasInheritedOrientation()1638 boolean hasInheritedOrientation() { 1639 // To force a different orientation, the transparent one needs to have an explicit one 1640 // otherwise the existing one is fine and the actual orientation will depend on the 1641 // bounds. 1642 // To avoid wrong behaviour, we're not forcing orientation for activities with not 1643 // fixed orientation (e.g. permission dialogs). 1644 return hasInheritedLetterboxBehavior() 1645 && mActivityRecord.getOverrideOrientation() 1646 != SCREEN_ORIENTATION_UNSPECIFIED; 1647 } 1648 getInheritedMinAspectRatio()1649 float getInheritedMinAspectRatio() { 1650 return mInheritedMinAspectRatio; 1651 } 1652 getInheritedMaxAspectRatio()1653 float getInheritedMaxAspectRatio() { 1654 return mInheritedMaxAspectRatio; 1655 } 1656 getInheritedAppCompatState()1657 int getInheritedAppCompatState() { 1658 return mInheritedAppCompatState; 1659 } 1660 1661 @Configuration.Orientation getInheritedOrientation()1662 int getInheritedOrientation() { 1663 return mInheritedOrientation; 1664 } 1665 getInheritedCompatDisplayInsets()1666 ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { 1667 return mInheritedCompatDisplayInsets; 1668 } 1669 clearInheritedCompatDisplayInsets()1670 void clearInheritedCompatDisplayInsets() { 1671 mInheritedCompatDisplayInsets = null; 1672 } 1673 1674 /** 1675 * In case of translucent activities, it consumes the {@link ActivityRecord} of the first opaque 1676 * activity beneath using the given consumer and returns {@code true}. 1677 */ applyOnOpaqueActivityBelow(@onNull Consumer<ActivityRecord> consumer)1678 boolean applyOnOpaqueActivityBelow(@NonNull Consumer<ActivityRecord> consumer) { 1679 return findOpaqueNotFinishingActivityBelow() 1680 .map(activityRecord -> { 1681 consumer.accept(activityRecord); 1682 return true; 1683 }).orElse(false); 1684 } 1685 1686 /** 1687 * @return The first not finishing opaque activity beneath the current translucent activity 1688 * if it exists and the strategy is enabled. 1689 */ findOpaqueNotFinishingActivityBelow()1690 Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { 1691 if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) { 1692 return Optional.empty(); 1693 } 1694 return Optional.ofNullable(mActivityRecord.getTask().getActivity( 1695 FIRST_OPAQUE_NOT_FINISHING_ACTIVITY_PREDICATE /* callback */, 1696 mActivityRecord /* boundary */, false /* includeBoundary */, 1697 true /* traverseTopToBottom */)); 1698 } 1699 1700 // When overriding translucent activities configuration we need to keep some of the 1701 // original properties fromOriginalTranslucentConfig(Configuration translucentConfig)1702 private Configuration fromOriginalTranslucentConfig(Configuration translucentConfig) { 1703 final Configuration configuration = new Configuration(translucentConfig); 1704 // The values for the following properties will be defined during the configuration 1705 // resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the 1706 // properties inherited from the first not finishing opaque activity beneath. 1707 configuration.orientation = ORIENTATION_UNDEFINED; 1708 configuration.screenWidthDp = configuration.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED; 1709 configuration.screenHeightDp = 1710 configuration.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED; 1711 configuration.smallestScreenWidthDp = 1712 configuration.compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; 1713 return configuration; 1714 } 1715 inheritConfiguration(ActivityRecord firstOpaque)1716 private void inheritConfiguration(ActivityRecord firstOpaque) { 1717 // To avoid wrong behaviour, we're not forcing a specific aspect ratio to activities 1718 // which are not already providing one (e.g. permission dialogs) and presumably also 1719 // not resizable. 1720 if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { 1721 mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio(); 1722 } 1723 if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) { 1724 mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio(); 1725 } 1726 mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); 1727 mInheritedAppCompatState = firstOpaque.getAppCompatState(); 1728 mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); 1729 } 1730 clearInheritedConfig()1731 private void clearInheritedConfig() { 1732 mLetterboxConfigListener = null; 1733 mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; 1734 mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; 1735 mInheritedOrientation = ORIENTATION_UNDEFINED; 1736 mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; 1737 mInheritedCompatDisplayInsets = null; 1738 } 1739 } 1740