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

Commit 21055243 authored by chihtinglo's avatar chihtinglo
Browse files

Update MMAD settings to radio buttons

Keep the Standard/Expanded selectable when dark theme main toggle is off

Bug: 416328688
Test: atest ForceInvertPreferenceControllerTest
Test: Manual. See screenshot in the bug.
Flag: android.view.accessibility.force_invert_color
Change-Id: I6a90a1a24fd5d0f0853eae5746de62fe28d2804b
parent 4c4c978d
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -5824,10 +5824,16 @@
        screen magnification on app transitions</string>
    <!-- Title for the accessibility preference to power button to end a call. [CHAR LIMIT=35] -->
    <string name="accessibility_power_button_ends_call_prerefence_title">Power button ends call</string>
    <!-- Title for the dark theme group category -->
    <string name="dark_theme_version_category">Mode</string>
    <!-- Title for the accessibility preference for applying the original dark theme. [CHAR LIMIT=35] -->
    <string name="accessibility_standard_dark_theme_title">Standard</string>
    <!-- Summary for the accessibility preference for applying the original dark theme. [CHAR LIMIT=100] -->
    <string name="accessibility_standard_dark_theme_summary">Applies Dark theme across your device and supported apps</string>
    <!-- Title for the accessibility preference for forcing all apps to use dark theme. [CHAR LIMIT=35] -->
    <string name="accessibility_force_invert_title">Make more apps dark</string>
    <string name="accessibility_expanded_dark_theme_title">Expanded</string>
    <!-- Summary for the accessibility preference for forcing all apps to use dark theme. [CHAR LIMIT=100] -->
    <string name="accessibility_force_invert_summary">Expands dark theme to more apps. May not work with all apps.</string>
    <string name="accessibility_expanded_dark_theme_summary">Automatically applies Dark theme to more apps for improved accessibility</string>
    <!-- Title for the accessibility preference for disabling animations. [CHAR LIMIT=35] -->
    <string name="accessibility_disable_animations">Remove animations</string>
    <!-- Summary for the accessibility preference for disabling animations. [CHAR LIMIT=60] -->
+17 −7
Original line number Diff line number Diff line
@@ -36,15 +36,25 @@
        settings:controller="com.android.settings.display.TwilightLocationPreferenceController"/>

    <PreferenceCategory
        android:key="display_category"
        android:title="@string/accessibility_screen_option">
        android:key="dark_theme_group"
        android:title="@string/dark_theme_version_category"
        settings:controller="com.android.settings.accessibility.ForceInvertPreferenceController">
        <com.android.settingslib.widget.SelectorWithWidgetPreference
            android:key="standard_dark_theme"
            android:title="@string/accessibility_standard_dark_theme_title"
            android:summary="@string/accessibility_standard_dark_theme_summary"
            settings:searchable="true"/>

        <SwitchPreferenceCompat
            android:key="toggle_force_invert"
            android:summary="@string/accessibility_force_invert_summary"
            android:title="@string/accessibility_force_invert_title"
            settings:controller="com.android.settings.accessibility.ToggleForceInvertPreferenceController"/>
        <com.android.settingslib.widget.SelectorWithWidgetPreference
            android:key="expanded_dark_theme"
            android:title="@string/accessibility_expanded_dark_theme_title"
            android:summary="@string/accessibility_expanded_dark_theme_summary"
            settings:searchable="true"/>
    </PreferenceCategory>

    <PreferenceCategory
        android:key="display_category"
        android:title="@string/accessibility_screen_option">
        <DropDownPreference
            android:key="dark_ui_auto_mode"
            android:title="@string/dark_ui_auto_mode_title"
+103 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 * 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.
@@ -20,50 +20,84 @@ import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;

import android.content.Context;
import android.content.res.Configuration;
import android.provider.Settings;
import android.view.accessibility.Flags;

import androidx.annotation.NonNull;
import androidx.preference.Preference;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.widget.SelectorWithWidgetPreference;

