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

Commit 4edcbc29 authored by Shivangi Dubey's avatar Shivangi Dubey
Browse files

Create new device state auto rotate setting manager

As part of auto-rotate setting refactor, create a new manager to be used between sysui and settings.
This manager will replace the current DeviceStateRotationLockManager.
Next CL: Integrate this manager to be used when auto-rotate refactor flag is ON.
For more info:go/auto-rotate-refactor
Bug: 394303723
Flag: com.android.window.flags.enable_device_state_auto_rotate_setting_refactor
Test: atest DeviceStateAutoRotateSettingManagerImplTest

Change-Id: I5cd81df5ea602adc3942d869a1294e5ba232d770
parent 8220a96d
Loading
Loading
Loading
Loading
+69 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2025 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.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK

/**
 * Interface for managing [DEVICE_STATE_ROTATION_LOCK] setting.
 *
 * It provides methods to register/unregister listeners for setting changes, update the setting for
 * specific device states, retrieve the setting value, and check if rotation is locked for specific
 * or all device states.
 */
interface DeviceStateAutoRotateSettingManager {
    // TODO: b/397928958 - Rename all terms from rotationLock to autoRotate in all apis.

    /** Listener for changes in device-state based auto rotate setting. */
    interface DeviceStateAutoRotateSettingListener {
        /** Called whenever the setting has changed. */
        fun onSettingsChanged()
    }

    /** Register listener for changes to [DEVICE_STATE_ROTATION_LOCK] setting. */
    fun registerListener(settingListener: DeviceStateAutoRotateSettingListener)

    /** Unregister listener for changes to [DEVICE_STATE_ROTATION_LOCK] setting. */
    fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener)

    /**
     * Write [deviceState]'s setting value as [autoRotate], for [DEVICE_STATE_ROTATION_LOCK] setting.
     */
    fun updateSetting(deviceState: Int, autoRotate: Boolean)

    /** Get [DEVICE_STATE_ROTATION_LOCK] setting value for [deviceState]. */
    fun getRotationLockSetting(deviceState: Int): Int

    /** Returns true if auto-rotate setting is OFF for [deviceState]. */
    fun isRotationLocked(deviceState: Int): Boolean

    /** Returns true if the auto-rotate setting value for all device states is OFF. */
    fun isRotationLockedForAllStates(): Boolean

    /** Returns a list of device states and their respective auto rotate setting availability. */
    fun getSettableDeviceStates(): List<SettableDeviceState>
}

/** Represents a device state and whether it has an auto-rotation setting. */
data class SettableDeviceState(
    /** Returns the device state associated with this object. */
    val deviceState: Int,
    /** Returns whether there is an auto-rotation setting for this device state. */
    val isSettable: Boolean
)

+191 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2025 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.database.ContentObserver
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
import android.util.Log
import android.util.SparseIntArray
import com.android.internal.R
import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener
import com.android.window.flags.Flags
import java.util.concurrent.Executor

/**
 * Implementation of [DeviceStateAutoRotateSettingManager]. This implementation is a part of
 * refactoring, it should be used when [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR]
 * is enabled.
 */
