Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fc89f1c7 authored by Daniel Norman's avatar Daniel Norman
Browse files

feat(force invert): force invert the entire light-themed app

Instead of forcing dark each rendernode individually, we force invert
the entire app. This helps us avoid issues where some text or icons
don't get detected as dark, and they get inverted, causing black-on-
black text, etc.

The lightness detection in this change uses a simple theme check that
will return the wrong answer if the app theme is incorrect. This will
be improved upon in future changes to check other heuristics like the
main window background color, and to calculate the RenderNode color
area polarity in real time.

Note: this disables the old force dark behavior when the user has the
new force invert dark theme toggle enabled.

Bug: 372561761
Flag: android.view.accessibility.force_invert_color
Test: manual only; observe inverted apps. See attachments in bug.
Change-Id: I86e3e095af206368b5cd3d61330aa1d457103e7d
parent 85ca0f4c
Loading
Loading
Loading
Loading
+32 −18
Original line number Diff line number Diff line
@@ -2071,13 +2071,25 @@ public final class ViewRootImpl implements ViewParent,
     */
    @VisibleForTesting
    public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
        try {
            if (forceInvertColor()) {
                // 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 both force invert and
            // the system's dark theme are enabled.
            if (getUiModeManager().getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK) {
                // 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 both
                // force invert and the system's dark theme are enabled.
                if (getUiModeManager().getForceInvertState() ==
                        UiModeManager.FORCE_INVERT_TYPE_DARK) {
                    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) {
                        return ForceDarkType.FORCE_INVERT_COLOR_DARK;
                    } else {
                        return ForceDarkType.NONE;
                    }
                }
            }
@@ -2085,12 +2097,14 @@ public final class ViewRootImpl implements ViewParent,
            if (useAutoDark) {
                boolean forceDarkAllowedDefault =
                        SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
                useAutoDark = a.getBoolean(R.styleable.Theme_isLightTheme, true)
                    && a.getBoolean(R.styleable.Theme_forceDarkAllowed, forceDarkAllowedDefault);
            a.recycle();
                        && a.getBoolean(R.styleable.Theme_forceDarkAllowed,
                            forceDarkAllowedDefault);
            }
            return useAutoDark ? ForceDarkType.FORCE_DARK : ForceDarkType.NONE;
        } finally {
            a.recycle();
        }
    }
    private void updateForceDarkMode() {
+30 −14
Original line number Diff line number Diff line
@@ -55,12 +55,20 @@ SkColor makeDark(SkColor color) {
    }
}

SkColor invert(SkColor color) {
    Lab lab = sRGBToLab(color);
    lab.L = 100 - lab.L;
    return LabToSRGB(lab, SkColorGetA(color));
}

SkColor transformColor(ColorTransform transform, SkColor color) {
    switch (transform) {
        case ColorTransform::Light:
            return makeLight(color);
        case ColorTransform::Dark:
            return makeDark(color);
        case ColorTransform::Invert:
            return invert(color);
        default:
            return color;
    }
@@ -80,19 +88,6 @@ SkColor transformColorInverse(ColorTransform transform, SkColor color) {
static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
    if (transform == ColorTransform::None) return;

    if (transform == ColorTransform::Invert) {
        auto filter = SkHighContrastFilter::Make(
                {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness,
                 /* contrast= */ 0.0f});

        if (paint.getColorFilter()) {
            paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
        } else {
            paint.setColorFilter(filter);
        }
        return;
    }

    SkColor newColor = transformColor(transform, paint.getColor());
    paint.setColor(newColor);

@@ -112,6 +107,22 @@ static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
            paint.setShader(SkGradientShader::MakeLinear(
                    info.fPoints, info.fColors, info.fColorOffsets, info.fColorCount,
                    info.fTileMode, info.fGradientFlags, nullptr));
        } else {
            if (transform == ColorTransform::Invert) {
                // Since we're trying to invert every thing around this draw call, we invert
                // the color of the draw call if we don't know what it is.
                auto filter = SkHighContrastFilter::Make(
                        {/* grayscale= */ false,
                         SkHighContrastConfig::InvertStyle::kInvertLightness,
                         /* contrast= */ 0.0f});

                if (paint.getColorFilter()) {
                    paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
                } else {
                    paint.setColorFilter(filter);
                }
                return;
            }
        }
    }

