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