1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.content.res;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.graphics.Typeface;
21 import android.util.AttributeSet;
22 import android.util.Log;
23 import android.util.Xml;
24 
25 import com.android.internal.R;
26 
27 import org.xmlpull.v1.XmlPullParser;
28 import org.xmlpull.v1.XmlPullParserException;
29 
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
34 
35 /**
36  * Parser for xml type font resources.
37  * @hide
38  */
39 public class FontResourcesParser {
40     private static final String TAG = "FontResourcesParser";
41 
42     // A class represents single entry of font-family in xml file.
43     public interface FamilyResourceEntry {}
44 
45     // A class represents font provider based font-family element in xml file.
46     public static final class ProviderResourceEntry implements FamilyResourceEntry {
47         private final @NonNull String mProviderAuthority;
48         private final @NonNull String mProviderPackage;
49         private final @NonNull String mQuery;
50         private final @Nullable List<List<String>> mCerts;
51 
ProviderResourceEntry(@onNull String authority, @NonNull String pkg, @NonNull String query, @Nullable List<List<String>> certs)52         public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg,
53                 @NonNull String query, @Nullable List<List<String>> certs) {
54             mProviderAuthority = authority;
55             mProviderPackage = pkg;
56             mQuery = query;
57             mCerts = certs;
58         }
59 
getAuthority()60         public @NonNull String getAuthority() {
61             return mProviderAuthority;
62         }
63 
getPackage()64         public @NonNull String getPackage() {
65             return mProviderPackage;
66         }
67 
getQuery()68         public @NonNull String getQuery() {
69             return mQuery;
70         }
71 
getCerts()72         public @Nullable List<List<String>> getCerts() {
73             return mCerts;
74         }
75     }
76 
77     // A class represents font element in xml file which points a file in resource.
78     public static final class FontFileResourceEntry {
79         public static final int RESOLVE_BY_FONT_TABLE = Typeface.RESOLVE_BY_FONT_TABLE;
80         public static final int UPRIGHT = 0;
81         public static final int ITALIC = 1;
82 
83         private final @NonNull String mFileName;
84         private int mWeight;
85         private int mItalic;
86         private int mTtcIndex;
87         private String mVariationSettings;
88         private int mResourceId;
89 
FontFileResourceEntry(@onNull String fileName, int weight, int italic, @Nullable String variationSettings, int ttcIndex)90         public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
91                 @Nullable String variationSettings, int ttcIndex) {
92             mFileName = fileName;
93             mWeight = weight;
94             mItalic = italic;
95             mVariationSettings = variationSettings;
96             mTtcIndex = ttcIndex;
97         }
98 
getFileName()99         public @NonNull String getFileName() {
100             return mFileName;
101         }
102 
getWeight()103         public int getWeight() {
104             return mWeight;
105         }
106 
getItalic()107         public int getItalic() {
108             return mItalic;
109         }
110 
getVariationSettings()111         public @Nullable String getVariationSettings() {
112             return mVariationSettings;
113         }
114 
getTtcIndex()115         public int getTtcIndex() {
116             return mTtcIndex;
117         }
118     }
119 
120     // A class represents file based font-family element in xml file.
121     public static final class FontFamilyFilesResourceEntry implements FamilyResourceEntry {
122         private final @NonNull FontFileResourceEntry[] mEntries;
123 
FontFamilyFilesResourceEntry(@onNull FontFileResourceEntry[] entries)124         public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) {
125             mEntries = entries;
126         }
127 
getEntries()128         public @NonNull FontFileResourceEntry[] getEntries() {
129             return mEntries;
130         }
131     }
132 
parse(XmlPullParser parser, Resources resources)133     public static @Nullable FamilyResourceEntry parse(XmlPullParser parser, Resources resources)
134             throws XmlPullParserException, IOException {
135         int type;
136         while ((type=parser.next()) != XmlPullParser.START_TAG
137                 && type != XmlPullParser.END_DOCUMENT) {
138             // Empty loop.
139         }
140 
141         if (type != XmlPullParser.START_TAG) {
142             throw new XmlPullParserException("No start tag found");
143         }
144         return readFamilies(parser, resources);
145     }
146 
readFamilies(XmlPullParser parser, Resources resources)147     private static @Nullable FamilyResourceEntry readFamilies(XmlPullParser parser,
148             Resources resources) throws XmlPullParserException, IOException {
149         parser.require(XmlPullParser.START_TAG, null, "font-family");
150         String tag = parser.getName();
151         FamilyResourceEntry result = null;
152         if (tag.equals("font-family")) {
153             return readFamily(parser, resources);
154         } else {
155             skip(parser);
156             Log.e(TAG, "Failed to find font-family tag");
157             return null;
158         }
159     }
160 
readFamily(XmlPullParser parser, Resources resources)161     private static @Nullable FamilyResourceEntry readFamily(XmlPullParser parser,
162             Resources resources) throws XmlPullParserException, IOException {
163         AttributeSet attrs = Xml.asAttributeSet(parser);
164         TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily);
165         String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
166         String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
167         String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
168         int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0);
169         array.recycle();
170         if (authority != null && providerPackage != null && query != null) {
171             while (parser.next() != XmlPullParser.END_TAG) {
172                 skip(parser);
173             }
174             List<List<String>> certs = null;
175             if (certsId != 0) {
176                 TypedArray typedArray = resources.obtainTypedArray(certsId);
177                 if (typedArray.length() > 0) {
178                     certs = new ArrayList<>();
179                     boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0;
180                     if (isArrayOfArrays) {
181                         for (int i = 0; i < typedArray.length(); i++) {
182                             int certId = typedArray.getResourceId(i, 0);
183                             String[] certsArray = resources.getStringArray(certId);
184                             List<String> certsList = Arrays.asList(certsArray);
185                             certs.add(certsList);
186                         }
187                     } else {
188                         String[] certsArray = resources.getStringArray(certsId);
189                         List<String> certsList = Arrays.asList(certsArray);
190                         certs.add(certsList);
191                     }
192                 }
193             }
194             return new ProviderResourceEntry(authority, providerPackage, query, certs);
195         }
196         List<FontFileResourceEntry> fonts = new ArrayList<>();
197         while (parser.next() != XmlPullParser.END_TAG) {
198             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
199             String tag = parser.getName();
200             if (tag.equals("font")) {
201                 final FontFileResourceEntry entry = readFont(parser, resources);
202                 if (entry != null) {
203                     fonts.add(entry);
204                 }
205             } else {
206                 skip(parser);
207             }
208         }
209         if (fonts.isEmpty()) {
210             return null;
211         }
212         return new FontFamilyFilesResourceEntry(fonts.toArray(
213                 new FontFileResourceEntry[fonts.size()]));
214     }
215 
readFont(XmlPullParser parser, Resources resources)216     private static FontFileResourceEntry readFont(XmlPullParser parser, Resources resources)
217             throws XmlPullParserException, IOException {
218         AttributeSet attrs = Xml.asAttributeSet(parser);
219         TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont);
220         int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight,
221                 Typeface.RESOLVE_BY_FONT_TABLE);
222         int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
223                 FontFileResourceEntry.RESOLVE_BY_FONT_TABLE);
224         String variationSettings = array.getString(
225                 R.styleable.FontFamilyFont_fontVariationSettings);
226         int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
227         String filename = array.getString(R.styleable.FontFamilyFont_font);
228         array.recycle();
229         while (parser.next() != XmlPullParser.END_TAG) {
230             skip(parser);
231         }
232         if (filename == null) {
233             return null;
234         }
235         return new FontFileResourceEntry(filename, weight, italic, variationSettings, ttcIndex);
236     }
237 
skip(XmlPullParser parser)238     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
239         int depth = 1;
240         while (depth > 0) {
241             switch (parser.next()) {
242                 case XmlPullParser.START_TAG:
243                     depth++;
244                     break;
245                 case XmlPullParser.END_TAG:
246                     depth--;
247                     break;
248             }
249         }
250     }
251 }
252