From 4bf537a815367cc49f9651d664ceb2e60c8ff0a0 Mon Sep 17 00:00:00 2001 From: Isaac Chai Date: Fri, 12 Apr 2024 17:38:43 +0000 Subject: [PATCH] Color correction saturation level settings Adding settings UI for color saturation level change, guarded by feature flag. Test: Locally tested + unit tests added Bug: 322829049 Flag: com.android.server.accessibility.enable_color_correction_saturation Change-Id: Ifa816647b16534ab6da770584d3de0628734aed5 --- res/values/strings.xml | 2 + res/xml/accessibility_daltonizer_settings.xml | 6 + ...SaturationSeekbarPreferenceController.java | 97 +++++++++ .../ToggleDaltonizerPreferenceFragment.java | 6 +- ...rationSeekbarPreferenceControllerTest.java | 197 ++++++++++++++++++ 5 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index a11c96c7f07..a65911c0f70 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5371,6 +5371,8 @@ Blue-yellow Grayscale + + Intensity Green weak, deuteranomaly diff --git a/res/xml/accessibility_daltonizer_settings.xml b/res/xml/accessibility_daltonizer_settings.xml index 91b163bf68c..1f770306890 100644 --- a/res/xml/accessibility_daltonizer_settings.xml +++ b/res/xml/accessibility_daltonizer_settings.xml @@ -51,4 +51,10 @@ android:persistent="false" android:title="@string/daltonizer_mode_grayscale_title" /> + diff --git a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java new file mode 100644 index 00000000000..7dcd6612ac4 --- /dev/null +++ b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 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.settings.accessibility; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.PreferenceScreen; + +import com.android.server.accessibility.Flags; +import com.android.settings.core.SliderPreferenceController; +import com.android.settings.widget.SeekBarPreference; + +/** + * The controller of the seekbar preference for the saturation level of color correction. + */ +public class DaltonizerSaturationSeekbarPreferenceController extends SliderPreferenceController { + + private static final int DEFAULT_SATURATION_LEVEL = 7; + private static final int SATURATION_MAX = 10; + private static final int SATURATION_MIN = 0; + + private int mSliderPosition; + private final ContentResolver mContentResolver; + + public DaltonizerSaturationSeekbarPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + mContentResolver = context.getContentResolver(); + mSliderPosition = Settings.Secure.getInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + DEFAULT_SATURATION_LEVEL); + setSliderPosition(mSliderPosition); + // TODO: Observer color correction on/off and enable/disable based on secure settings. + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + SeekBarPreference preference = screen.findPreference(getPreferenceKey()); + preference.setMax(getMax()); + preference.setMin(getMin()); + preference.setProgress(mSliderPosition); + preference.setContinuousUpdates(true); + } + + @Override + public int getAvailabilityStatus() { + if (Flags.enableColorCorrectionSaturation()) { + return AVAILABLE; + } + return CONDITIONALLY_UNAVAILABLE; + } + + @Override + public int getSliderPosition() { + return mSliderPosition; + } + + @Override + public boolean setSliderPosition(int position) { + if (position < getMin() || position > getMax()) { + return false; + } + mSliderPosition = position; + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + mSliderPosition); + return true; + } + + @Override + public int getMax() { + return SATURATION_MAX; + } + + @Override + public int getMin() { + return SATURATION_MIN; + } +} diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java index bc34e01ee8d..0cfa756778e 100644 --- a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java @@ -62,6 +62,9 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF static final String KEY_TRITANOMEALY = "daltonizer_mode_tritanomaly"; @VisibleForTesting static final String KEY_GRAYSCALE = "daltonizer_mode_grayscale"; + @VisibleForTesting + static final String KEY_SATURATION = "daltonizer_saturation"; + private static final List sControllers = new ArrayList<>(); private static List buildPreferenceControllers(Context context, @@ -79,7 +82,6 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF return sControllers; } - @Override protected void registerKeysToObserverCallback( AccessibilitySettingsContentObserver contentObserver) { @@ -138,6 +140,8 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF lists.add(KEY_TOP_INTRO_PREFERENCE); lists.add(KEY_PREVIEW); lists.add(KEY_USE_SERVICE_PREFERENCE); + // Putting saturation level close to the preview so users can see what is changing. + lists.add(KEY_SATURATION); lists.add(KEY_DEUTERANOMALY); lists.add(KEY_PROTANOMALY); lists.add(KEY_TRITANOMEALY); diff --git a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java new file mode 100644 index 00000000000..98ed4422dc8 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2024 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.settings.accessibility; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.Settings; + +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.server.accessibility.Flags; +import com.android.settings.widget.SeekBarPreference; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link DaltonizerSaturationSeekbarPreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class DaltonizerSaturationSeekbarPreferenceControllerTest { + + private ContentResolver mContentResolver; + private DaltonizerSaturationSeekbarPreferenceController mController; + + private int mOriginalSaturationLevel = -1; + + private PreferenceScreen mScreen; + + @Mock + private SeekBarPreference mPreference; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + Context context = ApplicationProvider.getApplicationContext(); + mContentResolver = context.getContentResolver(); + mOriginalSaturationLevel = Settings.Secure.getInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + 7); + + mScreen = spy(new PreferenceScreen(context, /* attrs= */ null)); + when(mScreen.findPreference(ToggleDaltonizerPreferenceFragment.KEY_SATURATION)) + .thenReturn(mPreference); + + mController = new DaltonizerSaturationSeekbarPreferenceController( + context, + ToggleDaltonizerPreferenceFragment.KEY_SATURATION); + } + + @After + public void cleanup() { + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + mOriginalSaturationLevel); + } + + + @Test + @DisableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagDisabled_unavailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabled_available() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void constructor_defaultValuesMatch() { + assertThat(mController.getSliderPosition()).isEqualTo(7); + assertThat(mController.getMax()).isEqualTo(10); + assertThat(mController.getMin()).isEqualTo(0); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void displayPreference_enabled_visible() { + mController.displayPreference(mScreen); + + verify(mPreference).setMax(eq(10)); + verify(mPreference).setMin(eq(0)); + verify(mPreference).setProgress(eq(7)); + verify(mPreference).setContinuousUpdates(eq(true)); + verify(mPreference).setOnPreferenceChangeListener(eq(mController)); + verify(mPreference).setVisible(eq(true)); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void displayPreference_disabled_notVisible() { + mController.displayPreference(mScreen); + + verify(mPreference).setMax(eq(10)); + verify(mPreference).setMin(eq(0)); + verify(mPreference).setProgress(eq(7)); + verify(mPreference).setContinuousUpdates(eq(true)); + verify(mPreference, never()).setOnPreferenceChangeListener(any()); + verify(mPreference).setVisible(eq(false)); + } + + @Test + public void setSliderPosition_inRange_secureSettingsUpdated() { + var isSliderSet = mController.setSliderPosition(9); + + assertThat(isSliderSet).isTrue(); + assertThat(Settings.Secure.getInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + 7)).isEqualTo(9); + } + + @Test + public void setSliderPosition_min_secureSettingsUpdated() { + var isSliderSet = mController.setSliderPosition(0); + + assertThat(isSliderSet).isTrue(); + assertThat(Settings.Secure.getInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + 7)).isEqualTo(0); + } + + @Test + public void setSliderPosition_max_secureSettingsUpdated() { + var isSliderSet = mController.setSliderPosition(10); + + assertThat(isSliderSet).isTrue(); + assertThat(Settings.Secure.getInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + 7)).isEqualTo(10); + } + + @Test + public void setSliderPosition_tooLarge_secureSettingsNotUpdated() { + var isSliderSet = mController.setSliderPosition(11); + + assertThat(isSliderSet).isFalse(); + assertThat(Settings.Secure.getInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + 7)).isEqualTo(7); + } + + @Test + public void setSliderPosition_tooSmall_secureSettingsNotUpdated() { + var isSliderSet = mController.setSliderPosition(-1); + + assertThat(isSliderSet).isFalse(); + assertThat(Settings.Secure.getInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + 7)).isEqualTo(7); + } +}