Force Kids app to use LANDSCAPE when in REVERSE_LANDSCAPE

A few apps in Kids Space request "reverseLandscape" orientation when
Display#getRotation returns ROTATION_270 expecting it to correspond
to the seascape display orientation while it may correspond to the
landscape one when config_reverseDefaultRotation is set to true.

This CL overrides the "reverseLandscape" orientation with "landscape"
in the context of apps running in the Kids space when
config_reverseDefaultRotation is set to true

Fixes: 265589619
Test: Run `atest WmTests:WindowManagerServiceTests`
      Run `atest WMShellUnitTests:KidsModeTaskOrganizerTest`
      Run `atest WmTests:LetterboxUiControllerTest`

Change-Id: I85688413571478f5acaa340624bb470f5aeb422f
This commit is contained in:
Massimo Carli
2023-03-06 15:45:02 +00:00
parent 05557900e4
commit 65ffa6db92
9 changed files with 134 additions and 25 deletions

View File

@@ -73,11 +73,17 @@ interface ITaskOrganizerController {
/**
* Controls whether ignore orientation request logic in {@link
* com.android.server.wm.DisplayArea} is disabled at runtime.
* com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
* requested orientations to others.
*
* @param isDisabled when {@code true}, the system always ignores the value of {@link
* com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
* requested orientation is respected.
* @param fromOrientations The orientations we want to map to the correspondent orientations
* in toOrientation.
* @param toOrientations The orientations we map to the ones in fromOrientations at the same
* index
*/
void setIsIgnoreOrientationRequestDisabled(boolean isDisabled);
void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
in int[] fromOrientations, in int[] toOrientations);
}

View File

@@ -270,17 +270,24 @@ public class TaskOrganizer extends WindowOrganizer {
/**
* Controls whether ignore orientation request logic in {@link
* com.android.server.wm.DisplayArea} is disabled at runtime.
* com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some
* requested orientation to others.
*
* @param isDisabled when {@code true}, the system always ignores the value of {@link
* com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app
* requested orientation is respected.
* @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the
* value of {@link com.android.server.wm.DisplayArea#getIgnoreOrientationRequest}
* and app requested orientation is respected.
* @param fromOrientations The orientations we want to map to the correspondent orientations
* in toOrientation.
* @param toOrientations The orientations we map to the ones in fromOrientations at the same
* index
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
@Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
try {
mTaskOrganizerController.setIsIgnoreOrientationRequestDisabled(isDisabled);
mTaskOrganizerController.setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
fromOrientations, toOrientations);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -21,6 +21,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.app.ActivityManager;
@@ -33,6 +35,7 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.view.Display;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
@@ -43,6 +46,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -80,6 +84,12 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
/**
* The value of the {@link R.bool.config_reverseDefaultRotation} property which defines how
* {@link Display#getRotation} values are mapped to screen orientations
*/
private final boolean mReverseDefaultRotationEnabled;
@VisibleForTesting
ActivityManager.RunningTaskInfo mLaunchRootTask;
@VisibleForTesting
@@ -168,6 +178,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mDisplayInsetsController = displayInsetsController;
mKidsModeSettingsObserver = kidsModeSettingsObserver;
shellInit.addInitCallback(this::onInit, this);
mReverseDefaultRotationEnabled = context.getResources().getBoolean(
R.bool.config_reverseDefaultRotation);
}
public KidsModeTaskOrganizer(
@@ -191,6 +203,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
shellInit.addInitCallback(this::onInit, this);
mReverseDefaultRotationEnabled = context.getResources().getBoolean(
R.bool.config_reverseDefaultRotation);
}
/**
@@ -269,7 +283,14 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
// Needed since many Kids apps aren't optimised to support both orientations and it will be
// hard for kids to understand the app compat mode.
// TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible.
setIsIgnoreOrientationRequestDisabled(true);
if (mReverseDefaultRotationEnabled) {
setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
/* fromOrientations */ new int[]{SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
/* toOrientations */ new int[]{SCREEN_ORIENTATION_LANDSCAPE});
} else {
setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
/* fromOrientations */ null, /* toOrientations */ null);
}
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
if (displayLayout != null) {
mDisplayWidth = displayLayout.width();
@@ -290,7 +311,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
@VisibleForTesting
void disable() {
setIsIgnoreOrientationRequestDisabled(false);
setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ false,
/* fromOrientations */ null, /* toOrientations */ null);
mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
mOnInsetsChangedListener);
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);

View File

@@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -77,6 +78,7 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
@Mock private ShellInit mShellInit;
@Mock private ShellCommandHandler mShellCommandHandler;
@Mock private DisplayInsetsController mDisplayInsetsController;
@Mock private Resources mResources;
KidsModeTaskOrganizer mOrganizer;
@@ -89,10 +91,12 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
} catch (RemoteException e) {
}
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler,
mTaskOrganizerController, mSyncTransactionQueue, mDisplayController,
mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver,
mTestExecutor, mHandler));
doReturn(mResources).when(mContext).getResources();
final KidsModeTaskOrganizer kidsModeTaskOrganizer = new KidsModeTaskOrganizer(mContext,
mShellInit, mShellCommandHandler, mTaskOrganizerController, mSyncTransactionQueue,
mDisplayController, mDisplayInsetsController, Optional.empty(), Optional.empty(),
mObserver, mTestExecutor, mHandler);
mOrganizer = spy(kidsModeTaskOrganizer);
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
}
@@ -112,6 +116,8 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
verify(mOrganizer, times(1)).registerOrganizer();
verify(mOrganizer, times(1)).createRootTask(
eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie));
verify(mOrganizer, times(1))
.setOrientationRequestPolicy(eq(true), any(), any());
final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
@@ -132,10 +138,11 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase {
doReturn(false).when(mObserver).isEnabled();
mOrganizer.updateKidsModeState();
verify(mOrganizer, times(1)).disable();
verify(mOrganizer, times(1)).unregisterOrganizer();
verify(mOrganizer, times(1)).deleteRootTask(rootTask.token);
verify(mOrganizer, times(1))
.setOrientationRequestPolicy(eq(false), any(), any());
assertThat(mOrganizer.mLaunchRootLeash).isNull();
assertThat(mOrganizer.mLaunchRootTask).isNull();
}

