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

Commit 88bc34f7 authored by Christian Göllner's avatar Christian Göllner
Browse files

Device state rotation: fallback to defaults for invalid ignored state

Detect when we have a persisted invalid ignored state, and fallback to
defaults.
This can happen when the ids of device states change.

This will cause persisted settings to be wiped, and defaults to be
used.

+ Add more logging for easier debugging

Bug: 269822414
Bug: 261968395
Test: Unit tests
Test: Dumpsys
Change-Id: I6d1845c758cac80ba5af14a6f6b2de40120bec0d
parent 7c4a157a
Loading
Loading
Loading
Loading
+43 −3
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseIntArray;

@@ -36,6 +37,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -57,6 +59,7 @@ public final class DeviceStateRotationLockSettingsManager {
    private final SecureSettings mSecureSettings;
    private String[] mDeviceStateRotationLockDefaults;
    private SparseIntArray mDeviceStateRotationLockSettings;
    private SparseIntArray mDeviceStateDefaultRotationLockSettings;
    private SparseIntArray mDeviceStateRotationLockFallbackSettings;
    private String mLastSettingValue;
    private List<SettableDeviceState> mSettableDeviceStates;
@@ -93,9 +96,7 @@ public final class DeviceStateRotationLockSettingsManager {
    /** 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;
                .getStringArray(R.array.config_perDeviceStateRotationLockDefaults).length > 0;
    }

    private void listenForSettingsChange() {
@@ -228,6 +229,15 @@ public final class DeviceStateRotationLockSettingsManager {
            try {
                key = Integer.parseInt(values[i++]);
                value = Integer.parseInt(values[i++]);
                boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED;
                boolean isDefaultValueIgnored = mDeviceStateDefaultRotationLockSettings.get(key)
                        == DEVICE_STATE_ROTATION_LOCK_IGNORED;
                if (isPersistedValueIgnored != isDefaultValueIgnored) {
                    Log.w(TAG, "Conflict for ignored device state " + key
                            + ". Falling back on defaults");
                    fallbackOnDefaults();
                    return;
                }
                mDeviceStateRotationLockSettings.put(key, value);
            } catch (NumberFormatException e) {
                Log.wtf(TAG, "Error deserializing one of the saved settings", e);
@@ -276,6 +286,9 @@ public final class DeviceStateRotationLockSettingsManager {
    }

    private void persistSettingIfChanged(String newSettingValue) {
        Log.v(TAG, "persistSettingIfChanged: "
                + "last=" + mLastSettingValue + ", "
                + "new=" + newSettingValue);
        if (TextUtils.equals(mLastSettingValue, newSettingValue)) {
            return;
        }
@@ -288,6 +301,8 @@ public final class DeviceStateRotationLockSettingsManager {

    private void loadDefaults() {
        mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length);
        mDeviceStateDefaultRotationLockSettings = new SparseIntArray(
                mDeviceStateRotationLockDefaults.length);
        mDeviceStateRotationLockSettings = new SparseIntArray(
                mDeviceStateRotationLockDefaults.length);
        mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
@@ -311,6 +326,7 @@ public final class DeviceStateRotationLockSettingsManager {
                boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
                mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
                mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
                mDeviceStateDefaultRotationLockSettings.put(deviceState, rotationLockSetting);
            } catch (NumberFormatException e) {
                Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
                return;
@@ -318,6 +334,22 @@ public final class DeviceStateRotationLockSettingsManager {
        }
    }

    /** Dumps internal state. */
    public void dump(IndentingPrintWriter pw) {
        pw.println("DeviceStateRotationLockSettingsManager");
        pw.increaseIndent();
        pw.println("mDeviceStateRotationLockDefaults: " + Arrays.toString(
                mDeviceStateRotationLockDefaults));
        pw.println("mDeviceStateDefaultRotationLockSettings: "
                + mDeviceStateDefaultRotationLockSettings);
        pw.println("mDeviceStateRotationLockSettings: " + mDeviceStateRotationLockSettings);
        pw.println("mDeviceStateRotationLockFallbackSettings: "
                + mDeviceStateRotationLockFallbackSettings);
        pw.println("mSettableDeviceStates: " + mSettableDeviceStates);
        pw.println("mLastSettingValue: " + mLastSettingValue);
        pw.decreaseIndent();
    }

    /**
     * Called when the persisted settings have changed, requiring a reinitialization of the
     * in-memory map.
@@ -372,5 +404,13 @@ public final class DeviceStateRotationLockSettingsManager {
        public int hashCode() {
            return Objects.hash(mDeviceState, mIsSettable);
        }

        @Override
        public String toString() {
            return "SettableDeviceState{"
                    + "mDeviceState=" + mDeviceState
                    + ", mIsSettable=" + mIsSettable
                    + '}';
        }
    }
}
+41 −0
Original line number Diff line number Diff line
@@ -33,7 +33,10 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;

import com.google.common.truth.Expect;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +48,8 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class DeviceStateRotationLockSettingsManagerTest {

    @Rule public Expect mExpect = Expect.create();

    @Mock private Context mMockContext;
    @Mock private Resources mMockResources;

@@ -117,4 +122,40 @@ public class DeviceStateRotationLockSettingsManagerTest {
                new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false)
        ).inOrder();
    }

    @Test
    public void persistedInvalidIgnoredState_returnsDefaults() {
        when(mMockResources.getStringArray(
                R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
                new String[]{"0:1", "1:0:2", "2:2"});
        // Here 2 has IGNORED, and in the defaults 1 has IGNORED.
        persistSettings("0:2:2:0:1:2");
        DeviceStateRotationLockSettingsManager manager =
                new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);

        mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(1);
        mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(2);
        mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(2);
    }

    @Test
    public void persistedValidValues_returnsPersistedValues() {
        when(mMockResources.getStringArray(
                R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
                new String[]{"0:1", "1:0:2", "2:2"});
        persistSettings("0:2:1:0:2:1");
        DeviceStateRotationLockSettingsManager manager =
                new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);

        mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(2);
        mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(1);
        mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(1);
    }

    private void persistSettings(String value) {
        mFakeSecureSettings.putStringForUser(
                Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
                value,
                UserHandle.USER_CURRENT);
    }
}
+30 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.log.dagger;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;

import javax.inject.Qualifier;

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface DeviceStateAutoRotationLog {
}
+10 −0
Original line number Diff line number Diff line
@@ -369,6 +369,16 @@ public class LogModule {
        return factory.create("KeyguardFaceAuthManagerLog", 300);
    }

    /**
     * Provides a {@link LogBuffer} for Device State Auto-Rotation logs.
     */
    @Provides
    @SysUISingleton
    @DeviceStateAutoRotationLog
    public static LogBuffer provideDeviceStateAutoRotationLogBuffer(LogBufferFactory factory) {
        return factory.create("DeviceStateAutoRotationLog", 100);
    }

    /**
     * Provides a {@link LogBuffer} for bluetooth-related logs.
     */
+49 −32
Original line number Diff line number Diff line
@@ -22,13 +22,18 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Trace;
import android.util.Log;
import android.util.IndentingPrintWriter;

import androidx.annotation.NonNull;

import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;

import java.io.PrintWriter;
import java.util.concurrent.Executor;

import javax.inject.Inject;
@@ -39,19 +44,19 @@ import javax.inject.Inject;
 */
@SysUISingleton
public final class DeviceStateRotationLockSettingController
        implements Listenable, RotationLockController.RotationLockControllerCallback {

    private static final String TAG = "DSRotateLockSettingCon";
        implements Listenable, RotationLockController.RotationLockControllerCallback, Dumpable {

    private final RotationPolicyWrapper mRotationPolicyWrapper;
    private final DeviceStateManager mDeviceStateManager;
    private final Executor mMainExecutor;
    private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager;
    private final DeviceStateRotationLockSettingControllerLogger mLogger;

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

@@ -60,21 +65,27 @@ public final class DeviceStateRotationLockSettingController
            RotationPolicyWrapper rotationPolicyWrapper,
            DeviceStateManager deviceStateManager,
            @Main Executor executor,
            DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) {
            DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager,
            DeviceStateRotationLockSettingControllerLogger logger,
            DumpManager dumpManager) {
        mRotationPolicyWrapper = rotationPolicyWrapper;
        mDeviceStateManager = deviceStateManager;
        mMainExecutor = executor;
        mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager;
        mLogger = logger;
        dumpManager.registerDumpable(this);
    }

    @Override
    public void setListening(boolean listening) {
        mLogger.logListeningChange(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);
            mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState);
            mDeviceStateRotationLockSettingsListener = () ->
                    readPersistedSetting("deviceStateRotationLockChange", mDeviceState);
            mDeviceStateRotationLockSettingsManager.registerListener(
                    mDeviceStateRotationLockSettingsListener);
        } else {
@@ -89,35 +100,28 @@ public final class DeviceStateRotationLockSettingController
    }

    @Override
    public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
        if (mDeviceState == -1) {
            Log.wtf(TAG, "Device state was not initialized.");
    public void onRotationLockStateChanged(boolean newRotationLocked, boolean affordanceVisible) {
        int deviceState = mDeviceState;
        boolean currentRotationLocked = mDeviceStateRotationLockSettingsManager
                .isRotationLocked(deviceState);
        mLogger.logRotationLockStateChanged(deviceState, newRotationLocked, currentRotationLocked);
        if (deviceState == -1) {
            return;
        }

        if (rotationLocked
                == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) {
            Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
        if (newRotationLocked == currentRotationLocked) {
            return;
        }

        saveNewRotationLockSetting(rotationLocked);
        saveNewRotationLockSetting(newRotationLocked);
    }

    private void saveNewRotationLockSetting(boolean isRotationLocked) {
        Log.v(
                TAG,
                "saveNewRotationLockSetting [state="
                        + mDeviceState
                        + "] [isRotationLocked="
                        + isRotationLocked
                        + "]");

        mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked);
        int deviceState = mDeviceState;
        mLogger.logSaveNewRotationLockSetting(isRotationLocked, deviceState);
        mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked);
    }

    private void updateDeviceState(int state) {
        Log.v(TAG, "updateDeviceState [state=" + state + "]");
        mLogger.logUpdateDeviceState(mDeviceState, state);
        if (Trace.isEnabled()) {
            Trace.traceBegin(
                    Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
@@ -127,22 +131,26 @@ public final class DeviceStateRotationLockSettingController
                return;
            }

            readPersistedSetting(state);
            readPersistedSetting("updateDeviceState", state);
        } finally {
            Trace.endSection();
        }
    }

    private void readPersistedSetting(int state) {
    private void readPersistedSetting(String caller, int state) {
        int rotationLockSetting =
                mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
        boolean shouldBeLocked = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
        boolean isLocked = mRotationPolicyWrapper.isRotationLocked();

        mLogger.readPersistedSetting(caller, state, rotationLockSetting, shouldBeLocked, isLocked);

        if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
            // This should not happen. Device states that have an ignored setting, should also
            // specify a fallback device state which is not 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.w(TAG, "Missing fallback. Ignoring new device state: " + state);
            return;
        }

@@ -150,9 +158,18 @@ public final class DeviceStateRotationLockSettingController
        mDeviceState = 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);
        if (shouldBeLocked != isLocked) {
            mRotationPolicyWrapper.setRotationLock(shouldBeLocked);
        }
    }

    @Override
    public void dump(@NonNull PrintWriter printWriter, @NonNull String[] args) {
        IndentingPrintWriter pw = new IndentingPrintWriter(printWriter);
        mDeviceStateRotationLockSettingsManager.dump(pw);
        pw.println("DeviceStateRotationLockSettingController");
        pw.increaseIndent();
        pw.println("mDeviceState: " + mDeviceState);
        pw.decreaseIndent();
    }
}
Loading