Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/java/android/app/ActivityThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@
import com.android.internal.util.Preconditions;
import com.android.internal.util.StringCache;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.halcyon.FontController;
import com.android.org.conscrypt.TrustedCertificateStore;
import com.android.server.am.BitmapDumpProto;
import com.android.server.am.MemInfoDumpProto;
Expand Down Expand Up @@ -7187,6 +7188,8 @@ public void handleConfigurationChanged(Configuration config, int deviceId) {
mConfigurationController.handleConfigurationChanged(config);
updateDeviceIdForNonUIContexts(deviceId);

FontController.OnConfigurationChanged(getApplication().getResources());

// These are only done to maintain @UnsupportedAppUsage and should be removed someday.
mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
mConfiguration = mConfigurationController.getConfiguration();
Expand Down Expand Up @@ -7941,6 +7944,9 @@ private void handleBindApplication(AppBindData data) {
data.info = getPackageInfo(data.appInfo, mCompatibilityInfo, null /* baseLoader */,
false /* securityViolation */, true /* includeCode */,
false /* registerPackage */, isSdkSandbox);

FontController.OnConfigurationChanged(data.info.getResources());

if (isSdkSandbox) {
data.info.setSdkSandboxStorage(data.sdkSandboxClientAppVolumeUuid,
data.sdkSandboxClientAppPackage);
Expand Down
4 changes: 4 additions & 0 deletions core/java/android/app/ConfigurationController.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
import android.graphics.Typeface;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.LocaleList;
import android.os.Trace;
import android.util.DisplayMetrics;
Expand Down Expand Up @@ -198,6 +201,7 @@ private void handleConfigurationChangedInner(@Nullable Configuration config,

final Application app = mActivityThread.getApplication();
final Resources appResources = app.getResources();
Typeface.updateDefaultFont(appResources);
mResourcesManager.applyConfigurationToResources(config, compat);
updateLocaleListFromAppContext(app.getApplicationContext());

Expand Down
15 changes: 15 additions & 0 deletions core/java/android/widget/TextView.java
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ private void applyErrorDrawableIfNeeded(int layoutDirection) {
// more bold.
private int mFontWeightAdjustment;
private Typeface mOriginalTypeface;
private String mFontFamily;

// True if setKeyListener() has been explicitly called
private boolean mListenerChanged = false;
Expand Down Expand Up @@ -4388,6 +4389,7 @@ private void readTextAppearance(Context context, TypedArray appearance,
attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex);
if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
attributes.mFontFamily = null;
mFontFamily = null;
}
break;
case com.android.internal.R.styleable.TextAppearance_fontFamily:
Expand All @@ -4400,6 +4402,7 @@ private void readTextAppearance(Context context, TypedArray appearance,
}
if (attributes.mFontTypeface == null) {
attributes.mFontFamily = appearance.getString(attr);
mFontFamily = attributes.mFontFamily;
}
attributes.mFontFamilyExplicit = true;
break;
Expand Down Expand Up @@ -4495,6 +4498,7 @@ private void applyTextAppearance(TextAppearanceAttributes attributes) {

if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) {
attributes.mFontFamily = null;
mFontFamily = null;
}
setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily,
attributes.mTypefaceIndex, attributes.mTextStyle, attributes.mFontWeight);
Expand Down Expand Up @@ -4528,6 +4532,10 @@ private void applyTextAppearance(TextAppearanceAttributes attributes) {
setFontVariationSettings(attributes.mFontVariationSettings);
}

if (Typeface.getFontName().equals("inter")) {
setFontFeatureSettings("'ss01'");
}

if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
Expand Down Expand Up @@ -4669,6 +4677,13 @@ protected void onConfigurationChanged(Configuration newConfig) {
invalidate();
}
}

if (!TextUtils.equals(mFontFamily, Typeface.getFontName())) {
Typeface tf = Typeface.getOverrideTypeface(mFontFamily);
setTypeface(tf);
mFontFamily = Typeface.getFontName();
}

if (mFontWeightAdjustment != newConfig.fontWeightAdjustment) {
mFontWeightAdjustment = newConfig.fontWeightAdjustment;
setTypeface(getTypeface());
Expand Down
183 changes: 183 additions & 0 deletions core/java/com/android/internal/util/halcyon/FontController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Copyright (C) 2025 AxionOS
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.util.halcyon;

import android.app.ActivityThread;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.Set;

public class FontController {

private static final String TAG = "FontController";

private static FontController sInstance = null;

private static final Set<String> OVERRIDE_FONTS = new HashSet<>(Arrays.asList(
"google", "sans-serif", "gsf-"
));

private static final Set<String> EXCLUDED_APPS = new HashSet<>(Arrays.asList(
"it.subito",
"tv.arte.plus7",
"com.google.android.gm"
));

private static final Map<String, Integer> WEIGHT_MAP = new ArrayMap<>();
static {
WEIGHT_MAP.put("thin", 100);
WEIGHT_MAP.put("extralight", 200);
WEIGHT_MAP.put("light", 300);
WEIGHT_MAP.put("normal", 400);
WEIGHT_MAP.put("regular", 400);
WEIGHT_MAP.put("medium", 500);
WEIGHT_MAP.put("semibold", 600);
WEIGHT_MAP.put("bold", 700);
WEIGHT_MAP.put("extrabold", 800);
WEIGHT_MAP.put("black", 900);
}

public static FontController get() {
if (sInstance == null) {
sInstance = new FontController();
}
return sInstance;
}

private FontController() {}

public static void OnConfigurationChanged(Resources res) {
get().handleOnConfiguration(res);
}

public static Typeface getOverrideTypeface(String fontToOverride) {
if (fontToOverride == null) return null;

String pkgName = getCurrentPackageName();
if (pkgName != null && EXCLUDED_APPS.contains(pkgName)) {
logger("Excluded app, skipping override: " + pkgName);
return null;
}

String currentFont = Typeface.getFontName();

if (fontToOverride.matches("^" + Pattern.quote(currentFont) + "(-.*)?$")) {
logger(fontToOverride + " matches current font root '" + currentFont + "', skipping override!");
return null;
}

boolean override = OVERRIDE_FONTS.stream().anyMatch(fontToOverride::contains);
if (!override) {
logger("Not on override list, skipping override: " + fontToOverride);
return null;
}

int adjustment = getFontWeightAdjustment();
return TypefaceFactory.create(fontToOverride, currentFont, adjustment);
}

private void handleOnConfiguration(Resources res) {
String pkgName = getCurrentPackageName();
if (pkgName == null || EXCLUDED_APPS.contains(pkgName)) return;
logger("handleOnConfiguration: Changing default font to: " + Typeface.getFontName());
Typeface.changeFont(res);
}

private static String getCurrentPackageName() {
try {
return ActivityThread.currentPackageName();
} catch (Exception e) {
logger("getCurrentPackageName failed: " + e.getMessage());
return null;
}
}

private static Resources getResources() {
try {
return Resources.getSystem();
} catch (Exception e) {
logger("getResources failed: " + e.getMessage());
return null;
}
}

private static int getFontWeightAdjustment() {
try {
Resources res = getResources();
if (res == null) return 0;
Configuration cfg = res.getConfiguration();
return cfg != null ? cfg.fontWeightAdjustment : 0;
} catch (Exception e) {
logger("getFontWeightAdjustment failed: " + e.getMessage());
return 0;
}
}

private static void logger(String msg) {
if (SystemProperties.getBoolean("persist.sys.ax_font_debug", false)) {
Log.d(TAG, msg);
}
}

private static class TypefaceFactory {

public static Typeface create(String fontToOverride, String currentFont, int fontWeightAdjustment) {
int weight = resolveWeightByName(fontToOverride);

if (fontWeightAdjustment != 0) {
weight = Math.min(1000, Math.max(100, weight + fontWeightAdjustment));
}

boolean isBold = weight >= 700;
boolean isItalic = fontToOverride.contains("italic");

int style = Typeface.NORMAL;
if (isBold && isItalic) style = Typeface.BOLD_ITALIC;
else if (isBold) style = Typeface.BOLD;
else if (isItalic) style = Typeface.ITALIC;

Typeface base = Typeface.getSystemDefaultTypeface(currentFont);
Typeface result = Typeface.create(base, style);
result = Typeface.create(result, weight, isItalic);

logger("TypefaceFactory.create: fontToOverride=" + fontToOverride +
", style=" + style +
", weight=" + weight +
", adj=" + fontWeightAdjustment +
", isItalic=" + isItalic +
", success=" + (result != null));

return result;
}

private static int resolveWeightByName(String familyName) {
for (Map.Entry<String, Integer> entry : WEIGHT_MAP.entrySet()) {
if (familyName.contains(entry.getKey())) {
return entry.getValue();
}
}
return 400;
}
}
}
Loading