• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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