blob: 57d5c710f6bdf83f5d39069305269836a5f25fee [file] [log] [blame]
Philip P. Moltmann9dcb86a2016-03-14 14:31:12 -07001/*
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}