Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4831a179 authored by Alex Florescu's avatar Alex Florescu
Browse files

Extract device state rotation lock settings management to a separate class.

This will be followed-up by a CL moving it to SettingsLib so it can be
shared with Settings

Bug: 195757480
Test: atest SystemUITests:DeviceStateRotationLockSettingControllerTest

Change-Id: Ic9c720d7c911d9b8db70ea6bb7fbab209a602abe
parent aec1bdf2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -10525,7 +10525,7 @@ public final class Settings {
                DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
        })
        @Retention(RetentionPolicy.SOURCE)
        @interface DeviceStateRotationLockSetting {
        public @interface DeviceStateRotationLockSetting {
        }
        /**
+37 −130
Original line number Diff line number Diff line
@@ -16,110 +16,53 @@

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.
 **/
 * 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 {
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 final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager;

    private SparseIntArray mDeviceStateRotationLockSettings;
    // TODO(b/183001527): Add API to query current device state and initialize this.
    // On registration for DeviceStateCallback, we will receive a callback with the current state
    // and this will be initialized.
    private int mDeviceState = -1;
    @Nullable
    private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;

    @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
    private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
            mDeviceStateRotationLockSettingsListener;

    @Inject
    public DeviceStateRotationLockSettingController(
            SecureSettings secureSettings,
            RotationPolicyWrapper rotationPolicyWrapper,
            DeviceStateManager deviceStateManager,
            @Main Executor executor,
            @Named(DEVICE_STATE_ROTATION_LOCK_DEFAULTS) String[] deviceStateRotationLockDefaults
    ) {
        mSecureSettings = secureSettings;
            DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) {
        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();
        mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager;
    }

    @Override
@@ -129,10 +72,17 @@ public final class DeviceStateRotationLockSettingController implements Listenabl
            // is no user action.
            mDeviceStateCallback = this::updateDeviceState;
            mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
            mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState);
            mDeviceStateRotationLockSettingsManager.registerListener(
                    mDeviceStateRotationLockSettingsListener);
        } else {
            if (mDeviceStateCallback != null) {
                mDeviceStateManager.unregisterCallback(mDeviceStateCallback);
            }
            if (mDeviceStateRotationLockSettingsListener != null) {
                mDeviceStateRotationLockSettingsManager.unregisterListener(
                        mDeviceStateRotationLockSettingsListener);
            }
        }
    }

@@ -143,7 +93,8 @@ public final class DeviceStateRotationLockSettingController implements Listenabl
            return;
        }

        if (rotationLocked == isRotationLockedForCurrentState()) {
        if (rotationLocked
                == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) {
            Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
            return;
        }
@@ -152,19 +103,15 @@ public final class DeviceStateRotationLockSettingController implements Listenabl
    }

    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();
    }
        Log.v(
                TAG,
                "saveNewRotationLockSetting [state="
                        + mDeviceState
                        + "] [isRotationLocked="
                        + isRotationLocked
                        + "]");

    private boolean isRotationLockedForCurrentState() {
        return mDeviceStateRotationLockSettings.get(mDeviceState,
                DEVICE_STATE_ROTATION_LOCK_IGNORED) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
        mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked);
    }

    private void updateDeviceState(int state) {
@@ -173,8 +120,12 @@ public final class DeviceStateRotationLockSettingController implements Listenabl
            return;
        }

        readPersistedSetting(state);
    }

    private void readPersistedSetting(int state) {
        int rotationLockSetting =
                mDeviceStateRotationLockSettings.get(state, DEVICE_STATE_ROTATION_LOCK_IGNORED);
                mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
        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
@@ -186,54 +137,10 @@ public final class DeviceStateRotationLockSettingController implements Listenabl
        // Accept the new state
        mDeviceState = state;

        // Update the rotation lock setting if needed for this new device state
        // Update the rotation policy, 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);
            }
        }
    }

}
+266 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;

import java.util.HashSet;
import java.util.Set;

/**
 * Manages device-state based rotation lock settings. Handles reading, writing, and listening for
 * changes.
 */
public final class DeviceStateRotationLockSettingsManager {

    private static final String TAG = "DSRotLockSettingsMngr";
    private static final String SEPARATOR_REGEX = ":";

    private static DeviceStateRotationLockSettingsManager sSingleton;

    private final ContentResolver mContentResolver;
    private final String[] mDeviceStateRotationLockDefaults;
    private final Handler mMainHandler = Handler.getMain();
    private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
    private SparseIntArray mDeviceStateRotationLockSettings;

    private DeviceStateRotationLockSettingsManager(Context context) {
        mContentResolver = context.getContentResolver();
        mDeviceStateRotationLockDefaults =
                context.getResources()
                        .getStringArray(R.array.config_perDeviceStateRotationLockDefaults);
        initializeInMemoryMap();
        listenForSettingsChange(context);
    }

