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

Commit 62e5f877 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[2nd attempt] Use pending Typeface not to overwrite static final fields" into main

parents 56961180 e14e44bf
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -239,6 +239,15 @@ flag {
  bug: "430485331"
}

flag {
  name: "do_not_overwrite_static_final_field"
  namespace: "text"
  description: "Removing code that write static final field which will not be supported in the upcoming Java"
  bug: "421199194"
  # Being read only because this is used in Zygote
  is_fixed_read_only: true
}

flag {
  name: "fix_paint_reset_inconsistency"
  namespace: "text"
+34 −0
Original line number Diff line number Diff line
@@ -278,4 +278,38 @@ public class TypefaceTest {
        paint.setTypeface(typeface);
        return paint.measureText(text);
    }

    @SmallTest
    @Test
    public void testInitializePendingTypefaceLocked() throws Exception {
        // Scenario 1: The font family exists in the map.
        Map<String, Typeface> systemFontMap = new HashMap<>();
        Typeface realSansSerif = Typeface.create("sans-serif", Typeface.NORMAL);
        assertNotNull("Precondition: sans-serif should exist", realSansSerif);
        systemFontMap.put("sans-serif", realSansSerif);

        Typeface pendingSansSerif = new Typeface(Typeface.NORMAL, 400, "sans-serif");
        Typeface.initializePendingTypefaceLocked(pendingSansSerif, "sans-serif", systemFontMap);

        // Verify the pending Typeface was initialized with the real one.
        assertEquals(realSansSerif.getNativeInstance(), pendingSansSerif.getNativeInstance());
        // Verify the map was updated to point to the pending instance.
        assertEquals(pendingSansSerif, systemFontMap.get("sans-serif"));


        // Scenario 2: The font family does not exist, causing a fallback to the default.
        Map<String, Typeface> systemFontMapWithFallback = new HashMap<>();
        Typeface defaultTypeface = Typeface.create((String) null, Typeface.NORMAL);
        assertNotNull("Precondition: default typeface should exist", defaultTypeface);
        systemFontMapWithFallback.put(Typeface.DEFAULT_FAMILY, defaultTypeface);

        Typeface pendingNonExistent = new Typeface(Typeface.NORMAL, 400, "non-existent-family");
        Typeface.initializePendingTypefaceLocked(
                pendingNonExistent, "non-existent-family", systemFontMapWithFallback);

        // Verify the pending Typeface was initialized with the default typeface.
        assertEquals(defaultTypeface.getNativeInstance(), pendingNonExistent.getNativeInstance());
        // Verify the map was updated.
        assertEquals(pendingNonExistent, systemFontMapWithFallback.get("non-existent-family"));
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -1596,7 +1596,7 @@ public class Paint {
     * @return         typeface
     */
    public Typeface setTypeface(Typeface typeface) {
        final long typefaceNative = typeface == null ? 0 : typeface.native_instance;
        final long typefaceNative = typeface == null ? 0 : typeface.getNativeInstance();
        nSetTypeface(mNativePaint, typefaceNative);
        mTypeface = typeface;
        return typeface;
+172 −35
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;

/**
@@ -103,19 +104,19 @@ public class Typeface {
    }

    /** The default NORMAL typeface object */
    public static final Typeface DEFAULT = null;
    public static final Typeface DEFAULT;
    /**
     * The default BOLD typeface object. Note: this may be not actually be
     * bold, depending on what fonts are installed. Call getStyle() to know
     * for sure.
     */
    public static final Typeface DEFAULT_BOLD = null;
    public static final Typeface DEFAULT_BOLD;
    /** The NORMAL style of the default sans serif typeface. */
    public static final Typeface SANS_SERIF = null;
    public static final Typeface SANS_SERIF;
    /** The NORMAL style of the default serif typeface. */
    public static final Typeface SERIF = null;
    public static final Typeface SERIF;
    /** The NORMAL style of the default monospace typeface. */
    public static final Typeface MONOSPACE = null;
    public static final Typeface MONOSPACE;

    /**
     * The default {@link Typeface}s for different text styles.
@@ -214,16 +215,54 @@ public class Typeface {
    }

    /**
     * DO NOT USE THIS FIELD DIRECTLY: This value is now 0 in case of pending Typeface.
     * @hide
     */
    @UnsupportedAppUsage
    public final long native_instance;

    private final Typeface mDerivedFrom;
    /** @hide */
    public long getNativeInstance() {
        if (mPendingTypeface != null) {
            Typeface pendingTypeface = mPendingTypeface.get();
            if (pendingTypeface == null) {
                throw new IllegalStateException("The Typeface is not fully initialized.");
            }
            return pendingTypeface.getNativeInstance();
        } else {
            return native_instance;
        }
    }

    private final @Nullable Typeface mDerivedFrom;

    private final String mSystemFontFamilyName;
    private final @Nullable String mSystemFontFamilyName;

    private final @Nullable Runnable mCleaner;

    /**
     * A special reference for lazily initializing static Typeface fields (e.g., Typeface.SERIF).
     *
     * Problem: Public static final Typeface fields must be initialized when the Typeface class is
     * loaded by Zygote. However, the actual font resources aren't available until later, during
     * application startup. Since these fields are final, they cannot be reassigned.
     *
     * Solution: These fields are initially assigned a placeholder (referring to null) for pending
     * Typeface objects in Zygote. Later, during app startup, this field is updated to point
     * to the fully initialized Typeface.
     */
    private final @Nullable AtomicReference<Typeface> mPendingTypeface;

    private final Runnable mCleaner;
    private void completeTypefaceInitialization(@NonNull Typeface initializedTypeface) {
        if (mPendingTypeface == null) {
            throw new IllegalStateException(
                    "Do not call this method other than placeholder Typeface.");
        }
        if (!mPendingTypeface.compareAndSet(null, initializedTypeface)) {
            throw new IllegalStateException("The pending Typeface is already initialized."
                    + " Do not call this method multiple times.");
        }
    }

    /** @hide */
    @IntDef(value = {NORMAL, BOLD, ITALIC, BOLD_ITALIC})
@@ -252,6 +291,30 @@ public class Typeface {
     */
    public static final String DEFAULT_FAMILY = "sans-serif";

    static {
        if (Flags.doNotOverwriteStaticFinalField()) {
            DEFAULT_BOLD = new Typeface(Typeface.BOLD, 700, null);
            SANS_SERIF = new Typeface(Typeface.NORMAL, 400, "sans-serif");
            SERIF = new Typeface(Typeface.NORMAL, 400, "serif");
            MONOSPACE = new Typeface(Typeface.NORMAL, 400, "monospace");
            // To pass an existing Typeface reference check with Typeface.DEFAULT,
            // e.g., Typeface.DEFAULT == Typeface.SANS_SERIF, use the same instance if the default
            // family matches with the other key of the static field.
            DEFAULT = switch (DEFAULT_FAMILY) {
                case "sans-serif" -> SANS_SERIF;
                case "serif" -> SERIF;
                case "monospace" -> MONOSPACE;
                default -> new Typeface(Typeface.NORMAL, 400, null);
            };
        } else {
            DEFAULT = null;
            DEFAULT_BOLD = null;
            SANS_SERIF = null;
            SERIF = null;
            MONOSPACE = null;
        }
    }

    // Style value for building typeface.
    private static final int STYLE_NORMAL = 0;
    private static final int STYLE_ITALIC = 1;
@@ -269,7 +332,7 @@ public class Typeface {
    private static void setDefault(Typeface t) {
        synchronized (SYSTEM_FONT_MAP_LOCK) {
            sDefaultTypeface = t;
            nativeSetDefault(t.native_instance);
            nativeSetDefault(t.getNativeInstance());
        }
    }

@@ -918,7 +981,7 @@ public class Typeface {
            final int italic =
                    (mStyle == null || mStyle.getSlant() == FontStyle.FONT_SLANT_UPRIGHT) ?  0 : 1;
            return new Typeface(nativeCreateFromArray(
                    ptrArray, fallbackTypeface.native_instance, weight, italic), null);
                    ptrArray, fallbackTypeface.getNativeInstance(), weight, italic), null);
        }
    }

@@ -967,7 +1030,7 @@ public class Typeface {
            return family;
        }

        final long ni = family.native_instance;
        final long ni = family.getNativeInstance();

        Typeface typeface;
        synchronized (sStyledCacheLock) {
@@ -1040,10 +1103,10 @@ public class Typeface {

        Typeface typeface;
        synchronized(sWeightCacheLock) {
            SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.native_instance);
            SparseArray<Typeface> innerCache = sWeightTypefaceCache.get(base.getNativeInstance());
            if (innerCache == null) {
                innerCache = new SparseArray<>(4);
                sWeightTypefaceCache.put(base.native_instance, innerCache);
                sWeightTypefaceCache.put(base.getNativeInstance(), innerCache);
            } else {
                typeface = innerCache.get(key);
                if (typeface != null) {
@@ -1052,7 +1115,8 @@ public class Typeface {
            }

            typeface = new Typeface(
                    nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic),
                    nativeCreateFromTypefaceWithExactStyle(base.getNativeInstance(), weight,
                            italic),
                    base.getSystemFontFamilyName());
            innerCache.put(key, typeface);
        }
@@ -1085,11 +1149,12 @@ public class Typeface {
            final String key = axesToVarKey(axes);

            synchronized (sVariableCacheLock) {
                LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance);
                LruCache<String, Typeface> innerCache = sVariableCache.get(
                        base.getNativeInstance());
                if (innerCache == null) {
                    // Cache up to 16 var instance per root Typeface
                    innerCache = new LruCache<>(16);
                    sVariableCache.put(base.native_instance, innerCache);
                    sVariableCache.put(base.getNativeInstance(), innerCache);
                } else {
                    Typeface cached = innerCache.get(key);
                    if (cached != null) {
@@ -1097,7 +1162,7 @@ public class Typeface {
                    }
                }
                Typeface typeface = new Typeface(
                        nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
                        nativeCreateFromTypefaceWithVariation(base.getNativeInstance(), axes),
                        base.getSystemFontFamilyName(), base);
                innerCache.put(key, typeface);
                return typeface;
@@ -1106,7 +1171,7 @@ public class Typeface {

        final Typeface base = family == null ? Typeface.DEFAULT : family;
        Typeface typeface = new Typeface(
                nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
                nativeCreateFromTypefaceWithVariation(base.getNativeInstance(), axes),
                base.getSystemFontFamilyName());
        return typeface;
    }
@@ -1258,7 +1323,7 @@ public class Typeface {
            ptrArray[i] = families[i].mNativePtr;
        }
        return new Typeface(nativeCreateFromArray(
                ptrArray, fallbackTypeface.native_instance, weight, italic), null);
                ptrArray, fallbackTypeface.getNativeInstance(), weight, italic), null);
    }

    // don't allow clients to call this directly
@@ -1287,6 +1352,20 @@ public class Typeface {
        mWeight = nativeGetWeight(ni);
        mSystemFontFamilyName = systemFontFamilyName;
        mDerivedFrom = derivedFrom;
        mPendingTypeface = null;
    }

    // Constructor for pending Typeface. Do not use this other than Zygote init.
    /** @hide */
    @VisibleForTesting
    public Typeface(@Style int style, int weight, @Nullable String systemFontFamilyName) {
        native_instance = 0;
        mCleaner = () -> {};
        mStyle = style;
        mWeight = weight;
        mSystemFontFamilyName = systemFontFamilyName;
        mDerivedFrom = null;
        mPendingTypeface = new AtomicReference<>(null);
    }

    /**
@@ -1329,7 +1408,7 @@ public class Typeface {
            }
            final int weight = alias.getWeight();
            final Typeface newFace = weight == 400 ? base : new Typeface(
                    nativeCreateWeightAlias(base.native_instance, weight), alias.getName());
                    nativeCreateWeightAlias(base.getNativeInstance(), weight), alias.getName());
            outSystemFontMap.put(alias.getName(), newFace);
        }
    }
@@ -1337,7 +1416,7 @@ public class Typeface {
    private static void registerGenericFamilyNative(@NonNull String familyName,
            @Nullable Typeface typeface) {
        if (typeface != null) {
            nativeRegisterGenericFamily(familyName, typeface.native_instance);
            nativeRegisterGenericFamily(familyName, typeface.getNativeInstance());
        }
    }

@@ -1354,7 +1433,7 @@ public class Typeface {
        ByteArrayOutputStream namesBytes = new ByteArrayOutputStream();
        int i = 0;
        for (Map.Entry<String, Typeface> entry : fontMap.entrySet()) {
            nativePtrs[i++] = entry.getValue().native_instance;
            nativePtrs[i++] = entry.getValue().getNativeInstance();
            writeString(namesBytes, entry.getKey());
        }
        // int (typefacesBytesCount), typefaces, namesBytes
@@ -1482,6 +1561,29 @@ public class Typeface {
        }
    }

    /** @hide */
    @GuardedBy("SYSTEM_FONT_MAP_LOCK")
    @VisibleForTesting
    public static void initializePendingTypefaceLocked(Typeface pending, String familyName,
            Map<String, Typeface> systemFontMap) {

        Typeface typeface = systemFontMap.get(familyName);
        if (typeface == null) {
            // If the requested font family is not installed, fall back to the default font family.
            // This is a long-standing behavior.
            Log.i(TAG, "The typeface for " + familyName + " is not installed in this device.");
            pending.completeTypefaceInitialization(systemFontMap.get(DEFAULT_FAMILY));
        } else {
            pending.completeTypefaceInitialization(typeface);
        }

        // The pending typeface is now fully initialized.
        // To ensure that instance equality checks (e.g.,
        // `Typeface.SANS_SERIF == Typeface.create("sans-serif", Typeface.NORMAL)`)
        // pass, replace the instance in the system font map with the pending Typeface.
        systemFontMap.put(familyName, pending);
    }

    /** @hide */
    @VisibleForTesting
    public static void setSystemFontMap(Map<String, Typeface> systemFontMap) {
@@ -1491,16 +1593,51 @@ public class Typeface {

            // We can't assume DEFAULT_FAMILY available on Roboletric.
            if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
                if (Flags.doNotOverwriteStaticFinalField()) {
                    initializePendingTypefaceLocked(SANS_SERIF, "sans-serif", sSystemFontMap);
                    initializePendingTypefaceLocked(SERIF, "serif", sSystemFontMap);
                    initializePendingTypefaceLocked(MONOSPACE, "monospace", sSystemFontMap);

                    setDefault(sSystemFontMap.get(DEFAULT_FAMILY));

                    // Skip initializing Typeface.DEFAULT if it is an alias for another static field
                    // (e.g., SANS_SERIF), as that field has already been initialized.
                    if (DEFAULT != SANS_SERIF && DEFAULT != SERIF && DEFAULT != MONOSPACE) {
                        DEFAULT.completeTypefaceInitialization(
                                create(DEFAULT_FAMILY, Typeface.NORMAL));
                    }
                    DEFAULT_BOLD.completeTypefaceInitialization(
                            create(DEFAULT_FAMILY, Typeface.BOLD));
                } else {
                    setDefault(sSystemFontMap.get(DEFAULT_FAMILY));

                    // Set up defaults and typefaces exposed in public API
            // Use sDefaultTypeface here, because create(String, int) uses DEFAULT as fallback.
            nativeForceSetStaticFinalField("DEFAULT", create(sDefaultTypeface, 0));
            nativeForceSetStaticFinalField("DEFAULT_BOLD", create(sDefaultTypeface, Typeface.BOLD));
            nativeForceSetStaticFinalField("SANS_SERIF", create("sans-serif", 0));
            nativeForceSetStaticFinalField("SERIF", create("serif", 0));
            nativeForceSetStaticFinalField("MONOSPACE", create("monospace", 0));
                    // Use sDefaultTypeface here, because create(String, int) uses DEFAULT as
                    // fallback.
                    nativeForceSetStaticFinalField("DEFAULT",
                            create(sDefaultTypeface, Typeface.NORMAL));
                    nativeForceSetStaticFinalField("DEFAULT_BOLD",
                            create(sDefaultTypeface, Typeface.BOLD));
                    nativeForceSetStaticFinalField("SANS_SERIF",
                            create("sans-serif", Typeface.NORMAL));
                    nativeForceSetStaticFinalField("SERIF", create("serif", Typeface.NORMAL));
                    nativeForceSetStaticFinalField("MONOSPACE",
                            create("monospace", Typeface.NORMAL));
                }
            } else {
                // Robolectric disables Typeface static initializer and call
                // loadPreinstalledSystemFontMap to load system font map manually when the class is
                // loaded.
                nativeForceSetStaticFinalField("DEFAULT",
                        create(sDefaultTypeface, Typeface.NORMAL));
                nativeForceSetStaticFinalField("DEFAULT_BOLD",
                        create(sDefaultTypeface, Typeface.BOLD));
                nativeForceSetStaticFinalField("SANS_SERIF",
                        create("sans-serif", Typeface.NORMAL));
                nativeForceSetStaticFinalField("SERIF", create("serif", Typeface.NORMAL));
                nativeForceSetStaticFinalField("MONOSPACE",
                        create("monospace", Typeface.NORMAL));
            }

            sDefaults = new Typeface[]{
                DEFAULT,
@@ -1670,7 +1807,7 @@ public class Typeface {
    public static void loadNativeSystemFonts() {
        synchronized (SYSTEM_FONT_MAP_LOCK) {
            for (var type : sSystemFontMap.values()) {
                nativeAddFontCollections(type.native_instance);
                nativeAddFontCollections(type.getNativeInstance());
            }
        }
    }
@@ -1688,7 +1825,7 @@ public class Typeface {

        Typeface typeface = (Typeface) o;

        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
        return mStyle == typeface.mStyle && getNativeInstance() == typeface.getNativeInstance();
    }

    @Override
@@ -1698,7 +1835,7 @@ public class Typeface {
         * http://developer.android.com/reference/java/lang/Object.html
         */
        int result = 17;
        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
        result = 31 * result + (int) (getNativeInstance() ^ (getNativeInstance() >>> 32));
        result = 31 * result + mStyle;
        return result;
    }
@@ -1707,7 +1844,7 @@ public class Typeface {
    public boolean isSupportedAxes(int axis) {
        synchronized (this) {
            if (mSupportedAxes == null) {
                mSupportedAxes = nativeGetSupportedAxes(native_instance);
                mSupportedAxes = nativeGetSupportedAxes(getNativeInstance());
                if (mSupportedAxes == null) {
                    mSupportedAxes = EMPTY_AXES;
                }