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