blob: 41cacf5142fdd5c9e6cf249305b49db9d73100b8 [file] [log] [blame]
/*
* 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);
}
}
}
}