From 581fbc8dfa0f3e0bed2ee17ba4410440d76e492e Mon Sep 17 00:00:00 2001 From: Isaac Chai Date: Thu, 11 Jul 2024 00:16:50 +0000 Subject: [PATCH] feat(color correction): Disable slider when not applicable We received feedback that slider should be disabled when it's not applicable, namely when it's off and when mode == gray scale. Bug: 351920992 Test: Locally tested + unit tests Flag: com.android.server.accessibility.enable_color_correction_saturation Change-Id: I1162a501a797a1d2b30da76f0c75e5fdea3f61d2 --- res/values/strings.xml | 2 + ...SaturationSeekbarPreferenceController.java | 80 +++++- ...rationSeekbarPreferenceControllerTest.java | 252 +++++++++++++++--- 3 files changed, 298 insertions(+), 36 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 572851ed06e..c179eba8ae9 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5465,6 +5465,8 @@ Grayscale Intensity + + Unavailable for grayscale mode or when color correction is disabled Green weak, deuteranomaly diff --git a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java index 7dcd6612ac4..29971854e6b 100644 --- a/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java +++ b/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceController.java @@ -17,26 +17,50 @@ package com.android.settings.accessibility; import android.content.ContentResolver; import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; import android.provider.Settings; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.server.accessibility.Flags; +import com.android.settings.R; 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 { +public class DaltonizerSaturationSeekbarPreferenceController + extends SliderPreferenceController + implements DefaultLifecycleObserver { private static final int DEFAULT_SATURATION_LEVEL = 7; private static final int SATURATION_MAX = 10; - private static final int SATURATION_MIN = 0; + private static final int SATURATION_MIN = 1; private int mSliderPosition; private final ContentResolver mContentResolver; + @Nullable + private SeekBarPreference mPreference; + + public final ContentObserver mContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + if (mPreference != null) { + updateState(mPreference); + } + } + }; + public DaltonizerSaturationSeekbarPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -49,10 +73,33 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe // TODO: Observer color correction on/off and enable/disable based on secure settings. } + @Override + public void onStart(@NonNull LifecycleOwner owner) { + if (!isAvailable()) return; + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER), + true, + mContentObserver + ); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED), + true, + mContentObserver + ); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + if (!isAvailable()) return; + mContentResolver.unregisterContentObserver(mContentObserver); + mPreference = null; + } + @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); SeekBarPreference preference = screen.findPreference(getPreferenceKey()); + mPreference = preference; preference.setMax(getMax()); preference.setMin(getMin()); preference.setProgress(mSliderPosition); @@ -62,7 +109,7 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe @Override public int getAvailabilityStatus() { if (Flags.enableColorCorrectionSaturation()) { - return AVAILABLE; + return shouldSeekBarEnabled() ? AVAILABLE : DISABLED_DEPENDENT_SETTING; } return CONDITIONALLY_UNAVAILABLE; } @@ -85,6 +132,21 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe return true; } + @Override + public void updateState(Preference preference) { + if (preference == null) { + return; + } + + var shouldSeekbarEnabled = shouldSeekBarEnabled(); + // setSummary not working yet on SeekBarPreference. + String summary = shouldSeekbarEnabled + ? "" + : mContext.getString(R.string.daltonizer_saturation_unavailable_summary); + preference.setSummary(summary); + preference.setEnabled(shouldSeekbarEnabled); + } + @Override public int getMax() { return SATURATION_MAX; @@ -94,4 +156,16 @@ public class DaltonizerSaturationSeekbarPreferenceController extends SliderPrefe public int getMin() { return SATURATION_MIN; } + + private boolean shouldSeekBarEnabled() { + int enabled = Settings.Secure.getInt( + mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, 0); + int mode = Settings.Secure.getInt( + mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, -1); + + // enabled == 0 is disabled and also default. + // mode == 0 is gray scale where saturation level isn't applicable. + // mode == -1 is disabled and also default. + return enabled != 0 && mode != -1 && mode != 0; + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java index 98ed4422dc8..5fd11f910fa 100644 --- a/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/DaltonizerSaturationSeekbarPreferenceControllerTest.java @@ -16,38 +16,39 @@ package com.android.settings.accessibility; +import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; +import static androidx.lifecycle.Lifecycle.Event.ON_STOP; + import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; +import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; 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 static org.robolectric.Shadows.shadowOf; import android.content.ContentResolver; import android.content.Context; +import android.os.Looper; 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.lifecycle.LifecycleOwner; +import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.server.accessibility.Flags; import com.android.settings.widget.SeekBarPreference; +import com.android.settingslib.core.lifecycle.Lifecycle; 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}. */ @@ -60,8 +61,9 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { private int mOriginalSaturationLevel = -1; private PreferenceScreen mScreen; + private LifecycleOwner mLifecycleOwner; + private Lifecycle mLifecycle; - @Mock private SeekBarPreference mPreference; @Rule @@ -69,7 +71,6 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { @Before public void setup() { - MockitoAnnotations.initMocks(this); Context context = ApplicationProvider.getApplicationContext(); mContentResolver = context.getContentResolver(); mOriginalSaturationLevel = Settings.Secure.getInt( @@ -77,10 +78,13 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, 7); - mScreen = spy(new PreferenceScreen(context, /* attrs= */ null)); - when(mScreen.findPreference(ToggleDaltonizerPreferenceFragment.KEY_SATURATION)) - .thenReturn(mPreference); + mPreference = new SeekBarPreference(context); + mPreference.setKey(ToggleDaltonizerPreferenceFragment.KEY_SATURATION); + mScreen = new PreferenceManager(context).createPreferenceScreen(context); + mScreen.addPreference(mPreference); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); mController = new DaltonizerSaturationSeekbarPreferenceController( context, ToggleDaltonizerPreferenceFragment.KEY_SATURATION); @@ -94,6 +98,12 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { mOriginalSaturationLevel); } + @Test + public void constructor_defaultValuesMatch() { + assertThat(mController.getSliderPosition()).isEqualTo(7); + assertThat(mController.getMax()).isEqualTo(10); + assertThat(mController.getMin()).isEqualTo(1); + } @Test @DisableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) @@ -103,28 +113,72 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { @Test @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) - public void getAvailabilityStatus_flagEnabled_available() { + public void getAvailabilityStatus_flagEnabledProtanEnabled_available() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); 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); + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabledDeutranEnabled_available() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 12); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } @Test @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) - public void displayPreference_enabled_visible() { + public void getAvailabilityStatus_flagEnabledTritanEnabled_available() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 13); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabledGrayScale_disabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 0); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabledColorCorrectionDisabled_disabled() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void getAvailabilityStatus_flagEnabledColorCorrectionDisabledGrayScale_disabled() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0); + assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void displayPreference_flagEnabledColorCorrectionEnabled_enabledWithDefaultValues() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); 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)); + assertThat(mPreference.isEnabled()).isTrue(); + assertThat(mPreference.getMax()).isEqualTo(10); + assertThat(mPreference.getMin()).isEqualTo(1); + assertThat(mPreference.getProgress()).isEqualTo(7); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getOnPreferenceChangeListener()).isEqualTo(mController); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_COLOR_CORRECTION_SATURATION) + public void displayPreference_flagEnabledColorCorrectionDisabled_disabledWithDefaultValues() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11); + mController.displayPreference(mScreen); + + assertThat(mPreference.isEnabled()).isFalse(); + assertThat(mPreference.getMax()).isEqualTo(10); + assertThat(mPreference.getMin()).isEqualTo(1); + assertThat(mPreference.getProgress()).isEqualTo(7); + assertThat(mPreference.isVisible()).isTrue(); + assertThat(mPreference.getOnPreferenceChangeListener()).isEqualTo(mController); } @Test @@ -132,12 +186,8 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { 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)); + assertThat(mPreference.isVisible()).isFalse(); + assertThat(mPreference.getOnPreferenceChangeListener()).isNull(); } @Test @@ -153,13 +203,13 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { @Test public void setSliderPosition_min_secureSettingsUpdated() { - var isSliderSet = mController.setSliderPosition(0); + var isSliderSet = mController.setSliderPosition(1); assertThat(isSliderSet).isTrue(); assertThat(Settings.Secure.getInt( mContentResolver, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, - 7)).isEqualTo(0); + 7)).isEqualTo(1); } @Test @@ -194,4 +244,140 @@ public class DaltonizerSaturationSeekbarPreferenceControllerTest { Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, 7)).isEqualTo(7); } + + @Test + public void updateState_enabledProtan_preferenceEnabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void updateState_enabledDeuteran_preferenceEnabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 12); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void updateState_enabledTritan_preferenceEnabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 13); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void updateState_disabledGrayScale_preferenceDisabled() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void updateState_nullPreference_noError() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 0); + + mController.updateState(null); + } + + @Test + public void updateState_enabledGrayScale_preferenceDisabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 0); + + mController.updateState(mPreference); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void onResume_daltonizerEnabledAfterResumed_preferenceEnabled() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11); + mController.displayPreference(mScreen); + assertThat(mPreference.isEnabled()).isFalse(); + + mLifecycle.addObserver(mController); + mLifecycle.handleLifecycleEvent(ON_RESUME); + + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + 1); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mPreference.isEnabled()).isTrue(); + } + + @Test + public void onResume_daltonizerDisabledAfterResumed_preferenceDisabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); + mController.displayPreference(mScreen); + assertThat(mPreference.isEnabled()).isTrue(); + + mLifecycle.addObserver(mController); + mLifecycle.handleLifecycleEvent(ON_RESUME); + + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + 0); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void onResume_daltonizerGrayScaledAfterResumed_preferenceDisabled() { + setDaltonizerMode(/* enabled= */ 1, /* mode= */ 11); + mController.displayPreference(mScreen); + assertThat(mPreference.isEnabled()).isTrue(); + + mLifecycle.addObserver(mController); + mLifecycle.handleLifecycleEvent(ON_RESUME); + + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, + 0); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + @Test + public void onStop_daltonizerEnabledAfterOnStop_preferenceNotChanged() { + setDaltonizerMode(/* enabled= */ 0, /* mode= */ 11); + mController.displayPreference(mScreen); + assertThat(mPreference.isEnabled()).isFalse(); + + mLifecycle.addObserver(mController); + mLifecycle.handleLifecycleEvent(ON_STOP); + + // enabled. + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + 1); + shadowOf(Looper.getMainLooper()).idle(); + + assertThat(mPreference.isEnabled()).isFalse(); + } + + private void setDaltonizerMode(int enabled, int mode) { + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, + enabled); + Settings.Secure.putInt( + mContentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, + mode); + } }