• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.car;
18 
19 import static android.car.builtin.view.DisplayHelper.INVALID_PORT;
20 import static android.car.drivingstate.CarUxRestrictionsConfiguration.Builder.SpeedRange.MAX_SPEED;
21 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
22 
23 import android.annotation.Nullable;
24 import android.annotation.XmlRes;
25 import android.car.builtin.util.Slogf;
26 import android.car.drivingstate.CarDrivingStateEvent;
27 import android.car.drivingstate.CarUxRestrictions;
28 import android.car.drivingstate.CarUxRestrictionsConfiguration;
29 import android.car.drivingstate.CarUxRestrictionsConfiguration.Builder;
30 import android.car.drivingstate.CarUxRestrictionsConfiguration.DrivingStateRestrictions;
31 import android.content.Context;
32 import android.content.res.XmlResourceParser;
33 import android.util.Log;
34 
35 import org.xmlpull.v1.XmlPullParserException;
36 
37 import java.io.IOException;
38 import java.util.ArrayList;
39 import java.util.List;
40 import java.util.Locale;
41 
42 /**
43  * @hide
44  */
45 public final class CarUxRestrictionsConfigurationXmlParser {
46     private static final String TAG = CarLog.tagFor(CarUxRestrictionsConfigurationXmlParser.class);
47     private static final int UX_RESTRICTIONS_UNKNOWN = -1;
48     private static final float INVALID_SPEED = -1f;
49     private static final String XML_NAMESPACE = null;
50 
51     // XML tags
52     private static final String XML_ROOT_ELEMENT = "UxRestrictions";
53     private static final String XML_RESTRICTION_MAPPING = "RestrictionMapping";
54     private static final String XML_RESTRICTION_PARAMETERS = "RestrictionParameters";
55     private static final String XML_DRIVING_STATE = "DrivingState";
56     private static final String XML_RESTRICTIONS = "Restrictions";
57     private static final String XML_STRING_RESTRICTIONS = "StringRestrictions";
58     private static final String XML_CONTENT_RESTRICTIONS = "ContentRestrictions";
59 
60     // XML attributes
61     private static final String XML_PHYSICAL_PORT = "physicalPort";
62     private static final String XML_STATE = "state";
63     private static final String XML_MIN_SPEED = "minSpeed";
64     private static final String XML_MAX_SPEED = "maxSpeed";
65     private static final String XML_MODE = "mode";
66     private static final String XML_UXR = "uxr";
67     private static final String XML_REQUIRES_DISTRACTION_OPTIMIZATION =
68             "requiresDistractionOptimization";
69     private static final String XML_MAX_LENGTH = "maxLength";
70     private static final String XML_MAX_CUMULATIVE_ITEMS = "maxCumulativeItems";
71     private static final String XML_MAX_DEPTH = "maxDepth";
72 
73     // XML attribute values
74     private static final String XML_STATE_PARKED = "parked";
75     private static final String XML_STATE_IDLING = "idling";
76     private static final String XML_STATE_MOVING = "moving";
77     private static final String XML_UXR_BASELINE = "baseline";
78     private static final String XML_UXR_NO_DIALPAD = "no_dialpad";
79     private static final String XML_UXR_NO_FILTERING = "no_filtering";
80     private static final String XML_UXR_LIMIT_STRING_LENGTH = "limit_string_length";
81     private static final String XML_UXR_NO_KEYBOARD = "no_keyboard";
82     private static final String XML_UXR_NO_VIDEO = "no_video";
83     private static final String XML_UXR_LIMIT_CONTENT = "limit_content";
84     private static final String XML_UXR_NO_SETUP = "no_setup";
85     private static final String XML_UXR_NO_TEXT_MESSAGE = "no_text_message";
86     private static final String XML_UXR_NO_VOICE_TRANSCRIPTION = "no_voice_transcription";
87     private static final String XML_UXR_FULLY_RESTRICTED = "fully_restricted";
88 
89     private final Context mContext;
90 
91     private int mMaxRestrictedStringLength = UX_RESTRICTIONS_UNKNOWN;
92     private int mMaxCumulativeContentItems = UX_RESTRICTIONS_UNKNOWN;
93     private int mMaxContentDepth = UX_RESTRICTIONS_UNKNOWN;
94     private final List<CarUxRestrictionsConfiguration.Builder> mConfigBuilders = new ArrayList<>();
95 
CarUxRestrictionsConfigurationXmlParser(Context context)96     private CarUxRestrictionsConfigurationXmlParser(Context context) {
97         mContext = context;
98     }
99 
100     /**
101      * Loads the UX restrictions related information from the XML resource.
102      *
103      * @return parsed CarUxRestrictionsConfiguration; {@code null} if the XML is malformed.
104      */
105     @Nullable
parse( Context context, @XmlRes int xmlResource)106     public static List<CarUxRestrictionsConfiguration> parse(
107             Context context, @XmlRes int xmlResource)
108             throws IOException, XmlPullParserException {
109         return new CarUxRestrictionsConfigurationXmlParser(context).parse(xmlResource);
110     }
111 
112     @Nullable
parse(@mlRes int xmlResource)113     private List<CarUxRestrictionsConfiguration> parse(@XmlRes int xmlResource)
114             throws IOException, XmlPullParserException {
115 
116         XmlResourceParser parser = mContext.getResources().getXml(xmlResource);
117         if (parser == null) {
118             Slogf.e(TAG, "Invalid Xml resource");
119             return null;
120         }
121 
122         if (!traverseUntilStartTag(parser)) {
123             Slogf.e(TAG, "XML root element invalid: " + parser.getName());
124             return null;
125         }
126 
127         if (!traverseUntilEndOfDocument(parser)) {
128             Slogf.e(TAG, "Could not parse XML to end");
129             return null;
130         }
131 
132         List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
133         for (CarUxRestrictionsConfiguration.Builder builder : mConfigBuilders) {
134             builder.setMaxStringLength(mMaxRestrictedStringLength)
135                     .setMaxCumulativeContentItems(mMaxCumulativeContentItems)
136                     .setMaxContentDepth(mMaxContentDepth);
137             configs.add(builder.build());
138         }
139         return configs;
140     }
141 
traverseUntilStartTag(XmlResourceParser parser)142     private boolean traverseUntilStartTag(XmlResourceParser parser)
143             throws IOException, XmlPullParserException {
144         int type;
145         // Traverse till we get to the first tag
146         while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
147                 && type != XmlResourceParser.START_TAG) {
148             // Do nothing.
149         }
150         return XML_ROOT_ELEMENT.equals(parser.getName());
151     }
152 
traverseUntilEndOfDocument(XmlResourceParser parser)153     private boolean traverseUntilEndOfDocument(XmlResourceParser parser)
154             throws XmlPullParserException, IOException {
155         while (parser.getEventType() != XmlResourceParser.END_DOCUMENT) {
156             // Every time we hit a start tag, check for the type of the tag
157             // and load the corresponding information.
158             if (parser.next() == XmlResourceParser.START_TAG) {
159                 switch (parser.getName()) {
160                     case XML_RESTRICTION_MAPPING:
161                         // Each RestrictionMapping tag represents a new set of rules.
162                         mConfigBuilders.add(new CarUxRestrictionsConfiguration.Builder());
163 
164                         if (!mapDrivingStateToRestrictions(parser)) {
165                             Slogf.e(TAG, "Could not map driving state to restriction.");
166                             return false;
167                         }
168                         break;
169                     case XML_RESTRICTION_PARAMETERS:
170                         if (!parseRestrictionParameters(parser)) {
171                             // Failure to parse is automatically handled by falling back to
172                             // defaults. Just log the information here.
173                             if (Slogf.isLoggable(TAG, Log.INFO)) {
174                                 Slogf.i(TAG, "Error reading restrictions parameters. "
175                                         + "Falling back to platform defaults.");
176                             }
177                         }
178                         break;
179                     default:
180                         Slogf.w(TAG, "Unknown class:" + parser.getName());
181                 }
182             }
183         }
184         return true;
185     }
186 
187     /**
188      * Parses the information in the <restrictionMapping> tag to construct the mapping from
189      * driving state to UX restrictions.
190      */
mapDrivingStateToRestrictions(XmlResourceParser parser)191     private boolean mapDrivingStateToRestrictions(XmlResourceParser parser)
192             throws IOException, XmlPullParserException {
193         if (parser == null) {
194             Slogf.e(TAG, "Invalid arguments");
195             return false;
196         }
197         // The parser should be at the <RestrictionMapping> tag at this point.
198         if (!XML_RESTRICTION_MAPPING.equals(parser.getName())) {
199             Slogf.e(TAG, "Parser not at RestrictionMapping element: " + parser.getName());
200             return false;
201         }
202         // read port
203         int portValue = parser.getAttributeIntValue(XML_NAMESPACE, XML_PHYSICAL_PORT,
204                 INVALID_PORT);
205         if (portValue != INVALID_PORT) {
206             int port = CarUxRestrictionsConfiguration.Builder.validatePort(portValue);
207             getCurrentBuilder().setPhysicalPort(port);
208         }
209 
210         if (!traverseToTag(parser, XML_DRIVING_STATE)) {
211             Slogf.e(TAG, "No <" + XML_DRIVING_STATE + "> tag in XML");
212             return false;
213         }
214         // Handle all the <DrivingState> tags.
215         while (XML_DRIVING_STATE.equals(parser.getName())) {
216             if (parser.getEventType() == XmlResourceParser.START_TAG) {
217                 // 1. Get the driving state attributes: driving state and speed range
218                 int drivingState = getDrivingState(
219                         parser.getAttributeValue(XML_NAMESPACE, XML_STATE));
220                 float minSpeed = 0;
221                 try {
222                     minSpeed = Float
223                             .parseFloat(parser.getAttributeValue(XML_NAMESPACE, XML_MIN_SPEED));
224                 } catch (NullPointerException | NumberFormatException e) {
225                     minSpeed = INVALID_SPEED;
226                 }
227 
228                 float maxSpeed = 0;
229                 try {
230                     maxSpeed = Float
231                             .parseFloat(parser.getAttributeValue(XML_NAMESPACE, XML_MAX_SPEED));
232                 } catch (NullPointerException | NumberFormatException e) {
233                     maxSpeed = MAX_SPEED;
234                 }
235 
236                 // 2. Traverse to the <Restrictions> tag
237                 if (!traverseToTag(parser, XML_RESTRICTIONS)) {
238                     Slogf.e(TAG, "No <" + XML_RESTRICTIONS + "> tag in XML");
239                     return false;
240                 }
241 
242                 // 3. Parse the restrictions for this driving state
243                 Builder.SpeedRange speedRange = parseSpeedRange(minSpeed, maxSpeed);
244                 if (!parseAllRestrictions(parser, drivingState, speedRange)) {
245                     Slogf.e(TAG, "Could not parse restrictions for driving state:" + drivingState);
246                     return false;
247                 }
248             }
249             parser.next();
250         }
251         return true;
252     }
253 
getDrivingState(String state)254     private int getDrivingState(String state) {
255         if (state == null) {
256             return CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
257         }
258 
259         switch (state.trim().toLowerCase(Locale.ROOT)) {
260             case XML_STATE_PARKED:
261                 return CarDrivingStateEvent.DRIVING_STATE_PARKED;
262             case XML_STATE_IDLING:
263                 return CarDrivingStateEvent.DRIVING_STATE_IDLING;
264             case XML_STATE_MOVING:
265                 return CarDrivingStateEvent.DRIVING_STATE_MOVING;
266             default:
267                 return CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
268         }
269     }
270 
271     /**
272      * Parses all <restrictions> tags nested with <drivingState> tag.
273      */
parseAllRestrictions(XmlResourceParser parser, int drivingState, Builder.SpeedRange speedRange)274     private boolean parseAllRestrictions(XmlResourceParser parser,
275             int drivingState, Builder.SpeedRange speedRange)
276             throws IOException, XmlPullParserException {
277         if (parser == null) {
278             Slogf.e(TAG, "Invalid arguments");
279             return false;
280         }
281         // The parser should be at the <Restrictions> tag at this point.
282         if (!XML_RESTRICTIONS.equals(parser.getName())) {
283             Slogf.e(TAG, "Parser not at Restrictions element: " + parser.getName());
284             return false;
285         }
286         while (XML_RESTRICTIONS.equals(parser.getName())) {
287             if (parser.getEventType() == XmlResourceParser.START_TAG) {
288                 // Parse one restrictions tag.
289                 DrivingStateRestrictions restrictions = parseRestrictions(parser);
290                 if (restrictions == null) {
291                     Slogf.e(TAG, "");
292                     return false;
293                 }
294                 restrictions.setSpeedRange(speedRange);
295 
296                 if (Slogf.isLoggable(TAG, Log.DEBUG)) {
297                     Slogf.d(TAG, "Map " + drivingState + " : " + restrictions);
298                 }
299 
300                 // Update the builder if the driving state and restrictions info are valid.
301                 if (drivingState != CarDrivingStateEvent.DRIVING_STATE_UNKNOWN
302                         && restrictions != null) {
303                     getCurrentBuilder().setUxRestrictions(drivingState, restrictions);
304                 }
305             }
306             parser.next();
307         }
308         return true;
309     }
310 
311     /**
312      * Parses the <restrictions> tag nested with the <drivingState>.  This provides the restrictions
313      * for the enclosing driving state.
314      */
315     @Nullable
parseRestrictions(XmlResourceParser parser)316     private DrivingStateRestrictions parseRestrictions(XmlResourceParser parser)
317             throws IOException, XmlPullParserException {
318         if (parser == null) {
319             Slogf.e(TAG, "Invalid Arguments");
320             return null;
321         }
322 
323         int restrictions = UX_RESTRICTIONS_UNKNOWN;
324         String restrictionMode = UX_RESTRICTION_MODE_BASELINE;
325         boolean requiresOpt = true;
326         while (XML_RESTRICTIONS.equals(parser.getName())
327                 && parser.getEventType() == XmlResourceParser.START_TAG) {
328             restrictions = getRestrictions(parser.getAttributeValue(XML_NAMESPACE, XML_UXR));
329             restrictionMode = parser.getAttributeValue(XML_NAMESPACE, XML_MODE);
330             requiresOpt = parser.getAttributeBooleanValue(XML_NAMESPACE,
331                     XML_REQUIRES_DISTRACTION_OPTIMIZATION, true);
332             parser.next();
333         }
334         if (restrictionMode == null) {
335             restrictionMode = UX_RESTRICTION_MODE_BASELINE;
336         }
337         return new DrivingStateRestrictions()
338                 .setDistractionOptimizationRequired(requiresOpt)
339                 .setRestrictions(restrictions)
340                 .setMode(restrictionMode);
341     }
342 
getRestrictions(String allRestrictions)343     private int getRestrictions(String allRestrictions) {
344         if (allRestrictions == null) {
345             return CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
346         }
347         int restrictionsValue = 0;
348         String[] restrictions = allRestrictions.split("\\|");
349         for (int i = 0; i < restrictions.length; i++) {
350             String restriction = restrictions[i].trim().toLowerCase(Locale.ROOT);
351             switch (restriction) {
352                 case XML_UXR_BASELINE:
353                     restrictionsValue = restrictionsValue
354                             | CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
355                     break;
356                 case XML_UXR_NO_DIALPAD:
357                     restrictionsValue = restrictionsValue
358                             | CarUxRestrictions.UX_RESTRICTIONS_NO_DIALPAD;
359                     break;
360                 case XML_UXR_NO_FILTERING:
361                     restrictionsValue = restrictionsValue
362                             | CarUxRestrictions.UX_RESTRICTIONS_NO_FILTERING;
363                     break;
364                 case XML_UXR_LIMIT_STRING_LENGTH:
365                     restrictionsValue = restrictionsValue
366                             | CarUxRestrictions.UX_RESTRICTIONS_LIMIT_STRING_LENGTH;
367                     break;
368                 case XML_UXR_NO_KEYBOARD:
369                     restrictionsValue = restrictionsValue
370                             | CarUxRestrictions.UX_RESTRICTIONS_NO_KEYBOARD;
371                     break;
372                 case XML_UXR_NO_VIDEO:
373                     restrictionsValue = restrictionsValue
374                             | CarUxRestrictions.UX_RESTRICTIONS_NO_VIDEO;
375                     break;
376                 case XML_UXR_LIMIT_CONTENT:
377                     restrictionsValue = restrictionsValue
378                             | CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT;
379                     break;
380                 case XML_UXR_NO_SETUP:
381                     restrictionsValue = restrictionsValue
382                             | CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP;
383                     break;
384                 case XML_UXR_NO_TEXT_MESSAGE:
385                     restrictionsValue = restrictionsValue
386                             | CarUxRestrictions.UX_RESTRICTIONS_NO_TEXT_MESSAGE;
387                     break;
388                 case XML_UXR_NO_VOICE_TRANSCRIPTION:
389                     restrictionsValue = restrictionsValue
390                             | CarUxRestrictions.UX_RESTRICTIONS_NO_VOICE_TRANSCRIPTION;
391                     break;
392                 case XML_UXR_FULLY_RESTRICTED:
393                     restrictionsValue = restrictionsValue
394                             | CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
395                     break;
396             }
397         }
398         return restrictionsValue;
399     }
400 
401     @Nullable
parseSpeedRange(float minSpeed, float maxSpeed)402     private Builder.SpeedRange parseSpeedRange(float minSpeed, float maxSpeed) {
403         if (Float.compare(minSpeed, 0) < 0 || Float.compare(maxSpeed, 0) < 0) {
404             return null;
405         }
406         return new CarUxRestrictionsConfiguration.Builder.SpeedRange(minSpeed, maxSpeed);
407     }
408 
traverseToTag(XmlResourceParser parser, String tag)409     private boolean traverseToTag(XmlResourceParser parser, String tag)
410             throws IOException, XmlPullParserException {
411         if (tag == null || parser == null) {
412             return false;
413         }
414         int type;
415         while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) {
416             if (type == XmlResourceParser.START_TAG && parser.getName().equals(tag)) {
417                 return true;
418             }
419         }
420         return false;
421     }
422 
423     /**
424      * Parses the information in the <RestrictionParameters> tag to read the parameters for the
425      * applicable UX restrictions
426      */
parseRestrictionParameters(XmlResourceParser parser)427     private boolean parseRestrictionParameters(XmlResourceParser parser)
428             throws IOException, XmlPullParserException {
429         if (parser == null) {
430             Slogf.e(TAG, "Invalid arguments");
431             return false;
432         }
433         // The parser should be at the <RestrictionParameters> tag at this point.
434         if (!XML_RESTRICTION_PARAMETERS.equals(parser.getName())) {
435             Slogf.e(TAG, "Parser not at RestrictionParameters element: " + parser.getName());
436             return false;
437         }
438         while (parser.getEventType() != XmlResourceParser.END_DOCUMENT) {
439             int type = parser.next();
440             // Break if we have parsed all <RestrictionParameters>
441             if (type == XmlResourceParser.END_TAG && XML_RESTRICTION_PARAMETERS.equals(
442                     parser.getName())) {
443                 return true;
444             }
445             if (type == XmlResourceParser.START_TAG) {
446                 switch (parser.getName()) {
447                     case XML_STRING_RESTRICTIONS:
448                         mMaxRestrictedStringLength = parser.getAttributeIntValue(XML_NAMESPACE,
449                                 XML_MAX_LENGTH, UX_RESTRICTIONS_UNKNOWN);
450                         break;
451                     case XML_CONTENT_RESTRICTIONS:
452                         mMaxCumulativeContentItems = parser.getAttributeIntValue(XML_NAMESPACE,
453                                 XML_MAX_CUMULATIVE_ITEMS, UX_RESTRICTIONS_UNKNOWN);
454                         mMaxContentDepth = parser.getAttributeIntValue(XML_NAMESPACE, XML_MAX_DEPTH,
455                                 UX_RESTRICTIONS_UNKNOWN);
456                         break;
457                     default:
458                         if (Slogf.isLoggable(TAG, Log.DEBUG)) {
459                             Slogf.d(TAG, "Unsupported Restriction Parameters in XML: "
460                                     + parser.getName());
461                         }
462                         break;
463                 }
464             }
465         }
466         return true;
467     }
468 
getCurrentBuilder()469     private CarUxRestrictionsConfiguration.Builder getCurrentBuilder() {
470         return mConfigBuilders.get(mConfigBuilders.size() - 1);
471     }
472 }
473