diff options
| author | 2023-08-15 13:43:35 +0000 | |
|---|---|---|
| committer | 2023-10-04 01:19:05 +0000 | |
| commit | 0a016bbab6b8aa9ed758af72e1bc4db2277dfbc8 (patch) | |
| tree | bf748bcdbb096937ca64f691fa2d73ff0a8695c2 | |
| parent | 14f2d20ed3d0b354b443639cd12e1fe3e9c306d8 (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.java | 27 | ||||
| -rw-r--r-- | core/tests/coretests/Android.bp | 2 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/view/ViewRootImplTest.java | 121 |
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); |