    /** Returns a singleton instance of this class */
    public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) {
        if (sSingleton == null) {
            sSingleton =
                    new DeviceStateRotationLockSettingsManager(context.getApplicationContext());
        }
        return sSingleton;
    }

    /** Returns true if device-state based rotation lock settings are enabled. */
    public static boolean isDeviceStateRotationLockEnabled(Context context) {
        return context.getResources()
                        .getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
                        .length
                > 0;
    }

    private void listenForSettingsChange(Context context) {
        context.getContentResolver()
                .registerContentObserver(
                        Settings.Secure.getUriFor(Settings.Secure.DEVICE_STATE_ROTATION_LOCK),
                        /* notifyForDescendents= */ false, //NOTYPO
                        new ContentObserver(mMainHandler) {
                            @Override
                            public void onChange(boolean selfChange) {
                                onPersistedSettingsChanged();
                            }
                        },
                        UserHandle.USER_CURRENT);
    }

    /**
     * Registers a {@link DeviceStateRotationLockSettingsListener} to be notified when the settings
     * change. Can be called multiple times with different listeners.
     */
    public void registerListener(DeviceStateRotationLockSettingsListener runnable) {
        mListeners.add(runnable);
    }

    /**
     * Unregisters a {@link DeviceStateRotationLockSettingsListener}. No-op if the given instance
     * was never registered.
     */
    public void unregisterListener(
            DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) {
        if (!mListeners.remove(deviceStateRotationLockSettingsListener)) {
            Log.w(TAG, "Attempting to unregister a listener hadn't been registered");
        }
    }

    /** Updates the rotation lock setting for a specified device state. */
    public void updateSetting(int deviceState, boolean rotationLocked) {
        mDeviceStateRotationLockSettings.put(
                deviceState,
                rotationLocked
                        ? DEVICE_STATE_ROTATION_LOCK_LOCKED
                        : DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
        persistSettings();
    }

    /**
     * Returns the {@link DeviceStateRotationLockSetting} for the given device state. If no setting
     * is specified for this device state, it will return {@link
     * DEVICE_STATE_ROTATION_LOCK_IGNORED}.
     */
    @Settings.Secure.DeviceStateRotationLockSetting
    public int getRotationLockSetting(int deviceState) {
        return mDeviceStateRotationLockSettings.get(
                deviceState, DEVICE_STATE_ROTATION_LOCK_IGNORED);
    }

    /** Returns true if the rotation is locked for the current device state */
    public boolean isRotationLocked(int deviceState) {
        return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
    }

    /**
     * Returns true if there is no device state for which the current setting is {@link
     * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}.
     */
    public boolean isRotationLockedForAllStates() {
        for (int i = 0; i < mDeviceStateRotationLockSettings.size(); i++) {
            if (mDeviceStateRotationLockSettings.valueAt(i)
                    == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) {
                return false;
            }
        }
        return true;
    }

    private void initializeInMemoryMap() {
        String serializedSetting =
                Settings.Secure.getStringForUser(
                        mContentResolver,
                        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();
    }

    private void persistSettings() {
        if (mDeviceStateRotationLockSettings.size() == 0) {
            Settings.Secure.putStringForUser(
                    mContentResolver,
                    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));
        }
        Settings.Secure.putStringForUser(
                mContentResolver,
                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);
            }
        }
    }

    /**
     * Called when the persisted settings have changed, requiring a reinitialization of the
     * in-memory map.
     */
    @VisibleForTesting
    public void onPersistedSettingsChanged() {
        initializeInMemoryMap();
        notifyListeners();
    }

    private void notifyListeners() {
        for (DeviceStateRotationLockSettingsListener r : mListeners) {
            r.onSettingsChanged();
        }
    }

    /** Listener for changes in device-state based rotation lock settings */
    public interface DeviceStateRotationLockSettingsListener {
        /** Called whenever the settings have changed. */
        void onSettingsChanged();
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -64,7 +64,6 @@ public final class RotationLockControllerImpl implements RotationLockController
        mDeviceStateRotationLockSettingController = deviceStateRotationLockSettingController;
        mIsPerDeviceStateRotationLockEnabled = deviceStateRotationLockDefaults.length > 0;
        if (mIsPerDeviceStateRotationLockEnabled) {
            deviceStateRotationLockSettingController.initialize();
            mCallbacks.add(mDeviceStateRotationLockSettingController);
        }

+10 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.statusbar.policy.dagger;

import android.content.Context;
import android.content.res.Resources;
import android.os.UserManager;

@@ -35,6 +36,7 @@ import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DevicePostureControllerImpl;
import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingsManager;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -163,6 +165,14 @@ public interface StatusBarPolicyModule {
        return controller;
    }

    /** Returns a singleton instance of DeviceStateRotationLockSettingsManager */
    @SysUISingleton
    @Provides
    static DeviceStateRotationLockSettingsManager provideAutoRotateSettingsManager(
            Context context) {
        return DeviceStateRotationLockSettingsManager.getInstance(context);
    }

    /**
     * Default values for per-device state rotation lock settings.
     */
Loading