Skip to content

Commit 224793b

Browse files
committed
fwb: Rework Typeface handling to only refresh theme when necessary
Set font at boot completed once and only when changed afterwards, don't hardcode true to always refresh theme on Zygote start. This improve app startup times due to not having to regenerate default Typeface and solves app starting issues caused by Typeface changes running OOM. Also rework the way we send the information to Zygote to also allow clearing the USAP pool on change. Further, adjust the code for Android 12/13 and it's changes in the Font engine. Disable ENABLE_LAZY_TYPEFACE_INITIALIZATION. With out implementation Typeface is initialized at any point anyways, no need to lazy load. Change-Id: I80a92e4fac4a134f48919b7e8a9bcceb528ebc72
1 parent d177d60 commit 224793b

10 files changed

Lines changed: 153 additions & 80 deletions

File tree

core/java/android/os/Process.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -730,14 +730,13 @@ public static ProcessStartResult start(@NonNull final String processClass,
730730
whitelistedDataInfoMap,
731731
boolean bindMountAppsData,
732732
boolean bindMountAppStorageDirs,
733-
boolean refreshTheme,
734733
@Nullable String[] zygoteArgs) {
735734
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
736735
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
737736
abi, instructionSet, appDataDir, invokeWith, packageName,
738737
zygotePolicyFlags, isTopApp, disabledCompatChanges,
739738
pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
740-
bindMountAppStorageDirs, refreshTheme, zygoteArgs);
739+
bindMountAppStorageDirs, zygoteArgs);
741740
}
742741

743742
/** @hide */
@@ -754,7 +753,6 @@ public static ProcessStartResult startWebView(@NonNull final String processClass
754753
@Nullable String invokeWith,
755754
@Nullable String packageName,
756755
@Nullable long[] disabledCompatChanges,
757-
boolean refreshTheme,
758756
@Nullable String[] zygoteArgs) {
759757
// Webview zygote can't access app private data files, so doesn't need to know its data
760758
// info.
@@ -763,7 +761,7 @@ public static ProcessStartResult startWebView(@NonNull final String processClass
763761
abi, instructionSet, appDataDir, invokeWith, packageName,
764762
/*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
765763
disabledCompatChanges, /* pkgDataInfoMap */ null,
766-
/* whitelistedDataInfoMap */ null, false, false, refreshTheme, zygoteArgs);
764+
/* whitelistedDataInfoMap */ null, false, false, zygoteArgs);
767765
}
768766

