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 com.android.internal.R;
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.graphics.Typeface;
22import android.util.AttributeSet;
23import android.util.Log;
24import android.util.Xml;
25
26import org.xmlpull.v1.XmlPullParser;
27import org.xmlpull.v1.XmlPullParserException;
28
29import java.io.IOException;
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.List;
33
34/**
35 * Parser for xml type font resources.
36 * @hide
37 */
38public class FontResourcesParser {
39    private static final String TAG = "FontResourcesParser";
40
41    // A class represents single entry of font-family in xml file.
42    public interface FamilyResourceEntry {}
43
44    // A class represents font provider based font-family element in xml file.
45    public static final class ProviderResourceEntry implements FamilyResourceEntry {
46        private final @NonNull String mProviderAuthority;
47        private final @NonNull String mProviderPackage;
48        private final @NonNull String mQuery;
49        private final @Nullable List<List<String>> mCerts;
50
51        public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg,
52                @NonNull String query, @Nullable List<List<String>> certs) {
53            mProviderAuthority = authority;
54            mProviderPackage = pkg;
55            mQuery = query;
56            mCerts = certs;
57        }
58
59        public @NonNull String getAuthority() {
60            return mProviderAuthority;
61        }
62
63        public @NonNull String getPackage() {
64            return mProviderPackage;
65        }
66
67        public @NonNull String getQuery() {
68            return mQuery;
69        }
70
71        public @Nullable List<List<String>> getCerts() {
72            return mCerts;
73        }
74    }
75
76    // A class represents font element in xml file which points a file in resource.
77    public static final class FontFileResourceEntry {
78        private final @NonNull String mFileName;
79        private int mWeight;
80        private int mItalic;
81        private int mResourceId;
82
83        public FontFileResourceEntry(@NonNull String fileName, int weight, int italic) {
84            mFileName = fileName;
85            mWeight = weight;
86            mItalic = italic;
87        }
88
89        public @NonNull String getFileName() {
90            return mFileName;
91        }
92
93        public int getWeight() {
94            return mWeight;
95        }
96
97        public int getItalic() {
98            return mItalic;
99        }
100    }
101
102    // A class represents file based font-family element in xml file.
103    public static final class FontFamilyFilesResourceEntry implements FamilyResourceEntry {
104        private final @NonNull FontFileResourceEntry[] mEntries;
105
106        public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) {
107            mEntries = entries;
108        }
109
110        public @NonNull FontFileResourceEntry[] getEntries() {
111            return mEntries;
112        }
113    }
114
115    public static @Nullable FamilyResourceEntry parse(XmlPullParser parser, Resources resources)
116            throws XmlPullParserException, IOException {
117        int type;
118        while ((type=parser.next()) != XmlPullParser.START_TAG
119                && type != XmlPullParser.END_DOCUMENT) {
120            // Empty loop.
121        }
122
123        if (type != XmlPullParser.START_TAG) {
124            throw new XmlPullParserException("No start tag found");
125        }
126        return readFamilies(parser, resources);
127    }
128
129    private static @Nullable FamilyResourceEntry readFamilies(XmlPullParser parser,
130            Resources resources) throws XmlPullParserException, IOException {
131        parser.require(XmlPullParser.START_TAG, null, "font-family");
132        String tag = parser.getName();
133        FamilyResourceEntry result = null;
134        if (tag.equals("font-family")) {
135            return readFamily(parser, resources);
136        } else {
137            skip(parser);
138            Log.e(TAG, "Failed to find font-family tag");
139            return null;
140        }
141    }
142
143    private static @Nullable FamilyResourceEntry readFamily(XmlPullParser parser,
144            Resources resources) throws XmlPullParserException, IOException {
145        AttributeSet attrs = Xml.asAttributeSet(parser);
146        TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily);
147        String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
148        String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
149        String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
150        int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0);
151        array.recycle();
152        if (authority != null && providerPackage != null && query != null) {
153            while (parser.next() != XmlPullParser.END_TAG) {
154                skip(parser);
155            }
156            List<List<String>> certs = null;
157            if (certsId != 0) {
158                TypedArray typedArray = resources.obtainTypedArray(certsId);
159                if (typedArray.length() > 0) {
160                    certs = new ArrayList<>();
161                    boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0;
162                    if (isArrayOfArrays) {
163                        for (int i = 0; i < typedArray.length(); i++) {
164                            int certId = typedArray.getResourceId(i, 0);
165                            String[] certsArray = resources.getStringArray(certId);
166                            List<String> certsList = Arrays.asList(certsArray);
167                            certs.add(certsList);
168                        }
169                    } else {
170                        String[] certsArray = resources.getStringArray(certsId);
171                        List<String> certsList = Arrays.asList(certsArray);
172                        certs.add(certsList);
173                    }
174                }
175            }
176            return new ProviderResourceEntry(authority, providerPackage, query, certs);
177        }
178        List<FontFileResourceEntry> fonts = new ArrayList<>();
179        while (parser.next() != XmlPullParser.END_TAG) {
180            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
181            String tag = parser.getName();
182            if (tag.equals("font")) {
183                final FontFileResourceEntry entry = readFont(parser, resources);
184                if (entry != null) {
185                    fonts.add(entry);
186                }
187            } else {
188                skip(parser);
189            }
190        }
191        if (fonts.isEmpty()) {
192            return null;
193        }
194        return new FontFamilyFilesResourceEntry(fonts.toArray(
195                new FontFileResourceEntry[fonts.size()]));
196    }
197
198    private static FontFileResourceEntry readFont(XmlPullParser parser, Resources resources)
199            throws XmlPullParserException, IOException {
200        AttributeSet attrs = Xml.asAttributeSet(parser);
201        TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont);
202        int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight,
203                Typeface.RESOLVE_BY_FONT_TABLE);
204        int italic = array.getInt(R.styleable.FontFamilyFont_fontStyle,
205                Typeface.RESOLVE_BY_FONT_TABLE);
206        String filename = array.getString(R.styleable.FontFamilyFont_font);
207        array.recycle();
208        while (parser.next() != XmlPullParser.END_TAG) {
209            skip(parser);
210        }
211        if (filename == null) {
212            return null;
213        }
214        return new FontFileResourceEntry(filename, weight, italic);
215    }
216
217    private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
218        int depth = 1;
219        while (depth > 0) {
220            switch (parser.next()) {
221                case XmlPullParser.START_TAG:
222                    depth++;
223                    break;
224                case XmlPullParser.END_TAG:
225                    depth--;
226                    break;
227            }
228        }
229    }
230}
231