/** A toggle preference controller for force invert (force dark). */
public class ToggleForceInvertPreferenceController extends TogglePreferenceController {
public class ForceInvertPreferenceController extends BasePreferenceController
        implements SelectorWithWidgetPreference.OnClickListener {

    public ToggleForceInvertPreferenceController(Context context, String preferenceKey) {
    @VisibleForTesting
    static final String STANDARD_DARK_THEME_KEY = "standard_dark_theme";
    @VisibleForTesting
    static final String EXPANDED_DARK_THEME_KEY = "expanded_dark_theme";

    @Nullable
    private SelectorWithWidgetPreference mStandardDarkThemePreference;
    @Nullable
    private SelectorWithWidgetPreference mExpandedDarkThemePreference;

    public ForceInvertPreferenceController(
            @NonNull Context context, @NonNull String preferenceKey) {
        super(context, preferenceKey);
    }

    @Override
    public boolean isChecked() {
        return Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, OFF) != OFF;
    public int getAvailabilityStatus() {
        return Flags.forceInvertColor() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
    }

    @Override
    public boolean setChecked(boolean isChecked) {
        return Settings.Secure.putInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, isChecked ? ON : OFF);
    public int getSliceHighlightMenuRes() {
        return R.string.menu_key_accessibility;
    }

    @Override
    public void updateState(@NonNull Preference preference) {
        super.updateState(preference);
        final boolean isDarkModeActivated = (mContext.getResources().getConfiguration().uiMode
                & Configuration.UI_MODE_NIGHT_YES) != 0;
        preference.setEnabled(isDarkModeActivated);
    public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference preference) {
        boolean isForceInvertEnabled = preference.getKey().equals(EXPANDED_DARK_THEME_KEY);
        if (mExpandedDarkThemePreference == null
                || isForceInvertEnabled == mExpandedDarkThemePreference.isChecked()) {
            // User selects the same preference as before, we perform an early return to avoid
            // unnecessary UI updates and IO operations.
            return;
        }
        updateSelectorPreferenceStatus(isForceInvertEnabled);
        Settings.Secure.putInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
                isForceInvertEnabled ? ON : OFF);
    }

    @Override
    public int getAvailabilityStatus() {
        return Flags.forceInvertColor() ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
    public void displayPreference(@NonNull PreferenceScreen screen) {
        super.displayPreference(screen);
        // initialize the status of the radio buttons
        PreferenceCategory preferenceCategory = screen.findPreference(getPreferenceKey());
        mStandardDarkThemePreference =
                preferenceCategory.findPreference(STANDARD_DARK_THEME_KEY);
        mExpandedDarkThemePreference =
                preferenceCategory.findPreference(EXPANDED_DARK_THEME_KEY);
        mStandardDarkThemePreference.setOnClickListener(this);
        mExpandedDarkThemePreference.setOnClickListener(this);
        boolean isForceInvertEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, OFF) != OFF;
        updateSelectorPreferenceStatus(isForceInvertEnabled);
    }

    @Override
    public int getSliceHighlightMenuRes() {
        return R.string.menu_key_accessibility;
    private void updateSelectorPreferenceStatus(boolean isForceInvertEnabled) {
        if (mStandardDarkThemePreference == null || mExpandedDarkThemePreference == null) {
            return;
        }
        mStandardDarkThemePreference.setChecked(!isForceInvertEnabled);
        mExpandedDarkThemePreference.setChecked(isForceInvertEnabled);
    }
}
+150 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 * 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.
@@ -23,107 +23,124 @@ import static com.android.settings.accessibility.AccessibilityUtil.State.ON;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.res.Configuration;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;

import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;

import com.android.settings.core.BasePreferenceController;
import com.android.settingslib.widget.SelectorWithWidgetPreference;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;

/** Tests for {@link ToggleForceInvertPreferenceController}. */
/** Tests for {@link ForceInvertPreferenceController}. */
@RunWith(RobolectricTestRunner.class)
public class ToggleForceInvertPreferenceControllerTest {
public class ForceInvertPreferenceControllerTest {

    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
    @Rule
    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
    @Mock
    private PreferenceScreen mScreen;
    @Mock
    private PreferenceCategory mPreferenceCategory;

    private final Context mContext = ApplicationProvider.getApplicationContext();
    private ToggleForceInvertPreferenceController mController;
    private ForceInvertPreferenceController mController;
    private SelectorWithWidgetPreference mStandardDarkThemePreference;
    private SelectorWithWidgetPreference mExpandedDarkThemePreference;

    @Before
    public void setUp() {
        mController = new ToggleForceInvertPreferenceController(mContext, "toggle_force_invert");
        mController = new ForceInvertPreferenceController(mContext, "dark_theme_group");
        mStandardDarkThemePreference = new SelectorWithWidgetPreference(mContext);
        mStandardDarkThemePreference
                .setKey(ForceInvertPreferenceController.STANDARD_DARK_THEME_KEY);
        mExpandedDarkThemePreference = new SelectorWithWidgetPreference(mContext);
        mExpandedDarkThemePreference
                .setKey(ForceInvertPreferenceController.EXPANDED_DARK_THEME_KEY);
        when(mScreen.findPreference(mController.getPreferenceKey()))
                .thenReturn(mPreferenceCategory);
        when(mPreferenceCategory
                .findPreference(ForceInvertPreferenceController.STANDARD_DARK_THEME_KEY))
                .thenReturn(mStandardDarkThemePreference);
        when(mPreferenceCategory
                .findPreference(ForceInvertPreferenceController.EXPANDED_DARK_THEME_KEY))
                .thenReturn(mExpandedDarkThemePreference);
    }

    @Test
    @RequiresFlagsDisabled(FLAG_FORCE_INVERT_COLOR)
    public void flagOff_getAvailabilityStatus_shouldReturnUnsupported() {
    public void getAvailabilityStatus_flagOff_shouldReturnUnsupported() {
        assertThat(mController.getAvailabilityStatus())
                .isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE);
    }

    @Test
    @RequiresFlagsEnabled(FLAG_FORCE_INVERT_COLOR)
    public void flagOn_getAvailabilityStatus_shouldReturnAvailable() {
    public void getAvailabilityStatus_flagOn_shouldReturnAvailable() {
        assertThat(mController.getAvailabilityStatus())
                .isEqualTo(BasePreferenceController.AVAILABLE);
    }

    @Test
    public void updateState_darkModeOn_preferenceEnabled() {
        Configuration config = mContext.getResources().getConfiguration();
        config.uiMode = Configuration.UI_MODE_NIGHT_YES;
        mContext.getResources().updateConfiguration(config, null);
    public void settingOff_reflectsCorrectValue() {
        setEnabled(false);

        Preference preference = mock(Preference.class);
        mController.updateState(preference);
        mController.displayPreference(mScreen);

        verify(preference).setEnabled(true);
        assertThat(mStandardDarkThemePreference.isChecked()).isTrue();
        assertThat(mExpandedDarkThemePreference.isChecked()).isFalse();
    }

    @Test
    public void updateState_darkModeOff_preferenceDisabled() {
        Configuration config = mContext.getResources().getConfiguration();
        config.uiMode = Configuration.UI_MODE_NIGHT_NO;
        mContext.getResources().updateConfiguration(config, null);
    public void settingOn_reflectsCorrectValue() {
        setEnabled(true);

        Preference preference = mock(Preference.class);
        mController.updateState(preference);
        mController.displayPreference(mScreen);

        verify(preference).setEnabled(false);
        assertThat(mStandardDarkThemePreference.isChecked()).isFalse();
        assertThat(mExpandedDarkThemePreference.isChecked()).isTrue();
    }

    @Test
    public void settingOff_reflectsCorrectValue() {
        setEnabled(false);
        assertThat(mController.isChecked()).isFalse();
    }
    public void onRadioButtonClicked_standardDarkTheme_settingChanges() {
        mController.displayPreference(mScreen);
        mController.onRadioButtonClicked(mStandardDarkThemePreference);

    @Test
    public void settingOn_reflectsCorrectValue() {
        setEnabled(true);
        assertThat(mController.isChecked()).isTrue();
        assertThat(mStandardDarkThemePreference.isChecked()).isTrue();
        assertThat(mExpandedDarkThemePreference.isChecked()).isFalse();
        boolean isForceInvertEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, /* def=*/ -1) == ON;
        assertThat(isForceInvertEnabled).isFalse();
    }

    @Test
    public void onCheck_settingChanges() {
        setEnabled(false);

        mController.setChecked(true);
        assertThat(isEnabled()).isTrue();

        mController.setChecked(false);
        assertThat(isEnabled()).isFalse();
    }
    public void onRadioButtonClicked_expandedDarkTheme_settingChanges() {
        mController.displayPreference(mScreen);
        mController.onRadioButtonClicked(mExpandedDarkThemePreference);

    private boolean isEnabled() {
        return Settings.Secure.getInt(mContext.getContentResolver(),
        assertThat(mStandardDarkThemePreference.isChecked()).isFalse();
        assertThat(mExpandedDarkThemePreference.isChecked()).isTrue();
        boolean isForceInvertEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, /* def=*/ -1) == ON;
        assertThat(isForceInvertEnabled).isTrue();
    }

    private void setEnabled(boolean enabled) {
Loading