Merge "Introduce separate rotation lock settings per device state." into sc-v2-dev

This commit is contained in:
Alex Florescu
2021-07-30 18:42:17 +00:00
committed by Android (Google) Code Review
12 changed files with 843 additions and 18 deletions

View File

@@ -10119,6 +10119,61 @@ public final class Settings {
@Readable @Readable
public static final String GAME_DASHBOARD_ALWAYS_ON = "game_dashboard_always_on"; public static final String GAME_DASHBOARD_ALWAYS_ON = "game_dashboard_always_on";
/**
* For this device state, no specific auto-rotation lock setting should be applied.
* If the user toggles the auto-rotate lock in this state, the setting will apply to the
* previously valid device state.
* @hide
*/
public static final int DEVICE_STATE_ROTATION_LOCK_IGNORED = 0;
/**
* For this device state, the setting for auto-rotation is locked.
* @hide
*/
public static final int DEVICE_STATE_ROTATION_LOCK_LOCKED = 1;
/**
* For this device state, the setting for auto-rotation is unlocked.
* @hide
*/
public static final int DEVICE_STATE_ROTATION_LOCK_UNLOCKED = 2;
/**
* The different settings that can be used as values with
* {@link #DEVICE_STATE_ROTATION_LOCK}.
* @hide
*/
@IntDef(prefix = {"DEVICE_STATE_ROTATION_LOCK_"}, value = {
DEVICE_STATE_ROTATION_LOCK_IGNORED,
DEVICE_STATE_ROTATION_LOCK_LOCKED,
DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
})
@Retention(RetentionPolicy.SOURCE)
@interface DeviceStateRotationLockSetting {
}
/**
* Rotation lock setting keyed on device state.
*
* This holds a serialized map using int keys that represent Device States and value of
* {@link DeviceStateRotationLockSetting} representing the rotation lock setting for that
* device state.
*
* Serialized as key0:value0:key1:value1:...:keyN:valueN.
*
* Example: "0:1:1:2:2:1"
* This example represents a map of:
* <ul>
* <li>0 -> DEVICE_STATE_ROTATION_LOCK_LOCKED</li>
* <li>1 -> DEVICE_STATE_ROTATION_LOCK_UNLOCKED</li>
* <li>2 -> DEVICE_STATE_ROTATION_LOCK_IGNORED</li>
* </ul>
*
* @hide
*/
public static final String DEVICE_STATE_ROTATION_LOCK =
"device_state_rotation_lock";
/** /**
* These entries are considered common between the personal and the managed profile, * These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them. * since the managed profile doesn't get to change them.

View File

@@ -666,6 +666,17 @@
display is powered on at the same time. --> display is powered on at the same time. -->
<bool name="config_supportsConcurrentInternalDisplays">true</bool> <bool name="config_supportsConcurrentInternalDisplays">true</bool>
<!-- Map of DeviceState to rotation lock setting. Each entry must be in the format
"key:value", for example: "0:1".
The keys are device states, and the values are one of
Settings.Secure.DeviceStateRotationLockSetting.
Any device state that doesn't have a default set here will be treated as
DEVICE_STATE_ROTATION_LOCK_IGNORED meaning it will not have its own rotation lock setting.
If this map is missing, the feature is disabled and only one global rotation lock setting
will apply, regardless of device state. -->
<string-array name="config_perDeviceStateRotationLockDefaults" />
<!-- Desk dock behavior --> <!-- Desk dock behavior -->
<!-- The number of degrees to rotate the display when the device is in a desk dock. <!-- The number of degrees to rotate the display when the device is in a desk dock.

View File

@@ -3838,6 +3838,8 @@
<java-symbol type="string" name="config_foldedArea" /> <java-symbol type="string" name="config_foldedArea" />
<java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" /> <java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
<java-symbol type="bool" name="config_unfoldTransitionEnabled" /> <java-symbol type="bool" name="config_unfoldTransitionEnabled" />
<java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" />
<java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" /> <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
<java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" /> <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />

View File

@@ -34,7 +34,9 @@ import static android.provider.settings.validators.SettingsValidators.TILE_LIST_
import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR;
import android.provider.Settings.Secure; import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.ArraySet;
import java.util.Map; import java.util.Map;
@@ -287,5 +289,32 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DEVICE_STATE_ROTATION_LOCK, value -> {
if (TextUtils.isEmpty(value)) {
return true;
}
String[] intValues = value.split(":");
if (intValues.length % 2 != 0) {
return false;
}
InclusiveIntegerRangeValidator enumValidator =
new InclusiveIntegerRangeValidator(
Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED,
Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
ArraySet<String> keys = new ArraySet<>();
for (int i = 0; i < intValues.length - 1; ) {
String entryKey = intValues[i++];
String entryValue = intValues[i++];
if (!NON_NEGATIVE_INTEGER_VALIDATOR.validate(entryKey)
|| !enumValidator.validate(entryValue)) {
return false;
}
// If the same device state key was specified more than once, this is invalid
if (!keys.add(entryKey)) {
return false;
}
}
return true;
});
} }
} }

View File

@@ -0,0 +1,239 @@
/*
* 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.systemui.statusbar.policy;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS;
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import javax.inject.Named;
/**
* Handles reading and writing of rotation lock settings per device state, as well as setting
* the rotation lock when device state changes.
**/
@SysUISingleton
public final class DeviceStateRotationLockSettingController implements Listenable,
RotationLockController.RotationLockControllerCallback {
private static final String TAG = "DSRotateLockSettingCon";
private static final String SEPARATOR_REGEX = ":";
private final SecureSettings mSecureSettings;
private final RotationPolicyWrapper mRotationPolicyWrapper;
private final DeviceStateManager mDeviceStateManager;
private final Executor mMainExecutor;
private final String[] mDeviceStateRotationLockDefaults;
private SparseIntArray mDeviceStateRotationLockSettings;
// TODO(b/183001527): Add API to query current device state and initialize this.
private int mDeviceState = -1;
@Nullable
private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
@Inject
public DeviceStateRotationLockSettingController(
SecureSettings secureSettings,
RotationPolicyWrapper rotationPolicyWrapper,
DeviceStateManager deviceStateManager,
@Main Executor executor,
@Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
) {
mSecureSettings = secureSettings;
mRotationPolicyWrapper = rotationPolicyWrapper;
mDeviceStateManager = deviceStateManager;
mMainExecutor = executor;
mDeviceStateRotationLockDefaults = deviceStateRotationLockDefaults;
}
/**
* Loads the settings from storage.
*/
public void initialize() {
String serializedSetting =
mSecureSettings.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
UserHandle.USER_CURRENT);
if (TextUtils.isEmpty(serializedSetting)) {
// No settings saved, we should load the defaults and persist them.
fallbackOnDefaults();
return;
}
String[] values = serializedSetting.split(SEPARATOR_REGEX);
if (values.length % 2 != 0) {
// Each entry should be a key/value pair, so this is corrupt.
Log.wtf(TAG, "Can't deserialize saved settings, falling back on defaults");
fallbackOnDefaults();
return;
}
mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2);
int key;
int value;
for (int i = 0; i < values.length - 1; ) {
try {
key = Integer.parseInt(values[i++]);
value = Integer.parseInt(values[i++]);
mDeviceStateRotationLockSettings.put(key, value);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error deserializing one of the saved settings", e);
fallbackOnDefaults();
return;
}
}
}
private void fallbackOnDefaults() {
loadDefaults();
persistSettings();
}
@Override
public void setListening(boolean listening) {
if (listening) {
// Note that this is called once with the initial state of the device, even if there
// is no user action.
mDeviceStateCallback = this::updateDeviceState;
mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
} else {
if (mDeviceStateCallback != null) {
mDeviceStateManager.unregisterCallback(mDeviceStateCallback);
}
}
}
@Override
public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
if (mDeviceState == -1) {
Log.wtf(TAG, "Device state was not initialized.");
return;
}
if (rotationLocked == isRotationLockedForCurrentState()) {
Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
return;
}
saveNewRotationLockSetting(rotationLocked);
}
private void saveNewRotationLockSetting(boolean isRotationLocked) {
Log.v(TAG, "saveNewRotationLockSetting [state=" + mDeviceState + "] [isRotationLocked="
+ isRotationLocked + "]");
mDeviceStateRotationLockSettings.put(mDeviceState,
isRotationLocked
? DEVICE_STATE_ROTATION_LOCK_LOCKED
: DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
persistSettings();
}
private boolean isRotationLockedForCurrentState() {
return mDeviceStateRotationLockSettings.get(mDeviceState,
DEVICE_STATE_ROTATION_LOCK_IGNORED) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
}
private void updateDeviceState(int state) {
Log.v(TAG, "updateDeviceState [state=" + state + "]");
if (mDeviceState == state) {
return;
}
int rotationLockSetting =
mDeviceStateRotationLockSettings.get(state, DEVICE_STATE_ROTATION_LOCK_IGNORED);
if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
// We won't handle this device state. The same rotation lock setting as before should
// apply and any changes to the rotation lock setting will be written for the previous
// valid device state.
Log.v(TAG, "Ignoring new device state: " + state);
return;
}
// Accept the new state
mDeviceState = state;
// Update the rotation lock setting if needed for this new device state
boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) {
mRotationPolicyWrapper.setRotationLock(newRotationLockSetting);
}
}
private void persistSettings() {
if (mDeviceStateRotationLockSettings.size() == 0) {
mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
/* value= */"", UserHandle.USER_CURRENT);
return;
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(mDeviceStateRotationLockSettings.keyAt(0))
.append(SEPARATOR_REGEX)
.append(mDeviceStateRotationLockSettings.valueAt(0));
for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) {
stringBuilder
.append(SEPARATOR_REGEX)
.append(mDeviceStateRotationLockSettings.keyAt(i))
.append(SEPARATOR_REGEX)
.append(mDeviceStateRotationLockSettings.valueAt(i));
}
mSecureSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
stringBuilder.toString(), UserHandle.USER_CURRENT);
}
private void loadDefaults() {
if (mDeviceStateRotationLockDefaults.length == 0) {
Log.w(TAG, "Empty default settings");
mDeviceStateRotationLockSettings = new SparseIntArray(/* initialCapacity= */0);
return;
}
mDeviceStateRotationLockSettings =
new SparseIntArray(mDeviceStateRotationLockDefaults.length);
for (String serializedDefault : mDeviceStateRotationLockDefaults) {
String[] entry = serializedDefault.split(SEPARATOR_REGEX);
try {
int key = Integer.parseInt(entry[0]);
int value = Integer.parseInt(entry[1]);
mDeviceStateRotationLockSettings.put(key, value);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error deserializing default settings", e);
}
}
}
}

