From 4b5a4d221f377686a730182a3bffb8c6f190e313 Mon Sep 17 00:00:00 2001 From: Clara Bayarri Date: Fri, 27 Jan 2017 20:15:45 +0000 Subject: [PATCH] Declarative downloadable fonts Implement support for downloadable font requests in xml. Given the xml fonts feature in O, this adds support to not only declare local font files as font resources, but also Downloadable fonts from a fonts provider. A provider returns a font family (of one or more files) given a query, so the new attributes are added to the font-family tag. Additionally, add support to pre-declare downloadable font resources in the Android Manifest. These will then be fetched at app startup time so they are available to use from the Typeface cache asap. When retrieving downloadable fonts via resources, the cache is checked to see if the font is already there and is used, otherwise a request is sent to the provider and the default font is returned as we need a result synchronously. To do this, the developer declares an additional fonts xml resource file with the list of fonts to preload and links it in the manifest with a meta-data tag. E.g.: res/font/mydownloadedfont.xml res/font/preloaded_fonts.xml and in the AndroidManifest.xml Bug: 34660500, 34658116 Test: WIP, need to add more Change-Id: I1d92555e115e241bf23b59e6f5c6cca6c7361de7 --- api/current.txt | 2 + api/system-current.txt | 2 + api/test-current.txt | 2 + core/java/android/app/ActivityThread.java | 18 ++++ .../android/content/pm/ApplicationInfo.java | 5 ++ .../content/res/FontResourcesParser.java | 14 ++- core/java/android/content/res/Resources.java | 14 +++ .../android/content/res/ResourcesImpl.java | 39 ++++++++ core/java/android/text/FontConfig.java | 88 +++++++++++++++++-- core/res/res/values/attrs.xml | 6 ++ core/res/res/values/public.xml | 3 + .../res/font/samplexmldownloadedfont.xml | 5 ++ .../content/res/FontResourcesParserTest.java | 17 ++++ graphics/java/android/graphics/Typeface.java | 82 +++++++++++------ 14 files changed, 261 insertions(+), 36 deletions(-) create mode 100644 core/tests/coretests/res/font/samplexmldownloadedfont.xml diff --git a/api/current.txt b/api/current.txt index e0d895ed85cc7..0084461a4f44b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -601,6 +601,8 @@ package android { field public static final int font = 16844082; // 0x1010532 field public static final int fontFamily = 16843692; // 0x10103ac field public static final int fontFeatureSettings = 16843959; // 0x10104b7 + field public static final int fontProviderAuthority = 16844114; // 0x1010552 + field public static final int fontProviderQuery = 16844115; // 0x1010553 field public static final int fontStyle = 16844081; // 0x1010531 field public static final int fontWeight = 16844083; // 0x1010533 field public static final int footerDividersEnabled = 16843311; // 0x101022f diff --git a/api/system-current.txt b/api/system-current.txt index a5f30819e353e..6f527907feb3e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -713,6 +713,8 @@ package android { field public static final int font = 16844082; // 0x1010532 field public static final int fontFamily = 16843692; // 0x10103ac field public static final int fontFeatureSettings = 16843959; // 0x10104b7 + field public static final int fontProviderAuthority = 16844114; // 0x1010552 + field public static final int fontProviderQuery = 16844115; // 0x1010553 field public static final int fontStyle = 16844081; // 0x1010531 field public static final int fontWeight = 16844083; // 0x1010533 field public static final int footerDividersEnabled = 16843311; // 0x101022f diff --git a/api/test-current.txt b/api/test-current.txt index a983cf540b644..e6c8747e63808 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -601,6 +601,8 @@ package android { field public static final int font = 16844082; // 0x1010532 field public static final int fontFamily = 16843692; // 0x10103ac field public static final int fontFeatureSettings = 16843959; // 0x10104b7 + field public static final int fontProviderAuthority = 16844114; // 0x1010552 + field public static final int fontProviderQuery = 16844115; // 0x1010553 field public static final int fontStyle = 16844081; // 0x1010531 field public static final int fontWeight = 16844083; // 0x1010533 field public static final int footerDividersEnabled = 16843311; // 0x101022f diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index dffd81fbbcab6..aebe669b0c746 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -5587,6 +5587,24 @@ public final class ActivityThread { } finally { StrictMode.setThreadPolicy(savedPolicy); } + + // Preload fonts resources + try { + final ApplicationInfo info = + sPackageManager.getApplicationInfo( + data.appInfo.packageName, + PackageManager.GET_META_DATA /*flags*/, + UserHandle.myUserId()); + if (info.metaData != null) { + final int preloadedFontsResource = info.metaData.getInt( + ApplicationInfo.METADATA_PRELOADED_FONTS, 0); + if (preloadedFontsResource != 0) { + data.info.mResources.preloadFonts(preloadedFontsResource); + } + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /*package*/ final void finishInstrumentation(int resultCode, Bundle results) { diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 8465f0fd5571c..1fa4181cc2f8a 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -572,6 +572,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public int privateFlags; + /** + * @hide + */ + public static final String METADATA_PRELOADED_FONTS = "preloaded_fonts"; + /** * The required smallest screen width the application can run on. If 0, * nothing has been specified. Comes from diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java index 7ea62db3b1f00..3f8f90ebf0548 100644 --- a/core/java/android/content/res/FontResourcesParser.java +++ b/core/java/android/content/res/FontResourcesParser.java @@ -64,6 +64,17 @@ public class FontResourcesParser { private static FontConfig.Family readFamily(XmlPullParser parser, Resources resources) throws XmlPullParserException, IOException { + AttributeSet attrs = Xml.asAttributeSet(parser); + TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily); + String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority); + String query = array.getString(R.styleable.FontFamily_fontProviderQuery); + array.recycle(); + if (authority != null && query != null) { + while (parser.next() != XmlPullParser.END_TAG) { + skip(parser); + } + return new FontConfig.Family(authority, query); + } List fonts = new ArrayList<>(); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; @@ -84,11 +95,12 @@ public class FontResourcesParser { int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, NORMAL_WEIGHT); boolean isItalic = ITALIC == array.getInt(R.styleable.FontFamilyFont_fontStyle, 0); String filename = array.getString(R.styleable.FontFamilyFont_font); + int resourceId = array.getResourceId(R.styleable.FontFamilyFont_font, 0); array.recycle(); while (parser.next() != XmlPullParser.END_TAG) { skip(parser); } - return new FontConfig.Font(filename, 0, null, weight, isItalic); + return new FontConfig.Font(filename, 0, null, weight, isItalic, resourceId); } private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 04e4454a95c4f..21d4b2277a8e7 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -372,6 +372,20 @@ public class Resources { return mResourcesImpl.loadFont(this, value, id); } + /** + * @hide + */ + public void preloadFonts(@FontRes int id) { + final TypedValue value = obtainTempTypedValue(); + try { + final ResourcesImpl impl = mResourcesImpl; + impl.getValue(id, value, true); + impl.preloadFonts(this, value, id); + } finally { + releaseTempTypedValue(value); + } + } + /** * Returns the character sequence necessary for grammatically correct pluralization * of the given resource ID for the given quantity. diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index bf8109627e7e9..38efa49015534 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -32,6 +32,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; import android.content.res.Configuration.NativeConfig; import android.content.res.Resources.NotFoundException; +import android.graphics.FontFamily; import android.graphics.Typeface; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -52,6 +53,7 @@ import android.view.DisplayAdjustments; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.List; import java.util.Locale; /** @@ -781,6 +783,43 @@ public class ResourcesImpl { return null; } + /** + * @hide + */ + public void preloadFonts(Resources wrapper, TypedValue value, int id) { + if (value.string == null) { + throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" + + Integer.toHexString(id) + ") is not a Font: " + value); + } + + final String file = value.string.toString(); + + Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); + try { + final XmlResourceParser rp = loadXmlResourceParser( + file, id, value.assetCookie, "font"); + final FontConfig config = FontResourcesParser.parse(rp, wrapper); + final List families = config.getFamilies(); + if (families == null || families.isEmpty()) { + return; + } + for (int j = 0; j < families.size(); j++) { + final FontConfig.Family family = families.get(j); + final List fonts = family.getFonts(); + for (int i = 0; i < fonts.size(); i++) { + int resourceId = fonts.get(i).getResourceId(); + wrapper.getFont(resourceId); + } + } + } catch (XmlPullParserException e) { + Log.e(TAG, "Failed to parse xml resource " + file, e); + } catch (IOException e) { + Log.e(TAG, "Failed to read xml resource " + file, e); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); + } + } + /** * Given the value and id, we can get the XML filename as in value.data, based on that, we * first try to load CSL from the cache. If not found, try to get from the constant state. diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 3048a38820100..82e44dc86f897 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -170,14 +170,24 @@ public final class FontConfig implements Parcelable { private final int mWeight; private final boolean mIsItalic; private ParcelFileDescriptor mFd; + private final int mResourceId; - public Font(String fontName, int ttcIndex, List axes, int weight, boolean isItalic) { + /** + * @hide + */ + public Font(String fontName, int ttcIndex, List axes, int weight, boolean isItalic, + int resourceId) { mFontName = fontName; mTtcIndex = ttcIndex; mAxes = axes; mWeight = weight; mIsItalic = isItalic; mFd = null; + mResourceId = resourceId; + } + + public Font(String fontName, int ttcIndex, List axes, int weight, boolean isItalic) { + this(fontName, ttcIndex, axes, weight, isItalic, 0); } public Font(Font origin) { @@ -193,6 +203,7 @@ public final class FontConfig implements Parcelable { e.printStackTrace(); } } + mResourceId = origin.mResourceId; } /** @@ -251,6 +262,13 @@ public final class FontConfig implements Parcelable { mFd = fd; } + /** + * @hide + */ + public int getResourceId() { + return mResourceId; + } + /** * @hide */ @@ -269,6 +287,7 @@ public final class FontConfig implements Parcelable { } else { mFd = null; } + mResourceId = in.readInt(); } @Override @@ -285,6 +304,7 @@ public final class FontConfig implements Parcelable { if (mFd != null) { mFd.writeToParcel(out, flag); } + out.writeInt(mResourceId); } @Override @@ -382,22 +402,40 @@ public final class FontConfig implements Parcelable { private final List mFonts; private final String mLanguage; private final String mVariant; + private final String mProviderAuthority; + private final String mQuery; public Family(String name, List fonts, String language, String variant) { - this.mName = name; - this.mFonts = fonts; - this.mLanguage = language; - this.mVariant = variant; + mName = name; + mFonts = fonts; + mLanguage = language; + mVariant = variant; + mProviderAuthority = null; + mQuery = null; + } + + /** + * @hide + */ + public Family(String providerAuthority, String query) { + mName = null; + mFonts = null; + mLanguage = null; + mVariant = null; + mProviderAuthority = providerAuthority; + mQuery = query; } public Family(Family origin) { - this.mName = origin.mName; - this.mLanguage = origin.mLanguage; - this.mVariant = origin.mVariant; - this.mFonts = new ArrayList<>(); + mName = origin.mName; + mLanguage = origin.mLanguage; + mVariant = origin.mVariant; + mFonts = new ArrayList<>(); for (int i = 0; i < origin.mFonts.size(); i++) { mFonts.add(new Font(origin.mFonts.get(i))); } + mProviderAuthority = origin.mProviderAuthority; + mQuery = origin.mQuery; } /** @@ -428,6 +466,20 @@ public final class FontConfig implements Parcelable { return mVariant; } + /** + * @hide + */ + public String getProviderAuthority() { + return mProviderAuthority; + } + + /** + * @hide + */ + public String getQuery() { + return mQuery; + } + /** * @hide */ @@ -440,6 +492,16 @@ public final class FontConfig implements Parcelable { } mLanguage = in.readString(); mVariant = in.readString(); + if (in.readInt() == 1) { + mProviderAuthority = in.readString(); + } else { + mProviderAuthority = null; + } + if (in.readInt() == 1) { + mQuery = in.readString(); + } else { + mQuery = null; + } } @Override @@ -451,6 +513,14 @@ public final class FontConfig implements Parcelable { } out.writeString(mLanguage); out.writeString(mVariant); + out.writeInt(mProviderAuthority == null ? 0 : 1); + if (mProviderAuthority != null) { + out.writeString(mProviderAuthority); + } + out.writeInt(mQuery == null ? 0 : 1); + if (mQuery != null) { + out.writeString(mQuery); + } } @Override diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index a3bad54c1297a..bd2a6d402053e 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8508,6 +8508,12 @@ + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 34659aaa96795..78489ebc7e6e1 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2794,6 +2794,8 @@ + + @@ -2806,6 +2808,7 @@ +