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