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

Commit 69f053be authored by Shivangi Dubey's avatar Shivangi Dubey Committed by Android (Google) Code Review
Browse files

Merge "Create new device state auto rotate setting manager" into main

parents a17f2afa 4edcbc29
Loading
Loading
Loading
Loading
+69 −0
Original line number 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 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 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 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_UNLOCKED;

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

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

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

    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 PosturesHelper mPosturesHelper;
    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.
     */
    public void registerListener(DeviceStateRotationLockSettingsListener runnable) {
    public void registerListener(DeviceStateAutoRotateSettingListener 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.
     */
    public void unregisterListener(
            DeviceStateRotationLockSettingsListener deviceStateRotationLockSettingsListener) {
        if (!mListeners.remove(deviceStateRotationLockSettingsListener)) {
            DeviceStateAutoRotateSettingListener deviceStateAutoRotateSettingListener) {
        if (!mListeners.remove(deviceStateAutoRotateSettingListener)) {
            Log.w(TAG, "Attempting to unregister a listener hadn't been registered");
        }
    }
@@ -379,56 +380,8 @@ public final class DeviceStateRotationLockSettingsManager {
    }

    private void notifyListeners() {
        for (DeviceStateRotationLockSettingsListener r : mListeners) {
        for (DeviceStateAutoRotateSettingListener 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();
    }

    /** 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 Diff line number Diff line
@@ -19,7 +19,7 @@ package com.android.settingslib.devicestate;
import android.database.ContentObserver;

/** 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);

Loading