Merge changes I61bc6cc5,I6ab87574 into main
* changes:
feat(force invert): force force-dark if force invert is enabled
feat(force invert): add accessibility force invert secure setting
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 45b4935..522caac 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11656,6 +11656,21 @@
"accessibility_floating_menu_migration_tooltip_prompt";
/**
+ * For the force dark theme feature which inverts any apps that don't already support dark
+ * theme.
+ *
+ * If true, it will automatically invert any app that is mainly light.
+ *
+ * This is related to the force dark override setting, however it will always force the apps
+ * colors and will ignore any developer hints or opt-out APIs.
+ *
+ * @hide
+ */
+ @Readable
+ public static final String ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED =
+ "accessibility_force_invert_color_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e0fda7e..543c1ef 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -154,6 +154,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings;
import android.sysprop.DisplayProperties;
import android.text.TextUtils;
import android.util.AndroidRuntimeException;
@@ -1733,8 +1734,21 @@
return getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
}
- private void updateForceDarkMode() {
- if (mAttachInfo.mThreadedRenderer == null) return;
+ /** Returns true if force dark should be enabled according to various settings */
+ @VisibleForTesting
+ public boolean isForceDarkEnabled() {
+ boolean isForceInvertEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* def= */ 0,
+ UserHandle.myUserId()) == 1;
+ // Force invert ignores all developer opt-outs.
+ // We also ignore dark theme, since the app developer can override the user's preference
+ // for dark mode in configuration.uiMode. Instead, we assume that the force invert setting
+ // will be enabled at the same time dark theme is in the Settings app.
+ if (isForceInvertEnabled) {
+ return true;
+ }
boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
@@ -1746,8 +1760,12 @@
&& a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
a.recycle();
}
+ return useAutoDark;
+ }
- if (mAttachInfo.mThreadedRenderer.setForceDark(useAutoDark)) {
+ private void updateForceDarkMode() {
+ if (mAttachInfo.mThreadedRenderer == null) return;
+ if (mAttachInfo.mThreadedRenderer.setForceDark(isForceDarkEnabled())) {
// TODO: Don't require regenerating all display lists to apply this setting
invalidateWorld(mView);
}
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index ed0081c..ad88092 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -97,6 +97,7 @@
optional SettingProto accessibility_magnification_joystick_enabled = 50 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Settings for font scaling
optional SettingProto accessibility_font_scaling_has_been_changed = 51 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto accessibility_force_invert_color_enabled = 52 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 2afbb47..bd309b9 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -38,12 +38,17 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.app.Instrumentation;
+import android.app.UiModeManager;
import android.content.Context;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
+import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.util.Log;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
@@ -52,6 +57,9 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -71,6 +79,7 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ViewRootImplTest {
+ private static final String TAG = "ViewRootImplTest";
private ViewRootImpl mViewRootImpl;
private volatile boolean mKeyReceived = false;
@@ -101,6 +110,18 @@
mViewRootImpl = new ViewRootImpl(sContext, sContext.getDisplayNoVerify()));
}
+ @After
+ public void teardown() {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ Settings.Secure.resetToDefaults(sContext.getContentResolver(), TAG);
+
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+
+ setForceDarkSysProp(false);
+ });
+ }
+
@Test
public void adjustLayoutParamsForCompatibility_layoutFullscreen() {
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
@@ -400,6 +421,96 @@
assertThat(result).isFalse();
}
+ @Test
+ public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* value= */ 0
+ );
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
+ );
+
+ assertThat(mViewRootImpl.isForceDarkEnabled()).isFalse();
+ }
+
+ @Test
+ public void forceInvertOnDarkThemeOff_forceDarkModeEnabled() {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* value= */ 1
+ );
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
+ );
+
+ assertThat(mViewRootImpl.isForceDarkEnabled()).isTrue();
+ }
+
+ @Test
+ public void forceInvertOffForceDarkOff_forceDarkModeDisabled() {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* value= */ 0
+ );
+
+ // TODO(b/297556388): figure out how to set this without getting blocked by SELinux
+ assumeTrue(setForceDarkSysProp(true));
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
+ );
+
+ assertThat(mViewRootImpl.isForceDarkEnabled()).isFalse();
+ }
+
+ @Test
+ public void forceInvertOffForceDarkOn_forceDarkModeEnabled() {
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* value= */ 0
+ );
+
+ assumeTrue(setForceDarkSysProp(true));
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
+ );
+
+ assertThat(mViewRootImpl.isForceDarkEnabled()).isTrue();
+ }
+
+ private boolean setForceDarkSysProp(boolean isForceDarkEnabled) {
+ try {
+ SystemProperties.set(
+ ThreadedRenderer.DEBUG_FORCE_DARK,
+ Boolean.toString(isForceDarkEnabled)
+ );
+ return true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to set force_dark sysprop", e);
+ return false;
+ }
+ }
+
class KeyView extends View {
KeyView(Context context) {
super(context);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2d62e2a..8787c25 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -217,6 +217,7 @@
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
Settings.Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 4494765..dfc3cef 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -312,6 +312,7 @@
VALIDATORS.put(
Secure.ACCESSIBILITY_BUTTON_TARGETS,
ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_ACTIVATED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ONE_HANDED_MODE_TIMEOUT, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d2b444b..7186aba 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1842,6 +1842,10 @@
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ SecureSettingsProto.Accessibility
+ .ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED,
SecureSettingsProto.Accessibility
.ACCESSIBILITY_MAGNIFICATION_JOYSTICK_ENABLED);