Hide display white balance setting depending on accessibility

To avoid conflicting with display accessibility features, hide the
display white balance setting when these features are active.

Bug: 130263943
Test: make ROBOTEST_FILTER=DisplayWhiteBalancePreferenceControllerTest
RunSettingsRoboTests -j32

Change-Id: I9936a657521287a7a0482f2a934430bd79013cf3
diff --git a/src/com/android/settings/display/DisplayWhiteBalancePreferenceController.java b/src/com/android/settings/display/DisplayWhiteBalancePreferenceController.java
index dc569aa..6fc0b0e 100644
--- a/src/com/android/settings/display/DisplayWhiteBalancePreferenceController.java
+++ b/src/com/android/settings/display/DisplayWhiteBalancePreferenceController.java
@@ -13,17 +13,34 @@
  */
 package com.android.settings.display;
 
+import android.app.ActivityManager;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.database.ContentObserver;
 import android.hardware.display.ColorDisplayManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
+import android.provider.Settings.System;
 import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
 
 import com.android.settings.core.TogglePreferenceController;
 
-public class DisplayWhiteBalancePreferenceController extends TogglePreferenceController {
+public class DisplayWhiteBalancePreferenceController extends TogglePreferenceController
+    implements LifecycleObserver, OnStart, OnStop {
 
     private ColorDisplayManager mColorDisplayManager;
+    @VisibleForTesting
+    ContentObserver mContentObserver;
+    private Preference mPreference;
 
     public DisplayWhiteBalancePreferenceController(Context context, String key) {
         super(context, key);
@@ -31,12 +48,8 @@
 
     @Override
     public int getAvailabilityStatus() {
-        // Display white balance is only valid in linear light space. COLOR_MODE_SATURATED implies
-        // unmanaged color mode, and hence unknown color processing conditions.
-        return ColorDisplayManager.isDisplayWhiteBalanceAvailable(mContext) &&
-                getColorDisplayManager().getColorMode() !=
-                        ColorDisplayManager.COLOR_MODE_SATURATED ?
-                AVAILABLE : DISABLED_FOR_USER;
+        return getColorDisplayManager().isDisplayWhiteBalanceAvailable(mContext) ?
+            AVAILABLE : DISABLED_FOR_USER;
     }
 
     @Override
@@ -49,6 +62,50 @@
         return getColorDisplayManager().setDisplayWhiteBalanceEnabled(isChecked);
     }
 
+    @Override
+    public void onStart() {
+        if (!isAvailable()) {
+            return;
+        }
+
+        final ContentResolver cr = mContext.getContentResolver();
+        mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                super.onChange(selfChange, uri);
+                updateVisibility();
+            }
+        };
+        cr.registerContentObserver(
+                Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
+                false /* notifyForDescendants */, mContentObserver,
+                ActivityManager.getCurrentUser());
+        cr.registerContentObserver(
+                Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
+                false /* notifyForDescendants */, mContentObserver,
+                ActivityManager.getCurrentUser());
+        cr.registerContentObserver(
+                System.getUriFor(System.DISPLAY_COLOR_MODE),
+                false /* notifyForDescendants */, mContentObserver,
+                ActivityManager.getCurrentUser());
+
+        updateVisibility();
+    }
+
+    @Override
+    public void onStop() {
+        if (mContentObserver != null) {
+            mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+            mContentObserver = null;
+        }
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mPreference = screen.findPreference(getPreferenceKey());
+    }
+
     @VisibleForTesting
     ColorDisplayManager getColorDisplayManager() {
         if (mColorDisplayManager == null) {
@@ -56,4 +113,17 @@
         }
         return mColorDisplayManager;
     }
