Gets suggested screen rotations from RotationResolverService.

Bug: 162359770
Bug: 172857585
Test: atest WindowOrientationListenerTest
Change-Id: Ic8fc23b5a91b772df814d5893af15cd0e8c5d360
This commit is contained in:
Yi Jiang
2020-11-30 13:59:03 -08:00
parent 8bdb6c350d
commit 10d2031e1b
5 changed files with 283 additions and 14 deletions

View File

@@ -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
@@ -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.

View File

@@ -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);
@@ -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);

View File

@@ -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);

View File

@@ -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
@@ -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;
@@ -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;
@@ -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.
*
@@ -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() {
@@ -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,
@@ -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
@@ -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;

View File

@@ -0,0 +1,151 @@
/*
* 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;
}
}
}