summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mariia Sandrikova <mariiasand@google.com> 2022-12-22 06:22:05 +0000
committer Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> 2022-12-22 06:22:05 +0000
commit561fc8b41f3d9cf80b51f7dffc8043a8291b2bd9 (patch)
tree22a178e7dbb263d25aec5f18d4eeb45db4d716e7
parent40b7fb2080f756349d912ec992c028dbdeb28a10 (diff)
parenta1b8de23173a53d867db89a5d423c290271f5cf4 (diff)
[1/n] Camera Compat: Force rotate activities am: 83c300c0f9 am: a1b8de2317
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19601374 Change-Id: I6169f0c6eb1fe1f3844e7698534aa85ef12acf02 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--data/etc/services.core.protolog.json24
-rw-r--r--services/core/java/com/android/server/camera/CameraServiceProxy.java11
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java17
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java319
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java330
7 files changed, 708 insertions, 0 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 307707f6e152..35ff7e855b75 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5366,6 +5366,12 @@
TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
<bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
+ <!-- Whether camera compat treatment is enabled for issues caused by orientation mismatch
+ between camera buffers and an app window. This includes force rotation of fixed
+ orientation activities connected to the camera in fullscreen and showing a tooltip in
+ split screen. -->
+ <bool name="config_isWindowManagerCameraCompatTreatmentEnabled">false</bool>
+
<!-- Whether a camera compat controller is enabled to allow the user to apply or revert
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 923ef3245c21..c73f2f498355 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4432,6 +4432,7 @@
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
<java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
<java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
+ <java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 7425c95e9337..ffa847722ca6 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -313,6 +313,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-1812743677": {
+ "message": "Display id=%d is ignoring all orientation requests, camera is active and the top activity is eligible for force rotation, return %s,portrait activity: %b, is natural orientation portrait: %b.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-1810446914": {
"message": "Trying to update display configuration for system\/invalid process.",
"level": "WARN",
@@ -1375,6 +1381,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-799396645": {
+ "message": "Display id=%d is notified that Camera %s is closed, updating rotation.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-799003045": {
"message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
"level": "VERBOSE",
@@ -1603,6 +1615,12 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-627759820": {
+ "message": "Display id=%d is notified that Camera %s is open for package %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-622997754": {
"message": "postWindowRemoveCleanupLocked: %s",
"level": "VERBOSE",
@@ -2161,6 +2179,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/Session.java"
},
+ "-81260230": {
+ "message": "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-81121442": {
"message": "ImeContainer just became organized but it doesn't have a parent or the parent doesn't have a surface control. mSurfaceControl=%s imeParentSurfaceControl=%s",
"level": "ERROR",
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index e16ca0bbe1e1..0b03005af044 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -74,6 +74,7 @@ import android.view.Surface;
import android.view.WindowManagerGlobal;
import com.android.framework.protobuf.nano.MessageNano;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
@@ -389,6 +390,16 @@ public class CameraServiceProxy extends SystemService
return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
}
+ // When config_isWindowManagerCameraCompatTreatmentEnabled is true,
+ // DisplayRotationCompatPolicy in WindowManager force rotates fullscreen activities with
+ // fixed orientation to align them with the natural orientation of the device.
+ if (ctx.getResources().getBoolean(
+ R.bool.config_isWindowManagerCameraCompatTreatmentEnabled)) {
+ Slog.v(TAG, "Disable Rotate and Crop to avoid conflicts with"
+ + " WM force rotation treatment.");
+ return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+ }
+
// External cameras do not need crop-rotate-scale.
if (lensFacing != CameraMetadata.LENS_FACING_FRONT
&& lensFacing != CameraMetadata.LENS_FACING_BACK) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a8d26d6a6045..e26f6ca24066 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -449,6 +449,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
private final DisplayPolicy mDisplayPolicy;
private final DisplayRotation mDisplayRotation;
+ @Nullable private final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
DisplayFrames mDisplayFrames;
private boolean mInTouchMode;
@@ -1178,6 +1179,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
onDisplayChanged(this);
updateDisplayAreaOrganizers();
+ mDisplayRotationCompatPolicy =
+ DisplayRotationCompatPolicy.isTreatmentEnabled(mWmService.mContext)
+ ? new DisplayRotationCompatPolicy(this) : null;
+
mInputMonitor = new InputMonitor(mWmService, this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
@@ -2756,6 +2761,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
+ if (mDisplayRotationCompatPolicy != null) {
+ int compatOrientation = mDisplayRotationCompatPolicy.getOrientation();
+ if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ mLastOrientationSource = null;
+ return compatOrientation;
+ }
+ }
+
final int orientation = super.getOrientation();
if (!handlesOrientationChangeFromDescendant(orientation)) {
@@ -3318,6 +3331,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// on the next traversal if it's removed from RootWindowContainer child list.
getPendingTransaction().apply();
mWmService.mWindowPlacerLocked.requestTraversal();
+
+ if (mDisplayRotationCompatPolicy != null) {
+ mDisplayRotationCompatPolicy.dispose();
+ }
}
/** Returns true if a removal action is still being deferred. */
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
new file mode 100644
index 000000000000..a19539d10e5e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.screenOrientationToString;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.Display.TYPE_INTERNAL;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Controls camera compatibility treatment that handles orientation mismatch between camera
+ * buffers and an app window for a particular display that can lead to camera issues like sideways
+ * or stretched viewfinder.
+ *
+ * <p>This includes force rotation of fixed orientation activities connected to the camera.
+ *
+ * <p>The treatment is enabled for internal displays that have {@code ignoreOrientationRequest}
+ * display setting enabled and when {@code
+ * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
+ */
+ // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
+final class DisplayRotationCompatPolicy {
+
+ // Delay for updating display rotation after Camera connection is closed. Needed to avoid
+ // rotation flickering when an app is flipping between front and rear cameras or when size
+ // compat mode is restarted.
+ // TODO(b/263114289): Consider associating this delay with a specific activity so that if
+ // the new non-camera activity started on top of the camer one we can rotate faster.
+ private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
+ // Delay for updating display rotation after Camera connection is opened. This delay is
+ // selected to be long enough to avoid conflicts with transitions on the app's side.
+ // Using half CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
+ // is flipping between front and rear cameras (in case requested orientation changes at
+ // runtime at the same time) or when size compat mode is restarted.
+ private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
+ CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
+
+ private final DisplayContent mDisplayContent;
+ private final WindowManagerService mWmService;
+ private final CameraManager mCameraManager;
+ private final Handler mHandler;
+ // TODO(b/218352945): Add an ADB command.
+ private final boolean mIsTreatmentEnabled;
+
+ // Bi-directional map between package names and active camera IDs since we need to 1) get a
+ // camera id by a package name when determining rotation; 2) get a package name by a camera id
+ // when camera connection is closed and we need to clean up our records.
+ @GuardedBy("this")
+ private final CameraIdPackageNameBiMap mCameraIdPackageBiMap = new CameraIdPackageNameBiMap();
+ @GuardedBy("this")
+ private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
+ @GuardedBy("this")
+ private final Set<String> mScheduledOrientationUpdateCameraIdSet = new ArraySet<>();
+
+ private final CameraManager.AvailabilityCallback mAvailabilityCallback =
+ new CameraManager.AvailabilityCallback() {
+ @Override
+ public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
+ notifyCameraOpened(cameraId, packageId);
+ }
+
+ @Override
+ public void onCameraClosed(@NonNull String cameraId) {
+ notifyCameraClosed(cameraId);
+ }
+ };
+
+ DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent) {
+ this(displayContent, displayContent.mWmService.mH);
+ }
+
+ @VisibleForTesting
+ DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler) {
+ // This constructor is called from DisplayContent constructor. Don't use any fields in
+ // DisplayContent here since they aren't guaranteed to be set.
+ mHandler = handler;
+ mDisplayContent = displayContent;
+ mWmService = displayContent.mWmService;
+ mIsTreatmentEnabled = isTreatmentEnabled(mWmService.mContext);
+ mCameraManager = mWmService.mContext.getSystemService(CameraManager.class);
+ mCameraManager.registerAvailabilityCallback(
+ mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+ }
+
+ static boolean isTreatmentEnabled(@NonNull Context context) {
+ return context.getResources().getBoolean(
+ R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
+ }
+
+ void dispose() {
+ mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
+ }
+
+ /**
+ * Determines orientation for Camera compatibility.
+ *
+ * <p>The goal of this function is to compute a orientation which would align orientations of
+ * portrait app window and natural orientation of the device and set opposite to natural
+ * orientation for a landscape app window. This is one of the strongest assumptions that apps
+ * make when they implement camera previews. Since app and natural display orientations aren't
+ * guaranteed to match, the rotation can cause letterboxing.
+ *
+ * <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link
+ * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
+ */
+ @ScreenOrientation
+ synchronized int getOrientation() {
+ if (!isTreatmentEnabledForDisplay()) {
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+ ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+ /* considerKeyguardState= */ true);
+ if (!isTreatmentEnabledForActivity(topActivity)) {
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+ boolean isPortraitActivity =
+ topActivity.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+ boolean isNaturalDisplayOrientationPortrait =
+ mDisplayContent.getNaturalOrientation() == ORIENTATION_PORTRAIT;
+ // Rotate portrait-only activity in the natural orientation of the displays (and in the
+ // opposite to natural orientation for landscape-only) since many apps assume that those
+ // are aligned when they compute orientation of the preview.
+ // This means that even for a landscape-only activity and a device with landscape natural
+ // orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that
+ // natural orientation = portrait window = portait camera is the main wrong assumption
+ // that apps make when they implement camera previews so landscape windows need be
+ // rotated in the orientation oposite to the natural one even if it's portrait.
+ // TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions
+ // of the portrait and landscape orientation requests.
+ int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait)
+ || (!isPortraitActivity && !isNaturalDisplayOrientationPortrait)
+ ? SCREEN_ORIENTATION_PORTRAIT
+ : SCREEN_ORIENTATION_LANDSCAPE;
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is ignoring all orientation requests, camera is active "
+ + "and the top activity is eligible for force rotation, return %s,"
+ + "portrait activity: %b, is natural orientation portrait: %b.",
+ mDisplayContent.mDisplayId, screenOrientationToString(orientation),
+ isPortraitActivity, isNaturalDisplayOrientationPortrait);
+ return orientation;
+ }
+
+ /**
+ * Whether camera compat treatment is enabled for the display.
+ *
+ * <p>Conditions that need to be met:
+ * <ul>
+ * <li>{@code R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
+ * <li>Setting {@code ignoreOrientationRequest} is enabled for the display.
+ * <li>Associated {@link DisplayContent} is for internal display. See b/225928882
+ * that tracks supporting external displays in the future.
+ * </ul>
+ */
+ private boolean isTreatmentEnabledForDisplay() {
+ return mIsTreatmentEnabled && mDisplayContent.getIgnoreOrientationRequest()
+ // TODO(b/225928882): Support camera compat rotation for external displays
+ && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
+ }
+
+ /**
+ * Whether camera compat treatment is applicable for the given activity.
+ *
+ * <p>Conditions that need to be met:
+ * <ul>
+ * <li>{@link #isCameraActiveForPackage} is {@code true} for the activity.
+ * <li>The activity is in fullscreen
+ * <li>The activity has fixed orientation but not "locked" or "nosensor" one.
+ * </ul>
+ */
+ private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
+ return activity != null && !activity.inMultiWindowMode()
+ && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
+ // "locked" and "nosensor" values are often used by camera apps that can't
+ // handle dynamic changes so we shouldn't force rotate them.
+ && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
+ && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
+ && mCameraIdPackageBiMap.containsPackageName(activity.packageName);
+ }
+
+ private synchronized void notifyCameraOpened(
+ @NonNull String cameraId, @NonNull String packageName) {
+ // If an activity is restarting or camera is flipping, the camera connection can be
+ // quickly closed and reopened.
+ mScheduledToBeRemovedCameraIdSet.remove(cameraId);
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is notified that Camera %s is open for package %s",
+ mDisplayContent.mDisplayId, cameraId, packageName);
+ // Some apps can’t handle configuration changes coming at the same time with Camera setup
+ // so delaying orientation update to accomadate for that.
+ mScheduledOrientationUpdateCameraIdSet.add(cameraId);
+ mHandler.postDelayed(
+ () -> delayedUpdateOrientationWithWmLock(cameraId, packageName),
+ CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS);
+ // TODO(b/218352945): Restart activity after forced rotation to avoid issues cased by
+ // in-app caching of pre-rotation display / camera properties.
+ }
+
+ private void updateOrientationWithWmLock() {
+ synchronized (mWmService.mGlobalLock) {
+ mDisplayContent.updateOrientation();
+ }
+ }
+
+ private void delayedUpdateOrientationWithWmLock(
+ @NonNull String cameraId, @NonNull String packageName) {
+ synchronized (this) {
+ if (!mScheduledOrientationUpdateCameraIdSet.remove(cameraId)) {
+ // Orientation update has happened already or was cancelled because
+ // camera was closed.
+ return;
+ }
+ mCameraIdPackageBiMap.put(packageName, cameraId);
+ }
+ updateOrientationWithWmLock();
+ }
+
+ private synchronized void notifyCameraClosed(@NonNull String cameraId) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
+ mDisplayContent.mDisplayId, cameraId);
+ mScheduledToBeRemovedCameraIdSet.add(cameraId);
+ // No need to update orientation for this camera if it's already closed.
+ mScheduledOrientationUpdateCameraIdSet.remove(cameraId);
+ // Delay is needed to avoid rotation flickering when an app is flipping between front and
+ // rear cameras or when size compat mode is restarted.
+ mHandler.postDelayed(
+ () -> removeCameraId(cameraId),
+ CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS);
+ }
+
+ private void removeCameraId(String cameraId) {
+ synchronized (this) {
+ if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) {
+ // Already reconnected to this camera, no need to clean up.
+ return;
+ }
+ mCameraIdPackageBiMap.removeCameraId(cameraId);
+ }
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Display id=%d is notified that Camera %s is closed, updating rotation.",
+ mDisplayContent.mDisplayId, cameraId);
+ updateOrientationWithWmLock();
+ }
+
+ private static class CameraIdPackageNameBiMap {
+
+ private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
+ private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
+
+ void put(String packageName, String cameraId) {
+ // Always using the last connected camera ID for the package even for the concurrent
+ // camera use case since we can't guess which camera is more important anyway.
+ removePackageName(packageName);
+ removeCameraId(cameraId);
+ mPackageToCameraIdMap.put(packageName, cameraId);
+ mCameraIdToPackageMap.put(cameraId, packageName);
+ }
+
+ boolean containsPackageName(String packageName) {
+ return mPackageToCameraIdMap.containsKey(packageName);
+ }
+
+ void removeCameraId(String cameraId) {
+ String packageName = mCameraIdToPackageMap.get(cameraId);
+ if (packageName == null) {
+ return;
+ }
+ mPackageToCameraIdMap.remove(packageName, cameraId);
+ mCameraIdToPackageMap.remove(cameraId, packageName);
+ }
+
+ private void removePackageName(String packageName) {
+ String cameraId = mPackageToCameraIdMap.get(packageName);
+ if (cameraId == null) {
+ return;
+ }
+ mPackageToCameraIdMap.remove(packageName, cameraId);
+ mCameraIdToPackageMap.remove(cameraId, packageName);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
new file mode 100644
index 000000000000..fda578da3235
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration.Orientation;
+import android.content.res.Resources;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for {@link DisplayRotationCompatPolicy}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:DisplayRotationCompatPolicyTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
+
+ private static final String TEST_PACKAGE_1 = "com.test.package.one";
+ private static final String TEST_PACKAGE_2 = "com.test.package.two";
+ private static final String CAMERA_ID_1 = "camera-1";
+ private static final String CAMERA_ID_2 = "camera-2";
+
+ private CameraManager mMockCameraManager;
+ private Handler mMockHandler;
+ private Resources mResources;
+
+ private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
+ private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
+
+ private ActivityRecord mActivity;
+ private Task mTask;
+
+ @Before
+ public void setUp() throws Exception {
+ mResources = mContext.getResources();
+ spyOn(mResources);
+ when(mResources.getBoolean(R.bool.config_isWindowManagerCameraCompatTreatmentEnabled))
+ .thenReturn(true);
+
+ mMockCameraManager = mock(CameraManager.class);
+ doAnswer(invocation -> {
+ mCameraAvailabilityCallback = invocation.getArgument(1);
+ return null;
+ }).when(mMockCameraManager).registerAvailabilityCallback(
+ any(Executor.class), any(CameraManager.AvailabilityCallback.class));
+
+ spyOn(mContext);
+ when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager);
+
+ spyOn(mDisplayContent);
+
+ mDisplayContent.setIgnoreOrientationRequest(true);
+
+ mMockHandler = mock(Handler.class);
+
+ when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+ invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ });
+ mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
+ mDisplayContent, mMockHandler);
+ }
+
+ @Test
+ public void testGetOrientation_treatmentNotEnabled_returnUnspecified() {
+ when(mResources.getBoolean(R.bool.config_isWindowManagerCameraCompatTreatmentEnabled))
+ .thenReturn(false);
+
+ mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent);
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_multiWindowMode_returnUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mDisplayContent);
+ mActivity.getTask().reparent(organizer.mPrimary, WindowContainer.POSITION_TOP,
+ false /* moveParents */, "test" /* reason */);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertTrue(mActivity.inMultiWindowMode());
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_orientationUnspecified_returnUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_orientationLocked_returnUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_LOCKED);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_orientationNoSensor_returnUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_NOSENSOR);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_ignoreOrientationRequestIsFalse_returnUnspecified() {
+ mDisplayContent.setIgnoreOrientationRequest(false);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_displayNotInternal_returnUnspecified() {
+ Display display = mDisplayContent.getDisplay();
+ spyOn(display);
+
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ when(display.getType()).thenReturn(Display.TYPE_EXTERNAL);
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+
+ when(display.getType()).thenReturn(Display.TYPE_WIFI);
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+
+ when(display.getType()).thenReturn(Display.TYPE_OVERLAY);
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+
+ when(display.getType()).thenReturn(Display.TYPE_VIRTUAL);
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_noCameraConnection_returnUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_cameraReconnected_returnNotUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ @Test
+ public void testGetOrientation_reconnectedToDifferentCamera_returnNotUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ @Test
+ public void testGetOrientation_cameraConnectionClosed_returnUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_cameraOpenedForDifferentPackage_returnUnspecified() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ }
+
+ @Test
+ public void testGetOrientation_portraitActivity_portraitNaturalOrientation_returnPortrait() {
+ testGetOrientationForActivityAndNaturalOrientations(
+ /* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
+ /* naturalOrientation */ ORIENTATION_PORTRAIT,
+ /* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ @Test
+ public void testGetOrientation_portraitActivity_landscapeNaturalOrientation_returnLandscape() {
+ testGetOrientationForActivityAndNaturalOrientations(
+ /* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
+ /* naturalOrientation */ ORIENTATION_LANDSCAPE,
+ /* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
+ public void testGetOrientation_landscapeActivity_portraitNaturalOrientation_returnLandscape() {
+ testGetOrientationForActivityAndNaturalOrientations(
+ /* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
+ /* naturalOrientation */ ORIENTATION_PORTRAIT,
+ /* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
+ }
+
+ @Test
+ public void testGetOrientation_landscapeActivity_landscapeNaturalOrientation_returnPortrait() {
+ testGetOrientationForActivityAndNaturalOrientations(
+ /* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
+ /* naturalOrientation */ ORIENTATION_LANDSCAPE,
+ /* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
+ }
+
+ private void testGetOrientationForActivityAndNaturalOrientations(
+ @ScreenOrientation int activityOrientation,
+ @Orientation int naturalOrientation,
+ @ScreenOrientation int expectedOrientation) {
+ configureActivityAndDisplay(activityOrientation, naturalOrientation);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ expectedOrientation);
+ }
+
+ private void configureActivity(@ScreenOrientation int activityOrientation) {
+ configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
+ }
+
+ private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
+ @Orientation int naturalOrientation) {
+
+ mTask = new TaskBuilder(mSupervisor)
+ .setDisplay(mDisplayContent)
+ .build();
+
+ mActivity = new ActivityBuilder(mAtm)
+ .setComponent(new ComponentName(TEST_PACKAGE_1, ".TestActivity"))
+ .setScreenOrientation(activityOrientation)
+ .setTask(mTask)
+ .build();
+
+ doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
+ doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
+ }
+}