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