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 17package android.support.v4.content.res; 18 19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.content.res.Resources; 22import android.content.res.TypedArray; 23import android.support.annotation.ArrayRes; 24import android.support.annotation.IntDef; 25import android.support.annotation.NonNull; 26import android.support.annotation.Nullable; 27import android.support.annotation.RestrictTo; 28import android.support.compat.R; 29import android.support.v4.provider.FontRequest; 30import android.util.AttributeSet; 31import android.util.Base64; 32import android.util.Xml; 33 34import org.xmlpull.v1.XmlPullParser; 35import org.xmlpull.v1.XmlPullParserException; 36 37import java.io.IOException; 38import java.lang.annotation.Retention; 39import java.lang.annotation.RetentionPolicy; 40import java.util.ArrayList; 41import java.util.Collections; 42import java.util.List; 43 44/** 45 * Parser for xml type font resources. 46 * @hide 47 */ 48@RestrictTo(LIBRARY_GROUP) 49public class FontResourcesParserCompat { 50 private static final int NORMAL_WEIGHT = 400; 51 private static final int ITALIC = 1; 52 53 @IntDef({FETCH_STRATEGY_BLOCKING, FETCH_STRATEGY_ASYNC}) 54 @Retention(RetentionPolicy.SOURCE) 55 public @interface FetchStrategy {} 56 57 public static final int FETCH_STRATEGY_BLOCKING = 0; 58 public static final int FETCH_STRATEGY_ASYNC = 1; 59 60 // A special timeout value for infinite blocking. 61 public static final int INFINITE_TIMEOUT_VALUE = -1; 62 63 private static final int DEFAULT_TIMEOUT_MILLIS = 500; 64 65 /** 66 * A class that represents a single entry of font-family in an xml file. 67 */ 68 public interface FamilyResourceEntry {} 69 70 /** 71 * A class that represents a font provider based font-family element in an xml file. 72 */ 73 public static final class ProviderResourceEntry implements FamilyResourceEntry { 74 private final @NonNull FontRequest mRequest; 75 private final int mTimeoutMs; 76 private final @FetchStrategy int mStrategy; 77 78 public ProviderResourceEntry(@NonNull FontRequest request, @FetchStrategy int strategy, 79 int timeoutMs) { 80 mRequest = request; 81 mStrategy = strategy; 82 mTimeoutMs = timeoutMs; 83 } 84 85 public @NonNull FontRequest getRequest() { 86 return mRequest; 87 } 88 89 public @FetchStrategy int getFetchStrategy() { 90 return mStrategy; 91 } 92 93 public int getTimeout() { 94 return mTimeoutMs; 95 } 96 } 97 98 /** 99 * A class that represents a font element in an xml file which points to a file in resources. 100 */ 101 public static final class FontFileResourceEntry { 102 private int mWeight; 103 private boolean mItalic; 104 private int mResourceId; 105 106 public FontFileResourceEntry(int weight, boolean italic, int resourceId) { 107 mWeight = weight; 108 mItalic = italic; 109 mResourceId = resourceId; 110 } 111 112 public int getWeight() { 113 return mWeight; 114 } 115 116 public boolean isItalic() { 117 return mItalic; 118 } 119 120 public int getResourceId() { 121 return mResourceId; 122 } 123 } 124 125 /** 126 * A class that represents a file based font-family element in an xml font file. 127 */ 128 public static final class FontFamilyFilesResourceEntry implements FamilyResourceEntry { 129 private final @NonNull FontFileResourceEntry[] mEntries; 130 131 public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) { 132 mEntries = entries; 133 } 134 135 public @NonNull FontFileResourceEntry[] getEntries() { 136 return mEntries; 137 } 138 } 139 140 /** 141 * Parse an XML font resource. The result type will depend on the contents of the xml. 142 */ 143 public static @Nullable FamilyResourceEntry parse(XmlPullParser parser, Resources resources) 144 throws XmlPullParserException, IOException { 145 int type; 146 while ((type = parser.next()) != XmlPullParser.START_TAG 147 && type != XmlPullParser.END_DOCUMENT) { 148 // Empty loop. 149 } 150 151 if (type != XmlPullParser.START_TAG) { 152 throw new XmlPullParserException("No start tag found"); 153 } 154 return readFamilies(parser, resources); 155 } 156 157 private static @Nullable FamilyResourceEntry readFamilies(XmlPullParser parser, 158 Resources resources) throws XmlPullParserException, IOException { 159 parser.require(XmlPullParser.START_TAG, null, "font-family"); 160 String tag = parser.getName(); 161 if (tag.equals("font-family")) { 162 return readFamily(parser, resources); 163 } else { 164 skip(parser); 165 return null; 166 } 167 } 168 169 private static @Nullable FamilyResourceEntry readFamily(XmlPullParser parser, 170 Resources resources) throws XmlPullParserException, IOException { 171 AttributeSet attrs = Xml.asAttributeSet(parser); 172 TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily); 173 String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority); 174 String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage); 175 String query = array.getString(R.styleable.FontFamily_fontProviderQuery); 176 int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0); 177 int strategy = array.getInteger(R.styleable.FontFamily_fontProviderFetchStrategy, 178 FETCH_STRATEGY_ASYNC); 179 int timeoutMs = array.getInteger(R.styleable.FontFamily_fontProviderFetchTimeout, 180 DEFAULT_TIMEOUT_MILLIS); 181 array.recycle(); 182 if (authority != null && providerPackage != null && query != null) { 183 while (parser.next() != XmlPullParser.END_TAG) { 184 skip(parser); 185 } 186 List<List<byte[]>> certs = readCerts(resources, certsId); 187 return new ProviderResourceEntry( 188 new FontRequest(authority, providerPackage, query, certs), strategy, timeoutMs); 189 } 190 List<FontFileResourceEntry> fonts = new ArrayList<>(); 191 while (parser.next() != XmlPullParser.END_TAG) { 192 if (parser.getEventType() != XmlPullParser.START_TAG) continue; 193 String tag = parser.getName(); 194 if (tag.equals("font")) { 195 fonts.add(readFont(parser, resources)); 196 } else { 197 skip(parser); 198 } 199 } 200 if (fonts.isEmpty()) { 201 return null; 202 } 203 return new FontFamilyFilesResourceEntry(fonts.toArray( 204 new FontFileResourceEntry[fonts.size()])); 205 } 206 207 /** 208 * Creates the necessary cert structure given a resources array. This method is capable of 209 * loading one string array as well as an array of string arrays. 210 */ 211 public static List<List<byte[]>> readCerts(Resources resources, @ArrayRes int certsId) { 212 List<List<byte[]>> certs = null; 213 if (certsId != 0) { 214 TypedArray typedArray = resources.obtainTypedArray(certsId); 215 if (typedArray.length() > 0) { 216 certs = new ArrayList<>(); 217 boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0; 218 if (isArrayOfArrays) { 219 for (int i = 0; i < typedArray.length(); i++) { 220 int certId = typedArray.getResourceId(i, 0); 221 String[] certsArray = resources.getStringArray(certId); 222 List<byte[]> certsList = toByteArrayList(certsArray); 223 certs.add(certsList); 224 } 225 } else { 226 String[] certsArray = resources.getStringArray(certsId); 227 List<byte[]> certsList = toByteArrayList(certsArray); 228 certs.add(certsList); 229 } 230 } 231 } 232 return certs != null ? certs : Collections.<List<byte[]>>emptyList(); 233 } 234 235 private static List<byte[]> toByteArrayList(String[] stringArray) { 236 List<byte[]> result = new ArrayList<>(); 237 for (String item : stringArray) { 238 result.add(Base64.decode(item, Base64.DEFAULT)); 239 } 240 return result; 241 } 242 243 private static FontFileResourceEntry readFont(XmlPullParser parser, Resources resources) 244 throws XmlPullParserException, IOException { 245 AttributeSet attrs = Xml.asAttributeSet(parser); 246 TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont); 247 int weight = array.getInt(R.styleable.FontFamilyFont_fontWeight, NORMAL_WEIGHT); 248 boolean isItalic = ITALIC == array.getInt(R.styleable.FontFamilyFont_fontStyle, 0); 249 int resourceId = array.getResourceId(R.styleable.FontFamilyFont_font, 0); 250 array.recycle(); 251 while (parser.next() != XmlPullParser.END_TAG) { 252 skip(parser); 253 } 254 return new FontFileResourceEntry(weight, isItalic, resourceId); 255 } 256 257 private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { 258 int depth = 1; 259 while (depth > 0) { 260 switch (parser.next()) { 261 case XmlPullParser.START_TAG: 262 depth++; 263 break; 264 case XmlPullParser.END_TAG: 265 depth--; 266 break; 267 } 268 } 269 } 270} 271