Merge "Foldable rotation settings: persist postures instead of device states" into tm-qpr-dev

This commit is contained in:
Christian Göllner
2023-03-16 16:52:29 +00:00
committed by Android (Google) Code Review
7 changed files with 172 additions and 72 deletions

View File

@@ -10997,21 +10997,46 @@ public final class Settings {
public @interface DeviceStateRotationLockSetting {
}
/** @hide */
public static final int DEVICE_STATE_ROTATION_KEY_UNKNOWN = -1;
/** @hide */
public static final int DEVICE_STATE_ROTATION_KEY_FOLDED = 0;
/** @hide */
public static final int DEVICE_STATE_ROTATION_KEY_HALF_FOLDED = 1;
/** @hide */
public static final int DEVICE_STATE_ROTATION_KEY_UNFOLDED = 2;
/**
* The different postures that can be used as keys with
* {@link #DEVICE_STATE_ROTATION_LOCK}.
* @hide
*/
@IntDef(prefix = {"DEVICE_STATE_ROTATION_KEY_"}, value = {
DEVICE_STATE_ROTATION_KEY_UNKNOWN,
DEVICE_STATE_ROTATION_KEY_FOLDED,
DEVICE_STATE_ROTATION_KEY_HALF_FOLDED,
DEVICE_STATE_ROTATION_KEY_UNFOLDED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeviceStateRotationLockKey {
}
/**
* Rotation lock setting keyed on device state.
*
* This holds a serialized map using int keys that represent Device States and value of
* This holds a serialized map using int keys that represent postures in
* {@link DeviceStateRotationLockKey} and value of
* {@link DeviceStateRotationLockSetting} representing the rotation lock setting for that
* device state.
* posture.
*
* 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>
* <li>DEVICE_STATE_ROTATION_KEY_FOLDED -> DEVICE_STATE_ROTATION_LOCK_LOCKED</li>
* <li>DEVICE_STATE_ROTATION_KEY_HALF_FOLDED -> DEVICE_STATE_ROTATION_LOCK_UNLOCKED</li>
* <li>DEVICE_STATE_ROTATION_KEY_UNFOLDED -> DEVICE_STATE_ROTATION_LOCK_IGNORED</li>
* </ul>
*
* @hide

View File

@@ -736,14 +736,13 @@
display is powered on at the same time. -->
<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. -->
<!-- Map of device posture to rotation lock setting. Each entry must be in the format
"key:value", or "key:value:fallback_key" for example: "0:1" or "2:0:1". The keys are one of
Settings.Secure.DeviceStateRotationLockKey, and the values are one of
Settings.Secure.DeviceStateRotationLockSetting.
The fallback is a key to a device posture that can be specified when the value is
Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED.
-->
<string-array name="config_perDeviceStateRotationLockDefaults" />
<!-- Dock behavior -->

View File

@@ -10,7 +10,10 @@ package {
android_library {
name: "SettingsLibDeviceStateRotationLock",
srcs: ["src/**/*.java"],
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
min_sdk_version: "21",
}

View File

@@ -57,17 +57,19 @@ public final class DeviceStateRotationLockSettingsManager {
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
private final SecureSettings mSecureSettings;
private String[] mDeviceStateRotationLockDefaults;
private SparseIntArray mDeviceStateRotationLockSettings;
private SparseIntArray mDeviceStateDefaultRotationLockSettings;
private SparseIntArray mDeviceStateRotationLockFallbackSettings;
private final PosturesHelper mPosturesHelper;
private String[] mPostureRotationLockDefaults;
private SparseIntArray mPostureRotationLockSettings;
private SparseIntArray mPostureDefaultRotationLockSettings;
private SparseIntArray mPostureRotationLockFallbackSettings;
private String mLastSettingValue;
private List<SettableDeviceState> mSettableDeviceStates;
@VisibleForTesting
DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) {
this.mSecureSettings = secureSettings;
mDeviceStateRotationLockDefaults =
mSecureSettings = secureSettings;
mPosturesHelper = new PosturesHelper(context);
mPostureRotationLockDefaults =
context.getResources()
.getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
loadDefaults();
@@ -134,13 +136,14 @@ public final class DeviceStateRotationLockSettingsManager {
/** Updates the rotation lock setting for a specified device state. */
public void updateSetting(int deviceState, boolean rotationLocked) {
if (mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState) >= 0) {
// The setting for this device state is IGNORED, and has a fallback device state.
// The setting for that fallback device state should be the changed in this case.
deviceState = mDeviceStateRotationLockFallbackSettings.get(deviceState);
int posture = mPosturesHelper.deviceStateToPosture(deviceState);
if (mPostureRotationLockFallbackSettings.indexOfKey(posture) >= 0) {
// The setting for this device posture is IGNORED, and has a fallback posture.
// The setting for that fallback posture should be the changed in this case.
posture = mPostureRotationLockFallbackSettings.get(posture);
}
mDeviceStateRotationLockSettings.put(
deviceState,
mPostureRotationLockSettings.put(
posture,
rotationLocked
? DEVICE_STATE_ROTATION_LOCK_LOCKED
: DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
@@ -159,22 +162,23 @@ public final class DeviceStateRotationLockSettingsManager {
*/
@Settings.Secure.DeviceStateRotationLockSetting
public int getRotationLockSetting(int deviceState) {
int rotationLockSetting = mDeviceStateRotationLockSettings.get(
deviceState, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
int devicePosture = mPosturesHelper.deviceStateToPosture(deviceState);
int rotationLockSetting = mPostureRotationLockSettings.get(
devicePosture, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
rotationLockSetting = getFallbackRotationLockSetting(deviceState);
rotationLockSetting = getFallbackRotationLockSetting(devicePosture);
}
return rotationLockSetting;
}
private int getFallbackRotationLockSetting(int deviceState) {
int indexOfFallbackState = mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState);
if (indexOfFallbackState < 0) {
private int getFallbackRotationLockSetting(int devicePosture) {
int indexOfFallback = mPostureRotationLockFallbackSettings.indexOfKey(devicePosture);
if (indexOfFallback < 0) {
Log.w(TAG, "Setting is ignored, but no fallback was specified.");
return DEVICE_STATE_ROTATION_LOCK_IGNORED;
}
int fallbackState = mDeviceStateRotationLockFallbackSettings.valueAt(indexOfFallbackState);
return mDeviceStateRotationLockSettings.get(fallbackState,
int fallbackPosture = mPostureRotationLockFallbackSettings.valueAt(indexOfFallback);
return mPostureRotationLockSettings.get(fallbackPosture,
/* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED);
}
@@ -189,8 +193,8 @@ public final class DeviceStateRotationLockSettingsManager {
* DEVICE_STATE_ROTATION_LOCK_UNLOCKED}.
*/
public boolean isRotationLockedForAllStates() {
for (int i = 0; i < mDeviceStateRotationLockSettings.size(); i++) {
if (mDeviceStateRotationLockSettings.valueAt(i)
for (int i = 0; i < mPostureRotationLockSettings.size(); i++) {
if (mPostureRotationLockSettings.valueAt(i)
== DEVICE_STATE_ROTATION_LOCK_UNLOCKED) {
return false;
}
@@ -221,7 +225,7 @@ public final class DeviceStateRotationLockSettingsManager {
fallbackOnDefaults();
return;
}
mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2);
mPostureRotationLockSettings = new SparseIntArray(values.length / 2);
int key;
int value;
@@ -230,7 +234,7 @@ public final class DeviceStateRotationLockSettingsManager {
key = Integer.parseInt(values[i++]);
value = Integer.parseInt(values[i++]);
boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED;
boolean isDefaultValueIgnored = mDeviceStateDefaultRotationLockSettings.get(key)
boolean isDefaultValueIgnored = mPostureDefaultRotationLockSettings.get(key)
== DEVICE_STATE_ROTATION_LOCK_IGNORED;
if (isPersistedValueIgnored != isDefaultValueIgnored) {
Log.w(TAG, "Conflict for ignored device state " + key
@@ -238,7 +242,7 @@ public final class DeviceStateRotationLockSettingsManager {
fallbackOnDefaults();
return;
}
mDeviceStateRotationLockSettings.put(key, value);
mPostureRotationLockSettings.put(key, value);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error deserializing one of the saved settings", e);
fallbackOnDefaults();
@@ -253,7 +257,7 @@ public final class DeviceStateRotationLockSettingsManager {
*/
@VisibleForTesting
public void resetStateForTesting(Resources resources) {
mDeviceStateRotationLockDefaults =
mPostureRotationLockDefaults =
resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
fallbackOnDefaults();
}
@@ -264,23 +268,23 @@ public final class DeviceStateRotationLockSettingsManager {
}
private void persistSettings() {
if (mDeviceStateRotationLockSettings.size() == 0) {
if (mPostureRotationLockSettings.size() == 0) {
persistSettingIfChanged(/* newSettingValue= */ "");
return;
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append(mDeviceStateRotationLockSettings.keyAt(0))
.append(mPostureRotationLockSettings.keyAt(0))
.append(SEPARATOR_REGEX)
.append(mDeviceStateRotationLockSettings.valueAt(0));
.append(mPostureRotationLockSettings.valueAt(0));
for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) {
for (int i = 1; i < mPostureRotationLockSettings.size(); i++) {
stringBuilder
.append(SEPARATOR_REGEX)
.append(mDeviceStateRotationLockSettings.keyAt(i))
.append(mPostureRotationLockSettings.keyAt(i))
.append(SEPARATOR_REGEX)
.append(mDeviceStateRotationLockSettings.valueAt(i));
.append(mPostureRotationLockSettings.valueAt(i));
}
persistSettingIfChanged(stringBuilder.toString());
}
@@ -300,22 +304,20 @@ public final class DeviceStateRotationLockSettingsManager {
}
private void loadDefaults() {
mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length);
mDeviceStateDefaultRotationLockSettings = new SparseIntArray(
mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockSettings = new SparseIntArray(
mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
for (String entry : mDeviceStateRotationLockDefaults) {
mSettableDeviceStates = new ArrayList<>(mPostureRotationLockDefaults.length);
mPostureDefaultRotationLockSettings = new SparseIntArray(
mPostureRotationLockDefaults.length);
mPostureRotationLockSettings = new SparseIntArray(mPostureRotationLockDefaults.length);
mPostureRotationLockFallbackSettings = new SparseIntArray(1);
for (String entry : mPostureRotationLockDefaults) {
String[] values = entry.split(SEPARATOR_REGEX);
try {
int deviceState = Integer.parseInt(values[0]);
int posture = Integer.parseInt(values[0]);
int rotationLockSetting = Integer.parseInt(values[1]);
if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
if (values.length == 3) {
int fallbackDeviceState = Integer.parseInt(values[2]);
mDeviceStateRotationLockFallbackSettings.put(deviceState,
fallbackDeviceState);
int fallbackPosture = Integer.parseInt(values[2]);
mPostureRotationLockFallbackSettings.put(posture, fallbackPosture);
} else {
Log.w(TAG,
"Rotation lock setting is IGNORED, but values have unexpected "
@@ -324,9 +326,14 @@ public final class DeviceStateRotationLockSettingsManager {
}
}
boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
mDeviceStateDefaultRotationLockSettings.put(deviceState, rotationLockSetting);
Integer deviceState = mPosturesHelper.postureToDeviceState(posture);
if (deviceState != null) {
mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
} else {
Log.wtf(TAG, "No matching device state for posture: " + posture);
}
mPostureRotationLockSettings.put(posture, rotationLockSetting);
mPostureDefaultRotationLockSettings.put(posture, rotationLockSetting);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
return;
@@ -338,13 +345,11 @@ public final class DeviceStateRotationLockSettingsManager {
public void dump(IndentingPrintWriter pw) {
pw.println("DeviceStateRotationLockSettingsManager");
pw.increaseIndent();
pw.println("mDeviceStateRotationLockDefaults: " + Arrays.toString(
mDeviceStateRotationLockDefaults));
pw.println("mDeviceStateDefaultRotationLockSettings: "
+ mDeviceStateDefaultRotationLockSettings);
pw.println("mDeviceStateRotationLockSettings: " + mDeviceStateRotationLockSettings);
pw.println("mDeviceStateRotationLockFallbackSettings: "
+ mDeviceStateRotationLockFallbackSettings);
pw.println("mPostureRotationLockDefaults: "
+ Arrays.toString(mPostureRotationLockDefaults));
pw.println("mPostureDefaultRotationLockSettings: " + mPostureDefaultRotationLockSettings);
pw.println("mDeviceStateRotationLockSettings: " + mPostureRotationLockSettings);
pw.println("mPostureRotationLockFallbackSettings: " + mPostureRotationLockFallbackSettings);
pw.println("mSettableDeviceStates: " + mSettableDeviceStates);
pw.println("mLastSettingValue: " + mLastSettingValue);
pw.decreaseIndent();

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.devicestate
import android.content.Context
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_FOLDED
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_HALF_FOLDED
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNFOLDED
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNKNOWN
import android.provider.Settings.Secure.DeviceStateRotationLockKey
import com.android.internal.R
/** Helps to convert between device state and posture. */
class PosturesHelper(context: Context) {
private val foldedDeviceStates =
context.resources.getIntArray(R.array.config_foldedDeviceStates)
private val halfFoldedDeviceStates =
context.resources.getIntArray(R.array.config_halfFoldedDeviceStates)
private val unfoldedDeviceStates =
context.resources.getIntArray(R.array.config_openDeviceStates)
@DeviceStateRotationLockKey
fun deviceStateToPosture(deviceState: Int): Int {
return when (deviceState) {
in foldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_FOLDED
in halfFoldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_HALF_FOLDED
in unfoldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_UNFOLDED
else -> DEVICE_STATE_ROTATION_KEY_UNKNOWN
}
}
fun postureToDeviceState(@DeviceStateRotationLockKey posture: Int): Int? {
return when (posture) {
DEVICE_STATE_ROTATION_KEY_FOLDED -> foldedDeviceStates.firstOrNull()
DEVICE_STATE_ROTATION_KEY_HALF_FOLDED -> halfFoldedDeviceStates.firstOrNull()
DEVICE_STATE_ROTATION_KEY_UNFOLDED -> unfoldedDeviceStates.firstOrNull()
else -> null
}
}
}

View File

@@ -70,12 +70,20 @@ public class DeviceStateRotationLockSettingsManagerTest {
when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver());
when(mMockResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
.thenReturn(new String[]{"0:1", "1:0:2", "2:2"});
when(mMockResources.getIntArray(R.array.config_foldedDeviceStates))
.thenReturn(new int[]{0});
when(mMockResources.getIntArray(R.array.config_halfFoldedDeviceStates))
.thenReturn(new int[]{1});
when(mMockResources.getIntArray(R.array.config_openDeviceStates))
.thenReturn(new int[]{2});
mFakeSecureSettings.registerContentObserver(
Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
/* notifyForDescendents= */ false, //NOTYPO
mContentObserver,
UserHandle.USER_CURRENT);
mManager = new DeviceStateRotationLockSettingsManager(context, mFakeSecureSettings);
mManager = new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
}
@Test
@@ -109,7 +117,7 @@ public class DeviceStateRotationLockSettingsManagerTest {
public void getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() {
when(mMockResources.getStringArray(
R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
new String[]{"2:2", "4:0", "1:1", "0:0"});
new String[]{"2:1", "1:0:1", "0:2"});
List<SettableDeviceState> settableDeviceStates =
DeviceStateRotationLockSettingsManager.getInstance(
@@ -117,9 +125,8 @@ public class DeviceStateRotationLockSettingsManagerTest {
assertThat(settableDeviceStates).containsExactly(
new SettableDeviceState(/* deviceState= */ 2, /* isSettable= */ true),
new SettableDeviceState(/* deviceState= */ 4, /* isSettable= */ false),
new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ true),
new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false)
new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ false),
new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ true)
).inOrder();
}

View File

@@ -55,6 +55,9 @@ import org.mockito.MockitoAnnotations;
public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase {
private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"};
private static final int[] DEFAULT_FOLDED_STATES = new int[]{0};
private static final int[] DEFAULT_HALF_FOLDED_STATES = new int[]{2};
private static final int[] DEFAULT_UNFOLDED_STATES = new int[]{1};
@Mock private DeviceStateManager mDeviceStateManager;
@Mock private DeviceStateRotationLockSettingControllerLogger mLogger;
@@ -73,6 +76,9 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase
MockitoAnnotations.initMocks(/* testClass= */ this);
TestableResources resources = mContext.getOrCreateTestableResources();
resources.addOverride(R.array.config_perDeviceStateRotationLockDefaults, DEFAULT_SETTINGS);
resources.addOverride(R.array.config_foldedDeviceStates, DEFAULT_FOLDED_STATES);
resources.addOverride(R.array.config_halfFoldedDeviceStates, DEFAULT_HALF_FOLDED_STATES);
resources.addOverride(R.array.config_openDeviceStates, DEFAULT_UNFOLDED_STATES);
ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor =
ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);