View File

@@ -455,6 +455,10 @@ final class LetterboxUiController {
@ScreenOrientation
int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
// In some cases (e.g. Kids app) we need to map the candidate orientation to some other
// orientation.
candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate);
if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) {
return candidate;
}

View File

@@ -1106,12 +1106,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
@Override
public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
enforceTaskPermission("setIsIgnoreOrientationRequestDisabled()");
public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
@Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
enforceTaskPermission("setOrientationRequestPolicy()");
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
mService.mWindowManager.setIsIgnoreOrientationRequestDisabled(isDisabled);
mService.mWindowManager
.setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled,
fromOrientations, toOrientations);
}
} finally {
Binder.restoreCallingIdentity(origId);

View File

@@ -234,6 +234,7 @@ import android.util.MergedConfiguration;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
@@ -624,6 +625,13 @@ public class WindowManagerService extends IWindowManager.Stub
/** List of window currently causing non-system overlay windows to be hidden. */
private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>();
/**
* In some cases (e.g. when {@link R.bool.config_reverseDefaultRotation} has value
* {@value true}) we need to map some orientation to others. This {@link SparseIntArray}
* contains the relation between the source orientation and the one to use.
*/
private final SparseIntArray mOrientationMapping = new SparseIntArray();
final AccessibilityController mAccessibilityController;
private RecentsAnimationController mRecentsAnimationController;
@@ -4147,24 +4155,51 @@ public class WindowManagerService extends IWindowManager.Stub
/**
* Controls whether ignore orientation request logic in {@link DisplayArea} is disabled
* at runtime.
* at runtime and how to optionally map some requested orientations to others.
*
* <p>Note: this assumes that {@link #mGlobalLock} is held by the caller.
*
* @param isDisabled when {@code true}, the system always ignores the value of {@link
* DisplayArea#getIgnoreOrientationRequest} and app requested orientation is
* respected.
* @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the
* value of {@link DisplayArea#getIgnoreOrientationRequest} and app requested
* orientation is respected.
* @param fromOrientations The orientations we want to map to the correspondent orientations
* in toOrientation.
* @param toOrientations The orientations we map to the ones in fromOrientations at the same
* index
*/
void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) {
if (isDisabled == mIsIgnoreOrientationRequestDisabled) {
void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled,
@Nullable int[] fromOrientations, @Nullable int[] toOrientations) {
mOrientationMapping.clear();
if (fromOrientations != null && toOrientations != null
&& fromOrientations.length == toOrientations.length) {
for (int i = 0; i < fromOrientations.length; i++) {
mOrientationMapping.put(fromOrientations[i], toOrientations[i]);
}
}
if (isIgnoreOrientationRequestDisabled == mIsIgnoreOrientationRequestDisabled) {
return;
}
mIsIgnoreOrientationRequestDisabled = isDisabled;
mIsIgnoreOrientationRequestDisabled = isIgnoreOrientationRequestDisabled;
for (int i = mRoot.getChildCount() - 1; i >= 0; i--) {
mRoot.getChildAt(i).onIsIgnoreOrientationRequestDisabledChanged();
}
}
/**
* When {@link mIsIgnoreOrientationRequestDisabled} is {@value true} this method returns the
* orientation to use in place of the one in input. It returns the same requestedOrientation in
* input otherwise.
*
* @param requestedOrientation The orientation that can be mapped.
* @return The orientation to use in place of requestedOrientation.
*/
int mapOrientationRequest(int requestedOrientation) {
if (!mIsIgnoreOrientationRequestDisabled) {
return requestedOrientation;
}
return mOrientationMapping.get(requestedOrientation, requestedOrientation);
}
/**
* Whether the system ignores the value of {@link DisplayArea#getIgnoreOrientationRequest} and
* app requested orientation is respected.

View File

@@ -55,6 +55,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
import android.compat.testing.PlatformCompatChangeRule;
@@ -509,6 +510,16 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
// overrideOrientationIfNeeded
@Test
public void testOverrideOrientationIfNeeded_mapInvokedOnRequest() throws Exception {
mController = new LetterboxUiController(mWm, mActivity);
spyOn(mWm);
mController.overrideOrientationIfNeeded(SCREEN_ORIENTATION_PORTRAIT);
verify(mWm).mapOrientationRequest(SCREEN_ORIENTATION_PORTRAIT);
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()

View File

@@ -97,6 +97,20 @@ public class WindowManagerServiceTests extends WindowTestsBase {
PackageManager.FEATURE_AUTOMOTIVE);
}
@Test
public void testIsRequestedOrientationMapped() {
mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
/* fromOrientations */ new int[]{1}, /* toOrientations */ new int[]{2});
assertThat(mWm.mapOrientationRequest(1)).isEqualTo(2);
assertThat(mWm.mapOrientationRequest(3)).isEqualTo(3);
// Mapping disabled
mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ false,
/* fromOrientations */ null, /* toOrientations */ null);
assertThat(mWm.mapOrientationRequest(1)).isEqualTo(1);
assertThat(mWm.mapOrientationRequest(3)).isEqualTo(3);
}
@Test
public void testAddWindowToken() {
IBinder token = mock(IBinder.class);