summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tyler Freeman <fuego@google.com> 2023-08-15 13:43:35 +0000
committer Tyler Freeman <fuego@google.com> 2023-10-04 01:19:05 +0000
commit0a016bbab6b8aa9ed758af72e1bc4db2277dfbc8 (patch)
treebf748bcdbb096937ca64f691fa2d73ff0a8695c2
parent14f2d20ed3d0b354b443639cd12e1fe3e9c306d8 (diff)
feat(force invert): force force-dark if force invert is enabled
Force invert always forces apps dark, even if the developer has used one of the opt-out APIs. It also ignores the dark theme setting, since that can be overridden by the app. Bug: 282821643 Test: atest ViewRootImplTest Test: manual: 0. `adb shell device_config set_sync_disabled_for_tests persistent && adb shell device_config put accessibility android.view.flags.force_invert_color true` 1. `setprop debug.hwui.force_dark 1 && cmd uimode night yes"` 2. Most apps will be dark, except ones that opt-out (e.g. Twitter). 3. `setprop debug.hwui.force_dark 0 && settings put secure accessibility_force_invert_color_enabled 1 && cmd uimode night no` 4. All apps will be dark, regardless if they opt-out (e.g. Twitter). Change-Id: I425b401c3e1a59d9c9d9445c6c056613d4da7982
-rw-r--r--core/java/android/view/ViewRootImpl.java27
-rw-r--r--core/tests/coretests/Android.bp2
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java121
3 files changed, 147 insertions, 3 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0ba5d06b64dc..cc918deb031c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -84,6 +84,7 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_B
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
@@ -154,6 +155,7 @@ import android.os.SystemClock;
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;
@@ -1744,8 +1746,23 @@ public final class ViewRootImpl implements ViewParent,
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() {
+ if (forceInvertColor()) {
+ 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;
@@ -1757,8 +1774,12 @@ public final class ViewRootImpl implements ViewParent,
&& 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/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 9cde296b2cab..04622fda75df 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -44,12 +44,14 @@ android_test {
"frameworks-core-util-lib",
"mockwebserver",
"guava",
+ "android.view.accessibility.flags-aconfig-java",
"androidx.core_core",
"androidx.core_core-ktx",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
"androidx.test.runner",
"androidx.test.rules",
+ "flag-junit",
"junit-params",
"kotlin-test",
"mockito-target-minus-junit4",
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 2afbb476bc16..6a9fc04230f8 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -38,12 +39,18 @@ import static org.junit.Assert.assertEquals;
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.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.util.Log;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
@@ -52,9 +59,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
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;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,6 +82,10 @@ import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ViewRootImplTest {
+ private static final String TAG = "ViewRootImplTest";
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private ViewRootImpl mViewRootImpl;
private volatile boolean mKeyReceived = false;
@@ -101,6 +116,18 @@ public class ViewRootImplTest {
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 +427,100 @@ public class ViewRootImplTest {
assertThat(result).isFalse();
}
+ @Test
+ public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
+ mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
+ 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() {
+ mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
+ 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() {
+ mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
+ 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() {
+ mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
+ 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);