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