• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
21 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
22 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
23 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
24 import static android.view.InsetsState.ITYPE_STATUS_BAR;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.content.res.Configuration.Orientation;
29 import android.view.InsetsVisibilities;
30 import android.view.Surface;
31 
32 /**
33  * Policy to decide whether to enforce screen rotation lock for optimisation of the screen rotation
34  * user experience for immersive applications for compatibility when ignoring orientation request.
35  *
36  * <p>This is needed because immersive apps, such as games, are often not optimized for all
37  * orientations and can have a poor UX when rotated (e.g., state loss or entering size-compat mode).
38  * Additionally, some games rely on sensors for the gameplay so users can trigger such rotations
39  * accidentally when auto rotation is on.
40  */
41 final class DisplayRotationImmersiveAppCompatPolicy {
42 
43     @Nullable
createIfNeeded( @onNull final LetterboxConfiguration letterboxConfiguration, @NonNull final DisplayRotation displayRotation, @NonNull final DisplayContent displayContent)44     static DisplayRotationImmersiveAppCompatPolicy createIfNeeded(
45             @NonNull final LetterboxConfiguration letterboxConfiguration,
46             @NonNull final DisplayRotation displayRotation,
47             @NonNull final DisplayContent displayContent) {
48         if (!letterboxConfiguration
49                 .isDisplayRotationImmersiveAppCompatPolicyEnabled(/* checkDeviceConfig */ false)) {
50             return null;
51         }
52 
53         return new DisplayRotationImmersiveAppCompatPolicy(
54                 letterboxConfiguration, displayRotation, displayContent);
55     }
56 
57     private final DisplayRotation mDisplayRotation;
58     private final LetterboxConfiguration mLetterboxConfiguration;
59     private final DisplayContent mDisplayContent;
60 
DisplayRotationImmersiveAppCompatPolicy( @onNull final LetterboxConfiguration letterboxConfiguration, @NonNull final DisplayRotation displayRotation, @NonNull final DisplayContent displayContent)61     private DisplayRotationImmersiveAppCompatPolicy(
62             @NonNull final LetterboxConfiguration letterboxConfiguration,
63             @NonNull final DisplayRotation displayRotation,
64             @NonNull final DisplayContent displayContent) {
65         mDisplayRotation = displayRotation;
66         mLetterboxConfiguration = letterboxConfiguration;
67         mDisplayContent = displayContent;
68     }
69 
70     /**
71      * Decides whether it is necessary to lock screen rotation, preventing auto rotation, based on
72      * the top activity configuration and proposed screen rotation.
73      *
74      * <p>This is needed because immersive apps, such as games, are often not optimized for all
75      * orientations and can have a poor UX when rotated. Additionally, some games rely on sensors
76      * for the gameplay so users can trigger such rotations accidentally when auto rotation is on.
77      *
78      * <p>Screen rotation is locked when the following conditions are met:
79      * <ul>
80      *   <li>Top activity requests to hide status and navigation bars
81      *   <li>Top activity is fullscreen and in optimal orientation (without letterboxing)
82      *   <li>Rotation will lead to letterboxing due to fixed orientation.
83      *   <li>{@link DisplayContent#getIgnoreOrientationRequest} is {@code true}
84      *   <li>This policy is enabled on the device, for details see
85      *   {@link LetterboxConfiguration#isDisplayRotationImmersiveAppCompatPolicyEnabled}
86      * </ul>
87      *
88      * @param proposedRotation new proposed {@link Surface.Rotation} for the screen.
89      * @return {@code true}, if there is a need to lock screen rotation, {@code false} otherwise.
90      */
isRotationLockEnforced(@urface.Rotation final int proposedRotation)91     boolean isRotationLockEnforced(@Surface.Rotation final int proposedRotation) {
92         if (!mLetterboxConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled(
93                 /* checkDeviceConfig */ true)) {
94             return false;
95         }
96         synchronized (mDisplayContent.mWmService.mGlobalLock) {
97             return isRotationLockEnforcedLocked(proposedRotation);
98         }
99     }
100 
isRotationLockEnforcedLocked(@urface.Rotation final int proposedRotation)101     private boolean isRotationLockEnforcedLocked(@Surface.Rotation final int proposedRotation) {
102         if (!mDisplayContent.getIgnoreOrientationRequest()) {
103             return false;
104         }
105 
106         final ActivityRecord activityRecord = mDisplayContent.topRunningActivity();
107         if (activityRecord == null) {
108             return false;
109         }
110 
111         // Don't lock screen rotation if an activity hasn't requested to hide system bars.
112         if (!hasRequestedToHideStatusAndNavBars(activityRecord)) {
113             return false;
114         }
115 
116         // Don't lock screen rotation if activity is not in fullscreen. Checking windowing mode
117         // for a task rather than an activity to exclude activity embedding scenario.
118         if (activityRecord.getTask() == null
119                 || activityRecord.getTask().getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
120             return false;
121         }
122 
123         // Don't lock screen rotation if activity is letterboxed.
124         if (activityRecord.areBoundsLetterboxed()) {
125             return false;
126         }
127 
128         if (activityRecord.getRequestedConfigurationOrientation() == ORIENTATION_UNDEFINED) {
129             return false;
130         }
131 
132         // Lock screen rotation only if, after rotation the activity's orientation won't match
133         // the screen orientation, forcing the activity to enter letterbox mode after rotation.
134         return activityRecord.getRequestedConfigurationOrientation()
135                 != surfaceRotationToConfigurationOrientation(proposedRotation);
136     }
137 
138     /**
139      * Checks whether activity has requested to hide status and navigation bars.
140      */
hasRequestedToHideStatusAndNavBars(@onNull ActivityRecord activity)141     private boolean hasRequestedToHideStatusAndNavBars(@NonNull ActivityRecord activity) {
142         WindowState mainWindow = activity.findMainWindow();
143         if (mainWindow == null) {
144             return false;
145         }
146         InsetsVisibilities insetsVisibilities = mainWindow.getRequestedVisibilities();
147         return !insetsVisibilities.getVisibility(ITYPE_STATUS_BAR)
148                 && !insetsVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
149     }
150 
151     @Orientation
surfaceRotationToConfigurationOrientation(@urface.Rotation final int rotation)152     private int surfaceRotationToConfigurationOrientation(@Surface.Rotation final int rotation) {
153         if (mDisplayRotation.isAnyPortrait(rotation)) {
154             return ORIENTATION_PORTRAIT;
155         } else if (mDisplayRotation.isLandscapeOrSeascape(rotation)) {
156             return ORIENTATION_LANDSCAPE;
157         } else {
158             return ORIENTATION_UNDEFINED;
159         }
160     }
161 }
162