• 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION;
21 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
22 import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION;
23 import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE;
24 import static android.content.pm.ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION;
25 import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR;
26 import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT;
27 import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION;
28 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
29 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE;
30 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED;
31 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
32 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
33 
34 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
35 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
36 import static com.android.server.wm.AppCompatUtils.asLazy;
37 import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
38 
39 import android.annotation.NonNull;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.server.wm.utils.OptPropFactory;
43 
44 import java.util.function.BooleanSupplier;
45 import java.util.function.LongSupplier;
46 
47 /**
48  * Encapsulates all the configurations and overrides about orientation used by
49  * {@link AppCompatOrientationPolicy}.
50  */
51 class AppCompatOrientationOverrides {
52 
53     private static final String TAG = TAG_WITH_CLASS_NAME
54             ? "AppCompatOrientationOverrides" : TAG_ATM;
55 
56     @NonNull
57     private final ActivityRecord mActivityRecord;
58     @NonNull
59     private final AppCompatCameraOverrides mAppCompatCameraOverrides;
60     @NonNull
61     private final OptPropFactory.OptProp mIgnoreRequestedOrientationOptProp;
62     @NonNull
63     private final OptPropFactory.OptProp mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp;
64     @NonNull
65     private final OptPropFactory.OptProp mAllowOrientationOverrideOptProp;
66     @NonNull
67     private final OptPropFactory.OptProp mAllowDisplayOrientationOverrideOptProp;
68 
69     @NonNull
70     final OrientationOverridesState mOrientationOverridesState;
71 
AppCompatOrientationOverrides(@onNull ActivityRecord activityRecord, @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder, @NonNull AppCompatCameraOverrides appCompatCameraOverrides)72     AppCompatOrientationOverrides(@NonNull ActivityRecord activityRecord,
73             @NonNull AppCompatConfiguration appCompatConfiguration,
74             @NonNull OptPropFactory optPropBuilder,
75             @NonNull AppCompatCameraOverrides appCompatCameraOverrides) {
76         mActivityRecord = activityRecord;
77         mAppCompatCameraOverrides = appCompatCameraOverrides;
78         mOrientationOverridesState = new OrientationOverridesState(mActivityRecord,
79                 System::currentTimeMillis);
80         final BooleanSupplier isPolicyForIgnoringRequestedOrientationEnabled = asLazy(
81                 appCompatConfiguration::isPolicyForIgnoringRequestedOrientationEnabled);
82         mIgnoreRequestedOrientationOptProp = optPropBuilder.create(
83                 PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION,
84                 isPolicyForIgnoringRequestedOrientationEnabled);
85         mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp = optPropBuilder.create(
86                 PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED,
87                 isPolicyForIgnoringRequestedOrientationEnabled);
88         mAllowOrientationOverrideOptProp = optPropBuilder.create(
89                 PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE);
90         mAllowDisplayOrientationOverrideOptProp = optPropBuilder.create(
91                 PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE,
92                 () -> mActivityRecord.mDisplayContent != null
93                         && mActivityRecord.getTask() != null
94                         && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()
95                         && !mActivityRecord.getTask().inMultiWindowMode()
96                         && mActivityRecord.mDisplayContent.getNaturalOrientation()
97                             == ORIENTATION_LANDSCAPE
98         );
99     }
100 
shouldEnableIgnoreOrientationRequest()101     boolean shouldEnableIgnoreOrientationRequest() {
102         return mIgnoreRequestedOrientationOptProp.shouldEnableWithOverrideAndProperty(
103                 isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION));
104     }
105 
isOverrideRespectRequestedOrientationEnabled()106     boolean isOverrideRespectRequestedOrientationEnabled() {
107         return isChangeEnabled(mActivityRecord, OVERRIDE_RESPECT_REQUESTED_ORIENTATION);
108     }
109 
shouldRespectRequestedOrientationDueToOverride()110     boolean shouldRespectRequestedOrientationDueToOverride() {
111         // Checking TaskFragment rather than ActivityRecord to ensure that transition
112         // between fullscreen and PiP would work well. Checking TaskFragment rather than
113         // Task to ensure that Activity Embedding is excluded.
114         return mActivityRecord.isVisibleRequested() && mActivityRecord.getTaskFragment() != null
115                 && mActivityRecord.getTaskFragment().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
116                 && mActivityRecord.mAppCompatController.getOrientationOverrides()
117                     .isOverrideRespectRequestedOrientationEnabled();
118     }
119 
120     /**
121      * Whether an app is calling {@link android.app.Activity#setRequestedOrientation}
122      * in a loop and orientation request should be ignored.
123      *
124      * <p>This should only be called once in response to
125      * {@link android.app.Activity#setRequestedOrientation}. See
126      * {@link #shouldIgnoreRequestedOrientation} for more details.
127      *
128      * <p>This treatment is enabled when the following conditions are met:
129      * <ul>
130      *     <li>Flag gating the treatment is enabled
131      *     <li>Opt-out component property isn't enabled
132      *     <li>Per-app override is enabled
133      *     <li>App has requested orientation more than 2 times within 1-second
134      *     timer and activity is not letterboxed for fixed orientation
135      * </ul>
136      */
shouldIgnoreOrientationRequestLoop()137     boolean shouldIgnoreOrientationRequestLoop() {
138         final boolean loopDetectionEnabled = isCompatChangeEnabled(
139                 OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED);
140         if (!mAllowIgnoringOrientationRequestWhenLoopDetectedOptProp
141                 .shouldEnableWithOptInOverrideAndOptOutProperty(loopDetectionEnabled)) {
142             return false;
143         }
144         mOrientationOverridesState.updateOrientationRequestLoopState();
145 
146         return mOrientationOverridesState.shouldIgnoreRequestInLoop()
147                 && !mActivityRecord.mAppCompatController.getAspectRatioPolicy()
148                     .isLetterboxedForFixedOrientationAndAspectRatio();
149     }
150 
151     /**
152      * Whether should fix display orientation to landscape natural orientation when a task is
153      * fullscreen and the display is ignoring orientation requests.
154      *
155      * <p>This treatment is enabled when the following conditions are met:
156      * <ul>
157      *     <li>Opt-out component property isn't enabled
158      *     <li>Opt-in per-app override is enabled
159      *     <li>Task is in fullscreen.
160      *     <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
161      *     <li>Natural orientation of the display is landscape.
162      * </ul>
163      */
shouldUseDisplayLandscapeNaturalOrientation()164     boolean shouldUseDisplayLandscapeNaturalOrientation() {
165         return mAllowDisplayOrientationOverrideOptProp
166                 .shouldEnableWithOptInOverrideAndOptOutProperty(
167                         isChangeEnabled(mActivityRecord,
168                                 OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION));
169     }
170 
171     /**
172      * Sets whether an activity is relaunching after the app has called {@link
173      * android.app.Activity#setRequestedOrientation}.
174      */
setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching)175     void setRelaunchingAfterRequestedOrientationChanged(boolean isRelaunching) {
176         mOrientationOverridesState
177                 .mIsRelaunchingAfterRequestedOrientationChanged = isRelaunching;
178     }
179 
getIsRelaunchingAfterRequestedOrientationChanged()180     boolean getIsRelaunchingAfterRequestedOrientationChanged() {
181         return mOrientationOverridesState.mIsRelaunchingAfterRequestedOrientationChanged;
182     }
183 
isAllowOrientationOverrideOptOut()184     boolean isAllowOrientationOverrideOptOut() {
185         return mAllowOrientationOverrideOptProp.isFalse();
186     }
187 
188     @VisibleForTesting
getSetOrientationRequestCounter()189     int getSetOrientationRequestCounter() {
190         return mOrientationOverridesState.mSetOrientationRequestCounter;
191     }
192 
isCompatChangeEnabled(long overrideChangeId)193     private boolean isCompatChangeEnabled(long overrideChangeId) {
194         return mActivityRecord.info.isChangeEnabled(overrideChangeId);
195     }
196 
197     static class OrientationOverridesState {
198         // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
199         final boolean mIsOverrideToNosensorOrientationEnabled;
200         // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
201         final boolean mIsOverrideToPortraitOrientationEnabled;
202         // Corresponds to OVERRIDE_ANY_ORIENTATION
203         final boolean mIsOverrideAnyOrientationEnabled;
204         // Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE
205         final boolean mIsOverrideToReverseLandscapeOrientationEnabled;
206 
207         private boolean mIsRelaunchingAfterRequestedOrientationChanged;
208 
209         // Used to determine reset of mSetOrientationRequestCounter if next app requested
210         // orientation is after timeout value
211         @VisibleForTesting
212         static final int SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS = 1000;
213         // Minimum value of mSetOrientationRequestCounter before qualifying as orientation request
214         // loop
215         @VisibleForTesting
216         static final int MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP = 2;
217         // Updated when ActivityRecord#setRequestedOrientation is called
218         private long mTimeMsLastSetOrientationRequest = 0;
219         // Counter for ActivityRecord#setRequestedOrientation
220         private int mSetOrientationRequestCounter = 0;
221         @VisibleForTesting
222         LongSupplier mCurrentTimeMillisSupplier;
223 
OrientationOverridesState(@onNull ActivityRecord activityRecord, @NonNull LongSupplier currentTimeMillisSupplier)224         OrientationOverridesState(@NonNull ActivityRecord activityRecord,
225                 @NonNull LongSupplier currentTimeMillisSupplier) {
226             mCurrentTimeMillisSupplier = currentTimeMillisSupplier;
227             mIsOverrideToNosensorOrientationEnabled =
228                     activityRecord.info.isChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR);
229             mIsOverrideToPortraitOrientationEnabled =
230                     activityRecord.info.isChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
231             mIsOverrideAnyOrientationEnabled =
232                     activityRecord.info.isChangeEnabled(OVERRIDE_ANY_ORIENTATION);
233             mIsOverrideToReverseLandscapeOrientationEnabled = activityRecord.info
234                     .isChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE);
235         }
236 
237         /**
238          * @return {@code true} if we should start ignoring orientation in a orientation request
239          * loop.
240          */
shouldIgnoreRequestInLoop()241         boolean shouldIgnoreRequestInLoop() {
242             return mSetOrientationRequestCounter >= MIN_COUNT_TO_IGNORE_REQUEST_IN_LOOP;
243         }
244 
245         /**
246          * Updates the orientation request counter using a specific timeout.
247          */
updateOrientationRequestLoopState()248         void updateOrientationRequestLoopState() {
249             final long currTimeMs = mCurrentTimeMillisSupplier.getAsLong();
250             final long elapsedTime = currTimeMs - mTimeMsLastSetOrientationRequest;
251             if (elapsedTime < SET_ORIENTATION_REQUEST_COUNTER_TIMEOUT_MS) {
252                 mSetOrientationRequestCounter++;
253             } else {
254                 mSetOrientationRequestCounter = 0;
255             }
256             mTimeMsLastSetOrientationRequest = currTimeMs;
257         }
258     }
259 }
260