View File

@@ -16,36 +16,54 @@
package com.android.systemui.statusbar.policy; package com.android.systemui.statusbar.policy;
import android.content.Context; import static com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule.DEVICE_STATE_ROTATION_LOCK_DEFAULTS;
import android.os.UserHandle; import android.os.UserHandle;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.android.internal.view.RotationPolicy; import com.android.internal.view.RotationPolicy.RotationPolicyListener;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named;
/** Platform implementation of the rotation lock controller. **/ /** Platform implementation of the rotation lock controller. **/
@SysUISingleton @SysUISingleton
public final class RotationLockControllerImpl implements RotationLockController { public final class RotationLockControllerImpl implements RotationLockController {
private final Context mContext;
private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks = private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
new CopyOnWriteArrayList<RotationLockControllerCallback>(); new CopyOnWriteArrayList<>();
private final RotationPolicy.RotationPolicyListener mRotationPolicyListener = private final RotationPolicyListener mRotationPolicyListener =
new RotationPolicy.RotationPolicyListener() { new RotationPolicyListener() {
@Override @Override
public void onChange() { public void onChange() {
notifyChanged(); notifyChanged();
} }
}; };
private final RotationPolicyWrapper mRotationPolicy;
private final DeviceStateRotationLockSettingController
mDeviceStateRotationLockSettingController;
private final boolean mIsPerDeviceStateRotationLockEnabled;
@Inject @Inject
public RotationLockControllerImpl(Context context) { public RotationLockControllerImpl(
mContext = context; RotationPolicyWrapper rotationPolicyWrapper,
DeviceStateRotationLockSettingController deviceStateRotationLockSettingController,
@Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
) {
mRotationPolicy = rotationPolicyWrapper;
mDeviceStateRotationLockSettingController = deviceStateRotationLockSettingController;
mIsPerDeviceStateRotationLockEnabled = deviceStateRotationLockDefaults.length > 0;
if (mIsPerDeviceStateRotationLockEnabled) {
deviceStateRotationLockSettingController.initialize();
mCallbacks.add(mDeviceStateRotationLockSettingController);
}
setListening(true); setListening(true);
} }
@@ -61,32 +79,35 @@ public final class RotationLockControllerImpl implements RotationLockController
} }
public int getRotationLockOrientation() { public int getRotationLockOrientation() {
return RotationPolicy.getRotationLockOrientation(mContext); return mRotationPolicy.getRotationLockOrientation();
} }
public boolean isRotationLocked() { public boolean isRotationLocked() {
return RotationPolicy.isRotationLocked(mContext); return mRotationPolicy.isRotationLocked();
} }
public void setRotationLocked(boolean locked) { public void setRotationLocked(boolean locked) {
RotationPolicy.setRotationLock(mContext, locked); mRotationPolicy.setRotationLock(locked);
} }
public void setRotationLockedAtAngle(boolean locked, int rotation){ public void setRotationLockedAtAngle(boolean locked, int rotation){
RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation); mRotationPolicy.setRotationLockAtAngle(locked, rotation);
} }
public boolean isRotationLockAffordanceVisible() { public boolean isRotationLockAffordanceVisible() {
return RotationPolicy.isRotationLockToggleVisible(mContext); return mRotationPolicy.isRotationLockToggleVisible();
} }
@Override @Override
public void setListening(boolean listening) { public void setListening(boolean listening) {
if (listening) { if (listening) {
RotationPolicy.registerRotationPolicyListener(mContext, mRotationPolicyListener, mRotationPolicy.registerRotationPolicyListener(mRotationPolicyListener,
UserHandle.USER_ALL); UserHandle.USER_ALL);
} else { } else {
RotationPolicy.unregisterRotationPolicyListener(mContext, mRotationPolicyListener); mRotationPolicy.unregisterRotationPolicyListener(mRotationPolicyListener);
}
if (mIsPerDeviceStateRotationLockEnabled) {
mDeviceStateRotationLockSettingController.setListening(listening);
} }
} }
@@ -97,7 +118,7 @@ public final class RotationLockControllerImpl implements RotationLockController
} }
private void notifyChanged(RotationLockControllerCallback callback) { private void notifyChanged(RotationLockControllerCallback callback) {
callback.onRotationLockStateChanged(RotationPolicy.isRotationLocked(mContext), callback.onRotationLockStateChanged(mRotationPolicy.isRotationLocked(),
RotationPolicy.isRotationLockToggleVisible(mContext)); mRotationPolicy.isRotationLockToggleVisible());
} }
} }