@@ -150,8 +161,13 @@ bool transformPaint(ColorTransform transform, SkPaint* paint) {
}

bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) {
    palette = filterPalette(paint, palette);
    bool shouldInvert = false;
    if (transform == ColorTransform::Invert && palette != BitmapPalette::Colorful) {
        // When the transform is Invert we invert any image that is not deemed "colorful",
        // regardless of calculated image brightness.
        shouldInvert = true;
    }
    palette = filterPalette(paint, palette);
    if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) {
        shouldInvert = true;
    }
+17 −16
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@

#include "DamageAccumulator.h"
#include "Debug.h"
#include "FeatureFlags.h"
#include "Properties.h"
#include "TreeInfo.h"
#include "VectorDrawable.h"
@@ -398,26 +399,32 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
    deleteDisplayList(observer, info);
    mDisplayList = std::move(mStagingDisplayList);
    if (mDisplayList) {
        WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
        WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info) ||
                                                   (info && isForceInvertDark(*info))};
        mDisplayList.syncContents(syncData);
        handleForceDark(info);
    }
}

// Return true if the tree should use the force invert feature that inverts
// the entire tree to darken it.
inline bool RenderNode::isForceInvertDark(TreeInfo& info) {
    return CC_UNLIKELY(
             info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
    return CC_UNLIKELY(info.forceDarkType ==
                       android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK);
}

// Return true if the tree should use the force dark feature that selectively
// darkens light nodes on the tree.
inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
    return CC_UNLIKELY(
            info &&
            (!info->disableForceDark || isForceInvertDark(*info)));
    return CC_UNLIKELY(info && !info->disableForceDark);
}



void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
void RenderNode::handleForceDark(TreeInfo *info) {
    if (CC_UNLIKELY(view_accessibility_flags::force_invert_color() && info &&
                    isForceInvertDark(*info))) {
        mDisplayList.applyColorTransform(ColorTransform::Invert);
        return;
    }
    if (!shouldEnableForceDark(info)) {
        return;
    }
@@ -427,14 +434,8 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
        children.push_back(node);
    });
    if (mDisplayList.hasText()) {
        if (isForceInvertDark(*info) && mDisplayList.hasFill()) {
            // Handle a special case for custom views that draw both text and background in the
            // same RenderNode, which would otherwise be altered to white-on-white text.
            usage = UsageHint::Container;
        } else {
        usage = UsageHint::Foreground;
    }
    }
    if (usage == UsageHint::Unknown) {
        if (children.size() > 1) {
            usage = UsageHint::Background;
+11 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@
#include "Bitmap.h"

#include <android-base/file.h>

#include "FeatureFlags.h"
#include "HardwareBitmapUploader.h"
#include "Properties.h"
#ifdef __ANDROID__  // Layoutlib does not support render thread
@@ -547,9 +549,16 @@ BitmapPalette Bitmap::computePalette(const SkImageInfo& info, const void* addr,
    }

    ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = "
          "%f]",
          "%f] %d x %d",
          sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(),
          saturation.average());
          saturation.average(), info.width(), info.height());

    if (CC_UNLIKELY(view_accessibility_flags::force_invert_color())) {
        if (saturation.delta() > 0.1f ||
            (hue.delta() > 20 && saturation.average() > 0.2f && value.average() < 0.9f)) {
            return BitmapPalette::Colorful;
        }
    }

    if (hue.delta() <= 20 && saturation.delta() <= .1f) {
        if (value.average() >= .5f) {
+1 −0
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ enum class BitmapPalette {
    Unknown,
    Light,
    Dark,
    Colorful,
};

namespace uirenderer {