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

Commit 6837ea26 authored by Alex Florescu's avatar Alex Florescu Committed by Automerger Merge Worker
Browse files

Merge "Introduce separate rotation lock settings per device state." into sc-v2-dev am: a50aa7c8

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15107814

Change-Id: I9ebfa53a80d3989291b158fedf1773333ccdf19b
parents fdd6b8b6 a50aa7c8
Loading
Loading
Loading
Loading
+55 −0
Original line number Original line Diff line number Diff line
@@ -10119,6 +10119,61 @@ public final class Settings {
        @Readable
        @Readable
        public static final String GAME_DASHBOARD_ALWAYS_ON = "game_dashboard_always_on";
        public static final String GAME_DASHBOARD_ALWAYS_ON = "game_dashboard_always_on";
        /**
         * For this device state, no specific auto-rotation lock setting should be applied.
         * If the user toggles the auto-rotate lock in this state, the setting will apply to the
         * previously valid device state.
         * @hide
         */
        public static final int DEVICE_STATE_ROTATION_LOCK_IGNORED = 0;
        /**
         * For this device state, the setting for auto-rotation is locked.
         * @hide
         */
        public static final int DEVICE_STATE_ROTATION_LOCK_LOCKED = 1;
        /**
         * For this device state, the setting for auto-rotation is unlocked.
         * @hide
         */
        public static final int DEVICE_STATE_ROTATION_LOCK_UNLOCKED = 2;
        /**
         * The different settings that can be used as values with
         * {@link #DEVICE_STATE_ROTATION_LOCK}.
         * @hide
         */
        @IntDef(prefix = {"DEVICE_STATE_ROTATION_LOCK_"}, value = {
                DEVICE_STATE_ROTATION_LOCK_IGNORED,
                DEVICE_STATE_ROTATION_LOCK_LOCKED,
                DEVICE_STATE_ROTATION_LOCK_UNLOCKED,
        })
        @Retention(RetentionPolicy.SOURCE)
        @interface DeviceStateRotationLockSetting {
        }
        /**
         * Rotation lock setting keyed on device state.
         *
         * This holds a serialized map using int keys that represent Device States and value of
         * {@link DeviceStateRotationLockSetting} representing the rotation lock setting for that
         * device state.
         *
         * 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>
         * </ul>
         *
         * @hide
         */
        public static final String DEVICE_STATE_ROTATION_LOCK =
                "device_state_rotation_lock";
        /**
        /**
         * These entries are considered common between the personal and the managed profile,
         * These entries are considered common between the personal and the managed profile,
         * since the managed profile doesn't get to change them.
         * since the managed profile doesn't get to change them.
+11 −0
Original line number Original line Diff line number Diff line
@@ -666,6 +666,17 @@
         display is powered on at the same time. -->
         display is powered on at the same time. -->
    <bool name="config_supportsConcurrentInternalDisplays">true</bool>
    <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. -->
    <string-array name="config_perDeviceStateRotationLockDefaults" />


    <!-- Desk dock behavior -->
    <!-- Desk dock behavior -->


    <!-- The number of degrees to rotate the display when the device is in a desk dock.
    <!-- The number of degrees to rotate the display when the device is in a desk dock.
+2 −0
Original line number Original line Diff line number Diff line
@@ -3838,6 +3838,8 @@
  <java-symbol type="string" name="config_foldedArea" />
  <java-symbol type="string" name="config_foldedArea" />
  <java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
  <java-symbol type="bool" name="config_supportsConcurrentInternalDisplays" />
  <java-symbol type="bool" name="config_unfoldTransitionEnabled" />
  <java-symbol type="bool" name="config_unfoldTransitionEnabled" />
  <java-symbol type="array" name="config_perDeviceStateRotationLockDefaults" />



  <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
  <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
  <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />
  <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />
+29 −0
Original line number Original line Diff line number Diff line
@@ -34,7 +34,9 @@ import static android.provider.settings.validators.SettingsValidators.TILE_LIST_
import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.TTS_LIST_VALIDATOR;


import android.provider.Settings.Secure;
import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.ArraySet;


import java.util.Map;
import java.util.Map;


@@ -287,5 +289,32 @@ public class SecureSettingsValidators {
        VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR);
        VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR);
        VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
        VALIDATORS.put(Secure.NOTIFICATION_BUBBLES, BOOLEAN_VALIDATOR);
        VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR);
        VALIDATORS.put(Secure.LOCATION_TIME_ZONE_DETECTION_ENABLED, BOOLEAN_VALIDATOR);
        VALIDATORS.put(Secure.DEVICE_STATE_ROTATION_LOCK, value -> {
            if (TextUtils.isEmpty(value)) {
                return true;
            }
            String[] intValues = value.split(":");
            if (intValues.length % 2 != 0) {
                return false;
            }
            InclusiveIntegerRangeValidator enumValidator =
                    new InclusiveIntegerRangeValidator(
                            Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED,
                            Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
            ArraySet<String> keys = new ArraySet<>();
            for (int i = 0; i < intValues.length - 1; ) {
                String entryKey = intValues[i++];
                String entryValue = intValues[i++];
                if (!NON_NEGATIVE_INTEGER_VALIDATOR.validate(entryKey)
                        || !enumValidator.validate(entryValue)) {
                    return false;
                }
                // If the same device state key was specified more than once, this is invalid
                if (!keys.add(entryKey)) {
                    return false;
                }
            }
            return true;
        });
    }
    }
}
}
+239 −0
Original line number Original line Diff line number Diff line
/*
 * 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);
            }
        }
    }

}
Loading