Loading core/java/android/view/ViewRootImpl.java +15 −4 Original line number Diff line number Diff line Loading @@ -189,6 +189,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.RenderNode; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.hardware.SyncFence; Loading Loading @@ -292,6 +293,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneFallbackEventHandler; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.internal.util.FastPrintWriter; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; Loading Loading @@ -2078,12 +2080,21 @@ public final class ViewRootImpl implements ViewParent, // preference for dark mode in configuration.uiMode. Instead, we assume that both // force invert and the system's dark theme are enabled. if (shouldApplyForceInvertDark()) { final boolean isLightTheme = a.getBoolean(R.styleable.Theme_isLightTheme, false); // TODO: b/372558459 - Also check the background ColorDrawable color lightness // TODO: b/368725782 - Use hwui color area detection instead of / in // addition to these heuristics. if (isLightTheme) { final boolean isLightTheme = a.getBoolean(R.styleable.Theme_isLightTheme, false); final boolean isBackgroundColorLight; if (mView != null && mView.getBackground() instanceof ColorDrawable colorDrawable) { isBackgroundColorLight = !ContrastColorUtil.isColorDarkLab(colorDrawable.getColor()); } else { // Treat unknown as light, so that only isLightTheme is used to determine // force dark treatment. isBackgroundColorLight = true; } if (isLightTheme && isBackgroundColorLight) { return ForceDarkType.FORCE_INVERT_COLOR_DARK; } else { return ForceDarkType.NONE; Loading core/java/com/android/internal/util/ContrastColorUtil.java +7 −2 Original line number Diff line number Diff line Loading @@ -41,8 +41,6 @@ import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.WeakHashMap; Loading Loading @@ -381,6 +379,13 @@ public class ContrastColorUtil { return calculateLuminance(color) <= 0.17912878474; } /** Like {@link #isColorDark(int)} but converts to LAB before checking the L component. */ public static boolean isColorDarkLab(int color) { final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); ColorUtilsFromCompat.colorToLAB(color, result); return result[0] < 50; } private int processColor(int color) { return Color.argb(Color.alpha(color), 255 - Color.red(color), Loading core/tests/coretests/src/android/view/ViewRootImplTest.java +97 −44 Original line number Diff line number Diff line Loading @@ -16,8 +16,6 @@ package android.view; import static android.app.UiModeManager.MODE_NIGHT_NO; import static android.app.UiModeManager.MODE_NIGHT_YES; import static android.util.SequenceUtils.getInitSeq; import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING; import static android.view.InputDevice.SOURCE_ROTARY_ENCODER; Loading Loading @@ -69,10 +67,9 @@ import static org.junit.Assume.assumeTrue; import android.annotation.NonNull; import android.app.Instrumentation; import android.app.UiModeManager; import android.app.UiModeManager.ForceInvertType; import android.content.Context; import android.graphics.Color; import android.graphics.ForceDarkType; import android.graphics.ForceDarkType.ForceDarkTypeDef; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; Loading Loading @@ -101,8 +98,6 @@ import com.android.compatibility.common.util.TestUtils; import com.android.cts.input.BlockingQueueEventVerifier; import com.android.window.flags.Flags; import com.google.common.truth.Expect; import org.hamcrest.Matcher; import org.junit.After; import org.junit.AfterClass; Loading Loading @@ -131,8 +126,6 @@ public class ViewRootImplTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Rule public final Expect mExpect = Expect.create(); private ViewRootImpl mViewRootImpl; private View mView; Loading Loading @@ -1516,29 +1509,83 @@ public class ViewRootImplTest { } @Test @RequiresFlagsEnabled(FLAG_FORCE_INVERT_COLOR) public void updateConfiguration_returnsExpectedForceDarkMode() { @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_systemLightMode_returnsNone() throws Exception { waitForSystemNightModeActivated(false); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_systemNightModeAndDisableForceInvertColor_returnsNone() throws Exception { waitForSystemNightModeActivated(true); verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true, UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK); verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true, UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK); verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); enableForceInvertColor(false); waitForSystemNightModeActivated(false); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_isLightThemeAndIsLightBackground_returnsForceInvertColorDark() throws Exception { // Set up configurations for force invert color waitForSystemNightModeActivated(true); enableForceInvertColor(true); setUpViewAttributes(/* isLightTheme= */ true, /* isLightBackground = */ true); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.FORCE_INVERT_COLOR_DARK)); } @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_isLightThemeAndNotLightBackground_returnsNone() throws Exception { // Set up configurations for force invert color waitForSystemNightModeActivated(true); enableForceInvertColor(true); setUpViewAttributes(/* isLightTheme= */ true, /* isLightBackground = */ false); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_notLightThemeAndIsLightBackground_returnsNone() throws Exception { // Set up configurations for force invert color waitForSystemNightModeActivated(true); enableForceInvertColor(true); setUpViewAttributes(/* isLightTheme= */ false, /* isLightBackground = */ true); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_notLightThemeAndNotLightBackground_returnsNone() throws Exception { // Set up configurations for force invert color waitForSystemNightModeActivated(true); enableForceInvertColor(true); setUpViewAttributes(/* isLightTheme= */ false, /* isLightBackground = */ false); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } @Test Loading Loading @@ -1792,29 +1839,35 @@ public class ViewRootImplTest { sInstrumentation.waitForIdleSync(); } private void verifyForceDarkType(boolean isAppInNightMode, boolean isForceInvertEnabled, @ForceInvertType int expectedForceInvertType, @ForceDarkTypeDef int expectedForceDarkType) { var uiModeManager = sContext.getSystemService(UiModeManager.class); private void enableForceInvertColor(boolean enabled) { ShellIdentityUtils.invokeWithShellPermissions(() -> { uiModeManager.setApplicationNightMode( isAppInNightMode ? MODE_NIGHT_YES : MODE_NIGHT_NO); Settings.Secure.putInt( sContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, isForceInvertEnabled ? 1 : 0); enabled ? 1 : 0 ); }); sInstrumentation.runOnMainSync(() -> mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())); try { TestUtils.waitUntil("Waiting for force invert state changed", () -> (uiModeManager.getForceInvertState() == expectedForceInvertType)); } catch (Exception e) { Log.e(TAG, "Unexpected error trying to apply force invert state. " + e); e.printStackTrace(); } mExpect.that(mViewRootImpl.determineForceDarkType()).isEqualTo(expectedForceDarkType); private void setUpViewAttributes(boolean isLightTheme, boolean isLightBackground) { ShellIdentityUtils.invokeWithShellPermissions(() -> { sContext.setTheme(isLightTheme ? android.R.style.Theme_DeviceDefault_Light : android.R.style.Theme_DeviceDefault); }); sInstrumentation.runOnMainSync(() -> { View view = new View(sContext); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( TYPE_APPLICATION_OVERLAY); layoutParams.token = new Binder(); view.setLayoutParams(layoutParams); if (isLightBackground) { view.setBackgroundColor(Color.WHITE); } else { view.setBackgroundColor(Color.BLACK); } mViewRootImpl.setView(view, layoutParams, /* panelParentView= */ null); mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()); }); } } Loading
core/java/android/view/ViewRootImpl.java +15 −4 Original line number Diff line number Diff line Loading @@ -189,6 +189,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; import android.graphics.RenderNode; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; import android.hardware.SyncFence; Loading Loading @@ -292,6 +293,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneFallbackEventHandler; import com.android.internal.protolog.ProtoLog; import com.android.internal.util.ContrastColorUtil; import com.android.internal.util.FastPrintWriter; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; Loading Loading @@ -2078,12 +2080,21 @@ public final class ViewRootImpl implements ViewParent, // preference for dark mode in configuration.uiMode. Instead, we assume that both // force invert and the system's dark theme are enabled. if (shouldApplyForceInvertDark()) { final boolean isLightTheme = a.getBoolean(R.styleable.Theme_isLightTheme, false); // TODO: b/372558459 - Also check the background ColorDrawable color lightness // TODO: b/368725782 - Use hwui color area detection instead of / in // addition to these heuristics. if (isLightTheme) { final boolean isLightTheme = a.getBoolean(R.styleable.Theme_isLightTheme, false); final boolean isBackgroundColorLight; if (mView != null && mView.getBackground() instanceof ColorDrawable colorDrawable) { isBackgroundColorLight = !ContrastColorUtil.isColorDarkLab(colorDrawable.getColor()); } else { // Treat unknown as light, so that only isLightTheme is used to determine // force dark treatment. isBackgroundColorLight = true; } if (isLightTheme && isBackgroundColorLight) { return ForceDarkType.FORCE_INVERT_COLOR_DARK; } else { return ForceDarkType.NONE; Loading
core/java/com/android/internal/util/ContrastColorUtil.java +7 −2 Original line number Diff line number Diff line Loading @@ -41,8 +41,6 @@ import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import java.util.Arrays; import java.util.WeakHashMap; Loading Loading @@ -381,6 +379,13 @@ public class ContrastColorUtil { return calculateLuminance(color) <= 0.17912878474; } /** Like {@link #isColorDark(int)} but converts to LAB before checking the L component. */ public static boolean isColorDarkLab(int color) { final double[] result = ColorUtilsFromCompat.getTempDouble3Array(); ColorUtilsFromCompat.colorToLAB(color, result); return result[0] < 50; } private int processColor(int color) { return Color.argb(Color.alpha(color), 255 - Color.red(color), Loading
core/tests/coretests/src/android/view/ViewRootImplTest.java +97 −44 Original line number Diff line number Diff line Loading @@ -16,8 +16,6 @@ package android.view; import static android.app.UiModeManager.MODE_NIGHT_NO; import static android.app.UiModeManager.MODE_NIGHT_YES; import static android.util.SequenceUtils.getInitSeq; import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING; import static android.view.InputDevice.SOURCE_ROTARY_ENCODER; Loading Loading @@ -69,10 +67,9 @@ import static org.junit.Assume.assumeTrue; import android.annotation.NonNull; import android.app.Instrumentation; import android.app.UiModeManager; import android.app.UiModeManager.ForceInvertType; import android.content.Context; import android.graphics.Color; import android.graphics.ForceDarkType; import android.graphics.ForceDarkType.ForceDarkTypeDef; import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; Loading Loading @@ -101,8 +98,6 @@ import com.android.compatibility.common.util.TestUtils; import com.android.cts.input.BlockingQueueEventVerifier; import com.android.window.flags.Flags; import com.google.common.truth.Expect; import org.hamcrest.Matcher; import org.junit.After; import org.junit.AfterClass; Loading Loading @@ -131,8 +126,6 @@ public class ViewRootImplTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Rule public final Expect mExpect = Expect.create(); private ViewRootImpl mViewRootImpl; private View mView; Loading Loading @@ -1516,29 +1509,83 @@ public class ViewRootImplTest { } @Test @RequiresFlagsEnabled(FLAG_FORCE_INVERT_COLOR) public void updateConfiguration_returnsExpectedForceDarkMode() { @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_systemLightMode_returnsNone() throws Exception { waitForSystemNightModeActivated(false); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_systemNightModeAndDisableForceInvertColor_returnsNone() throws Exception { waitForSystemNightModeActivated(true); verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true, UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK); verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true, UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK); verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); enableForceInvertColor(false); waitForSystemNightModeActivated(false); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false, UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE); @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_isLightThemeAndIsLightBackground_returnsForceInvertColorDark() throws Exception { // Set up configurations for force invert color waitForSystemNightModeActivated(true); enableForceInvertColor(true); setUpViewAttributes(/* isLightTheme= */ true, /* isLightBackground = */ true); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.FORCE_INVERT_COLOR_DARK)); } @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_isLightThemeAndNotLightBackground_returnsNone() throws Exception { // Set up configurations for force invert color waitForSystemNightModeActivated(true); enableForceInvertColor(true); setUpViewAttributes(/* isLightTheme= */ true, /* isLightBackground = */ false); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_notLightThemeAndIsLightBackground_returnsNone() throws Exception { // Set up configurations for force invert color waitForSystemNightModeActivated(true); enableForceInvertColor(true); setUpViewAttributes(/* isLightTheme= */ false, /* isLightBackground = */ true); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } @Test @EnableFlags(FLAG_FORCE_INVERT_COLOR) public void determineForceDarkType_notLightThemeAndNotLightBackground_returnsNone() throws Exception { // Set up configurations for force invert color waitForSystemNightModeActivated(true); enableForceInvertColor(true); setUpViewAttributes(/* isLightTheme= */ false, /* isLightBackground = */ false); TestUtils.waitUntil("Waiting for ForceDarkType to be ready", () -> (mViewRootImpl.determineForceDarkType() == ForceDarkType.NONE)); } @Test Loading Loading @@ -1792,29 +1839,35 @@ public class ViewRootImplTest { sInstrumentation.waitForIdleSync(); } private void verifyForceDarkType(boolean isAppInNightMode, boolean isForceInvertEnabled, @ForceInvertType int expectedForceInvertType, @ForceDarkTypeDef int expectedForceDarkType) { var uiModeManager = sContext.getSystemService(UiModeManager.class); private void enableForceInvertColor(boolean enabled) { ShellIdentityUtils.invokeWithShellPermissions(() -> { uiModeManager.setApplicationNightMode( isAppInNightMode ? MODE_NIGHT_YES : MODE_NIGHT_NO); Settings.Secure.putInt( sContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED, isForceInvertEnabled ? 1 : 0); enabled ? 1 : 0 ); }); sInstrumentation.runOnMainSync(() -> mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())); try { TestUtils.waitUntil("Waiting for force invert state changed", () -> (uiModeManager.getForceInvertState() == expectedForceInvertType)); } catch (Exception e) { Log.e(TAG, "Unexpected error trying to apply force invert state. " + e); e.printStackTrace(); } mExpect.that(mViewRootImpl.determineForceDarkType()).isEqualTo(expectedForceDarkType); private void setUpViewAttributes(boolean isLightTheme, boolean isLightBackground) { ShellIdentityUtils.invokeWithShellPermissions(() -> { sContext.setTheme(isLightTheme ? android.R.style.Theme_DeviceDefault_Light : android.R.style.Theme_DeviceDefault); }); sInstrumentation.runOnMainSync(() -> { View view = new View(sContext); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( TYPE_APPLICATION_OVERLAY); layoutParams.token = new Binder(); view.setLayoutParams(layoutParams); if (isLightBackground) { view.setBackgroundColor(Color.WHITE); } else { view.setBackgroundColor(Color.BLACK); } mViewRootImpl.setView(view, layoutParams, /* panelParentView= */ null); mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()); }); } }