• 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 android.annotation.Nullable;
20 import android.annotation.XmlRes;
21 import android.car.drivingstate.CarDrivingStateEvent;
22 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
23 import android.car.drivingstate.CarUxRestrictions;
24 import android.content.Context;
25 import android.content.res.TypedArray;
26 import android.content.res.XmlResourceParser;
27 import android.os.SystemClock;
28 import android.util.AttributeSet;
29 import android.util.Log;
30 import android.util.Pair;
31 import android.util.Xml;
32 
33 import org.xmlpull.v1.XmlPullParserException;
34 
35 import java.io.IOException;
36 import java.io.PrintWriter;
37 import java.util.ArrayList;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 
42 /**
43  * Helper class to {@link CarUxRestrictionsManagerService} and it takes care of the foll:
44  * <ol>
45  * <li>Parses the given XML resource and builds a hashmap to store the driving state to UX
46  * restrictions mapping information provided in the XML.</li>
47  * <li>Finds the UX restrictions for the given driving state and speed from the data structure it
48  * built above.</li>
49  * </ol>
50  */
51 /* package */ class CarUxRestrictionsServiceHelper {
52     private static final String TAG = "UxRServiceHelper";
53     private static final int UX_RESTRICTIONS_UNKNOWN = -1;
54     // XML tags to parse
55     private static final String ROOT_ELEMENT = "UxRestrictions";
56     private static final String RESTRICTION_MAPPING = "RestrictionMapping";
57     private static final String RESTRICTION_PARAMETERS = "RestrictionParameters";
58     private static final String DRIVING_STATE = "DrivingState";
59     private static final String RESTRICTIONS = "Restrictions";
60     private static final String STRING_RESTRICTIONS = "StringRestrictions";
61     private static final String CONTENT_RESTRICTIONS = "ContentRestrictions";
62 
63     /* Hashmap that maps driving state to RestrictionsInfo.
64     RestrictionsInfo maintains a list of RestrictionsPerSpeedRange.
65     The list size will be one for Parked and Idling states, but could be more than one
66     for Moving state, if moving state supports multiple speed ranges.*/
67     private Map<Integer, RestrictionsInfo> mRestrictionsMap = new HashMap<>();
68     private RestrictionParameters mRestrictionParameters = new RestrictionParameters();
69     private final Context mContext;
70     @XmlRes
71     private final int mXmlResource;
72 
CarUxRestrictionsServiceHelper(Context context, @XmlRes int xmlRes)73     CarUxRestrictionsServiceHelper(Context context, @XmlRes int xmlRes) {
74         mContext = context;
75         mXmlResource = xmlRes;
76     }
77 
78     /**
79      * Loads the UX restrictions related information from the XML resource.
80      *
81      * @return true if successful, false if the XML is malformed
82      */
loadUxRestrictionsFromXml()83     public boolean loadUxRestrictionsFromXml() throws IOException, XmlPullParserException {
84         mRestrictionsMap.clear();
85         XmlResourceParser parser = mContext.getResources().getXml(mXmlResource);
86         AttributeSet attrs = Xml.asAttributeSet(parser);
87         int type;
88         // Traverse till we get to the first tag
89         while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT
90                 && type != XmlResourceParser.START_TAG) {
91         }
92         if (!ROOT_ELEMENT.equals(parser.getName())) {
93             Log.e(TAG, "XML root element invalid: " + parser.getName());
94             return false;
95         }
96 
97         // Traverse till the end and every time we hit a start tag, check for the type of the tag
98         // and load the corresponding information.
99         while (parser.getEventType() != XmlResourceParser.END_DOCUMENT) {
100             if (parser.next() == XmlResourceParser.START_TAG) {
101                 switch (parser.getName()) {
102                     case RESTRICTION_MAPPING:
103                         if (!mapDrivingStateToRestrictions(parser, attrs)) {
104                             return false;
105                         }
106                         break;
107                     case RESTRICTION_PARAMETERS:
108                         if (!parseRestrictionParameters(parser, attrs)) {
109                             // Failure to parse is automatically handled by falling back to
110                             // defaults.  Just log the information here
111                             if (Log.isLoggable(TAG, Log.INFO)) {
112                                 Log.i(TAG,
113                                         "Error reading restrictions parameters.  Falling back to "
114                                                 + "platform defaults.");
115                             }
116                         }
117                         break;
118                     default:
119                         Log.w(TAG, "Unknown class:" + parser.getName());
120                 }
121             }
122         }
123         return true;
124     }
125 
126     /**
127      * Parses the information in the <restrictionMapping> tag to construct the mapping from
128      * driving state to UX restrictions.
129      */
mapDrivingStateToRestrictions(XmlResourceParser parser, AttributeSet attrs)130     private boolean mapDrivingStateToRestrictions(XmlResourceParser parser, AttributeSet attrs)
131             throws IOException, XmlPullParserException {
132         if (parser == null || attrs == null) {
133             Log.e(TAG, "Invalid arguments");
134             return false;
135         }
136         // The parser should be at the <RestrictionMapping> tag at this point.
137         if (!RESTRICTION_MAPPING.equals(parser.getName())) {
138             Log.e(TAG, "Parser not at RestrictionMapping element: " + parser.getName());
139             return false;
140         }
141         if (!traverseToTag(parser, DRIVING_STATE)) {
142             Log.e(TAG, "No <" + DRIVING_STATE + "> tag in XML");
143             return false;
144         }
145         // Handle all the <DrivingState> tags.
146         while (DRIVING_STATE.equals(parser.getName())) {
147             if (parser.getEventType() == XmlResourceParser.START_TAG) {
148                 // 1. Get the driving state attributes: driving state and speed range
149                 TypedArray a = mContext.getResources().obtainAttributes(attrs,
150                         R.styleable.UxRestrictions_DrivingState);
151                 int drivingState = a
152                         .getInt(R.styleable.UxRestrictions_DrivingState_state,
153                                 CarDrivingStateEvent.DRIVING_STATE_UNKNOWN);
154                 float minSpeed = a
155                         .getFloat(
156                                 R.styleable
157                                         .UxRestrictions_DrivingState_minSpeed,
158                                 RestrictionsPerSpeedRange.SPEED_INVALID);
159                 float maxSpeed = a
160                         .getFloat(
161                                 R.styleable
162                                         .UxRestrictions_DrivingState_maxSpeed,
163                                 RestrictionsPerSpeedRange.SPEED_INVALID);
164                 a.recycle();
165 
166                 // 2. Traverse to the <Restrictions> tag
167                 if (!traverseToTag(parser, RESTRICTIONS)) {
168                     Log.e(TAG, "No <" + RESTRICTIONS + "> tag in XML");
169                     return false;
170                 }
171 
172                 // 3. Parse the restrictions for this driving state
173                 Pair<Boolean, Integer> restrictions = parseRestrictions(parser, attrs);
174                 if (Log.isLoggable(TAG, Log.DEBUG)) {
175                     Log.d(TAG, "Map " + drivingState + " : " + restrictions);
176                 }
177 
178                 // Update the hashmap if the driving state and restrictions info are valid.
179                 if (drivingState != CarDrivingStateEvent.DRIVING_STATE_UNKNOWN
180                         && restrictions != null) {
181                     addToRestrictionsMap(drivingState, minSpeed, maxSpeed, restrictions.first,
182                             restrictions.second);
183                 }
184             }
185             parser.next();
186         }
187         return true;
188     }
189 
190     /**
191      * Parses the <restrictions> tag nested with the <drivingState>.  This provides the restrictions
192      * for the enclosing driving state.
193      */
194     @Nullable
parseRestrictions(XmlResourceParser parser, AttributeSet attrs)195     private Pair<Boolean, Integer> parseRestrictions(XmlResourceParser parser, AttributeSet attrs)
196             throws IOException, XmlPullParserException {
197         int restrictions = UX_RESTRICTIONS_UNKNOWN;
198         boolean requiresOpt = true;
199         if (parser == null || attrs == null) {
200             Log.e(TAG, "Invalid Arguments");
201             return null;
202         }
203 
204         while (RESTRICTIONS.equals(parser.getName())
205                 && parser.getEventType() == XmlResourceParser.START_TAG) {
206             TypedArray a = mContext.getResources().obtainAttributes(attrs,
207                     R.styleable.UxRestrictions_Restrictions);
208             restrictions = a.getInt(
209                     R.styleable.UxRestrictions_Restrictions_uxr,
210                     CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
211             requiresOpt = a.getBoolean(
212                     R.styleable.UxRestrictions_Restrictions_requiresDistractionOptimization, true);
213             a.recycle();
214             parser.next();
215         }
216         return new Pair<>(requiresOpt, restrictions);
217     }
218 
addToRestrictionsMap(int drivingState, float minSpeed, float maxSpeed, boolean requiresOpt, int restrictions)219     private void addToRestrictionsMap(int drivingState, float minSpeed, float maxSpeed,
220             boolean requiresOpt, int restrictions) {
221         RestrictionsPerSpeedRange res = new RestrictionsPerSpeedRange(minSpeed, maxSpeed,
222                 restrictions, requiresOpt);
223         RestrictionsInfo restrictionsList = mRestrictionsMap.get(drivingState);
224         if (restrictionsList == null) {
225             restrictionsList = new RestrictionsInfo();
226         }
227         restrictionsList.addRestrictions(res);
228         mRestrictionsMap.put(drivingState, restrictionsList);
229     }
230 
traverseToTag(XmlResourceParser parser, String tag)231     private boolean traverseToTag(XmlResourceParser parser, String tag)
232             throws IOException, XmlPullParserException {
233         if (tag == null || parser == null) {
234             return false;
235         }
236         int type;
237         while ((type = parser.next()) != XmlResourceParser.END_DOCUMENT) {
238             if (type == XmlResourceParser.START_TAG && parser.getName().equals(tag)) {
239                 return true;
240             }
241         }
242         return false;
243     }
244 
245     /**
246      * Parses the information in the <RestrictionParameters> tag to read the parameters for the
247      * applicable UX restrictions
248      */
parseRestrictionParameters(XmlResourceParser parser, AttributeSet attrs)249     private boolean parseRestrictionParameters(XmlResourceParser parser, AttributeSet attrs)
250             throws IOException, XmlPullParserException {
251         if (parser == null || attrs == null) {
252             Log.e(TAG, "Invalid arguments");
253             return false;
254         }
255         // The parser should be at the <RestrictionParameters> tag at this point.
256         if (!RESTRICTION_PARAMETERS.equals(parser.getName())) {
257             Log.e(TAG, "Parser not at RestrictionParameters element: " + parser.getName());
258             return false;
259         }
260         while (parser.getEventType() != XmlResourceParser.END_DOCUMENT) {
261             int type = parser.next();
262             // Break if we have parsed all <RestrictionParameters>
263             if (type == XmlResourceParser.END_TAG && RESTRICTION_PARAMETERS.equals(
264                     parser.getName())) {
265                 return true;
266             }
267             if (type == XmlResourceParser.START_TAG) {
268                 TypedArray a = null;
269                 switch (parser.getName()) {
270                     case STRING_RESTRICTIONS:
271                         a = mContext.getResources().obtainAttributes(attrs,
272                                 R.styleable.UxRestrictions_StringRestrictions);
273                         mRestrictionParameters.mMaxStringLength = a
274                                 .getInt(R.styleable.UxRestrictions_StringRestrictions_maxLength,
275                                         UX_RESTRICTIONS_UNKNOWN);
276 
277                         break;
278                     case CONTENT_RESTRICTIONS:
279                         a = mContext.getResources().obtainAttributes(attrs,
280                                 R.styleable.UxRestrictions_ContentRestrictions);
281                         mRestrictionParameters.mMaxCumulativeContentItems = a.getInt(R.styleable
282                                         .UxRestrictions_ContentRestrictions_maxCumulativeItems,
283                                 UX_RESTRICTIONS_UNKNOWN);
284                         mRestrictionParameters.mMaxContentDepth = a
285                                 .getInt(R.styleable.UxRestrictions_ContentRestrictions_maxDepth,
286                                         UX_RESTRICTIONS_UNKNOWN);
287                         break;
288                     default:
289                         if (Log.isLoggable(TAG, Log.DEBUG)) {
290                             Log.d(TAG, "Unsupported Restriction Parameters in XML: "
291                                     + parser.getName());
292                         }
293                         break;
294                 }
295                 if (a != null) {
296                     a.recycle();
297                 }
298             }
299         }
300         return true;
301     }
302 
303     /**
304      * Dump the driving state to UX restrictions mapping.
305      */
dump(PrintWriter writer)306     public void dump(PrintWriter writer) {
307         for (Integer state : mRestrictionsMap.keySet()) {
308             RestrictionsInfo list = mRestrictionsMap.get(state);
309             writer.println("===========================================");
310             writer.println("Driving State to UXR");
311             if (list != null && list.mRestrictionsList != null) {
312                 writer.println("State:" + getDrivingStateName(state) + " num restrictions:"
313                         + list.mRestrictionsList.size());
314                 for (RestrictionsPerSpeedRange r : list.mRestrictionsList) {
315                     writer.println(
316                             "Speed Range: " + r.mMinSpeed + "-" + r.mMaxSpeed + " Requires DO? "
317                                     + r.mRequiresDistractionOptimization + " Restrictions: 0x"
318                                     + Integer.toHexString(r.mRestrictions));
319                     writer.println("===========================================");
320                 }
321             }
322         }
323         writer.println("Max String length: " + mRestrictionParameters.mMaxStringLength);
324         writer.println(
325                 "Max Cumul Content Items: " + mRestrictionParameters.mMaxCumulativeContentItems);
326         writer.println("Max Content depth: " + mRestrictionParameters.mMaxContentDepth);
327     }
328 
getDrivingStateName(int state)329     private static String getDrivingStateName(int state) {
330         switch (state) {
331             case 0:
332                 return "parked";
333             case 1:
334                 return "idling";
335             case 2:
336                 return "moving";
337             default:
338                 return "unknown";
339         }
340     }
341 
342     /**
343      * Get the UX restrictions for the given driving state and speed.
344      *
345      * @param drivingState driving state of the vehicle
346      * @param currentSpeed speed of the vehicle
347      * @return UX restrictions for the given driving state and speed.
348      */
getUxRestrictions(@arDrivingState int drivingState, float currentSpeed)349     public CarUxRestrictions getUxRestrictions(@CarDrivingState int drivingState,
350             float currentSpeed) {
351         RestrictionsPerSpeedRange restrictions;
352         RestrictionsInfo restrictionsList = mRestrictionsMap.get(drivingState);
353         // If the XML hasn't been parsed or if the given driving state is not supported in the
354         // XML, return fully restricted.
355         if (restrictionsList == null || restrictionsList.mRestrictionsList == null
356                 || restrictionsList.mRestrictionsList.isEmpty()) {
357             return createUxRestrictionsEvent(true,
358                     CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
359         }
360         // For Parked and Idling, the restrictions list will have only one item, since multiple
361         // speed ranges don't make sense in those driving states.
362         if (restrictionsList.mRestrictionsList.size() == 1) {
363             restrictions = restrictionsList.mRestrictionsList.get(0);
364         } else {
365             restrictions = restrictionsList.findRestrictions(currentSpeed);
366         }
367         if (restrictions != null) {
368             return createUxRestrictionsEvent(restrictions.mRequiresDistractionOptimization,
369                     restrictions.mRestrictions);
370         } else {
371             return createUxRestrictionsEvent(true,
372                     CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
373         }
374     }
375 
createUxRestrictionsEvent(boolean requiresOpt, @CarUxRestrictions.CarUxRestrictionsInfo int uxr)376     /* package */ CarUxRestrictions createUxRestrictionsEvent(boolean requiresOpt,
377             @CarUxRestrictions.CarUxRestrictionsInfo int uxr) {
378         // In case the UXR is not baseline, set requiresDistractionOptimization to true since it
379         // doesn't make sense to have an active non baseline restrictions without
380         // requiresDistractionOptimization set to true.
381         if (uxr != CarUxRestrictions.UX_RESTRICTIONS_BASELINE) {
382             requiresOpt = true;
383         }
384         CarUxRestrictions.Builder builder = new CarUxRestrictions.Builder(requiresOpt, uxr,
385                 SystemClock.elapsedRealtimeNanos());
386         if (mRestrictionParameters.mMaxStringLength != UX_RESTRICTIONS_UNKNOWN) {
387             builder.setMaxStringLength(mRestrictionParameters.mMaxStringLength);
388         }
389         if (mRestrictionParameters.mMaxCumulativeContentItems != UX_RESTRICTIONS_UNKNOWN) {
390             builder.setMaxCumulativeContentItems(
391                     mRestrictionParameters.mMaxCumulativeContentItems);
392         }
393         if (mRestrictionParameters.mMaxContentDepth != UX_RESTRICTIONS_UNKNOWN) {
394             builder.setMaxContentDepth(mRestrictionParameters.mMaxContentDepth);
395         }
396         return builder.build();
397     }
398 
399     /**
400      * Container for the UX restrictions that could be parametrized
401      */
402     private class RestrictionParameters {
403         int mMaxStringLength = UX_RESTRICTIONS_UNKNOWN;
404         int mMaxCumulativeContentItems = UX_RESTRICTIONS_UNKNOWN;
405         int mMaxContentDepth = UX_RESTRICTIONS_UNKNOWN;
406     }
407 
408     /**
409      * Container for UX restrictions for a speed range.
410      * Speed range is valid only for the {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}.
411      */
412     private class RestrictionsPerSpeedRange {
413         static final int SPEED_INVALID = -1;
414         final float mMinSpeed;
415         final float mMaxSpeed;
416         final int mRestrictions;
417         final boolean mRequiresDistractionOptimization;
418 
RestrictionsPerSpeedRange(float minSpeed, float maxSpeed, int restrictions, boolean requiresOpt)419         RestrictionsPerSpeedRange(float minSpeed, float maxSpeed, int restrictions,
420                 boolean requiresOpt) {
421             mMinSpeed = minSpeed;
422             mMaxSpeed = maxSpeed;
423             mRestrictions = restrictions;
424             mRequiresDistractionOptimization = requiresOpt;
425         }
426 
427         /**
428          * Return if the given speed is in the range of ( {@link #mMinSpeed}, {@link #mMaxSpeed}
429          *
430          * @param speed Speed to check
431          * @return true if in range false if not.
432          */
includes(float speed)433         boolean includes(float speed) {
434             if (mMinSpeed != SPEED_INVALID && mMaxSpeed == SPEED_INVALID) {
435                 // This is for a range [minSpeed, infinity).  If maxSpeed
436                 // is invalid and mMinSpeed is a valid, this represents a
437                 // anything greater than mMinSpeed.
438                 return speed >= mMinSpeed;
439             } else {
440                 return speed >= mMinSpeed && speed < mMaxSpeed;
441             }
442         }
443     }
444 
445     /**
446      * Container for a list of {@link RestrictionsPerSpeedRange} per driving state.
447      */
448     private class RestrictionsInfo {
449         private List<RestrictionsPerSpeedRange> mRestrictionsList = new ArrayList<>();
450 
addRestrictions(RestrictionsPerSpeedRange r)451         void addRestrictions(RestrictionsPerSpeedRange r) {
452             mRestrictionsList.add(r);
453         }
454 
455         /**
456          * Find the restrictions for the given speed.  It finds the range that the given speed falls
457          * in and gets the restrictions for that speed.
458          */
459         @Nullable
findRestrictions(float speed)460         RestrictionsPerSpeedRange findRestrictions(float speed) {
461             for (RestrictionsPerSpeedRange r : mRestrictionsList) {
462                 if (r.includes(speed)) {
463                     return r;
464                 }
465             }
466             return null;
467         }
468     }
469 }
470