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.SCREEN_ORIENTATION_UNSPECIFIED; 20 21 import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.CameraCompatTaskInfo; 26 import android.content.pm.ActivityInfo.ScreenOrientation; 27 import android.content.res.Configuration; 28 import android.widget.Toast; 29 import android.window.DesktopModeFlags; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 33 /** 34 * Encapsulate policy logic related to app compat display rotation. 35 */ 36 class AppCompatCameraPolicy { 37 38 @Nullable 39 @VisibleForTesting 40 final CameraStateMonitor mCameraStateMonitor; 41 @Nullable 42 @VisibleForTesting 43 final ActivityRefresher mActivityRefresher; 44 @Nullable 45 final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; 46 @Nullable 47 final CameraCompatFreeformPolicy mCameraCompatFreeformPolicy; 48 AppCompatCameraPolicy(@onNull WindowManagerService wmService, @NonNull DisplayContent displayContent)49 AppCompatCameraPolicy(@NonNull WindowManagerService wmService, 50 @NonNull DisplayContent displayContent) { 51 // Not checking DeviceConfig value here to allow enabling via DeviceConfig 52 // without the need to restart the device. 53 final boolean needsDisplayRotationCompatPolicy = 54 wmService.mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); 55 final boolean needsCameraCompatFreeformPolicy = 56 DesktopModeFlags.ENABLE_CAMERA_COMPAT_SIMULATE_REQUESTED_ORIENTATION.isTrue() 57 && DesktopModeHelper.canEnterDesktopMode(wmService.mContext); 58 if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) { 59 mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH); 60 mActivityRefresher = new ActivityRefresher(wmService, wmService.mH); 61 mDisplayRotationCompatPolicy = 62 needsDisplayRotationCompatPolicy ? new DisplayRotationCompatPolicy( 63 displayContent, mCameraStateMonitor, mActivityRefresher) : null; 64 mCameraCompatFreeformPolicy = 65 needsCameraCompatFreeformPolicy ? new CameraCompatFreeformPolicy(displayContent, 66 mCameraStateMonitor, mActivityRefresher) : null; 67 } else { 68 mDisplayRotationCompatPolicy = null; 69 mCameraCompatFreeformPolicy = null; 70 mCameraStateMonitor = null; 71 mActivityRefresher = null; 72 } 73 } 74 onActivityRefreshed(@onNull ActivityRecord activity)75 static void onActivityRefreshed(@NonNull ActivityRecord activity) { 76 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 77 if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) { 78 cameraPolicy.mActivityRefresher.onActivityRefreshed(activity); 79 } 80 } 81 82 @Nullable getAppCompatCameraPolicy(@onNull ActivityRecord activityRecord)83 static AppCompatCameraPolicy getAppCompatCameraPolicy(@NonNull ActivityRecord activityRecord) { 84 return activityRecord.mDisplayContent != null 85 ? activityRecord.mDisplayContent.mAppCompatCameraPolicy : null; 86 } 87 88 /** 89 * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle. 90 * This allows to clear cached values in apps (e.g. display or camera rotation) that influence 91 * camera preview and can lead to sideways or stretching issues persisting even after force 92 * rotation. 93 */ onActivityConfigurationChanging(@onNull ActivityRecord activity, @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig)94 static void onActivityConfigurationChanging(@NonNull ActivityRecord activity, 95 @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { 96 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 97 if (cameraPolicy != null && cameraPolicy.mActivityRefresher != null) { 98 cameraPolicy.mActivityRefresher.onActivityConfigurationChanging(activity, newConfig, 99 lastReportedConfig); 100 } 101 } 102 103 /** 104 * Notifies that animation in {@link ScreenRotationAnimation} has finished. 105 * 106 * <p>This class uses this signal as a trigger for notifying the user about forced rotation 107 * reason with the {@link Toast}. 108 */ onScreenRotationAnimationFinished()109 void onScreenRotationAnimationFinished() { 110 if (mDisplayRotationCompatPolicy != null) { 111 mDisplayRotationCompatPolicy.onScreenRotationAnimationFinished(); 112 } 113 } 114 isActivityEligibleForOrientationOverride(@onNull ActivityRecord activity)115 static boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) { 116 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 117 return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null 118 && cameraPolicy.mDisplayRotationCompatPolicy 119 .isActivityEligibleForOrientationOverride(activity); 120 } 121 122 /** 123 * Whether camera compat treatment is applicable for the given activity. 124 * 125 * <p>Conditions that need to be met: 126 * <ul> 127 * <li>Camera is active for the package. 128 * <li>The activity is in fullscreen 129 * <li>The activity has fixed orientation but not "locked" or "nosensor" one. 130 * </ul> 131 */ isTreatmentEnabledForActivity(@onNull ActivityRecord activity)132 static boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) { 133 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 134 return cameraPolicy != null && cameraPolicy.mDisplayRotationCompatPolicy != null 135 && cameraPolicy.mDisplayRotationCompatPolicy 136 .isTreatmentEnabledForActivity(activity); 137 } 138 start()139 void start() { 140 if (mDisplayRotationCompatPolicy != null) { 141 mDisplayRotationCompatPolicy.start(); 142 } 143 if (mCameraCompatFreeformPolicy != null) { 144 mCameraCompatFreeformPolicy.start(); 145 } 146 if (mCameraStateMonitor != null) { 147 mCameraStateMonitor.startListeningToCameraState(); 148 } 149 } 150 dispose()151 void dispose() { 152 if (mDisplayRotationCompatPolicy != null) { 153 mDisplayRotationCompatPolicy.dispose(); 154 } 155 if (mCameraCompatFreeformPolicy != null) { 156 mCameraCompatFreeformPolicy.dispose(); 157 } 158 if (mCameraStateMonitor != null) { 159 mCameraStateMonitor.dispose(); 160 } 161 } 162 hasDisplayRotationCompatPolicy()163 boolean hasDisplayRotationCompatPolicy() { 164 return mDisplayRotationCompatPolicy != null; 165 } 166 hasCameraCompatFreeformPolicy()167 boolean hasCameraCompatFreeformPolicy() { 168 return mCameraCompatFreeformPolicy != null; 169 } 170 hasCameraStateMonitor()171 boolean hasCameraStateMonitor() { 172 return mCameraStateMonitor != null; 173 } 174 175 @ScreenOrientation getOrientation()176 int getOrientation() { 177 return mDisplayRotationCompatPolicy != null 178 ? mDisplayRotationCompatPolicy.getOrientation() 179 : SCREEN_ORIENTATION_UNSPECIFIED; 180 } 181 182 // TODO(b/369070416): have policies implement the same interface. shouldCameraCompatControlOrientation(@onNull ActivityRecord activity)183 static boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) { 184 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 185 if (cameraPolicy == null) { 186 return false; 187 } 188 return (cameraPolicy.mDisplayRotationCompatPolicy != null 189 && cameraPolicy.mDisplayRotationCompatPolicy 190 .shouldCameraCompatControlOrientation(activity)) 191 || (cameraPolicy.mCameraCompatFreeformPolicy != null 192 && cameraPolicy.mCameraCompatFreeformPolicy 193 .shouldCameraCompatControlOrientation(activity)); 194 } 195 196 // TODO(b/369070416): have policies implement the same interface. isFreeformLetterboxingForCameraAllowed(@onNull ActivityRecord activity)197 static boolean isFreeformLetterboxingForCameraAllowed(@NonNull ActivityRecord activity) { 198 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 199 if (cameraPolicy == null) { 200 return false; 201 } 202 return cameraPolicy.mCameraCompatFreeformPolicy != null 203 && cameraPolicy.mCameraCompatFreeformPolicy 204 .isFreeformLetterboxingForCameraAllowed(activity); 205 } 206 207 // TODO(b/369070416): have policies implement the same interface. shouldCameraCompatControlAspectRatio(@onNull ActivityRecord activity)208 static boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) { 209 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 210 if (cameraPolicy == null) { 211 return false; 212 } 213 return (cameraPolicy.mDisplayRotationCompatPolicy != null 214 && cameraPolicy.mDisplayRotationCompatPolicy 215 .shouldCameraCompatControlAspectRatio(activity)) 216 || (cameraPolicy.mCameraCompatFreeformPolicy != null 217 && cameraPolicy.mCameraCompatFreeformPolicy 218 .shouldCameraCompatControlAspectRatio(activity)); 219 } 220 221 // TODO(b/369070416): have policies implement the same interface. 222 /** 223 * @return {@code true} if the Camera is active for the provided {@link ActivityRecord} and 224 * any camera compat treatment could be triggered for the current windowing mode. 225 */ isCameraRunningAndWindowingModeEligible( @onNull ActivityRecord activity)226 private static boolean isCameraRunningAndWindowingModeEligible( 227 @NonNull ActivityRecord activity) { 228 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 229 if (cameraPolicy == null) { 230 return false; 231 } 232 return (cameraPolicy.mDisplayRotationCompatPolicy != null 233 && cameraPolicy.mDisplayRotationCompatPolicy 234 .isCameraRunningAndWindowingModeEligible(activity, 235 /* mustBeFullscreen */ true)) 236 || (cameraPolicy.mCameraCompatFreeformPolicy != null 237 && cameraPolicy.mCameraCompatFreeformPolicy 238 .isCameraRunningAndWindowingModeEligible(activity)); 239 } 240 241 @Nullable getSummaryForDisplayRotationHistoryRecord()242 String getSummaryForDisplayRotationHistoryRecord() { 243 return mDisplayRotationCompatPolicy != null 244 ? mDisplayRotationCompatPolicy.getSummaryForDisplayRotationHistoryRecord() 245 : null; 246 } 247 248 // TODO(b/369070416): have policies implement the same interface. getCameraCompatMinAspectRatio(@onNull ActivityRecord activity)249 static float getCameraCompatMinAspectRatio(@NonNull ActivityRecord activity) { 250 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 251 if (cameraPolicy == null) { 252 return 1.0f; 253 } 254 float displayRotationCompatPolicyAspectRatio = 255 cameraPolicy.mDisplayRotationCompatPolicy != null 256 ? cameraPolicy.mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity) 257 : MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; 258 float cameraCompatFreeformPolicyAspectRatio = 259 cameraPolicy.mCameraCompatFreeformPolicy != null 260 ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity) 261 : MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; 262 return Math.max(displayRotationCompatPolicyAspectRatio, 263 cameraCompatFreeformPolicyAspectRatio); 264 } 265 266 @CameraCompatTaskInfo.FreeformCameraCompatMode getCameraCompatFreeformMode(@onNull ActivityRecord activity)267 static int getCameraCompatFreeformMode(@NonNull ActivityRecord activity) { 268 final AppCompatCameraPolicy cameraPolicy = getAppCompatCameraPolicy(activity); 269 return cameraPolicy != null && cameraPolicy.mCameraCompatFreeformPolicy != null 270 ? cameraPolicy.mCameraCompatFreeformPolicy.getCameraCompatMode(activity) 271 : CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_NONE; 272 } 273 274 /** 275 * Whether we should apply the min aspect ratio per-app override only when an app is connected 276 * to the camera. 277 */ shouldOverrideMinAspectRatioForCamera(@onNull ActivityRecord activityRecord)278 static boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) { 279 return AppCompatCameraPolicy.isCameraRunningAndWindowingModeEligible(activityRecord) 280 && activityRecord.mAppCompatController.getCameraOverrides() 281 .isOverrideMinAspectRatioForCameraEnabled(); 282 } 283 } 284