1 /*
2  * Copyright (C) 2021 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 
17 package com.android.safetycenter.config;
18 
19 import static android.os.Build.VERSION_CODES.TIRAMISU;
20 
21 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
22 import static org.xmlpull.v1.XmlPullParser.END_TAG;
23 import static org.xmlpull.v1.XmlPullParser.FEATURE_PROCESS_NAMESPACES;
24 import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT;
25 import static org.xmlpull.v1.XmlPullParser.START_TAG;
26 import static org.xmlpull.v1.XmlPullParser.TEXT;
27 
28 import static java.util.Locale.ROOT;
29 import static java.util.Objects.requireNonNull;
30 
31 import android.annotation.StringRes;
32 import android.content.res.Resources;
33 import android.safetycenter.config.SafetyCenterConfig;
34 import android.safetycenter.config.SafetySource;
35 import android.safetycenter.config.SafetySourcesGroup;
36 import android.util.Log;
37 
38 import androidx.annotation.RequiresApi;
39 
40 import com.android.modules.utils.build.SdkLevel;
41 import com.android.permission.flags.Flags;
42 
43 import org.xmlpull.v1.XmlPullParser;
44 import org.xmlpull.v1.XmlPullParserException;
45 import org.xmlpull.v1.XmlPullParserFactory;
46 
47 import java.io.IOException;
48 import java.io.InputStream;
49 
50 /** Parser and validator for {@link SafetyCenterConfig} objects. */
51 @RequiresApi(TIRAMISU)
52 public final class SafetyCenterConfigParser {
53 
54     private static final String TAG = "SafetyCenterConfigParser";
55     private static final String TAG_SAFETY_CENTER_CONFIG = "safety-center-config";
56     private static final String TAG_SAFETY_SOURCES_CONFIG = "safety-sources-config";
57     private static final String TAG_SAFETY_SOURCES_GROUP = "safety-sources-group";
58     private static final String TAG_STATIC_SAFETY_SOURCE = "static-safety-source";
59     private static final String TAG_DYNAMIC_SAFETY_SOURCE = "dynamic-safety-source";
60     private static final String TAG_ISSUE_ONLY_SAFETY_SOURCE = "issue-only-safety-source";
61     private static final String ATTR_SAFETY_SOURCES_GROUP_ID = "id";
62     private static final String ATTR_SAFETY_SOURCES_GROUP_TITLE = "title";
63     private static final String ATTR_SAFETY_SOURCES_GROUP_SUMMARY = "summary";
64     private static final String ATTR_SAFETY_SOURCES_GROUP_STATELESS_ICON_TYPE = "statelessIconType";
65     private static final String ATTR_SAFETY_SOURCES_GROUP_TYPE = "type";
66     private static final String ATTR_SAFETY_SOURCE_ID = "id";
67     private static final String ATTR_SAFETY_SOURCE_PACKAGE_NAME = "packageName";
68     private static final String ATTR_SAFETY_SOURCE_TITLE = "title";
69     private static final String ATTR_SAFETY_SOURCE_TITLE_FOR_WORK = "titleForWork";
70     private static final String ATTR_SAFETY_SOURCE_TITLE_FOR_PRIVATE_PROFILE =
71             "titleForPrivateProfile";
72     private static final String ATTR_SAFETY_SOURCE_SUMMARY = "summary";
73     private static final String ATTR_SAFETY_SOURCE_INTENT_ACTION = "intentAction";
74     private static final String ATTR_SAFETY_SOURCE_PROFILE = "profile";
75     private static final String ATTR_SAFETY_SOURCE_INITIAL_DISPLAY_STATE = "initialDisplayState";
76     private static final String ATTR_SAFETY_SOURCE_MAX_SEVERITY_LEVEL = "maxSeverityLevel";
77     private static final String ATTR_SAFETY_SOURCE_SEARCH_TERMS = "searchTerms";
78     private static final String ATTR_SAFETY_SOURCE_LOGGING_ALLOWED = "loggingAllowed";
79     private static final String ATTR_SAFETY_SOURCE_REFRESH_ON_PAGE_OPEN_ALLOWED =
80             "refreshOnPageOpenAllowed";
81     private static final String ATTR_SAFETY_SOURCE_NOTIFICATIONS_ALLOWED = "notificationsAllowed";
82     private static final String ATTR_SAFETY_SOURCE_DEDUPLICATION_GROUP = "deduplicationGroup";
83     private static final String ATTR_SAFETY_SOURCE_PACKAGE_CERT_HASHES = "packageCertificateHashes";
84     private static final String ENUM_STATELESS_ICON_TYPE_NONE = "none";
85     private static final String ENUM_STATELESS_ICON_TYPE_PRIVACY = "privacy";
86     private static final String ENUM_GROUP_TYPE_STATEFUL = "stateful";
87     private static final String ENUM_GROUP_TYPE_STATELESS = "stateless";
88     private static final String ENUM_GROUP_TYPE_HIDDEN = "hidden";
89     private static final String ENUM_PROFILE_PRIMARY = "primary_profile_only";
90     private static final String ENUM_PROFILE_ALL = "all_profiles";
91     private static final String ENUM_INITIAL_DISPLAY_STATE_ENABLED = "enabled";
92     private static final String ENUM_INITIAL_DISPLAY_STATE_DISABLED = "disabled";
93     private static final String ENUM_INITIAL_DISPLAY_STATE_HIDDEN = "hidden";
94 
SafetyCenterConfigParser()95     private SafetyCenterConfigParser() {}
96 
97     /**
98      * Parses and validates the given XML resource into a {@link SafetyCenterConfig} object.
99      *
100      * <p>It throws a {@link ParseException} if the given XML resource does not comply with the
101      * safety_center_config.xsd schema.
102      *
103      * @param in the raw XML resource representing the Safety Center configuration
104      * @param resources the {@link Resources} retrieved from the package that contains the Safety
105      *     Center configuration
106      */
parseXmlResource(InputStream in, Resources resources)107     public static SafetyCenterConfig parseXmlResource(InputStream in, Resources resources)
108             throws ParseException {
109         requireNonNull(in);
110         requireNonNull(resources);
111         try {
112             XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
113             parser.setFeature(FEATURE_PROCESS_NAMESPACES, true);
114             parser.setInput(in, null);
115             if (parser.getEventType() != START_DOCUMENT) {
116                 throw new ParseException("Unexpected parser state");
117             }
118             parser.nextTag();
119             validateElementStart(parser, TAG_SAFETY_CENTER_CONFIG);
120             SafetyCenterConfig safetyCenterConfig = parseSafetyCenterConfig(parser, resources);
121             if (parser.getEventType() == TEXT && parser.isWhitespace()) {
122                 parser.next();
123             }
124             if (parser.getEventType() != END_DOCUMENT) {
125                 throw new ParseException("Unexpected extra root element");
126             }
127             return safetyCenterConfig;
128         } catch (XmlPullParserException | IOException e) {
129             throw new ParseException("Exception while parsing the XML resource", e);
130         }
131     }
132 
parseSafetyCenterConfig( XmlPullParser parser, Resources resources)133     private static SafetyCenterConfig parseSafetyCenterConfig(
134             XmlPullParser parser, Resources resources)
135             throws XmlPullParserException, IOException, ParseException {
136         validateElementHasNoAttribute(parser, TAG_SAFETY_CENTER_CONFIG);
137         parser.nextTag();
138         validateElementStart(parser, TAG_SAFETY_SOURCES_CONFIG);
139         validateElementHasNoAttribute(parser, TAG_SAFETY_SOURCES_CONFIG);
140         SafetyCenterConfig.Builder builder = new SafetyCenterConfig.Builder();
141         parser.nextTag();
142         while (parser.getEventType() == START_TAG
143                 && parser.getName().equals(TAG_SAFETY_SOURCES_GROUP)) {
144             builder.addSafetySourcesGroup(parseSafetySourcesGroup(parser, resources));
145         }
146         validateElementEnd(parser, TAG_SAFETY_SOURCES_CONFIG);
147         parser.nextTag();
148         validateElementEnd(parser, TAG_SAFETY_CENTER_CONFIG);
149         parser.next();
150         try {
151             return builder.build();
152         } catch (IllegalStateException e) {
153             throw elementInvalid(TAG_SAFETY_SOURCES_CONFIG, e);
154         }
155     }
156 
parseSafetySourcesGroup( XmlPullParser parser, Resources resources)157     private static SafetySourcesGroup parseSafetySourcesGroup(
158             XmlPullParser parser, Resources resources)
159             throws XmlPullParserException, IOException, ParseException {
160         String name = TAG_SAFETY_SOURCES_GROUP;
161         SafetySourcesGroup.Builder builder = new SafetySourcesGroup.Builder();
162         for (int i = 0; i < parser.getAttributeCount(); i++) {
163             switch (parser.getAttributeName(i)) {
164                 case ATTR_SAFETY_SOURCES_GROUP_ID:
165                     builder.setId(
166                             parseStringResourceValue(
167                                     parser.getAttributeValue(i),
168                                     name,
169                                     parser.getAttributeName(i),
170                                     resources));
171                     break;
172                 case ATTR_SAFETY_SOURCES_GROUP_TITLE:
173                     builder.setTitleResId(
174                             parseStringResourceName(
175                                     parser.getAttributeValue(i),
176                                     name,
177                                     parser.getAttributeName(i),
178                                     resources));
179                     break;
180                 case ATTR_SAFETY_SOURCES_GROUP_SUMMARY:
181                     builder.setSummaryResId(
182                             parseStringResourceName(
183                                     parser.getAttributeValue(i),
184                                     name,
185                                     parser.getAttributeName(i),
186                                     resources));
187                     break;
188                 case ATTR_SAFETY_SOURCES_GROUP_STATELESS_ICON_TYPE:
189                     builder.setStatelessIconType(
190                             parseStatelessIconType(
191                                     parser.getAttributeValue(i),
192                                     name,
193                                     parser.getAttributeName(i),
194                                     resources));
195                     break;
196                 case ATTR_SAFETY_SOURCES_GROUP_TYPE:
197                     if (SdkLevel.isAtLeastU()) {
198                         builder.setType(
199                                 parseGroupType(
200                                         parser.getAttributeValue(i),
201                                         name,
202                                         parser.getAttributeName(i),
203                                         resources));
204                     } else {
205                         throw attributeUnexpected(name, parser.getAttributeName(i));
206                     }
207                     break;
208                 default:
209                     throw attributeUnexpected(name, parser.getAttributeName(i));
210             }
211         }
212         parser.nextTag();
213         loop:
214         while (parser.getEventType() == START_TAG) {
215             int type;
216             switch (parser.getName()) {
217                 case TAG_STATIC_SAFETY_SOURCE:
218                     type = SafetySource.SAFETY_SOURCE_TYPE_STATIC;
219                     break;
220                 case TAG_DYNAMIC_SAFETY_SOURCE:
221                     type = SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC;
222                     break;
223                 case TAG_ISSUE_ONLY_SAFETY_SOURCE:
224                     type = SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY;
225                     break;
226                 default:
227                     break loop;
228             }
229             builder.addSafetySource(parseSafetySource(parser, resources, type, parser.getName()));
230         }
231         validateElementEnd(parser, name);
232         parser.nextTag();
233         try {
234             return builder.build();
235         } catch (IllegalStateException e) {
236             throw elementInvalid(name, e);
237         }
238     }
239 
parseSafetySource( XmlPullParser parser, Resources resources, int safetySourceType, String name)240     private static SafetySource parseSafetySource(
241             XmlPullParser parser, Resources resources, int safetySourceType, String name)
242             throws XmlPullParserException, IOException, ParseException {
243         SafetySource.Builder builder = new SafetySource.Builder(safetySourceType);
244         for (int i = 0; i < parser.getAttributeCount(); i++) {
245             switch (parser.getAttributeName(i)) {
246                 case ATTR_SAFETY_SOURCE_ID:
247                     builder.setId(
248                             parseStringResourceValue(
249                                     parser.getAttributeValue(i),
250                                     name,
251                                     parser.getAttributeName(i),
252                                     resources));
253                     break;
254                 case ATTR_SAFETY_SOURCE_PACKAGE_NAME:
255                     builder.setPackageName(
256                             parseStringResourceValue(
257                                     parser.getAttributeValue(i),
258                                     name,
259                                     parser.getAttributeName(i),
260                                     resources));
261                     break;
262                 case ATTR_SAFETY_SOURCE_TITLE:
263                     builder.setTitleResId(
264                             parseStringResourceName(
265                                     parser.getAttributeValue(i),
266                                     name,
267                                     parser.getAttributeName(i),
268                                     resources));
269                     break;
270                 case ATTR_SAFETY_SOURCE_TITLE_FOR_WORK:
271                     builder.setTitleForWorkResId(
272                             parseStringResourceName(
273                                     parser.getAttributeValue(i),
274                                     name,
275                                     parser.getAttributeName(i),
276                                     resources));
277                     break;
278                 case ATTR_SAFETY_SOURCE_TITLE_FOR_PRIVATE_PROFILE:
279                     if (SdkLevel.isAtLeastV()) {
280                         if (Flags.privateProfileTitleApi()) {
281                             builder.setTitleForPrivateProfileResId(
282                                     parseStringResourceName(
283                                             parser.getAttributeValue(i),
284                                             name,
285                                             parser.getAttributeName(i),
286                                             resources));
287                         } else {
288                             Log.i(
289                                     TAG,
290                                     String.format(
291                                             "Ignoring attribute %s.%s",
292                                             name, ATTR_SAFETY_SOURCE_TITLE_FOR_PRIVATE_PROFILE));
293                         }
294                         break;
295                     } else {
296                         throw attributeUnexpected(name, parser.getAttributeName(i));
297                     }
298                 case ATTR_SAFETY_SOURCE_SUMMARY:
299                     builder.setSummaryResId(
300                             parseStringResourceName(
301                                     parser.getAttributeValue(i),
302                                     name,
303                                     parser.getAttributeName(i),
304                                     resources));
305                     break;
306                 case ATTR_SAFETY_SOURCE_INTENT_ACTION:
307                     builder.setIntentAction(
308                             parseStringResourceValue(
309                                     parser.getAttributeValue(i),
310                                     name,
311                                     parser.getAttributeName(i),
312                                     resources));
313                     break;
314                 case ATTR_SAFETY_SOURCE_PROFILE:
315                     builder.setProfile(
316                             parseProfile(
317                                     parser.getAttributeValue(i),
318                                     name,
319                                     parser.getAttributeName(i),
320                                     resources));
321                     break;
322                 case ATTR_SAFETY_SOURCE_INITIAL_DISPLAY_STATE:
323                     builder.setInitialDisplayState(
324                             parseInitialDisplayState(
325                                     parser.getAttributeValue(i),
326                                     name,
327                                     parser.getAttributeName(i),
328                                     resources));
329                     break;
330                 case ATTR_SAFETY_SOURCE_MAX_SEVERITY_LEVEL:
331                     builder.setMaxSeverityLevel(
332                             parseInteger(
333                                     parser.getAttributeValue(i),
334                                     name,
335                                     parser.getAttributeName(i),
336                                     resources));
337                     break;
338                 case ATTR_SAFETY_SOURCE_SEARCH_TERMS:
339                     builder.setSearchTermsResId(
340                             parseStringResourceName(
341                                     parser.getAttributeValue(i),
342                                     name,
343                                     parser.getAttributeName(i),
344                                     resources));
345                     break;
346                 case ATTR_SAFETY_SOURCE_LOGGING_ALLOWED:
347                     builder.setLoggingAllowed(
348                             parseBoolean(
349                                     parser.getAttributeValue(i),
350                                     name,
351                                     parser.getAttributeName(i),
352                                     resources));
353                     break;
354                 case ATTR_SAFETY_SOURCE_REFRESH_ON_PAGE_OPEN_ALLOWED:
355                     builder.setRefreshOnPageOpenAllowed(
356                             parseBoolean(
357                                     parser.getAttributeValue(i),
358                                     name,
359                                     parser.getAttributeName(i),
360                                     resources));
361                     break;
362                 case ATTR_SAFETY_SOURCE_NOTIFICATIONS_ALLOWED:
363                     if (SdkLevel.isAtLeastU()) {
364                         builder.setNotificationsAllowed(
365                                 parseBoolean(
366                                         parser.getAttributeValue(i),
367                                         name,
368                                         parser.getAttributeName(i),
369                                         resources));
370                     } else {
371                         throw attributeUnexpected(name, parser.getAttributeName(i));
372                     }
373                     break;
374                 case ATTR_SAFETY_SOURCE_DEDUPLICATION_GROUP:
375                     if (SdkLevel.isAtLeastU()) {
376                         builder.setDeduplicationGroup(
377                                 parseStringResourceValue(
378                                         parser.getAttributeValue(i),
379                                         name,
380                                         parser.getAttributeName(i),
381                                         resources));
382                     } else {
383                         throw attributeUnexpected(name, parser.getAttributeName(i));
384                     }
385                     break;
386                 case ATTR_SAFETY_SOURCE_PACKAGE_CERT_HASHES:
387                     if (SdkLevel.isAtLeastU()) {
388                         String commaSeparatedHashes =
389                                 parseStringResourceValue(
390                                         parser.getAttributeValue(i),
391                                         name,
392                                         parser.getAttributeName(i),
393                                         resources);
394                         String[] splits = commaSeparatedHashes.split(",");
395                         for (int j = 0; j < splits.length; j++) {
396                             builder.addPackageCertificateHash(splits[j]);
397                         }
398                     } else {
399                         throw attributeUnexpected(name, parser.getAttributeName(i));
400                     }
401                     break;
402                 default:
403                     throw attributeUnexpected(name, parser.getAttributeName(i));
404             }
405         }
406         parser.nextTag();
407         validateElementEnd(parser, name);
408         parser.nextTag();
409         try {
410             return builder.build();
411         } catch (IllegalStateException e) {
412             throw elementInvalid(name, e);
413         }
414     }
415 
validateElementStart(XmlPullParser parser, String name)416     private static void validateElementStart(XmlPullParser parser, String name)
417             throws XmlPullParserException, ParseException {
418         if (parser.getEventType() != START_TAG || !parser.getName().equals(name)) {
419             throw elementMissing(name);
420         }
421     }
422 
validateElementEnd(XmlPullParser parser, String name)423     private static void validateElementEnd(XmlPullParser parser, String name)
424             throws XmlPullParserException, ParseException {
425         if (parser.getEventType() != END_TAG || !parser.getName().equals(name)) {
426             throw elementNotClosed(name);
427         }
428     }
429 
validateElementHasNoAttribute(XmlPullParser parser, String name)430     private static void validateElementHasNoAttribute(XmlPullParser parser, String name)
431             throws ParseException {
432         if (parser.getAttributeCount() != 0) {
433             throw elementInvalid(name);
434         }
435     }
436 
elementMissing(String name)437     private static ParseException elementMissing(String name) {
438         return new ParseException(String.format("Element %s missing", name));
439     }
440 
elementNotClosed(String name)441     private static ParseException elementNotClosed(String name) {
442         return new ParseException(String.format("Element %s not closed", name));
443     }
444 
elementInvalid(String name)445     private static ParseException elementInvalid(String name) {
446         return new ParseException(String.format("Element %s invalid", name));
447     }
448 
elementInvalid(String name, Throwable e)449     private static ParseException elementInvalid(String name, Throwable e) {
450         return new ParseException(String.format("Element %s invalid", name), e);
451     }
452 
attributeUnexpected(String parent, String name)453     private static ParseException attributeUnexpected(String parent, String name) {
454         return new ParseException(String.format("Unexpected attribute %s.%s", parent, name));
455     }
456 
attributeInvalidString(String valueString, String parent, String name)457     private static String attributeInvalidString(String valueString, String parent, String name) {
458         return String.format("Attribute value \"%s\" in %s.%s invalid", valueString, parent, name);
459     }
460 
attributeInvalid(String valueString, String parent, String name)461     private static ParseException attributeInvalid(String valueString, String parent, String name) {
462         return new ParseException(attributeInvalidString(valueString, parent, name));
463     }
464 
attributeInvalid( String valueString, String parent, String name, Throwable ex)465     private static ParseException attributeInvalid(
466             String valueString, String parent, String name, Throwable ex) {
467         return new ParseException(attributeInvalidString(valueString, parent, name), ex);
468     }
469 
parseInteger( String valueString, String parent, String name, Resources resources)470     private static int parseInteger(
471             String valueString, String parent, String name, Resources resources)
472             throws ParseException {
473         String valueToParse = getValueToParse(valueString, parent, name, resources);
474         try {
475             return Integer.parseInt(valueToParse);
476         } catch (NumberFormatException e) {
477             throw attributeInvalid(valueToParse, parent, name, e);
478         }
479     }
480 
parseBoolean( String valueString, String parent, String name, Resources resources)481     private static boolean parseBoolean(
482             String valueString, String parent, String name, Resources resources)
483             throws ParseException {
484         String valueToParse =
485                 getValueToParse(valueString, parent, name, resources).toLowerCase(ROOT);
486         if (valueToParse.equals("true")) {
487             return true;
488         } else if (!valueToParse.equals("false")) {
489             throw attributeInvalid(valueToParse, parent, name);
490         }
491         return false;
492     }
493 
494     @StringRes
parseStringResourceName( String valueString, String parent, String name, Resources resources)495     private static int parseStringResourceName(
496             String valueString, String parent, String name, Resources resources)
497             throws ParseException {
498         if (valueString.isEmpty()) {
499             throw new ParseException(
500                     String.format("Resource name in %s.%s cannot be empty", parent, name));
501         }
502         if (valueString.charAt(0) != '@') {
503             throw new ParseException(
504                     String.format(
505                             "Resource name \"%s\" in %s.%s does not start with @",
506                             valueString, parent, name));
507         }
508         String[] colonSplit = valueString.substring(1).split(":", 2);
509         if (colonSplit.length != 2 || colonSplit[0].isEmpty()) {
510             throw new ParseException(
511                     String.format(
512                             "Resource name \"%s\" in %s.%s does not specify a package",
513                             valueString, parent, name));
514         }
515         String packageName = colonSplit[0];
516         String[] slashSplit = colonSplit[1].split("/", 2);
517         if (slashSplit.length != 2 || slashSplit[0].isEmpty()) {
518             throw new ParseException(
519                     String.format(
520                             "Resource name \"%s\" in %s.%s does not specify a type",
521                             valueString, parent, name));
522         }
523         String type = slashSplit[0];
524         if (!type.equals("string")) {
525             throw new ParseException(
526                     String.format(
527                             "Resource name \"%s\" in %s.%s is not a string",
528                             valueString, parent, name));
529         }
530         String entry = slashSplit[1];
531         int id = resources.getIdentifier(entry, type, packageName);
532         if (id == Resources.ID_NULL) {
533             throw new ParseException(
534                     String.format(
535                             "Resource name \"%s\" in %s.%s missing or invalid",
536                             valueString, parent, name));
537         }
538         return id;
539     }
540 
parseStringResourceValue( String valueString, String parent, String name, Resources resources)541     private static String parseStringResourceValue(
542             String valueString, String parent, String name, Resources resources) {
543         return getValueToParse(valueString, parent, name, resources);
544     }
545 
parseStatelessIconType( String valueString, String parent, String name, Resources resources)546     private static int parseStatelessIconType(
547             String valueString, String parent, String name, Resources resources)
548             throws ParseException {
549         String valueToParse = getValueToParse(valueString, parent, name, resources);
550         switch (valueToParse) {
551             case ENUM_STATELESS_ICON_TYPE_NONE:
552                 return SafetySourcesGroup.STATELESS_ICON_TYPE_NONE;
553             case ENUM_STATELESS_ICON_TYPE_PRIVACY:
554                 return SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY;
555             default:
556                 throw attributeInvalid(valueToParse, parent, name);
557         }
558     }
559 
parseGroupType( String valueString, String parent, String name, Resources resources)560     private static int parseGroupType(
561             String valueString, String parent, String name, Resources resources)
562             throws ParseException {
563         String valueToParse = getValueToParse(valueString, parent, name, resources);
564         switch (valueToParse) {
565             case ENUM_GROUP_TYPE_STATEFUL:
566                 return SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL;
567             case ENUM_GROUP_TYPE_STATELESS:
568                 return SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS;
569             case ENUM_GROUP_TYPE_HIDDEN:
570                 return SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN;
571             default:
572                 throw attributeInvalid(valueToParse, parent, name);
573         }
574     }
575 
parseProfile( String valueString, String parent, String name, Resources resources)576     private static int parseProfile(
577             String valueString, String parent, String name, Resources resources)
578             throws ParseException {
579         String valueToParse = getValueToParse(valueString, parent, name, resources);
580         switch (valueToParse) {
581             case ENUM_PROFILE_PRIMARY:
582                 return SafetySource.PROFILE_PRIMARY;
583             case ENUM_PROFILE_ALL:
584                 return SafetySource.PROFILE_ALL;
585             default:
586                 throw attributeInvalid(valueToParse, parent, name);
587         }
588     }
589 
parseInitialDisplayState( String valueString, String parent, String name, Resources resources)590     private static int parseInitialDisplayState(
591             String valueString, String parent, String name, Resources resources)
592             throws ParseException {
593         String valueToParse = getValueToParse(valueString, parent, name, resources);
594         switch (valueToParse) {
595             case ENUM_INITIAL_DISPLAY_STATE_ENABLED:
596                 return SafetySource.INITIAL_DISPLAY_STATE_ENABLED;
597             case ENUM_INITIAL_DISPLAY_STATE_DISABLED:
598                 return SafetySource.INITIAL_DISPLAY_STATE_DISABLED;
599             case ENUM_INITIAL_DISPLAY_STATE_HIDDEN:
600                 return SafetySource.INITIAL_DISPLAY_STATE_HIDDEN;
601             default:
602                 throw attributeInvalid(valueToParse, parent, name);
603         }
604     }
605 
getValueToParse( String valueString, String parent, String name, Resources resources)606     private static String getValueToParse(
607             String valueString, String parent, String name, Resources resources) {
608         try {
609             int id = parseStringResourceName(valueString, parent, name, resources);
610             return resources.getString(id);
611         } catch (ParseException e) {
612             return valueString;
613         }
614     }
615 }
616