769767
/**

core/java/android/os/ZygoteProcess.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,6 @@ public final Process.ProcessStartResult start(@NonNull final String processClass
362362
allowlistedDataInfoList,
363363
boolean bindMountAppsData,
364364
boolean bindMountAppStorageDirs,
365-
boolean refreshTheme,
366365
@Nullable String[] zygoteArgs) {
367366
// TODO (chriswailes): Is there a better place to check this value?
368367
if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -375,7 +374,7 @@ public final Process.ProcessStartResult start(@NonNull final String processClass
375374
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
376375
packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
377376
pkgDataInfoMap, allowlistedDataInfoList, bindMountAppsData,
378-
bindMountAppStorageDirs, refreshTheme, zygoteArgs);
377+
bindMountAppStorageDirs, zygoteArgs);
379378
} catch (ZygoteStartFailedEx ex) {
380379
Log.e(LOG_TAG,
381380
"Starting VM process through Zygote failed");
@@ -566,7 +565,8 @@ private static boolean policySpecifiesUsapPoolLaunch(int zygotePolicyFlags) {
566565
"--set-api-denylist-exemptions",
567566
"--hidden-api-log-sampling-rate",
568567
"--hidden-api-statslog-sampling-rate",
569-
"--invoke-with"
568+
"--invoke-with",
569+
"--refresh-typeface"
570570
};
571571

572572
/**
@@ -646,7 +646,6 @@ private Process.ProcessStartResult startViaZygote(@NonNull final String processC
646646
allowlistedDataInfoList,
647647
boolean bindMountAppsData,
648648
boolean bindMountAppStorageDirs,
649-
boolean refreshTheme,
650649
@Nullable String[] extraArgs)
651650
throws ZygoteStartFailedEx {
652651
ArrayList<String> argsForZygote = new ArrayList<>();
@@ -657,9 +656,6 @@ private Process.ProcessStartResult startViaZygote(@NonNull final String processC
657656
argsForZygote.add("--setuid=" + uid);
658657
argsForZygote.add("--setgid=" + gid);
659658
argsForZygote.add("--runtime-flags=" + runtimeFlags);
660-
if (refreshTheme) {
661-
argsForZygote.add("--refresh_theme");
662-
}
663659
if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
664660
argsForZygote.add("--mount-external-default");
665661
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {
@@ -968,6 +964,13 @@ public void setHiddenApiAccessStatslogSampleRate(int rate) {
968964
}
969965
}
970966

967+
public void refreshTypeface() {
968+
synchronized (mLock) {
969+
maybeRefreshTypeface(primaryZygoteState);
970+
maybeRefreshTypeface(secondaryZygoteState);
971+
}
972+
}
973+
971974
@GuardedBy("mLock")
972975
private boolean maybeSetApiDenylistExemptions(ZygoteState state, boolean sendIfEmpty) {
973976
if (state == null || state.isClosed()) {
@@ -1042,6 +1045,27 @@ private void maybeSetHiddenApiAccessStatslogSampleRate(ZygoteState state) {
10421045
}
10431046
}
10441047

1048+
@GuardedBy("mLock")
1049+
private void maybeRefreshTypeface(ZygoteState state) {
1050+
if (state == null || state.isClosed()) {
1051+
return;
1052+
}
1053+
1054+
try {
1055+
state.mZygoteOutputWriter.write(Integer.toString(1));
1056+
state.mZygoteOutputWriter.newLine();
1057+
state.mZygoteOutputWriter.write("--refresh-typeface");
1058+
state.mZygoteOutputWriter.newLine();
1059+
state.mZygoteOutputWriter.flush();
1060+
int status = state.mZygoteInputStream.readInt();
1061+
if (status != 0) {
1062+
Slog.e(LOG_TAG, "Failed to refresh USAP Pool for Typeface change status:" + status);
1063+
}
1064+
} catch (IOException ioe) {
1065+
Slog.e(LOG_TAG, "Failed to refresh USAP Pool for Typeface change", ioe);
1066+
}
1067+
}
1068+
10451069
/**
10461070
* Creates a ZygoteState for the primary zygote if it doesn't exist or has been disconnected.
10471071
*/
@@ -1053,6 +1077,7 @@ private void attemptConnectionToPrimaryZygote() throws IOException {
10531077

10541078
maybeSetApiDenylistExemptions(primaryZygoteState, false);
10551079
maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
1080+
maybeRefreshTypeface(primaryZygoteState);
10561081
}
10571082
}
10581083

@@ -1068,6 +1093,7 @@ private void attemptConnectionToSecondaryZygote() throws IOException {
10681093

10691094
maybeSetApiDenylistExemptions(secondaryZygoteState, false);
10701095
maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
1096+
maybeRefreshTypeface(secondaryZygoteState);
10711097
}
10721098
}
10731099

@@ -1324,7 +1350,7 @@ public ChildZygoteProcess startChildZygote(final String processClass,
13241350
ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
13251351
null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
13261352
null /* allowlistedDataInfoList */, true /* bindMountAppsData*/,
1327-
/* bindMountAppStorageDirs */ false, true /*refreshTheme*/, extraArgs);
1353+
/* bindMountAppStorageDirs */ false, extraArgs);
13281354
} catch (ZygoteStartFailedEx ex) {
13291355
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
13301356
}

