1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm; 18 19 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER; 20 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO; 21 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; 22 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9; 23 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2; 24 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3; 25 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT; 26 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE; 27 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN; 28 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN; 29 import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; 30 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; 31 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; 32 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE; 33 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE; 34 35 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; 36 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; 37 import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER; 38 import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; 39 import static com.android.server.wm.AppCompatUtils.isChangeEnabled; 40 import static com.android.server.wm.AppCompatUtils.isDisplayIgnoreActivitySizeRestrictions; 41 42 import android.annotation.NonNull; 43 import android.content.pm.IPackageManager; 44 import android.content.pm.PackageManager; 45 import android.content.res.Configuration; 46 import android.content.res.Resources; 47 import android.graphics.Rect; 48 import android.os.RemoteException; 49 import android.util.Slog; 50 51 import com.android.internal.R; 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.server.wm.utils.OptPropFactory; 54 55 /** 56 * Encapsulates app compat configurations and overrides related to aspect ratio. 57 */ 58 class AppCompatAspectRatioOverrides { 59 60 private static final String TAG = 61 TAG_WITH_CLASS_NAME ? "AppCompatAspectRatioOverrides" : TAG_ATM; 62 63 @NonNull 64 private final ActivityRecord mActivityRecord; 65 @NonNull 66 private final AppCompatConfiguration mAppCompatConfiguration; 67 @NonNull 68 private final UserAspectRatioState mUserAspectRatioState; 69 70 @NonNull 71 private final OptPropFactory.OptProp mAllowMinAspectRatioOverrideOptProp; 72 @NonNull 73 private final OptPropFactory.OptProp mAllowUserAspectRatioOverrideOptProp; 74 @NonNull 75 private final OptPropFactory.OptProp mAllowUserAspectRatioFullscreenOverrideOptProp; 76 @NonNull 77 private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp; 78 @NonNull 79 private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery; 80 @NonNull 81 private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides; 82 AppCompatAspectRatioOverrides(@onNull ActivityRecord activityRecord, @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder, @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery, @NonNull AppCompatReachabilityOverrides appCompatReachabilityOverrides)83 AppCompatAspectRatioOverrides(@NonNull ActivityRecord activityRecord, 84 @NonNull AppCompatConfiguration appCompatConfiguration, 85 @NonNull OptPropFactory optPropBuilder, 86 @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery, 87 @NonNull AppCompatReachabilityOverrides appCompatReachabilityOverrides) { 88 mActivityRecord = activityRecord; 89 mAppCompatConfiguration = appCompatConfiguration; 90 mAppCompatDeviceStateQuery = appCompatDeviceStateQuery; 91 mUserAspectRatioState = new UserAspectRatioState(); 92 mAppCompatReachabilityOverrides = appCompatReachabilityOverrides; 93 mAllowMinAspectRatioOverrideOptProp = optPropBuilder.create( 94 PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE); 95 mAllowUserAspectRatioOverrideOptProp = optPropBuilder.create( 96 PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, 97 mAppCompatConfiguration::isUserAppAspectRatioSettingsEnabled); 98 mAllowUserAspectRatioFullscreenOverrideOptProp = optPropBuilder.create( 99 PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE, 100 mAppCompatConfiguration::isUserAppAspectRatioFullscreenEnabled); 101 mAllowOrientationOverrideOptProp = optPropBuilder.create( 102 PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); 103 } 104 105 /** 106 * Whether we should apply the min aspect ratio per-app override. When this override is applied 107 * the min aspect ratio given in the app's manifest will be overridden to the largest enabled 108 * aspect ratio treatment unless the app's manifest value is higher. The treatment will also 109 * apply if no value is provided in the manifest. 110 * 111 * <p>This method returns {@code true} when the following conditions are met: 112 * <ul> 113 * <li>Opt-out component property isn't enabled 114 * <li>Per-app override is enabled 115 * </ul> 116 */ shouldOverrideMinAspectRatio()117 boolean shouldOverrideMinAspectRatio() { 118 return mAllowMinAspectRatioOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( 119 isChangeEnabled(mActivityRecord, OVERRIDE_MIN_ASPECT_RATIO)); 120 } 121 122 /** 123 * Whether we should apply the user aspect ratio override to the min aspect ratio for the 124 * current app. 125 */ shouldApplyUserMinAspectRatioOverride()126 boolean shouldApplyUserMinAspectRatioOverride() { 127 if (!shouldEnableUserAspectRatioSettings()) { 128 return false; 129 } 130 131 final int aspectRatio = getUserMinAspectRatioOverrideCode(); 132 133 return aspectRatio != USER_MIN_ASPECT_RATIO_UNSET 134 && aspectRatio != USER_MIN_ASPECT_RATIO_APP_DEFAULT 135 && aspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN; 136 } 137 userPreferenceCompatibleWithNonResizability()138 boolean userPreferenceCompatibleWithNonResizability() { 139 final int aspectRatio = getUserMinAspectRatioOverrideCode(); 140 return aspectRatio == USER_MIN_ASPECT_RATIO_UNSET 141 || aspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN; 142 } 143 shouldApplyUserFullscreenOverride()144 boolean shouldApplyUserFullscreenOverride() { 145 if (isUserFullscreenOverrideEnabled()) { 146 final int aspectRatio = getUserMinAspectRatioOverrideCode(); 147 148 return aspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN; 149 } 150 151 return false; 152 } 153 isUserFullscreenOverrideEnabled()154 boolean isUserFullscreenOverrideEnabled() { 155 if (mAllowUserAspectRatioOverrideOptProp.isFalse() 156 || mAllowUserAspectRatioFullscreenOverrideOptProp.isFalse() 157 || !mAppCompatConfiguration.isUserAppAspectRatioFullscreenEnabled()) { 158 return false; 159 } 160 return true; 161 } 162 isSystemOverrideToFullscreenEnabled()163 boolean isSystemOverrideToFullscreenEnabled() { 164 final int aspectRatio = getUserMinAspectRatioOverrideCode(); 165 166 return isChangeEnabled(mActivityRecord, OVERRIDE_ANY_ORIENTATION_TO_USER) 167 && !mAllowOrientationOverrideOptProp.isFalse() 168 && (aspectRatio == USER_MIN_ASPECT_RATIO_UNSET 169 || aspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN); 170 } 171 172 /** 173 * Whether we should enable users to resize the current app. 174 */ shouldEnableUserAspectRatioSettings()175 boolean shouldEnableUserAspectRatioSettings() { 176 // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has 177 // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null, 178 // the current app doesn't opt-out so the first part of the predicate is true. 179 return !mAllowUserAspectRatioOverrideOptProp.isFalse() 180 && mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled() 181 && mActivityRecord.mDisplayContent != null 182 && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest(); 183 } 184 185 /** 186 * Whether to ignore fixed orientation, aspect ratio and resizability of activity. 187 */ hasFullscreenOverride()188 boolean hasFullscreenOverride() { 189 return shouldApplyUserFullscreenOverride() || isSystemOverrideToFullscreenEnabled() 190 || shouldIgnoreActivitySizeRestrictionsForDisplay(); 191 } 192 shouldIgnoreActivitySizeRestrictionsForDisplay()193 boolean shouldIgnoreActivitySizeRestrictionsForDisplay() { 194 return isDisplayIgnoreActivitySizeRestrictions(mActivityRecord) 195 && !mAllowOrientationOverrideOptProp.isFalse(); 196 } 197 getUserMinAspectRatio()198 float getUserMinAspectRatio() { 199 switch (getUserMinAspectRatioOverrideCode()) { 200 case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE: 201 return getDisplaySizeMinAspectRatio(); 202 case USER_MIN_ASPECT_RATIO_SPLIT_SCREEN: 203 return getSplitScreenAspectRatio(); 204 case USER_MIN_ASPECT_RATIO_16_9: 205 return 16 / 9f; 206 case USER_MIN_ASPECT_RATIO_4_3: 207 return 4 / 3f; 208 case USER_MIN_ASPECT_RATIO_3_2: 209 return 3 / 2f; 210 default: 211 throw new AssertionError("Unexpected user min aspect ratio override: " 212 + mUserAspectRatioState.mUserAspectRatio); 213 } 214 } 215 getSplitScreenAspectRatio()216 float getSplitScreenAspectRatio() { 217 // Getting the same aspect ratio that apps get in split screen. 218 final DisplayArea displayArea = mActivityRecord.getDisplayArea(); 219 if (displayArea == null) { 220 return getDefaultMinAspectRatioForUnresizableApps(); 221 } 222 int dividerWindowWidth = 223 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness); 224 int dividerInsets = 225 getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); 226 int dividerSize = dividerWindowWidth - dividerInsets * 2; 227 final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds()); 228 if (bounds.width() >= bounds.height()) { 229 bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); 230 bounds.right = bounds.centerX(); 231 } else { 232 bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2); 233 bounds.bottom = bounds.centerY(); 234 } 235 return AppCompatUtils.computeAspectRatio(bounds); 236 } 237 getFixedOrientationLetterboxAspectRatio(@onNull Configuration parentConfiguration)238 float getFixedOrientationLetterboxAspectRatio(@NonNull Configuration parentConfiguration) { 239 return shouldUseSplitScreenAspectRatio(parentConfiguration) 240 ? getSplitScreenAspectRatio() 241 : mActivityRecord.shouldCreateAppCompatDisplayInsets() 242 ? getDefaultMinAspectRatioForUnresizableApps() 243 : getDefaultMinAspectRatio(); 244 } 245 getDefaultMinAspectRatioForUnresizableAppsFromConfig()246 float getDefaultMinAspectRatioForUnresizableAppsFromConfig() { 247 return mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps(); 248 } 249 isSplitScreenAspectRatioForUnresizableAppsEnabled()250 boolean isSplitScreenAspectRatioForUnresizableAppsEnabled() { 251 return mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled(); 252 } 253 254 @VisibleForTesting getDisplaySizeMinAspectRatio()255 float getDisplaySizeMinAspectRatio() { 256 final DisplayArea displayArea = mActivityRecord.getDisplayArea(); 257 if (displayArea == null) { 258 return mActivityRecord.info.getMinAspectRatio(); 259 } 260 final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds()); 261 return AppCompatUtils.computeAspectRatio(bounds); 262 } 263 shouldUseSplitScreenAspectRatio(@onNull Configuration parentConfiguration)264 private boolean shouldUseSplitScreenAspectRatio(@NonNull Configuration parentConfiguration) { 265 final boolean isBookMode = mAppCompatDeviceStateQuery 266 .isDisplayFullScreenAndInPosture(/* isTabletop */false); 267 final boolean isNotCenteredHorizontally = 268 mAppCompatReachabilityOverrides.getHorizontalPositionMultiplier(parentConfiguration) 269 != LETTERBOX_POSITION_MULTIPLIER_CENTER; 270 final boolean isTabletopMode = mAppCompatDeviceStateQuery 271 .isDisplayFullScreenAndInPosture(/* isTabletop */ true); 272 final boolean isLandscape = isFixedOrientationLandscape( 273 mActivityRecord.getOverrideOrientation()); 274 final AppCompatCameraOverrides cameraOverrides = 275 mActivityRecord.mAppCompatController.getCameraOverrides(); 276 // Don't resize to split screen size when in book mode if letterbox position is centered 277 return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape) 278 || cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed() 279 && AppCompatCameraPolicy.isTreatmentEnabledForActivity(mActivityRecord); 280 } 281 282 /** 283 * Returns the value of the user aspect ratio override property. If unset, return {@code true}. 284 */ getAllowUserAspectRatioOverridePropertyValue()285 boolean getAllowUserAspectRatioOverridePropertyValue() { 286 return !mAllowUserAspectRatioOverrideOptProp.isFalse(); 287 } 288 getUserMinAspectRatioOverrideCode()289 int getUserMinAspectRatioOverrideCode() { 290 return mUserAspectRatioState.getUserAspectRatio(mActivityRecord); 291 } 292 getDefaultMinAspectRatioForUnresizableApps()293 private float getDefaultMinAspectRatioForUnresizableApps() { 294 if (!mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled() 295 || mActivityRecord.getDisplayArea() == null) { 296 return mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps() 297 > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO 298 ? mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps() 299 : getDefaultMinAspectRatio(); 300 } 301 302 return getSplitScreenAspectRatio(); 303 } 304 getDefaultMinAspectRatio()305 float getDefaultMinAspectRatio() { 306 if (mActivityRecord.getDisplayArea() == null 307 || !mAppCompatConfiguration 308 .getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) { 309 return mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio(); 310 } 311 return getDisplaySizeMinAspectRatio(); 312 } 313 314 private static class UserAspectRatioState { 315 // The min aspect ratio override set by the user. 316 @PackageManager.UserMinAspectRatio 317 private int mUserAspectRatio = USER_MIN_ASPECT_RATIO_UNSET; 318 private boolean mHasBeenSet = false; 319 320 @PackageManager.UserMinAspectRatio getUserAspectRatio(@onNull ActivityRecord activityRecord)321 private int getUserAspectRatio(@NonNull ActivityRecord activityRecord) { 322 // Package manager can be null at construction time, so access should be on demand. 323 if (!mHasBeenSet) { 324 try { 325 final IPackageManager pm = activityRecord.mAtmService.getPackageManager(); 326 if (pm != null) { 327 mUserAspectRatio = pm.getUserMinAspectRatio(activityRecord.packageName, 328 activityRecord.mUserId); 329 mHasBeenSet = true; 330 } 331 } catch (RemoteException e) { 332 Slog.w(TAG, "Exception thrown retrieving aspect ratio user override " 333 + this, e); 334 } 335 } 336 337 return mUserAspectRatio; 338 } 339 } 340 getResources()341 private Resources getResources() { 342 return mActivityRecord.mWmService.mContext.getResources(); 343 } 344 } 345