• 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.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