• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
21 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
22 import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
23 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
24 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
25 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
26 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
27 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
28 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
29 import static android.content.pm.ActivityInfo.screenOrientationToString;
30 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
31 import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
32 import static android.view.Display.TYPE_INTERNAL;
33 
34 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
35 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
36 import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
37 
38 import android.annotation.NonNull;
39 import android.annotation.Nullable;
40 import android.annotation.StringRes;
41 import android.app.servertransaction.ClientTransaction;
42 import android.app.servertransaction.RefreshCallbackItem;
43 import android.app.servertransaction.ResumeActivityItem;
44 import android.content.pm.ActivityInfo.ScreenOrientation;
45 import android.content.res.Configuration;
46 import android.hardware.camera2.CameraManager;
47 import android.os.Handler;
48 import android.os.RemoteException;
49 import android.util.ArrayMap;
50 import android.util.ArraySet;
51 import android.widget.Toast;
52 
53 import com.android.internal.R;
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.internal.protolog.common.ProtoLog;
57 import com.android.server.UiThread;
58 
59 import java.util.Map;
60 import java.util.Set;
61 
62 /**
63  * Controls camera compatibility treatment that handles orientation mismatch between camera
64  * buffers and an app window for a particular display that can lead to camera issues like sideways
65  * or stretched viewfinder.
66  *
67  * <p>This includes force rotation of fixed orientation activities connected to the camera.
68  *
69  * <p>The treatment is enabled for internal displays that have {@code ignoreOrientationRequest}
70  * display setting enabled and when {@code
71  * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
72  */
73  // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
74 final class DisplayRotationCompatPolicy {
75 
76     // Delay for updating display rotation after Camera connection is closed. Needed to avoid
77     // rotation flickering when an app is flipping between front and rear cameras or when size
78     // compat mode is restarted.
79     // TODO(b/263114289): Consider associating this delay with a specific activity so that if
80     // the new non-camera activity started on top of the camer one we can rotate faster.
81     private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
82     // Delay for updating display rotation after Camera connection is opened. This delay is
83     // selected to be long enough to avoid conflicts with transitions on the app's side.
84     // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
85     // is flipping between front and rear cameras (in case requested orientation changes at
86     // runtime at the same time) or when size compat mode is restarted.
87     private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
88             CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
89     // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
90     // client process may not always report the event back to the server, such as process is
91     // crashed or got killed.
92     private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
93 
94     private final DisplayContent mDisplayContent;
95     private final WindowManagerService mWmService;
96     private final CameraManager mCameraManager;
97     private final Handler mHandler;
98 
99     // Bi-directional map between package names and active camera IDs since we need to 1) get a
100     // camera id by a package name when determining rotation; 2) get a package name by a camera id
101     // when camera connection is closed and we need to clean up our records.
102     @GuardedBy("this")
103     private final CameraIdPackageNameBiMap mCameraIdPackageBiMap = new CameraIdPackageNameBiMap();
104     @GuardedBy("this")
105     private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
106     @GuardedBy("this")
107     private final Set<String> mScheduledOrientationUpdateCameraIdSet = new ArraySet<>();
108 
109     private final CameraManager.AvailabilityCallback mAvailabilityCallback =
110             new  CameraManager.AvailabilityCallback() {
111                 @Override
112                 public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
113                     notifyCameraOpened(cameraId, packageId);
114                 }
115 
116                 @Override
117                 public void onCameraClosed(@NonNull String cameraId) {
118                     notifyCameraClosed(cameraId);
119                 }
120             };
121 
122     @ScreenOrientation
123     private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
124 
DisplayRotationCompatPolicy(@onNull DisplayContent displayContent)125     DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent) {
126         this(displayContent, displayContent.mWmService.mH);
127     }
128 
129     @VisibleForTesting
DisplayRotationCompatPolicy(@onNull DisplayContent displayContent, Handler handler)130     DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler) {
131         // This constructor is called from DisplayContent constructor. Don't use any fields in
132         // DisplayContent here since they aren't guaranteed to be set.
133         mHandler = handler;
134         mDisplayContent = displayContent;
135         mWmService = displayContent.mWmService;
136         mCameraManager = mWmService.mContext.getSystemService(CameraManager.class);
137         mCameraManager.registerAvailabilityCallback(
138                 mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
139     }
140 
dispose()141     void dispose() {
142         mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
143     }
144 
145     /**
146      * Determines orientation for Camera compatibility.
147      *
148      * <p>The goal of this function is to compute a orientation which would align orientations of
149      * portrait app window and natural orientation of the device and set opposite to natural
150      * orientation for a landscape app window. This is one of the strongest assumptions that apps
151      * make when they implement camera previews. Since app and natural display orientations aren't
152      * guaranteed to match, the rotation can cause letterboxing.
153      *
154      * <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link
155      * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
156      */
157     @ScreenOrientation
getOrientation()158     int getOrientation() {
159         mLastReportedOrientation = getOrientationInternal();
160         if (mLastReportedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
161             rememberOverriddenOrientationIfNeeded();
162         } else {
163             restoreOverriddenOrientationIfNeeded();
164         }
165         return mLastReportedOrientation;
166     }
167 
168     @ScreenOrientation
getOrientationInternal()169     private synchronized int getOrientationInternal() {
170         if (!isTreatmentEnabledForDisplay()) {
171             return SCREEN_ORIENTATION_UNSPECIFIED;
172         }
173         ActivityRecord topActivity = mDisplayContent.topRunningActivity(
174                 /* considerKeyguardState= */ true);
175         if (!isTreatmentEnabledForActivity(topActivity)) {
176             return SCREEN_ORIENTATION_UNSPECIFIED;
177         }
178         boolean isPortraitActivity =
179                 topActivity.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
180         boolean isNaturalDisplayOrientationPortrait =
181                 mDisplayContent.getNaturalOrientation() == ORIENTATION_PORTRAIT;
182         // Rotate portrait-only activity in the natural orientation of the displays (and in the
183         // opposite to natural orientation for landscape-only) since many apps assume that those
184         // are aligned when they compute orientation of the preview.
185         // This means that even for a landscape-only activity and a device with landscape natural
186         // orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that
187         // natural orientation = portrait window = portait camera is the main wrong assumption
188         // that apps make when they implement camera previews so landscape windows need be
189         // rotated in the orientation oposite to the natural one even if it's portrait.
190         // TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions
191         // of the portrait and landscape orientation requests.
192         int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait)
193                 || (!isPortraitActivity && !isNaturalDisplayOrientationPortrait)
194                         ? SCREEN_ORIENTATION_PORTRAIT
195                         : SCREEN_ORIENTATION_LANDSCAPE;
196         ProtoLog.v(WM_DEBUG_ORIENTATION,
197                 "Display id=%d is ignoring all orientation requests, camera is active "
198                         + "and the top activity is eligible for force rotation, return %s,"
199                         + "portrait activity: %b, is natural orientation portrait: %b.",
200                 mDisplayContent.mDisplayId, screenOrientationToString(orientation),
201                 isPortraitActivity, isNaturalDisplayOrientationPortrait);
202         return orientation;
203     }
204 
205     /**
206      * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
207      * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
208      * camera preview and can lead to sideways or stretching issues persisting even after force
209      * rotation.
210      */
onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig, Configuration lastReportedConfig)211     void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
212             Configuration lastReportedConfig) {
213         if (!isTreatmentEnabledForDisplay()
214                 || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
215                 || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
216             return;
217         }
218         boolean cycleThroughStop =
219                 mWmService.mLetterboxConfiguration
220                         .isCameraCompatRefreshCycleThroughStopEnabled()
221                 && !activity.mLetterboxUiController
222                         .shouldRefreshActivityViaPauseForCameraCompat();
223         try {
224             activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
225             ProtoLog.v(WM_DEBUG_STATES,
226                     "Refershing activity for camera compatibility treatment, "
227                             + "activityRecord=%s", activity);
228             final ClientTransaction transaction = ClientTransaction.obtain(
229                     activity.app.getThread(), activity.token);
230             transaction.addCallback(
231                     RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
232             transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(
233                     /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
234             activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction);
235             mHandler.postDelayed(
236                     () -> onActivityRefreshed(activity),
237                     REFRESH_CALLBACK_TIMEOUT_MS);
238         } catch (RemoteException e) {
239             activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
240         }
241     }
242 
onActivityRefreshed(@onNull ActivityRecord activity)243     void onActivityRefreshed(@NonNull ActivityRecord activity) {
244         activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
245     }
246 
247     /**
248      * Notifies that animation in {@link ScreenAnimationRotation} has finished.
249      *
250      * <p>This class uses this signal as a trigger for notifying the user about forced rotation
251      * reason with the {@link Toast}.
252      */
onScreenRotationAnimationFinished()253     void onScreenRotationAnimationFinished() {
254         if (!isTreatmentEnabledForDisplay() || mCameraIdPackageBiMap.isEmpty()) {
255             return;
256         }
257         ActivityRecord topActivity = mDisplayContent.topRunningActivity(
258                     /* considerKeyguardState= */ true);
259         if (!isTreatmentEnabledForActivity(topActivity)) {
260             return;
261         }
262         showToast(R.string.display_rotation_camera_compat_toast_after_rotation);
263     }
264 
getSummaryForDisplayRotationHistoryRecord()265     String getSummaryForDisplayRotationHistoryRecord() {
266         String summaryIfEnabled = "";
267         if (isTreatmentEnabledForDisplay()) {
268             ActivityRecord topActivity = mDisplayContent.topRunningActivity(
269                     /* considerKeyguardState= */ true);
270             summaryIfEnabled =
271                     " mLastReportedOrientation="
272                             + screenOrientationToString(mLastReportedOrientation)
273                     + " topActivity="
274                             + (topActivity == null ? "null" : topActivity.shortComponentName)
275                     + " isTreatmentEnabledForActivity="
276                             + isTreatmentEnabledForActivity(topActivity)
277                     + " CameraIdPackageNameBiMap="
278                             + mCameraIdPackageBiMap.getSummaryForDisplayRotationHistoryRecord();
279         }
280         return "DisplayRotationCompatPolicy{"
281                 + " isTreatmentEnabledForDisplay=" + isTreatmentEnabledForDisplay()
282                 + summaryIfEnabled
283                 + " }";
284     }
285 
restoreOverriddenOrientationIfNeeded()286     private void restoreOverriddenOrientationIfNeeded() {
287         if (!isOrientationOverridden()) {
288             return;
289         }
290         if (mDisplayContent.getRotationReversionController().revertOverride(
291                 REVERSION_TYPE_CAMERA_COMPAT)) {
292             ProtoLog.v(WM_DEBUG_ORIENTATION,
293                     "Reverting orientation after camera compat force rotation");
294             // Reset last orientation source since we have reverted the orientation.
295             mDisplayContent.mLastOrientationSource = null;
296         }
297     }
298 
isOrientationOverridden()299     private boolean isOrientationOverridden() {
300         return mDisplayContent.getRotationReversionController().isOverrideActive(
301                 REVERSION_TYPE_CAMERA_COMPAT);
302     }
303 
rememberOverriddenOrientationIfNeeded()304     private void rememberOverriddenOrientationIfNeeded() {
305         if (!isOrientationOverridden()) {
306             mDisplayContent.getRotationReversionController().beforeOverrideApplied(
307                     REVERSION_TYPE_CAMERA_COMPAT);
308             ProtoLog.v(WM_DEBUG_ORIENTATION,
309                     "Saving original orientation before camera compat, last orientation is %d",
310                     mDisplayContent.getLastOrientation());
311         }
312     }
313 
314     // Refreshing only when configuration changes after rotation.
shouldRefreshActivity(ActivityRecord activity, Configuration newConfig, Configuration lastReportedConfig)315     private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
316             Configuration lastReportedConfig) {
317         return newConfig.windowConfiguration.getDisplayRotation()
318                         != lastReportedConfig.windowConfiguration.getDisplayRotation()
319                 && isTreatmentEnabledForActivity(activity)
320                 && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat();
321     }
322 
323     /**
324      * Whether camera compat treatment is enabled for the display.
325      *
326      * <p>Conditions that need to be met:
327      * <ul>
328      *     <li>{@code R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
329      *     <li>Setting {@code ignoreOrientationRequest} is enabled for the display.
330      *     <li>Associated {@link DisplayContent} is for internal display. See b/225928882
331      *     that tracks supporting external displays in the future.
332      * </ul>
333      */
isTreatmentEnabledForDisplay()334     private boolean isTreatmentEnabledForDisplay() {
335         return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
336                     /* checkDeviceConfig */ true)
337                 && mDisplayContent.getIgnoreOrientationRequest()
338                 // TODO(b/225928882): Support camera compat rotation for external displays
339                 && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
340     }
341 
isActivityEligibleForOrientationOverride(@onNull ActivityRecord activity)342     boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
343         return isTreatmentEnabledForDisplay()
344                 && isCameraActive(activity, /* mustBeFullscreen */ true);
345     }
346 
347 
348     /**
349      * Whether camera compat treatment is applicable for the given activity.
350      *
351      * <p>Conditions that need to be met:
352      * <ul>
353      *     <li>Camera is active for the package.
354      *     <li>The activity is in fullscreen
355      *     <li>The activity has fixed orientation but not "locked" or "nosensor" one.
356      * </ul>
357      */
isTreatmentEnabledForActivity(@ullable ActivityRecord activity)358     boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
359         return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
360     }
361 
isTreatmentEnabledForActivity(@ullable ActivityRecord activity, boolean mustBeFullscreen)362     private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
363             boolean mustBeFullscreen) {
364         return activity != null && isCameraActive(activity, mustBeFullscreen)
365                 && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
366                 // "locked" and "nosensor" values are often used by camera apps that can't
367                 // handle dynamic changes so we shouldn't force rotate them.
368                 && activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR
369                 && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED;
370     }
371 
isCameraActive(@onNull ActivityRecord activity, boolean mustBeFullscreen)372     private boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
373         // Checking windowing mode on activity level because we don't want to
374         // apply treatment in case of activity embedding.
375         return (!mustBeFullscreen || !activity.inMultiWindowMode())
376                 && mCameraIdPackageBiMap.containsPackageName(activity.packageName)
377                 && activity.mLetterboxUiController.shouldForceRotateForCameraCompat();
378     }
379 
notifyCameraOpened( @onNull String cameraId, @NonNull String packageName)380     private synchronized void notifyCameraOpened(
381             @NonNull String cameraId, @NonNull String packageName) {
382         // If an activity is restarting or camera is flipping, the camera connection can be
383         // quickly closed and reopened.
384         mScheduledToBeRemovedCameraIdSet.remove(cameraId);
385         ProtoLog.v(WM_DEBUG_ORIENTATION,
386                 "Display id=%d is notified that Camera %s is open for package %s",
387                 mDisplayContent.mDisplayId, cameraId, packageName);
388         // Some apps can’t handle configuration changes coming at the same time with Camera setup
389         // so delaying orientation update to accomadate for that.
390         mScheduledOrientationUpdateCameraIdSet.add(cameraId);
391         mHandler.postDelayed(
392                 () ->  delayedUpdateOrientationWithWmLock(cameraId, packageName),
393                 CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS);
394     }
395 
delayedUpdateOrientationWithWmLock( @onNull String cameraId, @NonNull String packageName)396     private void delayedUpdateOrientationWithWmLock(
397             @NonNull String cameraId, @NonNull String packageName) {
398         synchronized (this) {
399             if (!mScheduledOrientationUpdateCameraIdSet.remove(cameraId)) {
400                 // Orientation update has happened already or was cancelled because
401                 // camera was closed.
402                 return;
403             }
404             mCameraIdPackageBiMap.put(packageName, cameraId);
405         }
406         synchronized (mWmService.mGlobalLock) {
407             ActivityRecord topActivity = mDisplayContent.topRunningActivity(
408                         /* considerKeyguardState= */ true);
409             if (topActivity == null || topActivity.getTask() == null) {
410                 return;
411             }
412             // Checking whether an activity in fullscreen rather than the task as this camera
413             // compat treatment doesn't cover activity embedding.
414             if (topActivity.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
415                 topActivity.mLetterboxUiController.recomputeConfigurationForCameraCompatIfNeeded();
416                 mDisplayContent.updateOrientation();
417                 return;
418             }
419             // Checking that the whole app is in multi-window mode as we shouldn't show toast
420             // for the activity embedding case.
421             if (topActivity.getTask().getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
422                     && isTreatmentEnabledForActivity(topActivity, /* mustBeFullscreen */ false)) {
423                 showToast(R.string.display_rotation_camera_compat_toast_in_split_screen);
424             }
425         }
426     }
427 
428     @VisibleForTesting
showToast(@tringRes int stringRes)429     void showToast(@StringRes int stringRes) {
430         UiThread.getHandler().post(
431                 () -> Toast.makeText(mWmService.mContext, stringRes, Toast.LENGTH_LONG).show());
432     }
433 
notifyCameraClosed(@onNull String cameraId)434     private synchronized void notifyCameraClosed(@NonNull String cameraId) {
435         ProtoLog.v(WM_DEBUG_ORIENTATION,
436                 "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
437                 mDisplayContent.mDisplayId, cameraId);
438         mScheduledToBeRemovedCameraIdSet.add(cameraId);
439         // No need to update orientation for this camera if it's already closed.
440         mScheduledOrientationUpdateCameraIdSet.remove(cameraId);
441         scheduleRemoveCameraId(cameraId);
442     }
443 
444     // Delay is needed to avoid rotation flickering when an app is flipping between front and
445     // rear cameras, when size compat mode is restarted or activity is being refreshed.
scheduleRemoveCameraId(@onNull String cameraId)446     private void scheduleRemoveCameraId(@NonNull String cameraId) {
447         mHandler.postDelayed(
448                 () -> removeCameraId(cameraId),
449                 CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS);
450     }
451 
removeCameraId(String cameraId)452     private void removeCameraId(String cameraId) {
453         synchronized (this) {
454             if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) {
455                 // Already reconnected to this camera, no need to clean up.
456                 return;
457             }
458             if (isActivityForCameraIdRefreshing(cameraId)) {
459                 ProtoLog.v(WM_DEBUG_ORIENTATION,
460                         "Display id=%d is notified that Camera %s is closed but activity is"
461                                 + " still refreshing. Rescheduling an update.",
462                         mDisplayContent.mDisplayId, cameraId);
463                 mScheduledToBeRemovedCameraIdSet.add(cameraId);
464                 scheduleRemoveCameraId(cameraId);
465                 return;
466             }
467             mCameraIdPackageBiMap.removeCameraId(cameraId);
468         }
469         ProtoLog.v(WM_DEBUG_ORIENTATION,
470                 "Display id=%d is notified that Camera %s is closed, updating rotation.",
471                 mDisplayContent.mDisplayId, cameraId);
472         synchronized (mWmService.mGlobalLock) {
473             ActivityRecord topActivity = mDisplayContent.topRunningActivity(
474                     /* considerKeyguardState= */ true);
475             if (topActivity == null
476                     // Checking whether an activity in fullscreen rather than the task as this
477                     // camera compat treatment doesn't cover activity embedding.
478                     || topActivity.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
479                 return;
480             }
481             topActivity.mLetterboxUiController.recomputeConfigurationForCameraCompatIfNeeded();
482             mDisplayContent.updateOrientation();
483         }
484     }
485 
isActivityForCameraIdRefreshing(String cameraId)486     private boolean isActivityForCameraIdRefreshing(String cameraId) {
487         ActivityRecord topActivity = mDisplayContent.topRunningActivity(
488                 /* considerKeyguardState= */ true);
489         if (!isTreatmentEnabledForActivity(topActivity)) {
490             return false;
491         }
492         String activeCameraId = mCameraIdPackageBiMap.getCameraId(topActivity.packageName);
493         if (activeCameraId == null || activeCameraId != cameraId) {
494             return false;
495         }
496         return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
497     }
498 
499     private static class CameraIdPackageNameBiMap {
500 
501         private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
502         private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
503 
isEmpty()504         boolean isEmpty() {
505             return mCameraIdToPackageMap.isEmpty();
506         }
507 
put(String packageName, String cameraId)508         void put(String packageName, String cameraId) {
509             // Always using the last connected camera ID for the package even for the concurrent
510             // camera use case since we can't guess which camera is more important anyway.
511             removePackageName(packageName);
512             removeCameraId(cameraId);
513             mPackageToCameraIdMap.put(packageName, cameraId);
514             mCameraIdToPackageMap.put(cameraId, packageName);
515         }
516 
containsPackageName(String packageName)517         boolean containsPackageName(String packageName) {
518             return mPackageToCameraIdMap.containsKey(packageName);
519         }
520 
521         @Nullable
getCameraId(String packageName)522         String getCameraId(String packageName) {
523             return mPackageToCameraIdMap.get(packageName);
524         }
525 
removeCameraId(String cameraId)526         void removeCameraId(String cameraId) {
527             String packageName = mCameraIdToPackageMap.get(cameraId);
528             if (packageName == null) {
529                 return;
530             }
531             mPackageToCameraIdMap.remove(packageName, cameraId);
532             mCameraIdToPackageMap.remove(cameraId, packageName);
533         }
534 
getSummaryForDisplayRotationHistoryRecord()535         String getSummaryForDisplayRotationHistoryRecord() {
536             return "{ mPackageToCameraIdMap=" + mPackageToCameraIdMap + " }";
537         }
538 
removePackageName(String packageName)539         private void removePackageName(String packageName) {
540             String cameraId = mPackageToCameraIdMap.get(packageName);
541             if (cameraId == null) {
542                 return;
543             }
544             mPackageToCameraIdMap.remove(packageName, cameraId);
545             mCameraIdToPackageMap.remove(cameraId, packageName);
546         }
547     }
548 }
549