Merge "Coordinate rotation for concurrent mode"
This commit is contained in:
@@ -27,6 +27,7 @@ import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.view.Surface;
|
||||
import android.view.Surface.Rotation;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
@@ -245,4 +246,23 @@ public class RotationUtils {
|
||||
throw new IllegalArgumentException("Unknown rotation: " + rotation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the rotation direction around the Z axis. Note that this method assumes all
|
||||
* rotations are relative to {@link Surface.ROTATION_0}.
|
||||
*
|
||||
* @param rotation the original rotation.
|
||||
* @return the new rotation that should be applied.
|
||||
*/
|
||||
@Surface.Rotation
|
||||
public static int reverseRotationDirectionAroundZAxis(@Surface.Rotation int rotation) {
|
||||
// Flipping 270 and 90 has the same effect as changing the direction which rotation is
|
||||
// applied.
|
||||
if (rotation == Surface.ROTATION_90) {
|
||||
rotation = Surface.ROTATION_270;
|
||||
} else if (rotation == Surface.ROTATION_270) {
|
||||
rotation = Surface.ROTATION_90;
|
||||
}
|
||||
return rotation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -983,6 +983,13 @@
|
||||
<integer-array name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis">
|
||||
</integer-array>
|
||||
|
||||
<!-- Boolean indicating whether secondary built-in displays should have their orientation
|
||||
match the active default display. This config assumes that the secondary display only
|
||||
requires swapping ROTATION_90 and ROTATION_270.
|
||||
TODO(b/265991392): This should eventually be configured and parsed in
|
||||
display_settings.xml -->
|
||||
<bool name="config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay">true</bool>
|
||||
|
||||
<!-- Indicate available ColorDisplayManager.COLOR_MODE_xxx. -->
|
||||
<integer-array name="config_availableColorModes">
|
||||
<!-- Example:
|
||||
|
||||
@@ -3404,6 +3404,13 @@
|
||||
TODO(b/265312193): Remove this workaround when this bug is fixed.-->
|
||||
<java-symbol type="array" name="config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis" />
|
||||
|
||||
<!-- Boolean indicating whether secondary built-in displays should have their orientation
|
||||
match the active default display. This config assumes that the secondary display only
|
||||
requires swapping ROTATION_90 and ROTATION_270.
|
||||
TODO(b/265991392): This should eventually be configured and parsed in
|
||||
display_settings.xml -->
|
||||
<java-symbol type="bool" name="config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay" />
|
||||
|
||||
<!-- Default user restrictions for the SYSTEM user -->
|
||||
<java-symbol type="array" name="config_defaultFirstUserRestrictions" />
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.os.Handler;
|
||||
import android.os.HandlerExecutor;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -48,9 +49,12 @@ final class DeviceStateController implements DeviceStateManager.DeviceStateCallb
|
||||
private final int[] mRearDisplayDeviceStates;
|
||||
@NonNull
|
||||
private final int[] mReverseRotationAroundZAxisStates;
|
||||
@GuardedBy("this")
|
||||
@NonNull
|
||||
private final List<Consumer<DeviceState>> mDeviceStateCallbacks = new ArrayList<>();
|
||||
|
||||
private final boolean mMatchBuiltInDisplayOrientationToDefaultDisplay;
|
||||
|
||||
@Nullable
|
||||
private DeviceState mLastDeviceState;
|
||||
private int mCurrentState;
|
||||
@@ -72,20 +76,19 @@ final class DeviceStateController implements DeviceStateManager.DeviceStateCallb
|
||||
.getIntArray(R.array.config_rearDisplayDeviceStates);
|
||||
mReverseRotationAroundZAxisStates = context.getResources()
|
||||
.getIntArray(R.array.config_deviceStatesToReverseDefaultDisplayRotationAroundZAxis);
|
||||
mMatchBuiltInDisplayOrientationToDefaultDisplay = context.getResources()
|
||||
.getBoolean(R.bool
|
||||
.config_matchSecondaryInternalDisplaysOrientationToReverseDefaultDisplay);
|
||||
|
||||
if (mDeviceStateManager != null) {
|
||||
mDeviceStateManager.registerCallback(new HandlerExecutor(handler), this);
|
||||
}
|
||||
}
|
||||
|
||||
void unregisterFromDeviceStateManager() {
|
||||
if (mDeviceStateManager != null) {
|
||||
mDeviceStateManager.unregisterCallback(this);
|
||||
}
|
||||
}
|
||||
|
||||
void registerDeviceStateCallback(@NonNull Consumer<DeviceState> callback) {
|
||||
mDeviceStateCallbacks.add(callback);
|
||||
synchronized (this) {
|
||||
mDeviceStateCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +98,15 @@ final class DeviceStateController implements DeviceStateManager.DeviceStateCallb
|
||||
return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if non-default built-in displays should match the default display's rotation.
|
||||
*/
|
||||
boolean shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay() {
|
||||
// TODO(b/265991392): This should come from display_settings.xml once it's easier to
|
||||
// extend with complex configurations.
|
||||
return mMatchBuiltInDisplayOrientationToDefaultDisplay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(int state) {
|
||||
mCurrentState = state;
|
||||
@@ -115,8 +127,10 @@ final class DeviceStateController implements DeviceStateManager.DeviceStateCallb
|
||||
if (mLastDeviceState == null || !mLastDeviceState.equals(deviceState)) {
|
||||
mLastDeviceState = deviceState;
|
||||
|
||||
for (Consumer<DeviceState> callback : mDeviceStateCallbacks) {
|
||||
callback.accept(mLastDeviceState);
|
||||
synchronized (this) {
|
||||
for (Consumer<DeviceState> callback : mDeviceStateCallbacks) {
|
||||
callback.accept(mLastDeviceState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,7 +595,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
final FixedRotationTransitionListener mFixedRotationTransitionListener =
|
||||
new FixedRotationTransitionListener();
|
||||
|
||||
private final DeviceStateController mDeviceStateController;
|
||||
@VisibleForTesting
|
||||
final DeviceStateController mDeviceStateController;
|
||||
private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
|
||||
final RemoteDisplayChangeController mRemoteDisplayChangeController;
|
||||
|
||||
@@ -1091,7 +1092,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
* @param display May not be null.
|
||||
* @param root {@link RootWindowContainer}
|
||||
*/
|
||||
DisplayContent(Display display, RootWindowContainer root) {
|
||||
DisplayContent(Display display, RootWindowContainer root,
|
||||
@NonNull DeviceStateController deviceStateController) {
|
||||
super(root.mWindowManager, "DisplayContent", FEATURE_ROOT);
|
||||
if (mWmService.mRoot.getDisplayContent(display.getDisplayId()) != null) {
|
||||
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
|
||||
@@ -1151,11 +1153,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
mWmService.mAtmService.getRecentTasks().getInputListener());
|
||||
}
|
||||
|
||||
mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH);
|
||||
mDeviceStateController = deviceStateController;
|
||||
|
||||
mDisplayPolicy = new DisplayPolicy(mWmService, this);
|
||||
mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address,
|
||||
mDeviceStateController);
|
||||
mDeviceStateController, root.getDisplayRotationCoordinator());
|
||||
|
||||
final Consumer<DeviceStateController.DeviceState> deviceStateConsumer =
|
||||
(@NonNull DeviceStateController.DeviceState newFoldState) -> {
|
||||
@@ -3326,7 +3328,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener);
|
||||
handleAnimatingStoppedAndTransition();
|
||||
mWmService.stopFreezingDisplayLocked();
|
||||
mDeviceStateController.unregisterFromDeviceStateManager();
|
||||
mDisplayRotation.removeDefaultDisplayRotationChangedCallback();
|
||||
super.removeImmediately();
|
||||
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
|
||||
mPointerEventDispatcher.dispose();
|
||||
|
||||
@@ -58,6 +58,7 @@ import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.ArraySet;
|
||||
import android.util.RotationUtils;
|
||||
import android.util.Slog;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
@@ -120,6 +121,11 @@ public class DisplayRotation {
|
||||
private FoldController mFoldController;
|
||||
@NonNull
|
||||
private final DeviceStateController mDeviceStateController;
|
||||
@NonNull
|
||||
private final DisplayRotationCoordinator mDisplayRotationCoordinator;
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
final Runnable mDefaultDisplayRotationChangedCallback;
|
||||
|
||||
@ScreenOrientation
|
||||
private int mCurrentAppOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
|
||||
@@ -221,17 +227,19 @@ public class DisplayRotation {
|
||||
private boolean mDemoRotationLock;
|
||||
|
||||
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
|
||||
DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController) {
|
||||
DisplayAddress displayAddress, @NonNull DeviceStateController deviceStateController,
|
||||
@NonNull DisplayRotationCoordinator displayRotationCoordinator) {
|
||||
this(service, displayContent, displayAddress, displayContent.getDisplayPolicy(),
|
||||
service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock(),
|
||||
deviceStateController);
|
||||
deviceStateController, displayRotationCoordinator);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
DisplayRotation(WindowManagerService service, DisplayContent displayContent,
|
||||
DisplayAddress displayAddress, DisplayPolicy displayPolicy,
|
||||
DisplayWindowSettings displayWindowSettings, Context context, Object lock,
|
||||
@NonNull DeviceStateController deviceStateController) {
|
||||
@NonNull DeviceStateController deviceStateController,
|
||||
@NonNull DisplayRotationCoordinator displayRotationCoordinator) {
|
||||
mService = service;
|
||||
mDisplayContent = displayContent;
|
||||
mDisplayPolicy = displayPolicy;
|
||||
@@ -252,6 +260,19 @@ public class DisplayRotation {
|
||||
int defaultRotation = readDefaultDisplayRotation(displayAddress);
|
||||
mRotation = defaultRotation;
|
||||
|
||||
mDisplayRotationCoordinator = displayRotationCoordinator;
|
||||
if (isDefaultDisplay) {
|
||||
mDisplayRotationCoordinator.setDefaultDisplayDefaultRotation(mRotation);
|
||||
}
|
||||
mDefaultDisplayRotationChangedCallback = this::updateRotationAndSendNewConfigIfChanged;
|
||||
|
||||
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(displayContent)
|
||||
&& mDeviceStateController
|
||||
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
|
||||
mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
|
||||
mDefaultDisplayRotationChangedCallback);
|
||||
}
|
||||
|
||||
if (isDefaultDisplay) {
|
||||
final Handler uiHandler = UiThread.getHandler();
|
||||
mOrientationListener =
|
||||
@@ -494,8 +515,11 @@ public class DisplayRotation {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Surface.Rotation
|
||||
final int oldRotation = mRotation;
|
||||
@ScreenOrientation
|
||||
final int lastOrientation = mLastOrientation;
|
||||
@Surface.Rotation
|
||||
int rotation = rotationForOrientation(lastOrientation, oldRotation);
|
||||
// Use the saved rotation for tabletop mode, if set.
|
||||
if (mFoldController != null && mFoldController.shouldRevertOverriddenRotation()) {
|
||||
@@ -507,6 +531,14 @@ public class DisplayRotation {
|
||||
Surface.rotationToString(oldRotation),
|
||||
Surface.rotationToString(prevRotation));
|
||||
}
|
||||
|
||||
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)
|
||||
&& mDeviceStateController
|
||||
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
|
||||
rotation = RotationUtils.reverseRotationDirectionAroundZAxis(
|
||||
mDisplayRotationCoordinator.getDefaultDisplayCurrentRotation());
|
||||
}
|
||||
|
||||
ProtoLog.v(WM_DEBUG_ORIENTATION,
|
||||
"Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
|
||||
+ "oldRotation=%s (%d)",
|
||||
@@ -525,6 +557,10 @@ public class DisplayRotation {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDefaultDisplay) {
|
||||
mDisplayRotationCoordinator.onDefaultDisplayRotationChanged(rotation);
|
||||
}
|
||||
|
||||
// Preemptively cancel the running recents animation -- SysUI can't currently handle this
|
||||
// case properly since the signals it receives all happen post-change. We do this earlier
|
||||
// in the rotation flow, since DisplayContent.updateDisplayOverrideConfigurationLocked seems
|
||||
@@ -1142,17 +1178,12 @@ public class DisplayRotation {
|
||||
return mUserRotation;
|
||||
}
|
||||
|
||||
@Surface.Rotation
|
||||
int sensorRotation = mOrientationListener != null
|
||||
? mOrientationListener.getProposedRotation() // may be -1
|
||||
: -1;
|
||||
if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
|
||||
// Flipping 270 and 90 has the same effect as changing the direction which rotation is
|
||||
// applied.
|
||||
if (sensorRotation == Surface.ROTATION_90) {
|
||||
sensorRotation = Surface.ROTATION_270;
|
||||
} else if (sensorRotation == Surface.ROTATION_270) {
|
||||
sensorRotation = Surface.ROTATION_90;
|
||||
}
|
||||
sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
|
||||
}
|
||||
mLastSensorRotation = sensorRotation;
|
||||
if (sensorRotation < 0) {
|
||||
@@ -1167,6 +1198,7 @@ public class DisplayRotation {
|
||||
final boolean deskDockEnablesAccelerometer =
|
||||
mDisplayPolicy.isDeskDockEnablesAccelerometer();
|
||||
|
||||
@Surface.Rotation
|
||||
final int preferredRotation;
|
||||
if (!isDefaultDisplay) {
|
||||
// For secondary displays we ignore things like displays sensors, docking mode and
|
||||
@@ -1536,6 +1568,12 @@ public class DisplayRotation {
|
||||
return shouldUpdateRotation;
|
||||
}
|
||||
|
||||
void removeDefaultDisplayRotationChangedCallback() {
|
||||
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
|
||||
mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void dump(String prefix, PrintWriter pw) {
|
||||
pw.println(prefix + "DisplayRotation");
|
||||
pw.println(prefix + " mCurrentAppOrientation="
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.view.Display;
|
||||
import android.view.Surface;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
/**
|
||||
* Singleton for coordinating rotation across multiple displays. Used to notify non-default
|
||||
* displays when the default display rotates.
|
||||
*
|
||||
* Note that this class does not need locking because it is always protected by WindowManagerService
|
||||
* mGlobalLock.
|
||||
*/
|
||||
class DisplayRotationCoordinator {
|
||||
|
||||
private static final String TAG = "DisplayRotationCoordinator";
|
||||
|
||||
@Surface.Rotation
|
||||
private int mDefaultDisplayDefaultRotation;
|
||||
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
Runnable mDefaultDisplayRotationChangedCallback;
|
||||
|
||||
@Surface.Rotation
|
||||
private int mDefaultDisplayCurrentRotation;
|
||||
|
||||
/**
|
||||
* Notifies clients when the default display rotation changes.
|
||||
*/
|
||||
void onDefaultDisplayRotationChanged(@Surface.Rotation int rotation) {
|
||||
mDefaultDisplayCurrentRotation = rotation;
|
||||
|
||||
if (mDefaultDisplayRotationChangedCallback != null) {
|
||||
mDefaultDisplayRotationChangedCallback.run();
|
||||
}
|
||||
}
|
||||
|
||||
void setDefaultDisplayDefaultRotation(@Surface.Rotation int rotation) {
|
||||
mDefaultDisplayDefaultRotation = rotation;
|
||||
}
|
||||
|
||||
@Surface.Rotation
|
||||
int getDefaultDisplayCurrentRotation() {
|
||||
return mDefaultDisplayCurrentRotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be notified when the default display's rotation changes. Clients can
|
||||
* query the default display's current rotation via {@link #getDefaultDisplayCurrentRotation()}.
|
||||
*/
|
||||
void setDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
|
||||
if (mDefaultDisplayRotationChangedCallback != null) {
|
||||
throw new UnsupportedOperationException("Multiple clients unsupported");
|
||||
}
|
||||
|
||||
mDefaultDisplayRotationChangedCallback = callback;
|
||||
|
||||
if (mDefaultDisplayCurrentRotation != mDefaultDisplayDefaultRotation) {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the callback that was added via
|
||||
* {@link #setDefaultDisplayRotationChangedCallback(Runnable)}.
|
||||
*/
|
||||
void removeDefaultDisplayRotationChangedCallback() {
|
||||
mDefaultDisplayRotationChangedCallback = null;
|
||||
}
|
||||
|
||||
static boolean isSecondaryInternalDisplay(@NonNull DisplayContent displayContent) {
|
||||
if (displayContent.isDefaultDisplay) {
|
||||
return false;
|
||||
} else if (displayContent.mDisplay == null) {
|
||||
return false;
|
||||
}
|
||||
return displayContent.mDisplay.getType() == Display.TYPE_INTERNAL;
|
||||
}
|
||||
}
|
||||
@@ -234,6 +234,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
|
||||
WindowManagerService mWindowManager;
|
||||
DisplayManager mDisplayManager;
|
||||
private DisplayManagerInternal mDisplayManagerInternal;
|
||||
@NonNull
|
||||
private final DeviceStateController mDeviceStateController;
|
||||
@NonNull
|
||||
private final DisplayRotationCoordinator mDisplayRotationCoordinator;
|
||||
|
||||
/** Reference to default display so we can quickly look it up. */
|
||||
private DisplayContent mDefaultDisplay;
|
||||
@@ -440,6 +444,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
|
||||
mTaskSupervisor = mService.mTaskSupervisor;
|
||||
mTaskSupervisor.mRootWindowContainer = this;
|
||||
mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
|
||||
mDeviceStateController = new DeviceStateController(service.mContext, service.mH);
|
||||
mDisplayRotationCoordinator = new DisplayRotationCoordinator();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1279,7 +1285,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
|
||||
final Display[] displays = mDisplayManager.getDisplays();
|
||||
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
|
||||
final Display display = displays[displayNdx];
|
||||
final DisplayContent displayContent = new DisplayContent(display, this);
|
||||
final DisplayContent displayContent =
|
||||
new DisplayContent(display, this, mDeviceStateController);
|
||||
addChild(displayContent, POSITION_BOTTOM);
|
||||
if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
|
||||
mDefaultDisplay = displayContent;
|
||||
@@ -1297,6 +1304,10 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
|
||||
return mDefaultDisplay;
|
||||
}
|
||||
|
||||
DisplayRotationCoordinator getDisplayRotationCoordinator() {
|
||||
return mDisplayRotationCoordinator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default display area on the device dedicated to app windows. This one should be used
|
||||
* only as a fallback location for activity launches when no target display area is specified,
|
||||
@@ -1358,7 +1369,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
|
||||
return null;
|
||||
}
|
||||
// The display hasn't been added to ActivityManager yet, create a new record now.
|
||||
displayContent = new DisplayContent(display, this);
|
||||
displayContent = new DisplayContent(display, this, mDeviceStateController);
|
||||
addChild(displayContent, POSITION_BOTTOM);
|
||||
return displayContent;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doCallRealMethod;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.WindowConfiguration;
|
||||
import android.content.res.Configuration;
|
||||
@@ -123,6 +124,7 @@ import android.platform.test.annotations.Presubmit;
|
||||
import android.util.ArraySet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.ContentRecordingSession;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayCutout;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.Gravity;
|
||||
@@ -1830,6 +1832,111 @@ public class DisplayContentTests extends WindowTestsBase {
|
||||
assertFalse(recentsActivity.hasFixedRotationTransform());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondaryInternalDisplayRotationFollowsDefaultDisplay() {
|
||||
// Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
|
||||
doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
|
||||
|
||||
final DisplayRotationCoordinator coordinator =
|
||||
mRootWindowContainer.getDisplayRotationCoordinator();
|
||||
final DisplayContent defaultDisplayContent = mDisplayContent;
|
||||
final DisplayRotation defaultDisplayRotation = defaultDisplayContent.getDisplayRotation();
|
||||
coordinator.removeDefaultDisplayRotationChangedCallback();
|
||||
|
||||
DeviceStateController deviceStateController = mock(DeviceStateController.class);
|
||||
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
|
||||
.thenReturn(true);
|
||||
|
||||
// Create secondary display
|
||||
final DisplayContent secondaryDisplayContent =
|
||||
createSecondaryDisplayContent(Display.TYPE_INTERNAL, deviceStateController);
|
||||
final DisplayRotation secondaryDisplayRotation =
|
||||
secondaryDisplayContent.getDisplayRotation();
|
||||
try {
|
||||
// TestDisplayContent bypasses this method but we need it for this test
|
||||
doCallRealMethod().when(secondaryDisplayRotation).updateRotationUnchecked(anyBoolean());
|
||||
|
||||
// TestDisplayContent creates this as a mock. Lets set it up to test our use case.
|
||||
when(secondaryDisplayContent.mDeviceStateController
|
||||
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()).thenReturn(
|
||||
true);
|
||||
|
||||
// Check that secondary display registered callback
|
||||
assertEquals(secondaryDisplayRotation.mDefaultDisplayRotationChangedCallback,
|
||||
coordinator.mDefaultDisplayRotationChangedCallback);
|
||||
|
||||
// Set the default display to a known orientation. This may be a zero or non-zero
|
||||
// rotation since mDisplayInfo.logicalWidth/Height depends on the DUT's default display
|
||||
defaultDisplayRotation.updateOrientation(SCREEN_ORIENTATION_PORTRAIT, false);
|
||||
assertEquals(defaultDisplayRotation.mPortraitRotation,
|
||||
defaultDisplayRotation.getRotation());
|
||||
assertEquals(defaultDisplayRotation.mPortraitRotation,
|
||||
coordinator.getDefaultDisplayCurrentRotation());
|
||||
|
||||
// Check that in the initial state, the secondary display is in the right rotation
|
||||
assertRotationsAreCorrectlyReversed(defaultDisplayRotation.getRotation(),
|
||||
secondaryDisplayRotation.getRotation());
|
||||
|
||||
// Update primary display rotation, check display coordinator rotation is the default
|
||||
// display's landscape rotation, and that the secondary display rotation is correct.
|
||||
defaultDisplayRotation.updateOrientation(SCREEN_ORIENTATION_LANDSCAPE, false);
|
||||
assertEquals(defaultDisplayRotation.mLandscapeRotation,
|
||||
defaultDisplayRotation.getRotation());
|
||||
assertEquals(defaultDisplayRotation.mLandscapeRotation,
|
||||
coordinator.getDefaultDisplayCurrentRotation());
|
||||
assertRotationsAreCorrectlyReversed(defaultDisplayRotation.getRotation(),
|
||||
secondaryDisplayRotation.getRotation());
|
||||
} finally {
|
||||
secondaryDisplayRotation.removeDefaultDisplayRotationChangedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondaryNonInternalDisplayDoesNotFollowDefaultDisplay() {
|
||||
// Skip freezing so the unrelated conditions in updateRotationUnchecked won't disturb.
|
||||
doNothing().when(mWm).startFreezingDisplay(anyInt(), anyInt(), any(), anyInt());
|
||||
|
||||
final DisplayRotationCoordinator coordinator =
|
||||
mRootWindowContainer.getDisplayRotationCoordinator();
|
||||
coordinator.removeDefaultDisplayRotationChangedCallback();
|
||||
|
||||
DeviceStateController deviceStateController = mock(DeviceStateController.class);
|
||||
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
|
||||
.thenReturn(true);
|
||||
|
||||
// Create secondary non-internal displays
|
||||
createSecondaryDisplayContent(Display.TYPE_EXTERNAL, deviceStateController);
|
||||
assertNull(coordinator.mDefaultDisplayRotationChangedCallback);
|
||||
createSecondaryDisplayContent(Display.TYPE_VIRTUAL, deviceStateController);
|
||||
assertNull(coordinator.mDefaultDisplayRotationChangedCallback);
|
||||
}
|
||||
|
||||
private DisplayContent createSecondaryDisplayContent(int displayType,
|
||||
@NonNull DeviceStateController deviceStateController) {
|
||||
final DisplayInfo secondaryDisplayInfo = new DisplayInfo();
|
||||
secondaryDisplayInfo.copyFrom(mDisplayInfo);
|
||||
secondaryDisplayInfo.type = displayType;
|
||||
|
||||
return new TestDisplayContent.Builder(mAtm, secondaryDisplayInfo)
|
||||
.setDeviceStateController(deviceStateController)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static void assertRotationsAreCorrectlyReversed(@Surface.Rotation int rotation1,
|
||||
@Surface.Rotation int rotation2) {
|
||||
if (rotation1 == ROTATION_0) {
|
||||
assertEquals(rotation1, rotation2);
|
||||
} else if (rotation1 == ROTATION_180) {
|
||||
assertEquals(rotation1, rotation2);
|
||||
} else if (rotation1 == ROTATION_90) {
|
||||
assertEquals(ROTATION_270, rotation2);
|
||||
} else if (rotation1 == ROTATION_270) {
|
||||
assertEquals(ROTATION_90, rotation2);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown rotation: " + rotation1 + ", " + rotation2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoteRotation() {
|
||||
final DisplayContent dc = mDisplayContent;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.view.Surface;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test class for {@link DisplayRotationCoordinator}
|
||||
*
|
||||
* Build/Install/Run:
|
||||
* atest DisplayRotationCoordinatorTests
|
||||
*/
|
||||
@SmallTest
|
||||
@Presubmit
|
||||
public class DisplayRotationCoordinatorTests {
|
||||
|
||||
@NonNull
|
||||
private final DisplayRotationCoordinator mCoordinator = new DisplayRotationCoordinator();
|
||||
|
||||
@Test
|
||||
public void testDefaultDisplayRotationChangedWhenNoCallbackRegistered() {
|
||||
// Does not cause NPE
|
||||
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
|
||||
}
|
||||
|
||||
@Test (expected = UnsupportedOperationException.class)
|
||||
public void testSecondRegistrationWithoutRemovingFirst() {
|
||||
Runnable callback1 = mock(Runnable.class);
|
||||
Runnable callback2 = mock(Runnable.class);
|
||||
mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
|
||||
mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
|
||||
assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecondRegistrationAfterRemovingFirst() {
|
||||
Runnable callback1 = mock(Runnable.class);
|
||||
mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
|
||||
mCoordinator.removeDefaultDisplayRotationChangedCallback();
|
||||
|
||||
Runnable callback2 = mock(Runnable.class);
|
||||
mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
|
||||
|
||||
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
|
||||
verify(callback2).run();
|
||||
verify(callback1, never()).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegisterThenDefaultDisplayRotationChanged() {
|
||||
Runnable callback = mock(Runnable.class);
|
||||
mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
|
||||
assertEquals(Surface.ROTATION_0, mCoordinator.getDefaultDisplayCurrentRotation());
|
||||
verify(callback, never()).run();
|
||||
|
||||
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
|
||||
verify(callback).run();
|
||||
assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultDisplayRotationChangedThenRegister() {
|
||||
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
|
||||
Runnable callback = mock(Runnable.class);
|
||||
mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
|
||||
verify(callback).run();
|
||||
assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
|
||||
}
|
||||
}
|
||||
@@ -1135,7 +1135,7 @@ public class DisplayRotationTests {
|
||||
mDeviceStateController = mock(DeviceStateController.class);
|
||||
mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
|
||||
mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object(),
|
||||
mDeviceStateController) {
|
||||
mDeviceStateController, mock(DisplayRotationCoordinator.class)) {
|
||||
@Override
|
||||
DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
|
||||
WindowManagerService service, DisplayContent displayContent) {
|
||||
|
||||
@@ -552,7 +552,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
|
||||
/** Please use the {@link Builder} to create. */
|
||||
DualDisplayContent(RootWindowContainer rootWindowContainer,
|
||||
Display display) {
|
||||
super(rootWindowContainer, display);
|
||||
super(rootWindowContainer, display, mock(DeviceStateController.class));
|
||||
|
||||
mFirstRoot = getGroupRoot(FEATURE_FIRST_ROOT);
|
||||
mSecondRoot = getGroupRoot(FEATURE_SECOND_ROOT);
|
||||
|
||||
@@ -27,8 +27,10 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
@@ -51,8 +53,9 @@ class TestDisplayContent extends DisplayContent {
|
||||
public static final int DEFAULT_LOGICAL_DISPLAY_DENSITY = 300;
|
||||
|
||||
/** Please use the {@link Builder} to create, visible for use in test builder overrides only. */
|
||||
TestDisplayContent(RootWindowContainer rootWindowContainer, Display display) {
|
||||
super(display, rootWindowContainer);
|
||||
TestDisplayContent(RootWindowContainer rootWindowContainer, Display display,
|
||||
@NonNull DeviceStateController deviceStateController) {
|
||||
super(display, rootWindowContainer, deviceStateController);
|
||||
// Normally this comes from display-properties as exposed by WM. Without that, just
|
||||
// hard-code to FULLSCREEN for tests.
|
||||
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
|
||||
@@ -97,6 +100,8 @@ class TestDisplayContent extends DisplayContent {
|
||||
private int mStatusBarHeight = 0;
|
||||
private SettingsEntry mOverrideSettings;
|
||||
private DisplayMetrics mDisplayMetrics;
|
||||
@NonNull
|
||||
private DeviceStateController mDeviceStateController = mock(DeviceStateController.class);
|
||||
@Mock
|
||||
Context mMockContext;
|
||||
@Mock
|
||||
@@ -198,8 +203,13 @@ class TestDisplayContent extends DisplayContent {
|
||||
com.android.internal.R.dimen.default_minimal_size_resizable_task);
|
||||
return this;
|
||||
}
|
||||
Builder setDeviceStateController(@NonNull DeviceStateController deviceStateController) {
|
||||
mDeviceStateController = deviceStateController;
|
||||
return this;
|
||||
}
|
||||
TestDisplayContent createInternal(Display display) {
|
||||
return new TestDisplayContent(mService.mRootWindowContainer, display);
|
||||
return new TestDisplayContent(mService.mRootWindowContainer, display,
|
||||
mDeviceStateController);
|
||||
}
|
||||
TestDisplayContent build() {
|
||||
SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
|
||||
|
||||
Reference in New Issue
Block a user