VendorConfig.java revision b87c08da82d50b1358f068a3ae44068022c7af2e
1/* 2 * Copyright (C) 2016 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 com.android.printservice.recommendation.plugin.mdnsFilter; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.content.Context; 22import android.content.res.XmlResourceParser; 23import android.util.ArrayMap; 24import com.android.internal.annotations.Immutable; 25import com.android.internal.util.Preconditions; 26import com.android.printservice.recommendation.R; 27import org.xmlpull.v1.XmlPullParser; 28import org.xmlpull.v1.XmlPullParserException; 29 30import java.io.IOException; 31import java.util.ArrayList; 32import java.util.Collection; 33import java.util.Collections; 34import java.util.List; 35 36/** 37 * Vendor configuration as read from {@link R.xml#vendorconfigs vendorconfigs.xml}. Configuration 38 * can be read via {@link #getConfig(Context, String)}. 39 */ 40@Immutable 41public class VendorConfig { 42 /** Lock for {@link #sConfigs} */ 43 private static final Object sLock = new Object(); 44 45 /** Strings used as XML tags */ 46 private static final String VENDORS_TAG = "vendors"; 47 private static final String VENDOR_TAG = "vendor"; 48 private static final String NAME_TAG = "name"; 49 private static final String PACKAGE_TAG = "package"; 50 private static final String MDNSNAMES_TAG = "mdns-names"; 51 private static final String MDNSNAME_TAG = "mdns-name"; 52 53 /** Map from vendor name to config. Initialized on first {@link #getConfig use}. */ 54 private static @Nullable ArrayMap<String, VendorConfig> sConfigs; 55 56 /** Localized vendor name */ 57 public final @NonNull String name; 58 59 /** Package name containing the print service for this vendor */ 60 public final @NonNull String packageName; 61 62 /** mDNS names used by this vendor */ 63 public final @NonNull List<String> mDNSNames; 64 65 /** 66 * Create an immutable configuration. 67 */ 68 private VendorConfig(@NonNull String name, @NonNull String packageName, 69 @NonNull List<String> mDNSNames) { 70 this.name = Preconditions.checkStringNotEmpty(name); 71 this.packageName = Preconditions.checkStringNotEmpty(packageName); 72 this.mDNSNames = Preconditions.checkCollectionElementsNotNull(mDNSNames, "mDNSName"); 73 } 74 75 /** 76 * Get the configuration for a vendor. 77 * 78 * @param context Calling context 79 * @param name The name of the config to read 80 * 81 * @return the config for the vendor or null if not found 82 * 83 * @throws IOException 84 * @throws XmlPullParserException 85 */ 86 public static @Nullable VendorConfig getConfig(@NonNull Context context, @NonNull String name) 87 throws IOException, XmlPullParserException { 88 synchronized (sLock) { 89 if (sConfigs == null) { 90 sConfigs = readVendorConfigs(context); 91 } 92 93 return sConfigs.get(name); 94 } 95 } 96 97 /** 98 * Get all known vendor configurations. 99 * 100 * @param context Calling context 101 * 102 * @return The known configurations 103 * 104 * @throws IOException 105 * @throws XmlPullParserException 106 */ 107 public static @NonNull Collection<VendorConfig> getAllConfigs(@NonNull Context context) 108 throws IOException, XmlPullParserException { 109 synchronized (sLock) { 110 if (sConfigs == null) { 111 sConfigs = readVendorConfigs(context); 112 } 113 114 return sConfigs.values(); 115 } 116 } 117 118 /** 119 * Read the text from a XML tag. 120 * 121 * @param parser XML parser to read from 122 * 123 * @return The text or "" if no text was found 124 * 125 * @throws IOException 126 * @throws XmlPullParserException 127 */ 128 private static @NonNull String readText(XmlPullParser parser) 129 throws IOException, XmlPullParserException { 130 String result = ""; 131 132 if (parser.next() == XmlPullParser.TEXT) { 133 result = parser.getText(); 134 parser.nextTag(); 135 } 136 137 return result; 138 } 139 140 /** 141 * Read a tag with a text content from the parser. 142 * 143 * @param parser XML parser to read from 144 * @param tagName The name of the tag to read 145 * 146 * @return The text content of the tag 147 * 148 * @throws IOException 149 * @throws XmlPullParserException 150 */ 151 private static @NonNull String readSimpleTag(@NonNull Context context, 152 @NonNull XmlPullParser parser, @NonNull String tagName, boolean resolveReferences) 153 throws IOException, XmlPullParserException { 154 parser.require(XmlPullParser.START_TAG, null, tagName); 155 String text = readText(parser); 156 parser.require(XmlPullParser.END_TAG, null, tagName); 157 158 if (resolveReferences && text.startsWith("@")) { 159 return context.getResources().getString( 160 context.getResources().getIdentifier(text, null, context.getPackageName())); 161 } else { 162 return text; 163 } 164 } 165 166 /** 167 * Read content of a list of tags. 168 * 169 * @param parser XML parser to read from 170 * @param tagName The name of the list tag 171 * @param subTagName The name of the list-element tags 172 * @param tagReader The {@link TagReader reader} to use to read the tag content 173 * @param <T> The type of the parsed tag content 174 * 175 * @return A list of {@link T} 176 * 177 * @throws XmlPullParserException 178 * @throws IOException 179 */ 180 private static @NonNull <T> ArrayList<T> readTagList(@NonNull XmlPullParser parser, 181 @NonNull String tagName, @NonNull String subTagName, @NonNull TagReader<T> tagReader) 182 throws XmlPullParserException, IOException { 183 ArrayList<T> entries = new ArrayList<>(); 184 185 parser.require(XmlPullParser.START_TAG, null, tagName); 186 while (parser.next() != XmlPullParser.END_TAG) { 187 if (parser.getEventType() != XmlPullParser.START_TAG) { 188 continue; 189 } 190 191 if (parser.getName().equals(subTagName)) { 192 entries.add(tagReader.readTag(parser, subTagName)); 193 } else { 194 throw new XmlPullParserException( 195 "Unexpected subtag of " + tagName + ": " + parser.getName()); 196 } 197 } 198 199 return entries; 200 } 201 202 /** 203 * Read the vendor configuration file. 204 * 205 * @param context The content issuing the read 206 * 207 * @return An map pointing from vendor name to config 208 * 209 * @throws IOException 210 * @throws XmlPullParserException 211 */ 212 private static @NonNull ArrayMap<String, VendorConfig> readVendorConfigs( 213 @NonNull final Context context) throws IOException, XmlPullParserException { 214 try (XmlResourceParser parser = context.getResources().getXml(R.xml.vendorconfigs)) { 215 // Skip header 216 int parsingEvent; 217 do { 218 parsingEvent = parser.next(); 219 } while (parsingEvent != XmlResourceParser.START_TAG); 220 221 ArrayList<VendorConfig> configs = readTagList(parser, VENDORS_TAG, VENDOR_TAG, 222 new TagReader<VendorConfig>() { 223 public VendorConfig readTag(XmlPullParser parser, String tagName) 224 throws XmlPullParserException, IOException { 225 return readVendorConfig(context, parser, tagName); 226 } 227 }); 228 229 ArrayMap<String, VendorConfig> configMap = new ArrayMap<>(configs.size()); 230 final int numConfigs = configs.size(); 231 for (int i = 0; i < numConfigs; i++) { 232 VendorConfig config = configs.get(i); 233 234 configMap.put(config.name, config); 235 } 236 237 return configMap; 238 } 239 } 240 241 /** 242 * Read a single vendor configuration. 243 * 244 * @param parser XML parser to read from 245 * @param tagName The vendor tag 246 * @param context Calling context 247 * 248 * @return A config 249 * 250 * @throws XmlPullParserException 251 * @throws IOException 252 */ 253 private static VendorConfig readVendorConfig(@NonNull final Context context, 254 @NonNull XmlPullParser parser, @NonNull String tagName) throws XmlPullParserException, 255 IOException { 256 parser.require(XmlPullParser.START_TAG, null, tagName); 257 258 String name = null; 259 String packageName = null; 260 List<String> mDNSNames = null; 261 262 while (parser.next() != XmlPullParser.END_TAG) { 263 if (parser.getEventType() != XmlPullParser.START_TAG) { 264 continue; 265 } 266 267 String subTagName = parser.getName(); 268 269 switch (subTagName) { 270 case NAME_TAG: 271 name = readSimpleTag(context, parser, NAME_TAG, false); 272 break; 273 case PACKAGE_TAG: 274 packageName = readSimpleTag(context, parser, PACKAGE_TAG, true); 275 break; 276 case MDNSNAMES_TAG: 277 mDNSNames = readTagList(parser, MDNSNAMES_TAG, MDNSNAME_TAG, 278 new TagReader<String>() { 279 public String readTag(XmlPullParser parser, String tagName) 280 throws XmlPullParserException, IOException { 281 return readSimpleTag(context, parser, tagName, true); 282 } 283 } 284 ); 285 break; 286 default: 287 throw new XmlPullParserException("Unexpected subtag of " + tagName + ": " 288 + subTagName); 289 290 } 291 } 292 293 if (name == null) { 294 throw new XmlPullParserException("name is required"); 295 } 296 297 if (packageName == null) { 298 throw new XmlPullParserException("package is required"); 299 } 300 301 if (mDNSNames == null) { 302 mDNSNames = Collections.emptyList(); 303 } 304 305 // A vendor config should be immutable 306 mDNSNames = Collections.unmodifiableList(mDNSNames); 307 308 return new VendorConfig(name, packageName, mDNSNames); 309 } 310 311 @Override 312 public String toString() { 313 return name + " -> " + packageName + ", " + mDNSNames; 314 } 315 316 /** 317 * Used a a "function pointer" when reading a tag in {@link #readTagList(XmlPullParser, String, 318 * String, TagReader)}. 319 * 320 * @param <T> The type of content to read 321 */ 322 private interface TagReader<T> { 323 T readTag(XmlPullParser parser, String tagName) throws XmlPullParserException, IOException; 324 } 325} 326