core/java/com/android/internal/os/Zygote.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -924,6 +924,9 @@ private static void validateUsapCommand(ZygoteArguments args) {
924924
} else if (args.mHiddenApiAccessStatslogSampleRate != -1) {
925925
throw new IllegalArgumentException(
926926
USAP_ERROR_PREFIX + "--hidden-api-statslog-sampling-rate=");
927+
} else if (args.refreshTypeface) {
928+
throw new IllegalArgumentException(
929+
USAP_ERROR_PREFIX + "--refresh-typeface");
927930
} else if (args.mInvokeWith != null) {
928931
throw new IllegalArgumentException(USAP_ERROR_PREFIX + "--invoke-with");
929932
} else if (args.mPermittedCapabilities != 0 || args.mEffectiveCapabilities != 0) {

core/java/com/android/internal/os/ZygoteArguments.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ class ZygoteArguments {
119119
/** from --package-name */
120120
String mPackageName;
121121

122-
/** from --refresh-theme */
123-
boolean refreshTheme;
122+
/** from --refresh-typeface */
123+
boolean refreshTypeface;
124124

125125
/**
126126
* Any args after and including the first non-option arg (or after a '--')
@@ -484,8 +484,9 @@ private void parseArgs(ZygoteCommandBuffer args, int argCount)
484484
mBindMountAppStorageDirs = true;
485485
} else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
486486
mBindMountAppDataDirs = true;
487-
} else if (arg.equals("--refresh_theme")) {
488-
refreshTheme = true;
487+
} else if (arg.equals("--refresh-typeface")) {
488+
refreshTypeface = true;
489+
expectRuntimeArgs = false;
489490
} else {
490491
unprocessedArg = arg;
491492
break;

core/java/com/android/internal/os/ZygoteConnection.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
187187
return null;
188188
}
189189

190+
if (parsedArgs.refreshTypeface) {
191+
return handleRefreshTypeface(zygoteServer);
192+
}
193+
190194
if (parsedArgs.mPermittedCapabilities != 0
191195
|| parsedArgs.mEffectiveCapabilities != 0) {
192196
throw new ZygoteSecurityException("Client may not specify capabilities: "
@@ -318,10 +322,6 @@ Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
318322
parsedArgs.mHiddenApiAccessStatslogSampleRate);
319323
}
320324

321-
if (parsedArgs.refreshTheme) {
322-
Typeface.recreateDefaults();
323-
}
324-
325325
throw new AssertionError("Shouldn't get here");
326326
}
327327

@@ -425,6 +425,11 @@ private Runnable handleApiDenylistExemptions(ZygoteServer zygoteServer, String[]
425425
() -> ZygoteInit.setApiDenylistExemptions(exemptions));
426426
}
427427

428+
private Runnable handleRefreshTypeface(ZygoteServer zygoteServer) {
429+
return stateChangeWithUsapPoolReset(zygoteServer,
430+
() -> Typeface.recreateDefaults());
431+
}
432+
428433
private Runnable handleUsapPoolStatusChange(ZygoteServer zygoteServer, boolean newStatus) {
429434
try {
430435
Runnable fpResult = zygoteServer.setUsapPoolStatus(newStatus, mSocket);

graphics/java/android/graphics/FontListParser.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.xmlpull.v1.XmlPullParser;
3232
import org.xmlpull.v1.XmlPullParserException;
3333

34+
import java.io.BufferedInputStream;
3435
import java.io.File;
3536
import java.io.FileInputStream;
3637
import java.io.IOException;
@@ -80,7 +81,7 @@ public static FontConfig parse(InputStream in) throws XmlPullParserException, IO
8081
}
8182

8283
public static FontConfig parse(File configFilename, String fontDir) throws XmlPullParserException, IOException {
83-
try (InputStream is = new FileInputStream(configFilename)) {
84+
try (InputStream is = new BufferedInputStream(new FileInputStream(configFilename))) {
8485
XmlPullParser parser = Xml.newPullParser();
8586
parser.setInput(is, null);
8687
parser.nextTag();
@@ -270,6 +271,10 @@ private static boolean keepReading(XmlPullParser parser)
270271
boolean allowNonExistingFile)
271272
throws XmlPullParserException, IOException {
272273

274+
if (!fontDir.endsWith("/")) {
275+
fontDir = fontDir + "/";
276+
}
277+
273278
String indexStr = parser.getAttributeValue(null, ATTR_INDEX);
274279
int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
275280
List<FontVariationAxis> axes = new ArrayList<>();

graphics/java/android/graphics/Typeface.java

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public class Typeface {
9393
private static String TAG = "Typeface";
9494

9595
/** @hide */
96-
public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = true;
96+
public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = false;
9797

9898
private static final NativeAllocationRegistry sRegistry =
9999
NativeAllocationRegistry.createMalloced(
@@ -184,6 +184,11 @@ public class Typeface {
184184

185185
static final String SANS_SERIF_FAMILY_NAME = "sans-serif";
186186

187+
static final String SYSTEM_FONT_CONFIG_LOCATION = "/system/etc/";
188+
static final String THEME_FONT_CONFIG_LOCATION = "/data/system/theme/fonts/";
189+
static final String THEME_FONT_DIR_LOCATION = "/data/system/theme/fonts/";
190+
static final String SYSTEM_FONT_DIR_LOCATION = "/system/fonts/";
191+
187192
/**
188193
* Returns the shared memory that used for creating Typefaces.
189194
*
@@ -1177,8 +1182,17 @@ private static Typeface createFromFamiliesWithDefault(android.graphics.FontFamil
11771182
for (int i = 0; i < families.length; i++) {
11781183
ptrArray[i] = families[i].mNativePtr;
11791184
}
1185+
1186+
long ni;
1187+
1188+
// getSystemDefaultTypeface can return null, so manually set the native_instance then
1189+
if (fallbackTypeface == null) {
1190+
ni = 0;
1191+
} else {
1192+
ni = fallbackTypeface.native_instance;
1193+
}
11801194
return new Typeface(nativeCreateFromArray(
1181-
ptrArray, fallbackTypeface.native_instance, weight, italic));
1195+
ptrArray, ni, weight, italic));
11821196
}
11831197

11841198
// don't allow clients to call this directly
@@ -1231,7 +1245,7 @@ private static android.graphics.FontFamily makeFamilyFromParsed(FontConfig.FontF
12311245
android.graphics.FontFamily fontFamily =
12321246
new android.graphics.FontFamily(family.getLanguages(), family.getVariant());
12331247
for (FontConfig.Font font : family.getFonts()) {
1234-
String fullPathName = font.getFontFamilyName();
1248+
String fullPathName = font.getFile().getAbsolutePath();
12351249
ByteBuffer fontBuffer = bufferForPath.get(fullPathName);
12361250
if (fontBuffer == null) {
12371251
try (FileInputStream file = new FileInputStream(fullPathName)) {
@@ -1325,30 +1339,27 @@ private static void addMissingFontAliases(FontConfig src,
13251339
}
13261340
private static void init() {
13271341
// Load font config and initialize Minikin state
1328-
File systemFontConfigLocation = getSystemFontConfigLocation();
1329-
File themeFontConfigLocation = getThemeFontConfigLocation();
1330-
File systemConfigFile = new File(systemFontConfigLocation, FONTS_CONFIG);
1331-
File themeConfigFile = new File(themeFontConfigLocation, FONTS_CONFIG);
1342+
File systemConfigFile = new File(SYSTEM_FONT_CONFIG_LOCATION, FONTS_CONFIG);
1343+
File themeConfigFile = new File(THEME_FONT_CONFIG_LOCATION, FONTS_CONFIG);
13321344
File configFile = null;
1333-
File fontDir;
1345+
String fontDir;
13341346
if (themeConfigFile.exists()) {
13351347
// /data/system/theme/fonts/ exists so use it and copy default fonts
13361348
configFile = themeConfigFile;
1337-
fontDir = getThemeFontDirLocation();
1349+
fontDir = THEME_FONT_DIR_LOCATION;
13381350
} else {
13391351
configFile = systemConfigFile;
1340-
fontDir = getSystemFontDirLocation();
1352+
fontDir = SYSTEM_FONT_DIR_LOCATION;
13411353
}
13421354
try {
13431355
FontConfig fontConfig = FontListParser.parse(configFile,
1344-
fontDir.getAbsolutePath());
1356+
fontDir);
13451357
FontConfig systemFontConfig = null;
13461358
// If the fonts are coming from a theme, we will need to make sure that we include
13471359
// any font families from the system fonts that the theme did not include.
13481360
// NOTE: All the system font families without names ALWAYS get added.
13491361
if (configFile == themeConfigFile) {
1350-
systemFontConfig = FontListParser.parse(systemConfigFile,
1351-
getSystemFontDirLocation().getAbsolutePath());
1362+
systemFontConfig = FontListParser.parse(systemConfigFile, SYSTEM_FONT_DIR_LOCATION);
13521363
addFallbackFontsForFamilyName(systemFontConfig, fontConfig, SANS_SERIF_FAMILY_NAME);
13531364
addMissingFontFamilies(systemFontConfig, fontConfig);
13541365
addMissingFontAliases(systemFontConfig, fontConfig);
@@ -1378,7 +1389,7 @@ private static void init() {
13781389
if (i == 0) {
13791390
// The first entry is the default typeface; no sense in
13801391
// duplicating the corresponding FontFamily.
1381-
typeface = sDefaultTypeface;
1392+
typeface = getDefault();
13821393
} else {
13831394
android.graphics.FontFamily fontFamily
13841395
= makeFamilyFromParsed(f, bufferForPath);
@@ -1416,20 +1427,13 @@ private static void init() {
14161427
/** @hide */
14171428
public static void recreateDefaults() {
14181429
sDynamicTypefaceCache.evictAll();
1419-
sSystemFontMap.clear();
14201430
sStyledTypefaceCache.clear();
14211431
init();
1422-
DEFAULT_BOLD_INTERNAL = create((String) null, Typeface.BOLD);
1423-
SANS_SERIF_INTERNAL = create("sans-serif", 0);
1424-
SERIF_INTERNAL = create("serif", 0);
1425-
MONOSPACE_INTERNAL = create("monospace", 0);
14261432
DEFAULT.native_instance = DEFAULT_INTERNAL.native_instance;
14271433
DEFAULT_BOLD.native_instance = DEFAULT_BOLD_INTERNAL.native_instance;
14281434
SANS_SERIF.native_instance = SANS_SERIF_INTERNAL.native_instance;
14291435
SERIF.native_instance = SERIF_INTERNAL.native_instance;
14301436
MONOSPACE.native_instance = MONOSPACE_INTERNAL.native_instance;
1431-
sDefaults[2] = create((String) null, Typeface.ITALIC);
1432-
sDefaults[3] = create((String) null, Typeface.BOLD_ITALIC);
14331437
}
14341438

14351439
/**
@@ -1674,22 +1678,6 @@ public static void loadPreinstalledSystemFontMap() {
16741678
}
16751679
}
16761680

1677-
private static File getSystemFontConfigLocation() {
1678-
return new File("/system/etc/");
1679-
}
1680-
1681-
private static File getSystemFontDirLocation() {
1682-
return new File("/system/fonts/");
1683-
}
1684-
1685-
private static File getThemeFontConfigLocation() {
1686-
return new File("/data/system/theme/fonts/");
1687-
}
1688-
1689-
private static File getThemeFontDirLocation() {
1690-
return new File("/data/system/theme/fonts/");
1691-
}
1692-
16931681
@Override
16941682
public boolean equals(Object o) {
16951683
if (this == o) return true;

0 commit comments

Comments
 (0)