class DeviceStateAutoRotateSettingManagerImpl(
    context: Context,
    backgroundExecutor: Executor,
    private val secureSettings: SecureSettings,
    private val mainHandler: Handler,
    private val posturesHelper: PosturesHelper,
) : DeviceStateAutoRotateSettingManager {
    // TODO: b/397928958 rename the fields and apis from rotationLock to autoRotate.

    private val settingListeners: MutableList<DeviceStateAutoRotateSettingListener> =
        mutableListOf()
    private val fallbackPostureMap = SparseIntArray()
    private val settableDeviceState: MutableList<SettableDeviceState> = mutableListOf()

    private val autoRotateSettingValue: String
        get() = secureSettings.getStringForUser(DEVICE_STATE_ROTATION_LOCK, UserHandle.USER_CURRENT)

    init {
        loadAutoRotateDeviceStates(context)
        val contentObserver =
            object : ContentObserver(mainHandler) {
                override fun onChange(selfChange: Boolean) = notifyListeners()
            }
        backgroundExecutor.execute {
            secureSettings.registerContentObserver(
                DEVICE_STATE_ROTATION_LOCK, false, contentObserver, UserHandle.USER_CURRENT
            )
        }
    }

    override fun registerListener(settingListener: DeviceStateAutoRotateSettingListener) {
        settingListeners.add(settingListener)
    }

    override fun unregisterListener(settingListener: DeviceStateAutoRotateSettingListener) {
        if (!settingListeners.remove(settingListener)) {
            Log.w(TAG, "Attempting to unregister a listener hadn't been registered")
        }
    }

    override fun getRotationLockSetting(deviceState: Int): Int {
        val devicePosture = posturesHelper.deviceStateToPosture(deviceState)
        val serializedSetting = autoRotateSettingValue
        val autoRotateSetting = extractSettingForDevicePosture(devicePosture, serializedSetting)

        // If the setting is ignored for this posture, check the fallback posture.
        if (autoRotateSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
            val fallbackPosture =
                fallbackPostureMap.get(devicePosture, DEVICE_STATE_ROTATION_LOCK_IGNORED)
            return extractSettingForDevicePosture(fallbackPosture, serializedSetting)
        }

        return autoRotateSetting
    }

    override fun isRotationLocked(deviceState: Int) =
        getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED

    override fun isRotationLockedForAllStates(): Boolean =
        convertSerializedSettingToMap(autoRotateSettingValue).all { (_, value) ->
            value == DEVICE_STATE_ROTATION_LOCK_LOCKED
        }

    override fun getSettableDeviceStates(): List<SettableDeviceState> = settableDeviceState

    override fun updateSetting(deviceState: Int, autoRotate: Boolean) {
        // TODO: b/350946537 - Create IPC to update the setting, and call it here.
        throw UnsupportedOperationException("API updateSetting is not implemented yet")
    }

    private fun notifyListeners() =
        settingListeners.forEach { listener -> listener.onSettingsChanged() }

    private fun loadAutoRotateDeviceStates(context: Context) {
        val perDeviceStateAutoRotateDefaults =
            context.resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
        for (entry in perDeviceStateAutoRotateDefaults) {
            entry.parsePostureEntry()?.let { (posture, autoRotate, fallbackPosture) ->
                if (autoRotate == DEVICE_STATE_ROTATION_LOCK_IGNORED && fallbackPosture != null) {
                    fallbackPostureMap.put(posture, fallbackPosture)
                }
                settableDeviceState.add(
                    SettableDeviceState(posture, autoRotate != DEVICE_STATE_ROTATION_LOCK_IGNORED)
                )
            }
        }
    }

    private fun convertSerializedSettingToMap(serializedSetting: String): Map<Int, Int> {
        if (serializedSetting.isEmpty()) return emptyMap()
        return try {
            serializedSetting
                .split(SEPARATOR_REGEX)
                .hasEvenSize()
                .chunked(2)
                .mapNotNull(::parsePostureSettingPair)
                .toMap()
        } catch (e: Exception) {
            Log.w(
                TAG,
                "Invalid format in serializedSetting=$serializedSetting: ${e.message}"
            )
            return emptyMap()
        }
    }

    private fun List<String>.hasEvenSize(): List<String> {
        if (this.size % 2 != 0) {
            throw IllegalStateException("Odd number of elements in the list")
        }
        return this
    }

    private fun parsePostureSettingPair(settingPair: List<String>): Pair<Int, Int>? {
        return settingPair.let { (keyStr, valueStr) ->
            val key = keyStr.toIntOrNull()
            val value = valueStr.toIntOrNull()
            if (key != null && value != null && value in 0..2) {
                key to value
            } else {
                Log.w(TAG, "Invalid key or value in pair: $keyStr, $valueStr")
                null // Invalid pair, skip it
            }
        }
    }

    private fun extractSettingForDevicePosture(
        devicePosture: Int,
        serializedSetting: String
    ): Int =
        convertSerializedSettingToMap(serializedSetting)[devicePosture]
            ?: DEVICE_STATE_ROTATION_LOCK_IGNORED

    private fun String.parsePostureEntry(): Triple<Int, Int, Int?>? {
        val values = split(SEPARATOR_REGEX)
        if (values.size !in 2..3) { // It should contain 2 or 3 values.
            Log.w(TAG, "Invalid number of values in entry: '$this'")
            return null
        }
        return try {
            val posture = values[0].toInt()
            val rotationLockSetting = values[1].toInt()
            val fallbackPosture = if (values.size == 3) values[2].toIntOrNull() else null
            Triple(posture, rotationLockSetting, fallbackPosture)
        } catch (e: NumberFormatException) {
            Log.w(TAG, "Invalid number format in '$this': ${e.message}")
            null
        }
    }

    companion object {
        private const val TAG = "DeviceStateAutoRotate"
        private const val SEPARATOR_REGEX = ":"
    }
}
+31 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2025 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.
 */
