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         private final @NonNull String mFileName;
80         private int mWeight;
81         private int mItalic;
82         private int mTtcIndex;
83         private String mVariationSettings;
84         private int mResourceId;
85 
FontFileResourceEntry(@onNull String fileName, int weight, int italic, @Nullable String variationSettings, int ttcIndex)86         public FontFileResourceEntry(@NonNull String fileName, int weight, int italic,
87                 @Nullable String variationSettings, int ttcIndex) {
88             mFileName = fileName;
89             mWeight = weight;
90             mItalic = italic;
91             mVariationSettings = variationSettings;
92             mTtcIndex = ttcIndex;
93         }
94 
getFileName()95         public @NonNull String getFileName() {
96             return mFileName;
97         }
98 
getWeight()99         public int getWeight() {
100             return mWeight;
101         }
102 
getItalic()103         public int getItalic() {
104             return mItalic;
105         }
106 
getVariationSettings()107         public @Nullable String getVariationSettings() {
108             return mVariationSettings;
109         }
110 
getTtcIndex()111         public int getTtcIndex() {
112             return mTtcIndex;
113         }
114     }
115 
116     // A class represents file based font-family element in xml file.
117     public static final class FontFamilyFilesResourceEntry implements FamilyResourceEntry {
118         private final @NonNull FontFileResourceEntry[] mEntries;
119 
FontFamilyFilesResourceEntry(@onNull FontFileResourceEntry[] entries)120         public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) {
121             mEntries = entries;
122         }
123 
getEntries()124         public @NonNull FontFileResourceEntry[] getEntries() {
125             return mEntries;
126         }
127     }
128 
parse(XmlPullParser parser, Resources resources)129     public static @Nullable FamilyResourceEntry parse(XmlPullParser parser, Resources resources)
130             throws XmlPullParserException, IOException {
131         int type;
132         while ((type=parser.next()) != XmlPullParser.START_TAG
133                 && type != XmlPullParser.END_DOCUMENT) {
134             // Empty loop.
135         }
136 
137         if (type != XmlPullParser.START_TAG) {
138             throw new XmlPullParserException("No start tag found");
139         }
140         return readFamilies(parser, resources);
141     }
142 
readFamilies(XmlPullParser parser, Resources resources)143     private static @Nullable FamilyResourceEntry readFamilies(XmlPullParser parser,
144             Resources resources) throws XmlPullParserException, IOException {
145         parser.require(XmlPullParser.START_TAG, null, "font-family");
146         String tag = parser.getName();
147         FamilyResourceEntry result = null;
148         if (tag.equals("font-family")) {
149             return readFamily(parser, resources);
150         } else {
151             skip(parser);
152             Log.e(TAG, "Failed to find font-family tag");
153             return null;
154         }
155     }
156 
readFamily(XmlPullParser parser, Resources resources)157     private static @Nullable FamilyResourceEntry readFamily(XmlPullParser parser,
158             Resources resources) throws XmlPullParserException, IOException {
159         AttributeSet attrs = Xml.asAttributeSet(parser);
160         TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily);
161         String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
162         String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
163         String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
164         int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0);
165         array.recycle();
166         if (authority != null && providerPackage != null && query != null) {
167             while (parser.next() != XmlPullParser.END_TAG) {
168                 skip(parser);
169             }
170             List<List<String>> certs = null;
171             if (certsId != 0) {
172                 TypedArray typedArray = resources.obtainTypedArray(certsId);
173                 if (typedArray.length() > 0) {
174                     certs = new ArrayList<>();
175                     boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0;
176                     if (isArrayOfArrays) {
177                         for (int i = 0; i < typedArray.length(); i++) {
178                             int certId = typedArray.getResourceId(i, 0);
179                             String[] certsArray = resources.getStringArray(certId);
180                             List<String> certsList = Arrays.asList(certsArray);
181                             certs.add(certsList);
182                         }
183                     } else {
184                         String[] certsArray = resources.getStringArray(certsId);
185                         List<String> certsList = Arrays.asList(certsArray);
186                         certs.add(certsList);
187                     }
188                 }
189             }
190             return new ProviderResourceEntry(authority, providerPackage, query, certs);
191         }
192         List<FontFileResourceEntry> fonts = new ArrayList<>();
193         while (parser.next() != XmlPullParser.END_TAG) {
194             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
195             String tag = parser.getName();
196             if (tag.equals("font")) {
197                 final FontFileResourceEntry entry = readFont(parser, resources);
198                 if (entry != null) {
199                     fonts.add(entry);
200                 }
201             } else {
202                 skip(parser);
203             }
204         }
205         if (fonts.isEmpty()) {
206             return null;
207         }
208         return new FontFamilyFilesResourceEntry(fonts.toArray(
209                 new FontFileResourceEntry[fonts.size()]));
210     }
211 
readFont(XmlPullParser parser, Resources resources)212     private static FontFileResourceEntry readFont(XmlPullParser parser, Resources resources)
213             throws XmlPullParserException, IOException {
214         AttributeSet attrs = Xml.asAttributeSet(parser);
215         TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont);
216         int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight,
217                 Typeface.RESOLVE_BY_FONT_TABLE);
218         int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
219                 Typeface.RESOLVE_BY_FONT_TABLE);
220         String variationSettings = array.getString(
221                 R.styleable.FontFamilyFont_fontVariationSettings);
222         int ttcIndex = array.getInt(R.styleable.FontFamilyFont_ttcIndex, 0);
223         String filename = array.getString(R.styleable.FontFamilyFont_font);
224         array.recycle();
225         while (parser.next() != XmlPullParser.END_TAG) {
226             skip(parser);
227         }
228         if (filename == null) {
229             return null;
230         }
231         return new FontFileResourceEntry(filename, weight, italic, variationSettings, ttcIndex);
232     }
233 
skip(XmlPullParser parser)234     private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
235         int depth = 1;
236         while (depth > 0) {
237             switch (parser.next()) {
238                 case XmlPullParser.START_TAG:
239                     depth++;
240                     break;
241                 case XmlPullParser.END_TAG:
242                     depth--;
243                     break;
244             }
245         }
246     }
247 }
248