1 /*
2  * Copyright (C) 2023 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.ROTATION_UNDEFINED;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 
22 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
23 import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_LOCKED;
24 
25 import android.annotation.Nullable;
26 import android.content.ActivityInfoProto;
27 import android.view.Surface;
28 
29 import com.android.internal.protolog.common.ProtoLog;
30 
31 /**
32  * Defines the behavior of reversion from device rotation overrides.
33  *
34  * <p>There are 3 override types:
35  * <ol>
36  *  <li>The top application has {@link SCREEN_ORIENTATION_NOSENSOR} set and is rotated to
37  *  {@link ROTATION_0}.
38  *  <li>Camera compat treatment has rotated the app {@link DisplayRotationCompatPolicy}.
39  *  <li>The device is half-folded and has auto-rotate is temporarily enabled.
40  * </ol>
41  *
42  * <p>Before an override is enabled, a component should call {@code beforeOverrideApplied}. When
43  * it wishes to revert, it should call {@code revertOverride}. The user rotation will be restored
44  * if there are no other overrides present.
45  */
46 final class DisplayRotationReversionController {
47 
48     static final int REVERSION_TYPE_NOSENSOR = 0;
49     static final int REVERSION_TYPE_CAMERA_COMPAT = 1;
50     static final int REVERSION_TYPE_HALF_FOLD = 2;
51     private static final int NUM_SLOTS = 3;
52 
53     @Surface.Rotation
54     private int mUserRotationOverridden = ROTATION_UNDEFINED;
55 
56     private final boolean[] mSlots = new boolean[NUM_SLOTS];
57     private final DisplayContent mDisplayContent;
58 
DisplayRotationReversionController(DisplayContent content)59     DisplayRotationReversionController(DisplayContent content) {
60         mDisplayContent = content;
61     }
62 
isRotationReversionEnabled()63     boolean isRotationReversionEnabled() {
64         return mDisplayContent.mDisplayRotationCompatPolicy != null
65                 || mDisplayContent.getDisplayRotation().mFoldController != null
66                 || mDisplayContent.getIgnoreOrientationRequest();
67     }
68 
beforeOverrideApplied(int slotIndex)69     void beforeOverrideApplied(int slotIndex) {
70         if (mSlots[slotIndex]) return;
71         maybeSaveUserRotation();
72         mSlots[slotIndex] = true;
73     }
74 
isOverrideActive(int slotIndex)75     boolean isOverrideActive(int slotIndex) {
76         return mSlots[slotIndex];
77     }
78 
79     @Nullable
getSlotsCopy()80     boolean[] getSlotsCopy() {
81         return isRotationReversionEnabled() ? mSlots.clone() : null;
82     }
83 
updateForNoSensorOverride()84     void updateForNoSensorOverride() {
85         if (!mSlots[REVERSION_TYPE_NOSENSOR]) {
86             if (isTopFullscreenActivityNoSensor()) {
87                 ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override detected");
88                 beforeOverrideApplied(REVERSION_TYPE_NOSENSOR);
89             }
90         } else {
91             if (!isTopFullscreenActivityNoSensor()) {
92                 ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override is absent: reverting");
93                 revertOverride(REVERSION_TYPE_NOSENSOR);
94             }
95         }
96     }
97 
isAnyOverrideActive()98     boolean isAnyOverrideActive() {
99         for (int i = 0; i < NUM_SLOTS; ++i) {
100             if (mSlots[i]) {
101                 return true;
102             }
103         }
104         return false;
105     }
106 
revertOverride(int slotIndex)107     boolean revertOverride(int slotIndex) {
108         if (!mSlots[slotIndex]) return false;
109         mSlots[slotIndex] = false;
110         if (isAnyOverrideActive()) {
111             ProtoLog.v(WM_DEBUG_ORIENTATION,
112                     "Other orientation overrides are in place: not reverting");
113             return false;
114         }
115         // Only override if the rotation is frozen and there are no other active slots.
116         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
117         if (mUserRotationOverridden != ROTATION_UNDEFINED
118                 && displayRotation.getUserRotationMode() == USER_ROTATION_LOCKED) {
119             displayRotation.setUserRotation(USER_ROTATION_LOCKED, mUserRotationOverridden,
120                 /* caller= */ "DisplayRotationReversionController#revertOverride");
121             mUserRotationOverridden = ROTATION_UNDEFINED;
122             return true;
123         } else {
124             return false;
125         }
126     }
127 
maybeSaveUserRotation()128     private void maybeSaveUserRotation() {
129         final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
130         if (!isAnyOverrideActive()
131                 && displayRotation.getUserRotationMode() == USER_ROTATION_LOCKED) {
132             mUserRotationOverridden = displayRotation.getUserRotation();
133         }
134     }
135 
isTopFullscreenActivityNoSensor()136     private boolean isTopFullscreenActivityNoSensor() {
137         final Task topFullscreenTask =
138                 mDisplayContent.getTask(
139                         t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
140         if (topFullscreenTask != null) {
141             final ActivityRecord topActivity =
142                     topFullscreenTask.topRunningActivity();
143             return topActivity != null && topActivity.getOrientation()
144                     == ActivityInfoProto.SCREEN_ORIENTATION_NOSENSOR;
145         }
146         return false;
147     }
148 }
149