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 */
16package android.content.res;
17
18import android.annotation.NonNull;
19import android.annotation.Nullable;
20import android.graphics.Typeface;
21import android.util.AttributeSet;
22import android.util.Log;
23import android.util.Xml;
24
25import com.android.internal.R;
26
27import org.xmlpull.v1.XmlPullParser;
28import org.xmlpull.v1.XmlPullParserException;
29
30import java.io.IOException;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.List;
34
35/**
36 * Parser for xml type font resources.
37 * @hide
38 */
39public 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
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
60        public @NonNull String getAuthority() {
61            return mProviderAuthority;
62        }
63
64        public @NonNull String getPackage() {
65            return mProviderPackage;
66        }
67
68        public @NonNull String getQuery() {
69            return mQuery;
70        }
71
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
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
95        public @NonNull String getFileName() {
96            return mFileName;
97        }
98
99        public int getWeight() {
100            return mWeight;
101        }
102
103        public int getItalic() {
104            return mItalic;
105        }
106
107        public @Nullable String getVariationSettings() {
108            return mVariationSettings;
109        }
110
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
120        public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) {
121            mEntries = entries;
122        }
123
124        public @NonNull FontFileResourceEntry[] getEntries() {
125            return mEntries;
126        }
127    }
128
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
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
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
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
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