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