Loading core/java/android/rotationresolver/RotationResolverInternal.java +1 −3 Original line number Diff line number Diff line Loading @@ -46,7 +46,6 @@ public abstract class RotationResolverInternal { * error is captured. {@link RotationResolverCallbackInternal} * @param proposedRotation the screen rotation that is proposed by the system. * @param currentRotation the current screen rotation. * @param packageName the package name of the current activity that is running in foreground. * @param timeoutMillis the timeout in millisecond for the query. If the query doesn't get * fulfilled within this amount of time. It will be discarded and the * callback will receive a failure result code {@link Loading @@ -55,8 +54,7 @@ public abstract class RotationResolverInternal { */ public abstract void resolveRotation(@NonNull RotationResolverCallbackInternal callback, @Surface.Rotation int proposedRotation, @Surface.Rotation int currentRotation, String packageName, @DurationMillisLong long timeoutMillis, @NonNull CancellationSignal cancellationSignal); @DurationMillisLong long timeoutMillis, @NonNull CancellationSignal cancellationSignal); /** * Internal interfaces for the rotation resolver callback. Loading services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java +3 −2 Original line number Diff line number Diff line Loading @@ -150,7 +150,7 @@ public class RotationResolverManagerService extends @Override public void resolveRotation( @NonNull RotationResolverCallbackInternal callbackInternal, int proposedRotation, int currentRotation, String packageName, long timeout, int currentRotation, long timeout, @NonNull CancellationSignal cancellationSignalInternal) { Objects.requireNonNull(callbackInternal); Objects.requireNonNull(cancellationSignalInternal); Loading @@ -159,7 +159,8 @@ public class RotationResolverManagerService extends final RotationResolverManagerPerUserService service = getServiceForUserLocked( UserHandle.getCallingUserId()); service.resolveRotationLocked(callbackInternal, proposedRotation, currentRotation, packageName, timeout, cancellationSignalInternal); currentRotation, /* packageName */ "", timeout, cancellationSignalInternal); } else { Slog.w(TAG, "Rotation Resolver service is disabled."); callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED); Loading services/core/java/com/android/server/wm/DisplayRotation.java +15 −0 Original line number Diff line number Diff line Loading @@ -1498,6 +1498,21 @@ public class DisplayRotation { } } @Override public boolean canUseRotationResolver() { if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED) return false; switch (mCurrentAppOrientation) { case ActivityInfo.SCREEN_ORIENTATION_FULL_USER: case ActivityInfo.SCREEN_ORIENTATION_USER: case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED: case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE: case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT: return true; } return false; } @Override public void onProposedRotationChanged(int rotation) { ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation); Loading services/core/java/com/android/server/wm/WindowOrientationListener.java +113 −9 Original line number Diff line number Diff line Loading @@ -16,25 +16,36 @@ package com.android.server.wm; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; import static com.android.server.wm.WindowOrientationListenerProto.ENABLED; import static com.android.server.wm.WindowOrientationListenerProto.ROTATION; import android.app.ActivityThread; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.CancellationSignal; import android.os.Handler; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.rotationresolver.RotationResolverInternal; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import java.io.PrintWriter; import java.util.List; import java.util.Set; /** * A special helper class used by the WindowManager Loading @@ -55,6 +66,9 @@ public abstract class WindowOrientationListener { private static final boolean USE_GRAVITY_SENSOR = false; private static final int DEFAULT_BATCH_LATENCY = 100000; private static final int DEFAULT_ROTATION_RESOLVER_ENABLED = 0; // disabled private static final String KEY_ROTATION_RESOLVER_TIMEOUT = "rotation_resolver_timeout_millis"; private static final long DEFAULT_ROTATION_RESOLVER_TIMEOUT_MILLIS = 700L; private Handler mHandler; private SensorManager mSensorManager; Loading @@ -62,7 +76,13 @@ public abstract class WindowOrientationListener { private int mRate; private String mSensorType; private Sensor mSensor; private OrientationJudge mOrientationJudge; @VisibleForTesting OrientationJudge mOrientationJudge; @VisibleForTesting RotationResolverInternal mRotationResolverService; private int mCurrentRotation = -1; private final Context mContext; private final WindowManagerConstants mConstants; Loading Loading @@ -255,6 +275,32 @@ public abstract class WindowOrientationListener { } } /** * Returns true if the current status of the phone is suitable for using rotation resolver * service. * * To reduce the power consumption of rotation resolver service, rotation query should run less * frequently than other low power orientation sensors. This method is used to check whether * the current status of the phone is necessary to request a suggested screen rotation from the * rotation resolver service. Note that it always returns {@code false} in the base class. It * should be overridden in the derived classes. */ public boolean canUseRotationResolver() { return false; } /** * Returns true if the rotation resolver feature is enabled by setting. It means {@link * WindowOrientationListener} will then ask {@link RotationResolverInternal} for the appropriate * screen rotation. */ @VisibleForTesting boolean isRotationResolverEnabled() { return Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.CAMERA_AUTOROTATE, DEFAULT_ROTATION_RESOLVER_ENABLED, UserHandle.USER_CURRENT) == 1; } /** * Called when the rotation view of the device has changed. * Loading Loading @@ -1045,6 +1091,30 @@ public abstract class WindowOrientationListener { private int mProposedRotation = -1; private int mDesiredRotation = -1; private boolean mRotationEvaluationScheduled; private long mRotationResolverTimeoutMillis; OrientationSensorJudge() { super(); setupRotationResolverParameters(); } private void setupRotationResolverParameters() { DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_WINDOW_MANAGER, ActivityThread.currentApplication().getMainExecutor(), (properties) -> { final Set<String> keys = properties.getKeyset(); if (keys.contains(KEY_ROTATION_RESOLVER_TIMEOUT)) { readRotationResolverParameters(); } }); readRotationResolverParameters(); } private void readRotationResolverParameters() { mRotationResolverTimeoutMillis = DeviceConfig.getLong( DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ROTATION_RESOLVER_TIMEOUT, DEFAULT_ROTATION_RESOLVER_TIMEOUT_MILLIS); } @Override public int getProposedRotationLocked() { Loading @@ -1069,19 +1139,13 @@ public abstract class WindowOrientationListener { @Override public void onSensorChanged(SensorEvent event) { int newRotation; int reportedRotation = (int) event.values[0]; if (reportedRotation < 0 || reportedRotation > 3) { return; } synchronized (mLock) { mDesiredRotation = reportedRotation; newRotation = evaluateRotationChangeLocked(); } if (newRotation >= 0) { onProposedRotationChanged(newRotation); // Log raw sensor rotation. if (evaluateRotationChangeLocked() >= 0) { if (mConstants.mRawSensorLoggingEnabled) { FrameworkStatsLog.write( FrameworkStatsLog.DEVICE_ROTATED, Loading @@ -1089,6 +1153,35 @@ public abstract class WindowOrientationListener { rotationToLogEnum(reportedRotation)); } } if (isRotationResolverEnabled() && canUseRotationResolver()) { if (mRotationResolverService == null) { mRotationResolverService = LocalServices.getService( RotationResolverInternal.class); } final CancellationSignal cancellationSignal = new CancellationSignal(); mRotationResolverService.resolveRotation( new RotationResolverInternal.RotationResolverCallbackInternal() { @Override public void onSuccess(int result) { finalizeRotation(result); } @Override public void onFailure(int error) { finalizeRotation(reportedRotation); } }, reportedRotation, mCurrentRotation, mRotationResolverTimeoutMillis, cancellationSignal); getHandler().postDelayed(cancellationSignal::cancel, mRotationResolverTimeoutMillis); } else { finalizeRotation(reportedRotation); } } @Override Loading Loading @@ -1131,6 +1224,17 @@ public abstract class WindowOrientationListener { return -1; } private void finalizeRotation(int reportedRotation) { int newRotation; synchronized (mLock) { mDesiredRotation = reportedRotation; newRotation = evaluateRotationChangeLocked(); } if (newRotation >= 0) { onProposedRotationChanged(newRotation); } } private boolean isDesiredRotationAcceptableLocked(long now) { if (mTouching) { return false; Loading services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; import android.hardware.input.InputSensorInfo; import android.os.CancellationSignal; import android.os.Handler; import android.rotationresolver.RotationResolverInternal; import android.view.Surface; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** * Tests for {@link com.android.server.wm.WindowOrientationListener} */ public class WindowOrientationListenerTest { @Mock private Context mMockContext; @Mock private Handler mMockHandler; @Mock private InputSensorInfo mMockInputSensorInfo; @Mock private SensorManager mMockSensorManager; @Mock private WindowManagerService mMockWindowManagerService; private TestableRotationResolver mFakeRotationResolverInternal; private com.android.server.wm.WindowOrientationListener mWindowOrientationListener; private int mFinalizedRotation; private boolean mRotationResolverEnabled; private boolean mCanUseRotationResolver; private SensorEvent mFakeSensorEvent; private Sensor mFakeSensor; @Before public void setUp() { MockitoAnnotations.initMocks(this); mRotationResolverEnabled = true; mCanUseRotationResolver = true; mFakeRotationResolverInternal = new TestableRotationResolver(); doReturn(mMockSensorManager).when(mMockContext).getSystemService(Context.SENSOR_SERVICE); mWindowOrientationListener = new TestableWindowOrientationListener(mMockContext, mMockHandler, mMockWindowManagerService); mWindowOrientationListener.mRotationResolverService = mFakeRotationResolverInternal; mFakeSensor = new Sensor(mMockInputSensorInfo); mFakeSensorEvent = new SensorEvent(mFakeSensor, /* accuracy */ 1, /* timestamp */ 1L, new float[]{(float) Surface.ROTATION_90}); } @Test public void testOnSensorChanged_rotationResolverDisabled_useSensorResult() { mRotationResolverEnabled = false; mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent); assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_90); } @Test public void testOnSensorChanged_cannotUseRotationResolver_useSensorResult() { mCanUseRotationResolver = false; mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent); assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_90); } @Test public void testOnSensorChanged_normalCase() { mFakeRotationResolverInternal.mResult = Surface.ROTATION_180; mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent); assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_180); } final class TestableRotationResolver extends RotationResolverInternal { @Surface.Rotation int mResult; @Override public boolean isRotationResolverSupported() { return true; } @Override public void resolveRotation(@NonNull RotationResolverCallbackInternal callback, @Surface.Rotation int proposedRotation, @Surface.Rotation int currentRotation, @DurationMillisLong long timeoutMillis, @NonNull CancellationSignal cancellationSignal) { callback.onSuccess(mResult); } } final class TestableWindowOrientationListener extends WindowOrientationListener { TestableWindowOrientationListener(Context context, Handler handler, WindowManagerService service) { super(context, handler, service); this.mOrientationJudge = new OrientationSensorJudge(); } @Override public void onProposedRotationChanged(int rotation) { mFinalizedRotation = rotation; } @Override public boolean canUseRotationResolver() { return mCanUseRotationResolver; } @Override public boolean isRotationResolverEnabled() { return mRotationResolverEnabled; } } } Loading
core/java/android/rotationresolver/RotationResolverInternal.java +1 −3 Original line number Diff line number Diff line Loading @@ -46,7 +46,6 @@ public abstract class RotationResolverInternal { * error is captured. {@link RotationResolverCallbackInternal} * @param proposedRotation the screen rotation that is proposed by the system. * @param currentRotation the current screen rotation. * @param packageName the package name of the current activity that is running in foreground. * @param timeoutMillis the timeout in millisecond for the query. If the query doesn't get * fulfilled within this amount of time. It will be discarded and the * callback will receive a failure result code {@link Loading @@ -55,8 +54,7 @@ public abstract class RotationResolverInternal { */ public abstract void resolveRotation(@NonNull RotationResolverCallbackInternal callback, @Surface.Rotation int proposedRotation, @Surface.Rotation int currentRotation, String packageName, @DurationMillisLong long timeoutMillis, @NonNull CancellationSignal cancellationSignal); @DurationMillisLong long timeoutMillis, @NonNull CancellationSignal cancellationSignal); /** * Internal interfaces for the rotation resolver callback. Loading
services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java +3 −2 Original line number Diff line number Diff line Loading @@ -150,7 +150,7 @@ public class RotationResolverManagerService extends @Override public void resolveRotation( @NonNull RotationResolverCallbackInternal callbackInternal, int proposedRotation, int currentRotation, String packageName, long timeout, int currentRotation, long timeout, @NonNull CancellationSignal cancellationSignalInternal) { Objects.requireNonNull(callbackInternal); Objects.requireNonNull(cancellationSignalInternal); Loading @@ -159,7 +159,8 @@ public class RotationResolverManagerService extends final RotationResolverManagerPerUserService service = getServiceForUserLocked( UserHandle.getCallingUserId()); service.resolveRotationLocked(callbackInternal, proposedRotation, currentRotation, packageName, timeout, cancellationSignalInternal); currentRotation, /* packageName */ "", timeout, cancellationSignalInternal); } else { Slog.w(TAG, "Rotation Resolver service is disabled."); callbackInternal.onFailure(ROTATION_RESULT_FAILURE_CANCELLED); Loading
services/core/java/com/android/server/wm/DisplayRotation.java +15 −0 Original line number Diff line number Diff line Loading @@ -1498,6 +1498,21 @@ public class DisplayRotation { } } @Override public boolean canUseRotationResolver() { if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED) return false; switch (mCurrentAppOrientation) { case ActivityInfo.SCREEN_ORIENTATION_FULL_USER: case ActivityInfo.SCREEN_ORIENTATION_USER: case ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED: case ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE: case ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT: return true; } return false; } @Override public void onProposedRotationChanged(int rotation) { ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation); Loading
services/core/java/com/android/server/wm/WindowOrientationListener.java +113 −9 Original line number Diff line number Diff line Loading @@ -16,25 +16,36 @@ package com.android.server.wm; import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER; import static com.android.server.wm.WindowOrientationListenerProto.ENABLED; import static com.android.server.wm.WindowOrientationListenerProto.ROTATION; import android.app.ActivityThread; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.CancellationSignal; import android.os.Handler; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.rotationresolver.RotationResolverInternal; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import com.android.server.LocalServices; import java.io.PrintWriter; import java.util.List; import java.util.Set; /** * A special helper class used by the WindowManager Loading @@ -55,6 +66,9 @@ public abstract class WindowOrientationListener { private static final boolean USE_GRAVITY_SENSOR = false; private static final int DEFAULT_BATCH_LATENCY = 100000; private static final int DEFAULT_ROTATION_RESOLVER_ENABLED = 0; // disabled private static final String KEY_ROTATION_RESOLVER_TIMEOUT = "rotation_resolver_timeout_millis"; private static final long DEFAULT_ROTATION_RESOLVER_TIMEOUT_MILLIS = 700L; private Handler mHandler; private SensorManager mSensorManager; Loading @@ -62,7 +76,13 @@ public abstract class WindowOrientationListener { private int mRate; private String mSensorType; private Sensor mSensor; private OrientationJudge mOrientationJudge; @VisibleForTesting OrientationJudge mOrientationJudge; @VisibleForTesting RotationResolverInternal mRotationResolverService; private int mCurrentRotation = -1; private final Context mContext; private final WindowManagerConstants mConstants; Loading Loading @@ -255,6 +275,32 @@ public abstract class WindowOrientationListener { } } /** * Returns true if the current status of the phone is suitable for using rotation resolver * service. * * To reduce the power consumption of rotation resolver service, rotation query should run less * frequently than other low power orientation sensors. This method is used to check whether * the current status of the phone is necessary to request a suggested screen rotation from the * rotation resolver service. Note that it always returns {@code false} in the base class. It * should be overridden in the derived classes. */ public boolean canUseRotationResolver() { return false; } /** * Returns true if the rotation resolver feature is enabled by setting. It means {@link * WindowOrientationListener} will then ask {@link RotationResolverInternal} for the appropriate * screen rotation. */ @VisibleForTesting boolean isRotationResolverEnabled() { return Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.CAMERA_AUTOROTATE, DEFAULT_ROTATION_RESOLVER_ENABLED, UserHandle.USER_CURRENT) == 1; } /** * Called when the rotation view of the device has changed. * Loading Loading @@ -1045,6 +1091,30 @@ public abstract class WindowOrientationListener { private int mProposedRotation = -1; private int mDesiredRotation = -1; private boolean mRotationEvaluationScheduled; private long mRotationResolverTimeoutMillis; OrientationSensorJudge() { super(); setupRotationResolverParameters(); } private void setupRotationResolverParameters() { DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_WINDOW_MANAGER, ActivityThread.currentApplication().getMainExecutor(), (properties) -> { final Set<String> keys = properties.getKeyset(); if (keys.contains(KEY_ROTATION_RESOLVER_TIMEOUT)) { readRotationResolverParameters(); } }); readRotationResolverParameters(); } private void readRotationResolverParameters() { mRotationResolverTimeoutMillis = DeviceConfig.getLong( DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ROTATION_RESOLVER_TIMEOUT, DEFAULT_ROTATION_RESOLVER_TIMEOUT_MILLIS); } @Override public int getProposedRotationLocked() { Loading @@ -1069,19 +1139,13 @@ public abstract class WindowOrientationListener { @Override public void onSensorChanged(SensorEvent event) { int newRotation; int reportedRotation = (int) event.values[0]; if (reportedRotation < 0 || reportedRotation > 3) { return; } synchronized (mLock) { mDesiredRotation = reportedRotation; newRotation = evaluateRotationChangeLocked(); } if (newRotation >= 0) { onProposedRotationChanged(newRotation); // Log raw sensor rotation. if (evaluateRotationChangeLocked() >= 0) { if (mConstants.mRawSensorLoggingEnabled) { FrameworkStatsLog.write( FrameworkStatsLog.DEVICE_ROTATED, Loading @@ -1089,6 +1153,35 @@ public abstract class WindowOrientationListener { rotationToLogEnum(reportedRotation)); } } if (isRotationResolverEnabled() && canUseRotationResolver()) { if (mRotationResolverService == null) { mRotationResolverService = LocalServices.getService( RotationResolverInternal.class); } final CancellationSignal cancellationSignal = new CancellationSignal(); mRotationResolverService.resolveRotation( new RotationResolverInternal.RotationResolverCallbackInternal() { @Override public void onSuccess(int result) { finalizeRotation(result); } @Override public void onFailure(int error) { finalizeRotation(reportedRotation); } }, reportedRotation, mCurrentRotation, mRotationResolverTimeoutMillis, cancellationSignal); getHandler().postDelayed(cancellationSignal::cancel, mRotationResolverTimeoutMillis); } else { finalizeRotation(reportedRotation); } } @Override Loading Loading @@ -1131,6 +1224,17 @@ public abstract class WindowOrientationListener { return -1; } private void finalizeRotation(int reportedRotation) { int newRotation; synchronized (mLock) { mDesiredRotation = reportedRotation; newRotation = evaluateRotationChangeLocked(); } if (newRotation >= 0) { onProposedRotationChanged(newRotation); } } private boolean isDesiredRotationAcceptableLocked(long now) { if (mTouching) { return false; Loading
services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java 0 → 100644 +151 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import android.annotation.DurationMillisLong; import android.annotation.NonNull; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorManager; import android.hardware.input.InputSensorInfo; import android.os.CancellationSignal; import android.os.Handler; import android.rotationresolver.RotationResolverInternal; import android.view.Surface; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** * Tests for {@link com.android.server.wm.WindowOrientationListener} */ public class WindowOrientationListenerTest { @Mock private Context mMockContext; @Mock private Handler mMockHandler; @Mock private InputSensorInfo mMockInputSensorInfo; @Mock private SensorManager mMockSensorManager; @Mock private WindowManagerService mMockWindowManagerService; private TestableRotationResolver mFakeRotationResolverInternal; private com.android.server.wm.WindowOrientationListener mWindowOrientationListener; private int mFinalizedRotation; private boolean mRotationResolverEnabled; private boolean mCanUseRotationResolver; private SensorEvent mFakeSensorEvent; private Sensor mFakeSensor; @Before public void setUp() { MockitoAnnotations.initMocks(this); mRotationResolverEnabled = true; mCanUseRotationResolver = true; mFakeRotationResolverInternal = new TestableRotationResolver(); doReturn(mMockSensorManager).when(mMockContext).getSystemService(Context.SENSOR_SERVICE); mWindowOrientationListener = new TestableWindowOrientationListener(mMockContext, mMockHandler, mMockWindowManagerService); mWindowOrientationListener.mRotationResolverService = mFakeRotationResolverInternal; mFakeSensor = new Sensor(mMockInputSensorInfo); mFakeSensorEvent = new SensorEvent(mFakeSensor, /* accuracy */ 1, /* timestamp */ 1L, new float[]{(float) Surface.ROTATION_90}); } @Test public void testOnSensorChanged_rotationResolverDisabled_useSensorResult() { mRotationResolverEnabled = false; mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent); assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_90); } @Test public void testOnSensorChanged_cannotUseRotationResolver_useSensorResult() { mCanUseRotationResolver = false; mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent); assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_90); } @Test public void testOnSensorChanged_normalCase() { mFakeRotationResolverInternal.mResult = Surface.ROTATION_180; mWindowOrientationListener.mOrientationJudge.onSensorChanged(mFakeSensorEvent); assertThat(mFinalizedRotation).isEqualTo(Surface.ROTATION_180); } final class TestableRotationResolver extends RotationResolverInternal { @Surface.Rotation int mResult; @Override public boolean isRotationResolverSupported() { return true; } @Override public void resolveRotation(@NonNull RotationResolverCallbackInternal callback, @Surface.Rotation int proposedRotation, @Surface.Rotation int currentRotation, @DurationMillisLong long timeoutMillis, @NonNull CancellationSignal cancellationSignal) { callback.onSuccess(mResult); } } final class TestableWindowOrientationListener extends WindowOrientationListener { TestableWindowOrientationListener(Context context, Handler handler, WindowManagerService service) { super(context, handler, service); this.mOrientationJudge = new OrientationSensorJudge(); } @Override public void onProposedRotationChanged(int rotation) { mFinalizedRotation = rotation; } @Override public boolean canUseRotationResolver() { return mCanUseRotationResolver; } @Override public boolean isRotationResolverEnabled() { return mRotationResolverEnabled; } } }