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