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