@file:JvmName("DeviceStateAutoRotateSettingUtils")

package com.android.settingslib.devicestate

import android.content.Context
import com.android.internal.R

/** Returns true if device-state based rotation lock settings are enabled. */
object DeviceStateAutoRotateSettingUtils {
    @JvmStatic
    fun isDeviceStateRotationLockEnabled(context: Context) =
        context.resources
            .getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
            .isNotEmpty()
}
+9 −56
Original line number Original line Diff line number Diff line
@@ -20,6 +20,8 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORE
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;


import static com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener;

import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
@@ -43,7 +45,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.HashSet;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.Set;


/**
/**
@@ -58,7 +59,7 @@ public final class DeviceStateRotationLockSettingsManager {
    private static DeviceStateRotationLockSettingsManager sSingleton;
    private static DeviceStateRotationLockSettingsManager sSingleton;


    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
    private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>();
    private final Set<DeviceStateAutoRotateSettingListener> mListeners = new HashSet<>();
    private final SecureSettings mSecureSettings;
    private final SecureSettings mSecureSettings;
    private final PosturesHelper mPosturesHelper;
    private final PosturesHelper mPosturesHelper;
    private String[] mPostureRotationLockDefaults;
    private String[] mPostureRotationLockDefaults;
@@ -127,20 +128,20 @@ public final class DeviceStateRotationLockSettingsManager {
    }
    }


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


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


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

    /** Listener for changes in device-state based rotation lock settings */
    public interface DeviceStateRotationLockSettingsListener {
        /** Called whenever the settings have changed. */
        void onSettingsChanged();
    }

    /** Represents a device state and whether it has an auto-rotation setting. */
    public static class SettableDeviceState {
        private final int mDeviceState;
        private final boolean mIsSettable;

        SettableDeviceState(int deviceState, boolean isSettable) {
            mDeviceState = deviceState;
            mIsSettable = isSettable;
        }

        /** Returns the device state associated with this object. */
        public int getDeviceState() {
            return mDeviceState;
        }

        /** Returns whether there is an auto-rotation setting for this device state. */
        public boolean isSettable() {
            return mIsSettable;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof SettableDeviceState)) return false;
            SettableDeviceState that = (SettableDeviceState) o;
            return mDeviceState == that.mDeviceState && mIsSettable == that.mIsSettable;
        }

        @Override
        public int hashCode() {
            return Objects.hash(mDeviceState, mIsSettable);
        }

        @Override
        public String toString() {
            return "SettableDeviceState{"
                    + "mDeviceState=" + mDeviceState
                    + ", mIsSettable=" + mIsSettable
                    + '}';
        }
    }
}
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -19,7 +19,7 @@ package com.android.settingslib.devicestate;
import android.database.ContentObserver;
import android.database.ContentObserver;


/** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */
/** Minimal wrapper interface around {@link android.provider.Settings.Secure} for easier testing. */
interface SecureSettings {
public interface SecureSettings {


    void putStringForUser(String name, String value, int userHandle);
    void putStringForUser(String name, String value, int userHandle);


Loading