+
+    @VisibleForTesting
+    void updateVisibility() {
+        if (mPreference != null) {
+            ColorDisplayManager cdm = getColorDisplayManager();
+
+            // Display white balance is only valid in linear light space. COLOR_MODE_SATURATED
+            // implies unmanaged color mode, and hence unknown color processing conditions.
+            // We also disallow display white balance when color accessibility features are enabled.
+            mPreference.setVisible(cdm.getColorMode() != ColorDisplayManager.COLOR_MODE_SATURATED &&
+                    !cdm.areAccessibilityTransformsEnabled(mContext));
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/display/DisplayWhiteBalancePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/DisplayWhiteBalancePreferenceControllerTest.java
index d0dbc0b..dfc13de 100644
--- a/tests/robotests/src/com/android/settings/display/DisplayWhiteBalancePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/display/DisplayWhiteBalancePreferenceControllerTest.java
@@ -1,16 +1,27 @@
 package com.android.settings.display;
 
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static com.google.common.truth.Truth.assertThat;
 
+
+import android.content.ContentResolver;
+import android.content.Context;
 import android.hardware.display.ColorDisplayManager;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
 import com.android.settings.testutils.shadow.SettingsShadowResources;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
@@ -27,6 +38,15 @@
 
   @Mock
   private ColorDisplayManager mColorDisplayManager;
+  private ContentResolver mContentResolver;
+  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+  private Context mContext;
+  @Mock
+  private PreferenceScreen mScreen;
+  @Mock
+  private Preference mPreference;
+
+  private final String PREFERENCE_KEY = "display_white_balance";
 
   @After
   public void tearDown() {
@@ -36,17 +56,21 @@
   @Before
   public void setUp() {
     MockitoAnnotations.initMocks(this);
-    mController = spy(new DisplayWhiteBalancePreferenceController(RuntimeEnvironment.application,
-        "display_white_balance"));
+
+    mContentResolver = RuntimeEnvironment.application.getContentResolver();
+    when(mContext.getContentResolver()).thenReturn(mContentResolver);
+    when(mContext.getResources()).thenReturn(RuntimeEnvironment.application.getResources());
+    when(mScreen.findPreference(PREFERENCE_KEY)).thenReturn(mPreference);
+
+    mController = spy(new DisplayWhiteBalancePreferenceController(mContext, PREFERENCE_KEY));
     doReturn(mColorDisplayManager).when(mController).getColorDisplayManager();
   }
 
   @Test
-  public void isAvailable_configuredAvailable() {
+  public void isAvailable() {
     SettingsShadowResources.overrideResource(
         com.android.internal.R.bool.config_displayWhiteBalanceAvailable, true);
-    when(mColorDisplayManager.getColorMode())
-        .thenReturn(ColorDisplayManager.COLOR_MODE_NATURAL);
+
     assertThat(mController.isAvailable()).isTrue();
   }
 
@@ -54,20 +78,7 @@
   public void isAvailable_configuredUnavailable() {
     SettingsShadowResources.overrideResource(
         com.android.internal.R.bool.config_displayWhiteBalanceAvailable, false);
-    when(mColorDisplayManager.getColorMode())
-        .thenReturn(ColorDisplayManager.COLOR_MODE_SATURATED);
-    assertThat(mController.isAvailable()).isFalse();
 
-    SettingsShadowResources.overrideResource(
-        com.android.internal.R.bool.config_displayWhiteBalanceAvailable, false);
-    when(mColorDisplayManager.getColorMode())
-        .thenReturn(ColorDisplayManager.COLOR_MODE_NATURAL);
-    assertThat(mController.isAvailable()).isFalse();
-
-    SettingsShadowResources.overrideResource(
-        com.android.internal.R.bool.config_displayWhiteBalanceAvailable, true);
-    when(mColorDisplayManager.getColorMode())
-        .thenReturn(ColorDisplayManager.COLOR_MODE_SATURATED);
     assertThat(mController.isAvailable()).isFalse();
   }
 
@@ -94,4 +105,101 @@
     when(mColorDisplayManager.isDisplayWhiteBalanceEnabled()).thenReturn(false);
     assertThat(mController.isChecked()).isFalse();
   }
+
+  @Test
+  public void onStart_configuredUnavailable() {
+    SettingsShadowResources.overrideResource(
+        com.android.internal.R.bool.config_displayWhiteBalanceAvailable, false);
+    mController.displayPreference(mScreen);
+    mController.onStart();
+    assertThat(mController.mContentObserver).isNull();
+  }
+
+  @Test
+  public void onStart_configuredAvailable() {
+    SettingsShadowResources.overrideResource(
+        com.android.internal.R.bool.config_displayWhiteBalanceAvailable, true);
+    when(mColorDisplayManager.getColorMode())
+        .thenReturn(ColorDisplayManager.COLOR_MODE_NATURAL);
+    toggleAccessibilityInversion(false);
+    toggleAccessibilityDaltonizer(false);
+
+    mController.displayPreference(mScreen);
+    mController.onStart();
+    assertThat(mController.mContentObserver).isNotNull();
+  }
+
+  @Test
+  public void visibility_configuredAvailableAccessibilityToggled() {
+    SettingsShadowResources.overrideResource(
+        com.android.internal.R.bool.config_displayWhiteBalanceAvailable, true);
+    mController.displayPreference(mScreen);
+
+    // Accessibility features disabled
+    toggleAccessibilityInversion(false);
+    reset(mPreference);
+    mController.updateVisibility();
+    verify(mPreference).setVisible(true);
+
+    toggleAccessibilityDaltonizer(false);
+    reset(mPreference);
+    mController.updateVisibility();
+    verify(mPreference).setVisible(true);
+
+    // Accessibility features enabled one by one
+    toggleAccessibilityInversion(true);
+    mController.updateVisibility();
+    verify(mPreference).setVisible(false);
+
+    toggleAccessibilityDaltonizer(true);
+    reset(mPreference);
+    mController.updateVisibility();
+    verify(mPreference).setVisible(false);
+
+    // Accessibility features disabled one by one
+    toggleAccessibilityInversion(false);
+    reset(mPreference);
+    mController.updateVisibility();
+    // Daltonizer is still enabled, so we expect the preference to still be invisible
+    verify(mPreference).setVisible(false);
+
+    // Now both a11y features are disabled, so we expect the preference to become visible
+    toggleAccessibilityDaltonizer(false);
+    mController.updateVisibility();
+    verify(mPreference).setVisible(true);
+  }
+
+  @Test
+  public void visibility_configuredAvailableColorModeChanged() {
+    SettingsShadowResources.overrideResource(
+            com.android.internal.R.bool.config_displayWhiteBalanceAvailable, true);
+    mController.displayPreference(mScreen);
+
+    // Non-Saturated color mode selected
+    when(mColorDisplayManager.getColorMode()).thenReturn(ColorDisplayManager.COLOR_MODE_NATURAL);
+    reset(mPreference);
+    mController.updateVisibility();
+    verify(mPreference).setVisible(true);
+
+    // Saturated color mode selected
+    when(mColorDisplayManager.getColorMode()).thenReturn(ColorDisplayManager.COLOR_MODE_SATURATED);
+    mController.updateVisibility();
+    verify(mPreference).setVisible(false);
+
+    // Switch back to non-Saturated color mode
+    when(mColorDisplayManager.getColorMode()).thenReturn(ColorDisplayManager.COLOR_MODE_NATURAL);
+    reset(mPreference);
+    mController.updateVisibility();
+    verify(mPreference).setVisible(true);
+  }
+
+  private void toggleAccessibilityInversion(boolean enable) {
+    Settings.Secure.putInt(mContentResolver,
+        Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, enable ? 1 : 0);
+  }
+
+  private void toggleAccessibilityDaltonizer(boolean enable) {
+    Settings.Secure.putInt(mContentResolver,
+        Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, enable ? 1 : 0);
+  }
 }