blob: 4c4d72f659e4d4a19619810837b16a3bafe4777b [file] [log] [blame]
Yao, Yuxing9dd71302018-11-27 17:14:29 -08001/*
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
17package com.android.car;
18
Yao, Yuxing59d1cb62019-03-04 14:39:18 -080019import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
20
Yao, Yuxing9dd71302018-11-27 17:14:29 -080021import android.annotation.Nullable;
22import android.annotation.XmlRes;
23import android.car.drivingstate.CarDrivingStateEvent;
24import android.car.drivingstate.CarUxRestrictions;
25import android.car.drivingstate.CarUxRestrictionsConfiguration;
Yao, Yuxing59d1cb62019-03-04 14:39:18 -080026import android.car.drivingstate.CarUxRestrictionsConfiguration.Builder;
27import android.car.drivingstate.CarUxRestrictionsConfiguration.DrivingStateRestrictions;
Yao, Yuxing9dd71302018-11-27 17:14:29 -080028import android.content.Context;
29import android.content.res.TypedArray;
30import android.content.res.XmlResourceParser;
31import android.util.AttributeSet;
32import android.util.Log;
Yao, Yuxing9dd71302018-11-27 17:14:29 -080033import android.util.Xml;
34
35import org.xmlpull.v1.XmlPullParserException;
36
37import java.io.IOException;
Yao, Yuxingceb6c552019-03-12 14:52:48 -070038import java.util.ArrayList;
39import java.util.List;
Yao, Yuxing9dd71302018-11-27 17:14:29 -080040
41/**
42 * @hide
43 */
44public final class CarUxRestrictionsConfigurationXmlParser {
45 private static final String TAG = "UxRConfigParser";
46 private static final int UX_RESTRICTIONS_UNKNOWN = -1;
47 private static final float INVALID_SPEED = -1f;
48 // XML tags to parse
49 private static final String ROOT_ELEMENT = "UxRestrictions";
50 private static final String RESTRICTION_MAPPING = "RestrictionMapping";
51 private static final String RESTRICTION_PARAMETERS = "RestrictionParameters";
52 private static final String DRIVING_STATE = "DrivingState";
53 private static final String RESTRICTIONS = "Restrictions";
54 private static final String STRING_RESTRICTIONS = "StringRestrictions";
55 private static final String CONTENT_RESTRICTIONS = "ContentRestrictions";
56
57 private final Context mContext;
58
Yao, Yuxingceb6c552019-03-12 14:52:48 -070059 private int mMaxRestrictedStringLength = UX_RESTRICTIONS_UNKNOWN;
60 private int mMaxCumulativeContentItems = UX_RESTRICTIONS_UNKNOWN;
61 private int mMaxContentDepth = UX_RESTRICTIONS_UNKNOWN;
62 private final List<CarUxRestrictionsConfiguration.Builder> mConfigBuilders = new ArrayList<>();
Yao, Yuxing9dd71302018-11-27 17:14:29 -080063
64 private CarUxRestrictionsConfigurationXmlParser(Context context) {
65 mContext = context;
66 }
67
68 /**
69 * Loads the UX restrictions related information from the XML resource.
70 *
71 * @return parsed CarUxRestrictionsConfiguration; {@code null} if the XML is malformed.
72 */
73 @Nullable
Yao, Yuxingceb6c552019-03-12 14:52:48 -070074 public static List<CarUxRestrictionsConfiguration> parse(
75 Context context, @XmlRes int xmlResource)
Yao, Yuxing9dd71302018-11-27 17:14:29 -080076 throws IOException, XmlPullParserException {
77 return new CarUxRestrictionsConfigurationXmlParser(context).parse(xmlResource);
78 }
79
80 @Nullable
Yao, Yuxingceb6c552019-03-12 14:52:48 -070081 private List<CarUxRestrictionsConfiguration> parse(@XmlRes int xmlResource)
Yao, Yuxing9dd71302018-11-27 17:14:29 -080082 throws IOException, XmlPullParserException {
Yao, Yuxing9dd71302018-11-27 17:14:29 -080083
84 XmlResourceParser parser = mContext.getResources().getXml(xmlResource);
85 if (parser == null) {
86 Log.e(TAG, "Invalid Xml resource");
87 return null;
88 }
89
90 if (!traverseUntilStartTag(parser)) {
91 Log.e(TAG, "XML root element invalid: " + parser.getName());
92 return null;
93 }
94
95 if (!traverseUntilEndOfDocument(parser)) {
96 Log.e(TAG, "Could not parse XML to end");
97 return null;
98 }
99
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700100 List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
101 for (CarUxRestrictionsConfiguration.Builder builder : mConfigBuilders) {
102 builder.setMaxStringLength(mMaxRestrictedStringLength)
103 .setMaxCumulativeContentItems(mMaxCumulativeContentItems)
104 .setMaxContentDepth(mMaxContentDepth);
105 configs.add(builder.build());
106 }
107 return configs;
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800108 }
109
110 private boolean traverseUntilStartTag(XmlResourceParser parser)
111 throws IOException, XmlPullParserException {
112 int type;
113 // Traverse till we get to the first tag
114 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
115 && type != XmlResourceParser.START_TAG) {
116 // Do nothing.
117 }
118 return ROOT_ELEMENT.equals(parser.getName());
119 }
120
121 private boolean traverseUntilEndOfDocument(XmlResourceParser parser)
122 throws XmlPullParserException, IOException {
123 AttributeSet attrs = Xml.asAttributeSet(parser);
124 while (parser.getEventType() != XmlResourceParser.END_DOCUMENT) {
125 // Every time we hit a start tag, check for the type of the tag
126 // and load the corresponding information.
127 if (parser.next() == XmlResourceParser.START_TAG) {
128 switch (parser.getName()) {
129 case RESTRICTION_MAPPING:
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700130 // Each RestrictionMapping tag represents a new set of rules.
131 mConfigBuilders.add(new CarUxRestrictionsConfiguration.Builder());
132
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800133 if (!mapDrivingStateToRestrictions(parser, attrs)) {
134 Log.e(TAG, "Could not map driving state to restriction.");
135 return false;
136 }
137 break;
138 case RESTRICTION_PARAMETERS:
139 if (!parseRestrictionParameters(parser, attrs)) {
140 // Failure to parse is automatically handled by falling back to
141 // defaults. Just log the information here.
142 if (Log.isLoggable(TAG, Log.INFO)) {
143 Log.i(TAG, "Error reading restrictions parameters. "
144 + "Falling back to platform defaults.");
145 }
146 }
147 break;
148 default:
149 Log.w(TAG, "Unknown class:" + parser.getName());
150 }
151 }
152 }
153 return true;
154 }
155
156 /**
157 * Parses the information in the <restrictionMapping> tag to construct the mapping from
158 * driving state to UX restrictions.
159 */
160 private boolean mapDrivingStateToRestrictions(XmlResourceParser parser, AttributeSet attrs)
161 throws IOException, XmlPullParserException {
162 if (parser == null || attrs == null) {
163 Log.e(TAG, "Invalid arguments");
164 return false;
165 }
166 // The parser should be at the <RestrictionMapping> tag at this point.
167 if (!RESTRICTION_MAPPING.equals(parser.getName())) {
168 Log.e(TAG, "Parser not at RestrictionMapping element: " + parser.getName());
169 return false;
170 }
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700171 {
172 // Use a floating block to limit the scope of TypedArray and ensure it's recycled.
173 TypedArray a = mContext.getResources().obtainAttributes(attrs,
174 R.styleable.UxRestrictions_RestrictionMapping);
175 if (a.hasValue(R.styleable.UxRestrictions_RestrictionMapping_physicalPort)) {
176 int portValue = a.getInt(
177 R.styleable.UxRestrictions_RestrictionMapping_physicalPort, 0);
Marin Shalamanove85bd562020-04-24 17:21:32 +0200178 int port = CarUxRestrictionsConfiguration.Builder.validatePort(portValue);
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700179 getCurrentBuilder().setPhysicalPort(port);
180 }
181 a.recycle();
182 }
183
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800184 if (!traverseToTag(parser, DRIVING_STATE)) {
185 Log.e(TAG, "No <" + DRIVING_STATE + "> tag in XML");
186 return false;
187 }
188 // Handle all the <DrivingState> tags.
189 while (DRIVING_STATE.equals(parser.getName())) {
190 if (parser.getEventType() == XmlResourceParser.START_TAG) {
191 // 1. Get the driving state attributes: driving state and speed range
192 TypedArray a = mContext.getResources().obtainAttributes(attrs,
193 R.styleable.UxRestrictions_DrivingState);
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800194 int drivingState = a.getInt(R.styleable.UxRestrictions_DrivingState_state,
195 CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
196 float minSpeed = a.getFloat(R.styleable.UxRestrictions_DrivingState_minSpeed,
197 INVALID_SPEED);
198 float maxSpeed = a.getFloat(R.styleable.UxRestrictions_DrivingState_maxSpeed,
199 Builder.SpeedRange.MAX_SPEED);
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800200 a.recycle();
201
202 // 2. Traverse to the <Restrictions> tag
203 if (!traverseToTag(parser, RESTRICTIONS)) {
204 Log.e(TAG, "No <" + RESTRICTIONS + "> tag in XML");
205 return false;
206 }
207
208 // 3. Parse the restrictions for this driving state
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800209 Builder.SpeedRange speedRange = parseSpeedRange(minSpeed, maxSpeed);
210 if (!parseAllRestrictions(parser, attrs, drivingState, speedRange)) {
211 Log.e(TAG, "Could not parse restrictions for driving state:" + drivingState);
212 return false;
213 }
214 }
215 parser.next();
216 }
217 return true;
218 }
219
220 /**
221 * Parses all <restrictions> tags nested with <drivingState> tag.
222 */
223 private boolean parseAllRestrictions(XmlResourceParser parser, AttributeSet attrs,
224 int drivingState, Builder.SpeedRange speedRange)
225 throws IOException, XmlPullParserException {
226 if (parser == null || attrs == null) {
227 Log.e(TAG, "Invalid arguments");
228 return false;
229 }
230 // The parser should be at the <Restrictions> tag at this point.
231 if (!RESTRICTIONS.equals(parser.getName())) {
232 Log.e(TAG, "Parser not at Restrictions element: " + parser.getName());
233 return false;
234 }
235 while (RESTRICTIONS.equals(parser.getName())) {
236 if (parser.getEventType() == XmlResourceParser.START_TAG) {
237 // Parse one restrictions tag.
238 DrivingStateRestrictions restrictions = parseRestrictions(parser, attrs);
239 if (restrictions == null) {
240 Log.e(TAG, "");
241 return false;
242 }
243 restrictions.setSpeedRange(speedRange);
244
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800245 if (Log.isLoggable(TAG, Log.DEBUG)) {
246 Log.d(TAG, "Map " + drivingState + " : " + restrictions);
247 }
248
249 // Update the builder if the driving state and restrictions info are valid.
250 if (drivingState != CarDrivingStateEvent.DRIVING_STATE_UNKNOWN
251 && restrictions != null) {
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700252 getCurrentBuilder().setUxRestrictions(drivingState, restrictions);
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800253 }
254 }
255 parser.next();
256 }
257 return true;
258 }
259
260 /**
261 * Parses the <restrictions> tag nested with the <drivingState>. This provides the restrictions
262 * for the enclosing driving state.
263 */
264 @Nullable
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800265 private DrivingStateRestrictions parseRestrictions(XmlResourceParser parser, AttributeSet attrs)
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800266 throws IOException, XmlPullParserException {
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800267 if (parser == null || attrs == null) {
268 Log.e(TAG, "Invalid Arguments");
269 return null;
270 }
271
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800272 int restrictions = UX_RESTRICTIONS_UNKNOWN;
Jordan Jozwiak0b0cf282020-01-17 12:26:42 -0800273 String restrictionMode = UX_RESTRICTION_MODE_BASELINE;
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800274 boolean requiresOpt = true;
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800275 while (RESTRICTIONS.equals(parser.getName())
276 && parser.getEventType() == XmlResourceParser.START_TAG) {
277 TypedArray a = mContext.getResources().obtainAttributes(attrs,
278 R.styleable.UxRestrictions_Restrictions);
279 restrictions = a.getInt(
280 R.styleable.UxRestrictions_Restrictions_uxr,
281 CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
282 requiresOpt = a.getBoolean(
283 R.styleable.UxRestrictions_Restrictions_requiresDistractionOptimization, true);
Jordan Jozwiak0b0cf282020-01-17 12:26:42 -0800284 restrictionMode = a.getString(R.styleable.UxRestrictions_Restrictions_mode);
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800285
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800286 a.recycle();
287 parser.next();
288 }
Jordan Jozwiak0b0cf282020-01-17 12:26:42 -0800289 if (restrictionMode == null) {
290 restrictionMode = UX_RESTRICTION_MODE_BASELINE;
291 }
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800292 return new DrivingStateRestrictions()
293 .setDistractionOptimizationRequired(requiresOpt)
294 .setRestrictions(restrictions)
295 .setMode(restrictionMode);
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800296 }
297
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800298 @Nullable
299 private Builder.SpeedRange parseSpeedRange(float minSpeed, float maxSpeed) {
300 if (Float.compare(minSpeed, 0) < 0 || Float.compare(maxSpeed, 0) < 0) {
301 return null;
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800302 }
Yao, Yuxing59d1cb62019-03-04 14:39:18 -0800303 return new CarUxRestrictionsConfiguration.Builder.SpeedRange(minSpeed, maxSpeed);
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800304 }
305
306 private boolean traverseToTag(XmlResourceParser parser, String tag)
307 throws IOException, XmlPullParserException {
308 if (tag == null || parser == null) {
309 return false;
310 }
311 int type;
312 while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) {
313 if (type == XmlResourceParser.START_TAG && parser.getName().equals(tag)) {
314 return true;
315 }
316 }
317 return false;
318 }
319
320 /**
321 * Parses the information in the <RestrictionParameters> tag to read the parameters for the
322 * applicable UX restrictions
323 */
324 private boolean parseRestrictionParameters(XmlResourceParser parser, AttributeSet attrs)
325 throws IOException, XmlPullParserException {
326 if (parser == null || attrs == null) {
327 Log.e(TAG, "Invalid arguments");
328 return false;
329 }
330 // The parser should be at the <RestrictionParameters> tag at this point.
331 if (!RESTRICTION_PARAMETERS.equals(parser.getName())) {
332 Log.e(TAG, "Parser not at RestrictionParameters element: " + parser.getName());
333 return false;
334 }
335 while (parser.getEventType() != XmlResourceParser.END_DOCUMENT) {
336 int type = parser.next();
337 // Break if we have parsed all <RestrictionParameters>
338 if (type == XmlResourceParser.END_TAG && RESTRICTION_PARAMETERS.equals(
339 parser.getName())) {
340 return true;
341 }
342 if (type == XmlResourceParser.START_TAG) {
343 TypedArray a = null;
344 switch (parser.getName()) {
345 case STRING_RESTRICTIONS:
346 a = mContext.getResources().obtainAttributes(attrs,
347 R.styleable.UxRestrictions_StringRestrictions);
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700348 mMaxRestrictedStringLength = a.getInt(
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800349 R.styleable.UxRestrictions_StringRestrictions_maxLength,
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700350 UX_RESTRICTIONS_UNKNOWN);
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800351
352 break;
353 case CONTENT_RESTRICTIONS:
354 a = mContext.getResources().obtainAttributes(attrs,
355 R.styleable.UxRestrictions_ContentRestrictions);
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700356 mMaxCumulativeContentItems = a.getInt(
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800357 R.styleable.UxRestrictions_ContentRestrictions_maxCumulativeItems,
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700358 UX_RESTRICTIONS_UNKNOWN);
359 mMaxContentDepth = a.getInt(
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800360 R.styleable.UxRestrictions_ContentRestrictions_maxDepth,
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700361 UX_RESTRICTIONS_UNKNOWN);
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800362 break;
363 default:
364 if (Log.isLoggable(TAG, Log.DEBUG)) {
365 Log.d(TAG, "Unsupported Restriction Parameters in XML: "
366 + parser.getName());
367 }
368 break;
369 }
370 if (a != null) {
371 a.recycle();
372 }
373 }
374 }
375 return true;
376 }
Yao, Yuxingceb6c552019-03-12 14:52:48 -0700377
378 private CarUxRestrictionsConfiguration.Builder getCurrentBuilder() {
379 return mConfigBuilders.get(mConfigBuilders.size() - 1);
380 }
Yao, Yuxing9dd71302018-11-27 17:14:29 -0800381}
382