View File

@@ -16,8 +16,10 @@
package com.android.systemui.statusbar.policy.dagger; package com.android.systemui.statusbar.policy.dagger;
import android.content.res.Resources;
import android.os.UserManager; import android.os.UserManager;
import com.android.internal.R;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.UserTracker;
@@ -57,6 +59,8 @@ import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.inject.Named;
import dagger.Binds; import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -65,6 +69,9 @@ import dagger.Provides;
/** Dagger Module for code in the statusbar.policy package. */ /** Dagger Module for code in the statusbar.policy package. */
@Module @Module
public interface StatusBarPolicyModule { public interface StatusBarPolicyModule {
String DEVICE_STATE_ROTATION_LOCK_DEFAULTS = "DEVICE_STATE_ROTATION_LOCK_DEFAULTS";
/** */ /** */
@Binds @Binds
BluetoothController provideBluetoothController(BluetoothControllerImpl controllerImpl); BluetoothController provideBluetoothController(BluetoothControllerImpl controllerImpl);
@@ -154,4 +161,14 @@ public interface StatusBarPolicyModule {
controller.init(); controller.init();
return controller; return controller;
} }
/**
* Default values for per-device state rotation lock settings.
*/
@Provides
@Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS)
static String[] providesDeviceStateRotationLockDefaults(@Main Resources resources) {
return resources.getStringArray(
R.array.config_perDeviceStateRotationLockDefaults);
}
} }

