Add contrast setting in dev options
See the attached video from b/266071578#comment2 to see how the setting
looks like.
Bug: 266071578
Test: atest ContrastDialogTest
Test: atest ContrastDialogControllerTest
Change-Id: Id9fc31a0562059814d4e342ea8095adc5b53d5f3
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ccf002a..863ae70 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2785,6 +2785,7 @@
</intent-filter>
<intent-filter>
<action android:name="com.android.settings.action.SETTINGS" />
+ <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" />
</intent-filter>
<meta-data android:name="com.android.settings.order" android:value="-40"/>
<meta-data android:name="com.android.settings.category"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f23fb5f..d08972d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4944,6 +4944,8 @@
<string name="keywords_color_correction">adjust color </string>
<!-- List of synonyms used in the settings search bar to find the “Color inversion”. [CHAR LIMIT=NONE] -->
<string name="keywords_color_inversion">turn screen dark, turn screen light</string>
+ <!-- List of synonyms used in the settings search bar to find the “Contrast”. [CHAR LIMIT=NONE] -->
+ <string name="keywords_contrast">color contrast</string>
<!-- List of synonyms used in the settings search bar to find the “Accessibility Menu”. [CHAR LIMIT=NONE] -->
<string name="keywords_accessibility_menu"></string>
<!-- List of synonyms used in the settings search bar to find the “Switch Access”. [CHAR LIMIT=NONE] -->
@@ -12017,6 +12019,15 @@
<!-- Button to close the dialog without saving in screen flash color selection dialog. [CHAR LIMIT=20] -->
<string name="color_selector_dialog_cancel">Cancel</string>
+ <!-- Title for the contrast preference fragment [CHAR LIMIT=35] -->
+ <string name="contrast_title">Contrast</string>
+ <!-- 'Standard' contrast option [CHAR LIMIT=20] -->
+ <string name="contrast_standard">Standard</string>
+ <!-- 'Medium' contrast option [CHAR LIMIT=20] -->
+ <string name="contrast_medium">Medium</string>
+ <!-- 'High' contrast option [CHAR LIMIT=20] -->
+ <string name="contrast_high">High</string>
+
<!-- Warning message when we try to dock an app not supporting multiple instances split into multiple sides [CHAR LIMIT=NONE] -->
<string name="dock_multi_instances_not_supported_text">"This app can only be opened in 1 window"</string>
</resources>
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index f3ac926..55a63d6 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -515,6 +515,12 @@
android:title="@string/transparent_navigation_bar"
android:summary="@string/transparent_navigation_bar_summary" />
+ <Preference
+ android:key="contrast_preference"
+ android:title="@string/contrast_title"
+ android:persistent="false"
+ settings:keywords="@string/keywords_contrast" />
+
</PreferenceCategory>
<PreferenceCategory
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 3180a79..68fe08e 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -19,6 +19,7 @@
import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES;
import android.app.Activity;
+import android.app.UiModeManager;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
@@ -63,6 +64,7 @@
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.actionbar.SearchMenuController;
+import com.android.settings.theme.ContrastPreferenceController;
import com.android.settings.widget.SettingsMainSwitchBar;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -685,6 +687,8 @@
controllers.add(new IngressRateLimitPreferenceController((context)));
controllers.add(new BackAnimationPreferenceController(context, fragment));
controllers.add(new PhantomProcessPreferenceController(context));
+ controllers.add(new ContrastPreferenceController(
+ context, context.getSystemService(UiModeManager.class)));
return controllers;
}
diff --git a/src/com/android/settings/theme/ContrastPreferenceController.kt b/src/com/android/settings/theme/ContrastPreferenceController.kt
new file mode 100644
index 0000000..7f3844a
--- /dev/null
+++ b/src/com/android/settings/theme/ContrastPreferenceController.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.settings.theme
+
+import android.app.UiModeManager
+import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH
+import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM
+import android.app.UiModeManager.ContrastUtils.toContrastLevel
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import android.text.TextUtils
+import androidx.preference.Preference
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settings.R
+import com.android.settings.core.BasePreferenceController
+
+/**
+ * Controller that opens the contrast dialog and updates the text describing the current contrast
+ */
+class ContrastPreferenceController(
+ private val context: Context,
+ private val uiModeManager: UiModeManager) : BasePreferenceController(context, KEY) {
+
+ companion object {
+ @VisibleForTesting
+ const val KEY = "contrast_preference"
+ }
+
+ override fun getAvailabilityStatus(): Int {
+ return AVAILABLE
+ }
+
+ override fun handlePreferenceTreeClick(preference: Preference): Boolean {
+ if (TextUtils.equals(preference.key, preferenceKey)) {
+ val intent = Intent(Intent.ACTION_SHOW_CONTRAST_DIALOG)
+ context.startActivityAsUser(intent, UserHandle(UserHandle.USER_CURRENT))
+ return true
+ }
+ return false
+ }
+
+ override fun getSummary(): CharSequence = getSummary(toContrastLevel(uiModeManager.contrast))
+
+ @VisibleForTesting
+ fun getSummary(contrast: Int): String {
+ return when (contrast) {
+ CONTRAST_LEVEL_HIGH -> context.getString(R.string.contrast_high)
+ CONTRAST_LEVEL_MEDIUM -> context.getString(R.string.contrast_medium)
+ else -> context.getString(R.string.contrast_standard)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
new file mode 100644
index 0000000..dbd3372
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/theme/ContrastPreferenceControllerTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.settings.theme;
+
+import static android.app.UiModeManager.ContrastUtils;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM;
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD;
+import static android.provider.Settings.Secure.CONTRAST_LEVEL;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.Context;
+import android.provider.Settings;
+
+import androidx.preference.Preference;
+import androidx.test.core.app.ApplicationProvider;
+
+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.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.stream.Stream;
+
+@RunWith(RobolectricTestRunner.class)
+public class ContrastPreferenceControllerTest {
+
+ @Rule
+ public MockitoRule mocks = MockitoJUnit.rule();
+
+ private ContrastPreferenceController mController;
+
+ @Mock
+ private UiModeManager mMockUiModeManager;
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
+ mController = new ContrastPreferenceController(mContext, mMockUiModeManager);
+ }
+
+ @Test
+ public void controllerIsAvailable() {
+ assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
+ }
+
+ @Test
+ public void testHandlePreferenceTreeClick() {
+ Preference preference = new Preference(mContext);
+ preference.setKey(ContrastPreferenceController.KEY);
+ assertThat(mController.handlePreferenceTreeClick(preference)).isTrue();
+
+ Preference otherPreference = new Preference(mContext);
+ otherPreference.setKey("wrong key");
+ assertThat(mController.handlePreferenceTreeClick(otherPreference)).isFalse();
+ }
+
+ @Test
+ public void controllerSummary() {
+ float initialContrast = mContext.getSystemService(UiModeManager.class).getContrast();
+ try {
+ allContrastValues().forEach(contrastLevel -> {
+ float contrast = ContrastUtils.fromContrastLevel(contrastLevel);
+ clearInvocations(mMockUiModeManager);
+ when(mMockUiModeManager.getContrast()).thenReturn(contrast);
+ String summary = mController.getSummary().toString();
+ verify(mMockUiModeManager).getContrast();
+ assertThat(summary).isEqualTo(mController.getSummary(contrastLevel));
+ });
+ } finally {
+ putContrastInSettings(initialContrast);
+ }
+ }
+
+ private static Stream<Integer> allContrastValues() {
+ return Stream.of(CONTRAST_LEVEL_STANDARD, CONTRAST_LEVEL_MEDIUM, CONTRAST_LEVEL_HIGH);
+ }
+
+ private void putContrastInSettings(float contrast) {
+ Settings.Secure.putFloat(mContext.getContentResolver(), CONTRAST_LEVEL, contrast);
+ }
+}