View File

@@ -18,12 +18,15 @@ package com.android.systemui.util.dagger;
import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.RingerModeTrackerImpl; import com.android.systemui.util.RingerModeTrackerImpl;
import com.android.systemui.util.wrapper.UtilWrapperModule;
import dagger.Binds; import dagger.Binds;
import dagger.Module; import dagger.Module;
/** Dagger Module for code in the util package. */ /** Dagger Module for code in the util package. */
@Module @Module(includes = {
UtilWrapperModule.class
})
public interface UtilModule { public interface UtilModule {
/** */ /** */
@Binds @Binds

View File

@@ -0,0 +1,67 @@
/*
* 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.systemui.util.wrapper
import android.content.Context
import com.android.internal.view.RotationPolicy
import com.android.internal.view.RotationPolicy.RotationPolicyListener
import javax.inject.Inject
/**
* Testable wrapper interface around RotationPolicy {link com.android.internal.view.RotationPolicy}
*/
interface RotationPolicyWrapper {
fun setRotationLock(enabled: Boolean)
fun setRotationLockAtAngle(enabled: Boolean, rotation: Int)
fun getRotationLockOrientation(): Int
fun isRotationLockToggleVisible(): Boolean
fun isRotationLocked(): Boolean
fun registerRotationPolicyListener(listener: RotationPolicyListener, userHandle: Int)
fun unregisterRotationPolicyListener(listener: RotationPolicyListener)
}
class RotationPolicyWrapperImpl @Inject constructor(private val context: Context) :
RotationPolicyWrapper {
override fun setRotationLock(enabled: Boolean) {
RotationPolicy.setRotationLock(context, enabled)
}
override fun setRotationLockAtAngle(enabled: Boolean, rotation: Int) {
RotationPolicy.setRotationLockAtAngle(context, enabled, rotation)
}
override fun getRotationLockOrientation(): Int =
RotationPolicy.getRotationLockOrientation(context)
override fun isRotationLockToggleVisible(): Boolean =
RotationPolicy.isRotationLockToggleVisible(context)
override fun isRotationLocked(): Boolean =
RotationPolicy.isRotationLocked(context)
override fun registerRotationPolicyListener(
listener: RotationPolicyListener,
userHandle: Int
) {
RotationPolicy.registerRotationPolicyListener(context, listener, userHandle)
}
override fun unregisterRotationPolicyListener(listener: RotationPolicyListener) {
RotationPolicy.unregisterRotationPolicyListener(context, listener)
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.systemui.util.wrapper
import com.android.systemui.dagger.SysUISingleton
import dagger.Binds
import dagger.Module
@Module
abstract class UtilWrapperModule {
@Binds
@SysUISingleton
abstract fun bindRotationPolicyWrapper(impl: RotationPolicyWrapperImpl): RotationPolicyWrapper
}

View File

@@ -0,0 +1,239 @@
/*
* 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.systemui.statusbar.policy;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import android.hardware.devicestate.DeviceStateManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableResources;
import androidx.test.filters.SmallTest;
import com.android.internal.view.RotationPolicy;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase {
private static final String[] DEFAULT_SETTINGS = new String[]{
"0:0",
"1:2"
};
private final FakeSettings mFakeSettings = new FakeSettings();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@Mock DeviceStateManager mDeviceStateManager;
RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(/* testClass= */ this);
TestableResources resources = mContext.getOrCreateTestableResources();
ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor =
ArgumentCaptor.forClass(
DeviceStateManager.DeviceStateCallback.class);
mDeviceStateRotationLockSettingController = new DeviceStateRotationLockSettingController(
mFakeSettings,
mFakeRotationPolicy,
mDeviceStateManager,
mFakeExecutor,
DEFAULT_SETTINGS
);
mDeviceStateRotationLockSettingController.setListening(true);
verify(mDeviceStateManager).registerCallback(any(),
deviceStateCallbackArgumentCaptor.capture());
mDeviceStateCallback = deviceStateCallbackArgumentCaptor.getValue();
}
@Test
public void whenSavedSettingsEmpty_defaultsLoadedAndSaved() {
mFakeSettings.putStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK, "",
UserHandle.USER_CURRENT);
mDeviceStateRotationLockSettingController.initialize();
assertThat(mFakeSettings
.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
UserHandle.USER_CURRENT))
.isEqualTo("0:0:1:2");
}
@Test
public void whenNoSavedValueForDeviceState_assumeIgnored() {
mFakeSettings.putStringForUser(
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
/* value= */"0:2:1:2",
UserHandle.USER_CURRENT);
mFakeRotationPolicy.setRotationLock(true);
mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(1);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
// Settings only exist for state 0 and 1
mDeviceStateCallback.onStateChanged(2);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
}
@Test
public void whenDeviceStateSwitched_loadCorrectSetting() {
mFakeSettings.putStringForUser(
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
/* value= */"0:2:1:1",
UserHandle.USER_CURRENT);
mFakeRotationPolicy.setRotationLock(true);
mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(0);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
mDeviceStateCallback.onStateChanged(1);
assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
}
@Test
public void whenUserChangesSetting_saveSettingForCurrentState() {
mFakeSettings.putStringForUser(
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
/* value= */"0:1:1:2",
UserHandle.USER_CURRENT);
mFakeRotationPolicy.setRotationLock(true);
mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(0);
assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
mDeviceStateRotationLockSettingController
.onRotationLockStateChanged(/* rotationLocked= */false,
/* affordanceVisible= */ true);
assertThat(mFakeSettings
.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
UserHandle.USER_CURRENT))
.isEqualTo("0:2:1:2");
}
@Test
public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() {
mFakeSettings.putStringForUser(
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
/* value= */"0:0:1:2",
UserHandle.USER_CURRENT);
mFakeRotationPolicy.setRotationLock(true);
mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(1);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
mDeviceStateCallback.onStateChanged(0);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
}
@Test
public void whenDeviceStateSwitchedToIgnoredState_newSettingsSaveForPreviousState() {
mFakeSettings.putStringForUser(
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
/* value= */"0:0:1:2",
UserHandle.USER_CURRENT);
mFakeRotationPolicy.setRotationLock(true);
mDeviceStateRotationLockSettingController.initialize();
mDeviceStateCallback.onStateChanged(1);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
mDeviceStateCallback.onStateChanged(0);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
mDeviceStateRotationLockSettingController
.onRotationLockStateChanged(/* rotationLocked= */true,
/* affordanceVisible= */ true);
assertThat(mFakeSettings
.getStringForUser(Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
UserHandle.USER_CURRENT))
.isEqualTo("0:0:1:1");
}
private static class FakeRotationPolicy implements RotationPolicyWrapper {
private boolean mRotationLock;
@Override
public void setRotationLock(boolean enabled) {
mRotationLock = enabled;
}
@Override
public void setRotationLockAtAngle(boolean enabled, int rotation) {
mRotationLock = enabled;
}
@Override
public int getRotationLockOrientation() {
throw new AssertionError("Not implemented");
}
@Override
public boolean isRotationLockToggleVisible() {
throw new AssertionError("Not implemented");
}
@Override
public boolean isRotationLocked() {
return mRotationLock;
}
@Override
public void registerRotationPolicyListener(RotationPolicy.RotationPolicyListener listener,
int userHandle) {
throw new AssertionError("Not implemented");
}
@Override
public void unregisterRotationPolicyListener(
RotationPolicy.RotationPolicyListener listener) {
throw new AssertionError("Not implemented");
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.systemui.statusbar.policy;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
import androidx.test.filters.SmallTest;
import com.android.internal.view.RotationPolicy;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class RotationLockControllerImplTest extends SysuiTestCase {
private static final String[] DEFAULT_SETTINGS = new String[]{
"0:0",
"1:2"
};
@Mock RotationPolicyWrapper mRotationPolicyWrapper;
@Mock DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
private TestableResources mResources;
private ArgumentCaptor<RotationPolicy.RotationPolicyListener>
mRotationPolicyListenerCaptor;
@Before
public void setUp() {
MockitoAnnotations.initMocks(/* testClass= */ this);
mResources = mContext.getOrCreateTestableResources();
mRotationPolicyListenerCaptor = ArgumentCaptor.forClass(
RotationPolicy.RotationPolicyListener.class);
}
@Test
public void whenFlagOff_doesntInteractWithDeviceStateRotationController() {
createRotationLockController(new String[0]);
verifyZeroInteractions(mDeviceStateRotationLockSettingController);
}
@Test
public void whenFlagOn_setListeningSetsListeningOnDeviceStateRotationController() {
createRotationLockController();
verify(mDeviceStateRotationLockSettingController).setListening(/* listening= */ true);
}
@Test
public void whenFlagOn_initializesDeviceStateRotationController() {
createRotationLockController();
verify(mDeviceStateRotationLockSettingController).initialize();
}
@Test
public void whenFlagOn_dviceStateRotationControllerAddedToCallbacks() {
createRotationLockController();
captureRotationPolicyListener().onChange();
verify(mDeviceStateRotationLockSettingController)
.onRotationLockStateChanged(anyBoolean(), anyBoolean());
}
private RotationPolicy.RotationPolicyListener captureRotationPolicyListener() {
verify(mRotationPolicyWrapper)
.registerRotationPolicyListener(mRotationPolicyListenerCaptor.capture(), anyInt());
return mRotationPolicyListenerCaptor.getValue();
}
private void createRotationLockController() {
createRotationLockController(DEFAULT_SETTINGS);
}
private void createRotationLockController(String[] deviceStateRotationLockDefaults) {
new RotationLockControllerImpl(
mRotationPolicyWrapper,
mDeviceStateRotationLockSettingController,
deviceStateRotationLockDefaults
);
}
}