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 package android.car.drivingstate;
17 
18 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING;
19 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
20 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
21 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
22 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
23 
24 import android.annotation.FloatRange;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
28 import android.os.Build;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.os.SystemClock;
32 import android.util.ArrayMap;
33 import android.util.JsonReader;
34 import android.util.JsonToken;
35 import android.util.JsonWriter;
36 import android.util.Log;
37 
38 import java.io.CharArrayWriter;
39 import java.io.IOException;
40 import java.io.PrintWriter;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Objects;
47 
48 /**
49  * Configuration for Car UX Restrictions service.
50  *
51  * @hide
52  */
53 public final class CarUxRestrictionsConfiguration implements Parcelable {
54     private static final String TAG = "CarUxRConfig";
55 
56     // Constants used by json de/serialization.
57     private static final String JSON_NAME_PHYSICAL_PORT = "physical_port";
58     private static final String JSON_NAME_MAX_CONTENT_DEPTH = "max_content_depth";
59     private static final String JSON_NAME_MAX_CUMULATIVE_CONTENT_ITEMS =
60             "max_cumulative_content_items";
61     private static final String JSON_NAME_MAX_STRING_LENGTH = "max_string_length";
62     private static final String JSON_NAME_MOVING_RESTRICTIONS = "moving_restrictions";
63     private static final String JSON_NAME_IDLING_RESTRICTIONS = "idling_restrictions";
64     private static final String JSON_NAME_PARKED_RESTRICTIONS = "parked_restrictions";
65     private static final String JSON_NAME_UNKNOWN_RESTRICTIONS = "unknown_restrictions";
66     private static final String JSON_NAME_REQ_OPT = "req_opt";
67     private static final String JSON_NAME_RESTRICTIONS = "restrictions";
68     private static final String JSON_NAME_SPEED_RANGE = "speed_range";
69     private static final String JSON_NAME_MIN_SPEED = "min_speed";
70     private static final String JSON_NAME_MAX_SPEED = "max_speed";
71 
72     private final int mMaxContentDepth;
73     private final int mMaxCumulativeContentItems;
74     private final int mMaxStringLength;
75     /**
76      * Mapping of a restriction mode name to its restrictions.
77      */
78     private final Map<String, RestrictionModeContainer> mRestrictionModes = new ArrayMap<>();
79 
80     // null means the port is not configured. It should apply to default display.
81     @Nullable
82     private final Byte mPhysicalPort;
83 
CarUxRestrictionsConfiguration(CarUxRestrictionsConfiguration.Builder builder)84     private CarUxRestrictionsConfiguration(CarUxRestrictionsConfiguration.Builder builder) {
85         mPhysicalPort = builder.mPhysicalPort;
86 
87         mMaxContentDepth = builder.mMaxContentDepth;
88         mMaxCumulativeContentItems = builder.mMaxCumulativeContentItems;
89         mMaxStringLength = builder.mMaxStringLength;
90 
91         // make an immutable copy from the builder
92         for (Map.Entry<String, RestrictionModeContainer> entry :
93                 builder.mRestrictionModes.entrySet()) {
94             String mode = entry.getKey();
95             RestrictionModeContainer container = new RestrictionModeContainer();
96             for (int drivingState : DRIVING_STATES) {
97                 container.setRestrictionsForDriveState(drivingState,
98                         Collections.unmodifiableList(
99                                 entry.getValue().getRestrictionsForDriveState(drivingState)));
100             }
101             mRestrictionModes.put(mode, container);
102         }
103     }
104 
105     /**
106      * Returns the restrictions for
107      * {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}
108      * based on current driving state.
109      *
110      * @param drivingState Driving state.
111      *                     See values in {@link CarDrivingStateEvent.CarDrivingState}.
112      * @param currentSpeed Current speed in meter per second.
113      */
getUxRestrictions( @arDrivingState int drivingState, float currentSpeed)114     public CarUxRestrictions getUxRestrictions(
115             @CarDrivingState int drivingState, float currentSpeed) {
116         return getUxRestrictions(drivingState, currentSpeed, UX_RESTRICTION_MODE_BASELINE);
117     }
118 
119     /**
120      * Returns the restrictions based on current driving state and restriction mode.
121      *
122      * <p>Restriction mode allows a different set of restrictions to be applied in the same driving
123      * state.
124      *
125      * @param drivingState Driving state.
126      *                     See values in {@link CarDrivingStateEvent.CarDrivingState}.
127      * @param currentSpeed Current speed in meter per second.
128      * @param mode         Current UX Restriction mode.
129      */
getUxRestrictions(@arDrivingState int drivingState, float currentSpeed, @NonNull String mode)130     public CarUxRestrictions getUxRestrictions(@CarDrivingState int drivingState,
131             float currentSpeed, @NonNull String mode) {
132         Objects.requireNonNull(mode, "mode must not be null");
133         RestrictionsPerSpeedRange restriction = null;
134         if (mRestrictionModes.containsKey(mode)) {
135             restriction = findUxRestrictionsInList(currentSpeed,
136                     mRestrictionModes.get(mode).getRestrictionsForDriveState(drivingState));
137         }
138 
139         if (restriction == null) {
140             if (Log.isLoggable(TAG, Log.DEBUG)) {
141                 Log.d(TAG,
142                         String.format("No restrictions specified for (mode: %s, drive state: %s)",
143                                 mode,
144                                 drivingState));
145             }
146             // Either mode does not have any configuration or the mode does not have a configuration
147             // for the specific drive state. In either case, fall-back to baseline configuration.
148             restriction = findUxRestrictionsInList(
149                     currentSpeed,
150                     mRestrictionModes.get(UX_RESTRICTION_MODE_BASELINE)
151                             .getRestrictionsForDriveState(drivingState));
152         }
153 
154         if (restriction == null) {
155             if (Build.IS_ENG || Build.IS_USERDEBUG) {
156                 throw new IllegalStateException("No restrictions for driving state "
157                         + getDrivingStateName(drivingState));
158             }
159             return createDefaultUxRestrictionsEvent();
160         }
161         return createUxRestrictionsEvent(restriction.mReqOpt, restriction.mRestrictions);
162     }
163 
164     /**
165      * Returns the port this configuration applies to.
166      *
167      * <p>Returns {@code null} if port is not set, meaning this configuration will apply
168      * to default display {@link android.view.Display#DEFAULT_DISPLAY}.
169      */
170     @Nullable
getPhysicalPort()171     public Byte getPhysicalPort() {
172         return mPhysicalPort;
173     }
174 
175     /**
176      * Returns the restrictions based on current driving state and speed.
177      */
178     @Nullable
findUxRestrictionsInList(float currentSpeed, List<RestrictionsPerSpeedRange> restrictions)179     private static RestrictionsPerSpeedRange findUxRestrictionsInList(float currentSpeed,
180             List<RestrictionsPerSpeedRange> restrictions) {
181         if (restrictions.isEmpty()) {
182             return null;
183         }
184 
185         if (restrictions.size() == 1 && restrictions.get(0).mSpeedRange == null) {
186             // Single restriction with no speed range implies it covers all.
187             return restrictions.get(0);
188         }
189 
190         for (RestrictionsPerSpeedRange r : restrictions) {
191             if (r.mSpeedRange != null && r.mSpeedRange.includes(currentSpeed)) {
192                 return r;
193             }
194         }
195         return null;
196     }
197 
createDefaultUxRestrictionsEvent()198     private CarUxRestrictions createDefaultUxRestrictionsEvent() {
199         return createUxRestrictionsEvent(true,
200                 CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
201     }
202 
203     /**
204      * Creates CarUxRestrictions with restrictions parameters from current configuration.
205      */
createUxRestrictionsEvent(boolean requiresOpt, @CarUxRestrictions.CarUxRestrictionsInfo int uxr)206     private CarUxRestrictions createUxRestrictionsEvent(boolean requiresOpt,
207             @CarUxRestrictions.CarUxRestrictionsInfo int uxr) {
208         // In case the UXR is not baseline, set requiresDistractionOptimization to true since it
209         // doesn't make sense to have an active non baseline restrictions without
210         // requiresDistractionOptimization set to true.
211         if (uxr != CarUxRestrictions.UX_RESTRICTIONS_BASELINE) {
212             requiresOpt = true;
213         }
214         CarUxRestrictions.Builder builder = new CarUxRestrictions.Builder(requiresOpt, uxr,
215                 SystemClock.elapsedRealtimeNanos());
216         if (mMaxStringLength != Builder.UX_RESTRICTIONS_UNKNOWN) {
217             builder.setMaxStringLength(mMaxStringLength);
218         }
219         if (mMaxCumulativeContentItems != Builder.UX_RESTRICTIONS_UNKNOWN) {
220             builder.setMaxCumulativeContentItems(mMaxCumulativeContentItems);
221         }
222         if (mMaxContentDepth != Builder.UX_RESTRICTIONS_UNKNOWN) {
223             builder.setMaxContentDepth(mMaxContentDepth);
224         }
225         return builder.build();
226     }
227 
228     // Json de/serialization methods.
229 
230     /**
231      * Writes current configuration as Json.
232      */
writeJson(@onNull JsonWriter writer)233     public void writeJson(@NonNull JsonWriter writer) throws IOException {
234         Objects.requireNonNull(writer, "writer must not be null");
235         // We need to be lenient to accept infinity number (as max speed).
236         writer.setLenient(true);
237 
238         writer.beginObject();
239         if (mPhysicalPort == null) {
240             writer.name(JSON_NAME_PHYSICAL_PORT).nullValue();
241         } else {
242             writer.name(JSON_NAME_PHYSICAL_PORT).value((int) mPhysicalPort.byteValue());
243         }
244         writer.name(JSON_NAME_MAX_CONTENT_DEPTH).value(mMaxContentDepth);
245         writer.name(JSON_NAME_MAX_CUMULATIVE_CONTENT_ITEMS).value(
246                 mMaxCumulativeContentItems);
247         writer.name(JSON_NAME_MAX_STRING_LENGTH).value(mMaxStringLength);
248 
249         for (Map.Entry<String, RestrictionModeContainer> entry : mRestrictionModes.entrySet()) {
250             writer.name(entry.getKey());
251             writeRestrictionMode(writer, entry.getValue());
252         }
253 
254         writer.endObject();
255     }
256 
writeRestrictionMode(JsonWriter writer, RestrictionModeContainer container)257     private void writeRestrictionMode(JsonWriter writer, RestrictionModeContainer container)
258             throws IOException {
259         writer.beginObject();
260         writer.name(JSON_NAME_PARKED_RESTRICTIONS);
261         writeRestrictionsList(writer, container.getRestrictionsForDriveState(DRIVING_STATE_PARKED));
262 
263         writer.name(JSON_NAME_IDLING_RESTRICTIONS);
264         writeRestrictionsList(writer, container.getRestrictionsForDriveState(DRIVING_STATE_IDLING));
265 
266         writer.name(JSON_NAME_MOVING_RESTRICTIONS);
267         writeRestrictionsList(writer, container.getRestrictionsForDriveState(DRIVING_STATE_MOVING));
268 
269         writer.name(JSON_NAME_UNKNOWN_RESTRICTIONS);
270         writeRestrictionsList(writer,
271                 container.getRestrictionsForDriveState(DRIVING_STATE_UNKNOWN));
272         writer.endObject();
273     }
274 
writeRestrictionsList(JsonWriter writer, List<RestrictionsPerSpeedRange> messages)275     private void writeRestrictionsList(JsonWriter writer, List<RestrictionsPerSpeedRange> messages)
276             throws IOException {
277         writer.beginArray();
278         for (RestrictionsPerSpeedRange restrictions : messages) {
279             writeRestrictions(writer, restrictions);
280         }
281         writer.endArray();
282     }
283 
writeRestrictions(JsonWriter writer, RestrictionsPerSpeedRange restrictions)284     private void writeRestrictions(JsonWriter writer, RestrictionsPerSpeedRange restrictions)
285             throws IOException {
286         writer.beginObject();
287         writer.name(JSON_NAME_REQ_OPT).value(restrictions.mReqOpt);
288         writer.name(JSON_NAME_RESTRICTIONS).value(restrictions.mRestrictions);
289         if (restrictions.mSpeedRange != null) {
290             writer.name(JSON_NAME_SPEED_RANGE);
291             writer.beginObject();
292             writer.name(JSON_NAME_MIN_SPEED).value(restrictions.mSpeedRange.mMinSpeed);
293             writer.name(JSON_NAME_MAX_SPEED).value(restrictions.mSpeedRange.mMaxSpeed);
294             writer.endObject();
295         }
296         writer.endObject();
297     }
298 
299     @Override
toString()300     public String toString() {
301         CharArrayWriter charWriter = new CharArrayWriter();
302         JsonWriter writer = new JsonWriter(charWriter);
303         writer.setIndent("\t");
304         try {
305             writeJson(writer);
306         } catch (IOException e) {
307             e.printStackTrace();
308         }
309         return charWriter.toString();
310     }
311 
312     /**
313      * Reads Json as UX restriction configuration with the specified schema version.
314      *
315      * <p>Supports reading files persisted in multiple JSON schemas, including the pre-R version 1
316      * format, and the R format version 2.
317      */
readJson(@onNull JsonReader reader, int schemaVersion)318     public static CarUxRestrictionsConfiguration readJson(@NonNull JsonReader reader,
319             int schemaVersion) throws IOException {
320         Objects.requireNonNull(reader, "reader must not be null");
321         // We need to be lenient to accept infinity number (as max speed).
322         reader.setLenient(true);
323 
324         RestrictionConfigurationParser parser = createConfigurationParser(schemaVersion);
325 
326         Builder builder = new Builder();
327         reader.beginObject();
328         while (reader.hasNext()) {
329             String name = reader.nextName();
330             switch (name) {
331                 case JSON_NAME_PHYSICAL_PORT:
332                     if (reader.peek() == JsonToken.NULL) {
333                         reader.nextNull();
334                     } else {
335                         builder.setPhysicalPort(Builder.validatePort(reader.nextInt()));
336                     }
337                     break;
338                 case JSON_NAME_MAX_CONTENT_DEPTH:
339                     builder.setMaxContentDepth(reader.nextInt());
340                     break;
341                 case JSON_NAME_MAX_CUMULATIVE_CONTENT_ITEMS:
342                     builder.setMaxCumulativeContentItems(reader.nextInt());
343                     break;
344                 case JSON_NAME_MAX_STRING_LENGTH:
345                     builder.setMaxStringLength(reader.nextInt());
346                     break;
347                 default:
348                     parser.readJson(reader, name, builder);
349             }
350         }
351         reader.endObject();
352         return builder.build();
353     }
354 
createConfigurationParser(int schemaVersion)355     private static RestrictionConfigurationParser createConfigurationParser(int schemaVersion) {
356         switch (schemaVersion) {
357             case 1:
358                 return new V1RestrictionConfigurationParser();
359             case 2:
360                 return new V2RestrictionConfigurationParser();
361             default:
362                 throw new IllegalArgumentException(
363                         "No parser supported for schemaVersion " + schemaVersion);
364         }
365     }
366 
367     private interface RestrictionConfigurationParser {
368         /**
369          * Handle reading any information within a particular name and add data to the builder.
370          */
readJson(JsonReader reader, String name, Builder builder)371         void readJson(JsonReader reader, String name, Builder builder) throws IOException;
372     }
373 
374     private static class V2RestrictionConfigurationParser implements
375             RestrictionConfigurationParser {
376         @Override
readJson(JsonReader reader, String name, Builder builder)377         public void readJson(JsonReader reader, String name, Builder builder) throws IOException {
378             readRestrictionsMode(reader, name, builder);
379         }
380     }
381 
382     private static class V1RestrictionConfigurationParser implements
383             RestrictionConfigurationParser {
384 
385         private static final String JSON_NAME_PASSENGER_MOVING_RESTRICTIONS =
386                 "passenger_moving_restrictions";
387         private static final String JSON_NAME_PASSENGER_IDLING_RESTRICTIONS =
388                 "passenger_idling_restrictions";
389         private static final String JSON_NAME_PASSENGER_PARKED_RESTRICTIONS =
390                 "passenger_parked_restrictions";
391         private static final String JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS =
392                 "passenger_unknown_restrictions";
393 
394         private static final String PASSENGER_MODE_NAME_FOR_MIGRATION = "passenger";
395 
396         @Override
readJson(JsonReader reader, String name, Builder builder)397         public void readJson(JsonReader reader, String name, Builder builder) throws IOException {
398             switch (name) {
399                 case JSON_NAME_PARKED_RESTRICTIONS:
400                     readRestrictionsList(reader, DRIVING_STATE_PARKED,
401                             UX_RESTRICTION_MODE_BASELINE, builder);
402                     break;
403                 case JSON_NAME_IDLING_RESTRICTIONS:
404                     readRestrictionsList(reader, DRIVING_STATE_IDLING,
405                             UX_RESTRICTION_MODE_BASELINE, builder);
406                     break;
407                 case JSON_NAME_MOVING_RESTRICTIONS:
408                     readRestrictionsList(reader, DRIVING_STATE_MOVING,
409                             UX_RESTRICTION_MODE_BASELINE, builder);
410                     break;
411                 case JSON_NAME_UNKNOWN_RESTRICTIONS:
412                     readRestrictionsList(reader, DRIVING_STATE_UNKNOWN,
413                             UX_RESTRICTION_MODE_BASELINE, builder);
414                     break;
415                 case JSON_NAME_PASSENGER_PARKED_RESTRICTIONS:
416                     readRestrictionsList(reader, DRIVING_STATE_PARKED,
417                             PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
418                     break;
419                 case JSON_NAME_PASSENGER_IDLING_RESTRICTIONS:
420                     readRestrictionsList(reader, DRIVING_STATE_IDLING,
421                             PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
422                     break;
423                 case JSON_NAME_PASSENGER_MOVING_RESTRICTIONS:
424                     readRestrictionsList(reader, DRIVING_STATE_MOVING,
425                             PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
426                     break;
427                 case JSON_NAME_PASSENGER_UNKNOWN_RESTRICTIONS:
428                     readRestrictionsList(reader, DRIVING_STATE_UNKNOWN,
429                             PASSENGER_MODE_NAME_FOR_MIGRATION, builder);
430                     break;
431                 default:
432                     Log.e(TAG, "Unknown name parsing json config: " + name);
433                     reader.skipValue();
434             }
435         }
436     }
437 
readRestrictionsMode(JsonReader reader, String mode, Builder builder)438     private static void readRestrictionsMode(JsonReader reader, String mode, Builder builder)
439             throws IOException {
440         reader.beginObject();
441         while (reader.hasNext()) {
442             String name = reader.nextName();
443             switch (name) {
444                 case JSON_NAME_PARKED_RESTRICTIONS:
445                     readRestrictionsList(reader, DRIVING_STATE_PARKED, mode, builder);
446                     break;
447                 case JSON_NAME_IDLING_RESTRICTIONS:
448                     readRestrictionsList(reader, DRIVING_STATE_IDLING, mode, builder);
449                     break;
450                 case JSON_NAME_MOVING_RESTRICTIONS:
451                     readRestrictionsList(reader, DRIVING_STATE_MOVING, mode, builder);
452                     break;
453                 case JSON_NAME_UNKNOWN_RESTRICTIONS:
454                     readRestrictionsList(reader, DRIVING_STATE_UNKNOWN, mode, builder);
455                     break;
456                 default:
457                     Log.e(TAG, "Unknown name parsing restriction mode json config: " + name);
458             }
459         }
460         reader.endObject();
461     }
462 
readRestrictionsList(JsonReader reader, @CarDrivingState int drivingState, String mode, Builder builder)463     private static void readRestrictionsList(JsonReader reader, @CarDrivingState int drivingState,
464             String mode, Builder builder) throws IOException {
465         reader.beginArray();
466         while (reader.hasNext()) {
467             DrivingStateRestrictions drivingStateRestrictions = readRestrictions(reader);
468             drivingStateRestrictions.setMode(mode);
469 
470             builder.setUxRestrictions(drivingState, drivingStateRestrictions);
471         }
472         reader.endArray();
473     }
474 
readRestrictions(JsonReader reader)475     private static DrivingStateRestrictions readRestrictions(JsonReader reader) throws IOException {
476         reader.beginObject();
477         boolean reqOpt = false;
478         int restrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
479         Builder.SpeedRange speedRange = null;
480         while (reader.hasNext()) {
481             String name = reader.nextName();
482             if (name.equals(JSON_NAME_REQ_OPT)) {
483                 reqOpt = reader.nextBoolean();
484             } else if (name.equals(JSON_NAME_RESTRICTIONS)) {
485                 restrictions = reader.nextInt();
486             } else if (name.equals(JSON_NAME_SPEED_RANGE)) {
487                 reader.beginObject();
488                 // Okay to set min initial value as MAX_SPEED because SpeedRange() won't allow it.
489                 float minSpeed = Builder.SpeedRange.MAX_SPEED;
490                 float maxSpeed = Builder.SpeedRange.MAX_SPEED;
491 
492                 while (reader.hasNext()) {
493                     String n = reader.nextName();
494                     if (n.equals(JSON_NAME_MIN_SPEED)) {
495                         minSpeed = Double.valueOf(reader.nextDouble()).floatValue();
496                     } else if (n.equals(JSON_NAME_MAX_SPEED)) {
497                         maxSpeed = Double.valueOf(reader.nextDouble()).floatValue();
498                     } else {
499                         Log.e(TAG, "Unknown name parsing json config: " + n);
500                         reader.skipValue();
501                     }
502                 }
503                 speedRange = new Builder.SpeedRange(minSpeed, maxSpeed);
504                 reader.endObject();
505             }
506         }
507         reader.endObject();
508         DrivingStateRestrictions drivingStateRestrictions = new DrivingStateRestrictions()
509                 .setDistractionOptimizationRequired(reqOpt)
510                 .setRestrictions(restrictions);
511         if (speedRange != null) {
512             drivingStateRestrictions.setSpeedRange(speedRange);
513         }
514         return drivingStateRestrictions;
515     }
516 
517     @Override
hashCode()518     public int hashCode() {
519         return Objects.hash(mPhysicalPort, mMaxStringLength, mMaxCumulativeContentItems,
520                 mMaxContentDepth, mRestrictionModes);
521     }
522 
523     @Override
equals(Object obj)524     public boolean equals(Object obj) {
525         if (this == obj) {
526             return true;
527         }
528         if (!(obj instanceof CarUxRestrictionsConfiguration)) {
529             return false;
530         }
531 
532         CarUxRestrictionsConfiguration other = (CarUxRestrictionsConfiguration) obj;
533 
534         return mPhysicalPort == other.mPhysicalPort
535                 && hasSameParameters(other)
536                 && mRestrictionModes.equals(other.mRestrictionModes);
537     }
538 
539     /**
540      * Compares {@code this} configuration object with {@code other} on restriction parameters.
541      */
hasSameParameters(@onNull CarUxRestrictionsConfiguration other)542     public boolean hasSameParameters(@NonNull CarUxRestrictionsConfiguration other) {
543         Objects.requireNonNull(other, "other must not be null");
544         return mMaxContentDepth == other.mMaxContentDepth
545                 && mMaxCumulativeContentItems == other.mMaxCumulativeContentItems
546                 && mMaxStringLength == other.mMaxStringLength;
547     }
548 
549     /**
550      * Dump the driving state to UX restrictions mapping.
551      */
dump(@onNull PrintWriter writer)552     public void dump(@NonNull PrintWriter writer) {
553         Objects.requireNonNull(writer, "writer must not be null");
554         writer.println("Physical display port: " + mPhysicalPort);
555 
556         for (Map.Entry<String, RestrictionModeContainer> entry : mRestrictionModes.entrySet()) {
557             writer.println("===========================================");
558             writer.println(entry.getKey() + " mode UXR:");
559             writer.println("-------------------------------------------");
560             dumpRestrictions(writer, entry.getValue().mDriveStateUxRestrictions);
561         }
562 
563         writer.println("Max String length: " + mMaxStringLength);
564         writer.println("Max Cumulative Content Items: " + mMaxCumulativeContentItems);
565         writer.println("Max Content depth: " + mMaxContentDepth);
566         writer.println("===========================================");
567     }
568 
dumpRestrictions( PrintWriter writer, Map<Integer, List<RestrictionsPerSpeedRange>> restrictions)569     private void dumpRestrictions(
570             PrintWriter writer, Map<Integer, List<RestrictionsPerSpeedRange>> restrictions) {
571         for (Integer state : restrictions.keySet()) {
572             List<RestrictionsPerSpeedRange> list = restrictions.get(state);
573             writer.println("State:" + getDrivingStateName(state)
574                     + " num restrictions:" + list.size());
575             for (RestrictionsPerSpeedRange r : list) {
576                 writer.println("Requires DO? " + r.mReqOpt
577                         + "\nRestrictions: 0x" + Integer.toHexString(r.mRestrictions)
578                         + "\nSpeed Range: "
579                         + (r.mSpeedRange == null
580                         ? "None"
581                         : (r.mSpeedRange.mMinSpeed + " - " + r.mSpeedRange.mMaxSpeed)));
582                 writer.println("-------------------------------------------");
583             }
584         }
585     }
586 
getDrivingStateName(@arDrivingState int state)587     private static String getDrivingStateName(@CarDrivingState int state) {
588         switch (state) {
589             case DRIVING_STATE_PARKED:
590                 return "parked";
591             case DRIVING_STATE_IDLING:
592                 return "idling";
593             case DRIVING_STATE_MOVING:
594                 return "moving";
595             case DRIVING_STATE_UNKNOWN:
596                 return "unknown";
597             default:
598                 throw new IllegalArgumentException("Unrecognized state value: " + state);
599         }
600     }
601 
602     // Parcelable methods/fields.
603 
604     // Used by Parcel methods to ensure de/serialization order.
605     private static final int[] DRIVING_STATES = new int[]{
606             DRIVING_STATE_UNKNOWN,
607             DRIVING_STATE_PARKED,
608             DRIVING_STATE_IDLING,
609             DRIVING_STATE_MOVING,
610     };
611 
612     public static final Parcelable.Creator<CarUxRestrictionsConfiguration> CREATOR =
613             new Parcelable.Creator<CarUxRestrictionsConfiguration>() {
614 
615                 @Override
616                 public CarUxRestrictionsConfiguration createFromParcel(Parcel source) {
617                     return new CarUxRestrictionsConfiguration(source);
618                 }
619 
620                 @Override
621                 public CarUxRestrictionsConfiguration[] newArray(int size) {
622                     return new CarUxRestrictionsConfiguration[size];
623                 }
624             };
625 
626     @Override
describeContents()627     public int describeContents() {
628         return 0;
629     }
630 
CarUxRestrictionsConfiguration(Parcel in)631     private CarUxRestrictionsConfiguration(Parcel in) {
632         int modesCount = in.readInt();
633         for (int i = 0; i < modesCount; i++) {
634             String modeName = in.readString();
635             RestrictionModeContainer container = new RestrictionModeContainer();
636             for (int drivingState : DRIVING_STATES) {
637                 List<RestrictionsPerSpeedRange> restrictions = new ArrayList<>();
638                 in.readTypedList(restrictions, RestrictionsPerSpeedRange.CREATOR);
639                 container.setRestrictionsForDriveState(drivingState, restrictions);
640             }
641             mRestrictionModes.put(modeName, container);
642         }
643 
644         boolean nullPhysicalPort = in.readBoolean();
645         byte physicalPort = in.readByte();
646         mPhysicalPort = nullPhysicalPort ? null : physicalPort;
647 
648         mMaxContentDepth = in.readInt();
649         mMaxCumulativeContentItems = in.readInt();
650         mMaxStringLength = in.readInt();
651     }
652 
653     @Override
writeToParcel(Parcel dest, int flags)654     public void writeToParcel(Parcel dest, int flags) {
655         dest.writeInt(mRestrictionModes.size());
656         for (Map.Entry<String, RestrictionModeContainer> entry : mRestrictionModes.entrySet()) {
657             dest.writeString(entry.getKey());
658             for (int drivingState : DRIVING_STATES) {
659                 dest.writeTypedList(entry.getValue().getRestrictionsForDriveState(drivingState));
660             }
661         }
662         boolean nullPhysicalPort = mPhysicalPort == null;
663         dest.writeBoolean(nullPhysicalPort);
664         // When physical port is null, 0 should be skipped.
665         dest.writeByte(nullPhysicalPort ? ((byte) 0) : mPhysicalPort.byteValue());
666 
667         dest.writeInt(mMaxContentDepth);
668         dest.writeInt(mMaxCumulativeContentItems);
669         dest.writeInt(mMaxStringLength);
670     }
671 
672     /**
673      * @hide
674      */
675     public static final class Builder {
676 
677         /**
678          * Validates integer value for port is within the value range of a byte.
679          *
680          * Throws exception if input value is outside the range.
681          *
682          * @return {@code port} as a byte.
683          */
validatePort(int port)684         public static byte validatePort(int port) {
685             if (Byte.MIN_VALUE <= port && port <= Byte.MAX_VALUE) {
686                 return (byte) port;
687             }
688             throw new IllegalArgumentException(
689                     "Port value should be within the range of a byte. Input is " + port);
690         }
691 
692         private static final int UX_RESTRICTIONS_UNKNOWN = -1;
693 
694         /**
695          * {@code null} means port is not set.
696          */
697         private Byte mPhysicalPort;
698 
699         private int mMaxContentDepth = UX_RESTRICTIONS_UNKNOWN;
700         private int mMaxCumulativeContentItems = UX_RESTRICTIONS_UNKNOWN;
701         private int mMaxStringLength = UX_RESTRICTIONS_UNKNOWN;
702 
703         public final Map<String, RestrictionModeContainer> mRestrictionModes = new ArrayMap<>();
704 
Builder()705         public Builder() {
706             mRestrictionModes.put(UX_RESTRICTION_MODE_BASELINE, new RestrictionModeContainer());
707         }
708 
709         /**
710          * Sets the display this configuration will apply to.
711          *
712          * <p>The display is identified by the physical {@code port}.
713          *
714          * @param port Port that is connected to a display.
715          *             See {@link android.view.DisplayAddress.Physical#getPort()}.
716          */
setPhysicalPort(byte port)717         public Builder setPhysicalPort(byte port) {
718             mPhysicalPort = port;
719             return this;
720         }
721 
722         /**
723          * Sets ux restrictions for driving state.
724          */
setUxRestrictions(@arDrivingState int drivingState, boolean requiresOptimization, @CarUxRestrictions.CarUxRestrictionsInfo int restrictions)725         public Builder setUxRestrictions(@CarDrivingState int drivingState,
726                 boolean requiresOptimization,
727                 @CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
728             return this.setUxRestrictions(drivingState, new DrivingStateRestrictions()
729                     .setDistractionOptimizationRequired(requiresOptimization)
730                     .setRestrictions(restrictions));
731         }
732 
733         /**
734          * Sets UX restrictions with speed range.
735          *
736          * @param drivingState         Restrictions will be set for this Driving state.
737          *                             See constants in {@link CarDrivingStateEvent}.
738          * @param speedRange           If set, restrictions will only apply when current speed is
739          *                             within the range. Only
740          *                             {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}
741          *                             supports speed range. {@code null} implies the full speed
742          *                             range, i.e. zero to {@link SpeedRange#MAX_SPEED}.
743          * @param requiresOptimization Whether distraction optimization (DO) is required for this
744          *                             driving state.
745          * @param restrictions         See constants in {@link CarUxRestrictions}.
746          * @deprecated Use {@link #setUxRestrictions(int, DrivingStateRestrictions)} instead.
747          */
748         @Deprecated
setUxRestrictions(@arDrivingState int drivingState, @NonNull SpeedRange speedRange, boolean requiresOptimization, @CarUxRestrictions.CarUxRestrictionsInfo int restrictions)749         public Builder setUxRestrictions(@CarDrivingState int drivingState,
750                 @NonNull SpeedRange speedRange, boolean requiresOptimization,
751                 @CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
752             return setUxRestrictions(drivingState, new DrivingStateRestrictions()
753                     .setDistractionOptimizationRequired(requiresOptimization)
754                     .setRestrictions(restrictions)
755                     .setSpeedRange(speedRange));
756         }
757 
758         /**
759          * Sets UX restriction.
760          *
761          * @param drivingState             Restrictions will be set for this Driving state.
762          *                                 See constants in {@link CarDrivingStateEvent}.
763          * @param drivingStateRestrictions Restrictions to set.
764          * @return This builder object for method chaining.
765          */
setUxRestrictions( int drivingState, DrivingStateRestrictions drivingStateRestrictions)766         public Builder setUxRestrictions(
767                 int drivingState, DrivingStateRestrictions drivingStateRestrictions) {
768             SpeedRange speedRange = drivingStateRestrictions.mSpeedRange;
769 
770             if (drivingState != DRIVING_STATE_MOVING && speedRange != null) {
771                 throw new IllegalArgumentException(
772                         "Non-moving driving state should not specify speed range.");
773             }
774 
775             RestrictionModeContainer container = mRestrictionModes.computeIfAbsent(
776                     drivingStateRestrictions.mMode, mode -> new RestrictionModeContainer());
777 
778             container.getRestrictionsForDriveState(drivingState).add(
779                     new RestrictionsPerSpeedRange(
780                             drivingStateRestrictions.mMode, drivingStateRestrictions.mReqOpt,
781                             drivingStateRestrictions.mRestrictions, speedRange));
782             return this;
783         }
784 
785 
786         /**
787          * Sets max string length.
788          */
setMaxStringLength(int maxStringLength)789         public Builder setMaxStringLength(int maxStringLength) {
790             mMaxStringLength = maxStringLength;
791             return this;
792         }
793 
794         /**
795          * Sets max cumulative content items.
796          */
setMaxCumulativeContentItems(int maxCumulativeContentItems)797         public Builder setMaxCumulativeContentItems(int maxCumulativeContentItems) {
798             mMaxCumulativeContentItems = maxCumulativeContentItems;
799             return this;
800         }
801 
802         /**
803          * Sets max content depth.
804          */
setMaxContentDepth(int maxContentDepth)805         public Builder setMaxContentDepth(int maxContentDepth) {
806             mMaxContentDepth = maxContentDepth;
807             return this;
808         }
809 
810         /**
811          * @return CarUxRestrictionsConfiguration based on builder configuration.
812          */
build()813         public CarUxRestrictionsConfiguration build() {
814             // Unspecified driving state should be fully restricted to be safe.
815             addDefaultRestrictionsToBaseline();
816 
817             validateBaselineModeRestrictions();
818             for (String mode : mRestrictionModes.keySet()) {
819                 if (UX_RESTRICTION_MODE_BASELINE.equals(mode)) {
820                     continue;
821                 }
822                 validateModeRestrictions(mode);
823             }
824 
825             return new CarUxRestrictionsConfiguration(this);
826         }
827 
addDefaultRestrictionsToBaseline()828         private void addDefaultRestrictionsToBaseline() {
829             RestrictionModeContainer container = mRestrictionModes.get(
830                     UX_RESTRICTION_MODE_BASELINE);
831             for (int drivingState : DRIVING_STATES) {
832                 List<RestrictionsPerSpeedRange> restrictions =
833                         container.getRestrictionsForDriveState(drivingState);
834                 if (restrictions.size() == 0) {
835                     Log.i(TAG, "Using default restrictions for driving state: "
836                             + getDrivingStateName(drivingState));
837                     restrictions.add(new RestrictionsPerSpeedRange(
838                             true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED));
839                 }
840             }
841         }
842 
validateBaselineModeRestrictions()843         private void validateBaselineModeRestrictions() {
844             RestrictionModeContainer container = mRestrictionModes.get(
845                     UX_RESTRICTION_MODE_BASELINE);
846             for (int drivingState : DRIVING_STATES) {
847                 List<RestrictionsPerSpeedRange> restrictions =
848                         container.getRestrictionsForDriveState(drivingState);
849                 if (drivingState != DRIVING_STATE_MOVING) {
850                     // Note: For non-moving state, setUxRestrictions() rejects UxRestriction with
851                     // speed range, so we don't check here.
852                     if (restrictions.size() != 1) {
853                         throw new IllegalStateException("Non-moving driving state should "
854                                 + "contain one set of restriction rules.");
855                     }
856                 }
857 
858                 // If there are multiple restrictions, each one should specify speed range.
859                 if (restrictions.size() > 1 && restrictions.stream().anyMatch(
860                         restriction -> restriction.mSpeedRange == null)) {
861                     StringBuilder error = new StringBuilder();
862                     for (RestrictionsPerSpeedRange restriction : restrictions) {
863                         error.append(restriction.toString()).append('\n');
864                     }
865                     throw new IllegalStateException(
866                             "Every restriction in MOVING state should contain driving state.\n"
867                                     + error.toString());
868                 }
869 
870                 // Sort restrictions based on speed range.
871                 Collections.sort(restrictions,
872                         Comparator.comparing(RestrictionsPerSpeedRange::getSpeedRange));
873 
874                 validateRangeOfSpeed(restrictions);
875                 validateContinuousSpeedRange(restrictions);
876             }
877         }
878 
validateModeRestrictions(String mode)879         private void validateModeRestrictions(String mode) {
880             if (!mRestrictionModes.containsKey(mode)) {
881                 return;
882             }
883             RestrictionModeContainer container = mRestrictionModes.get(mode);
884             List<RestrictionsPerSpeedRange> movingRestrictions =
885                     container.getRestrictionsForDriveState(DRIVING_STATE_MOVING);
886             Collections.sort(movingRestrictions,
887                     Comparator.comparing(RestrictionsPerSpeedRange::getSpeedRange));
888 
889             validateContinuousSpeedRange(movingRestrictions);
890         }
891 
892         /**
893          * Validates if combined speed ranges of given restrictions.
894          *
895          * <p>Restrictions are considered to contain valid speed ranges if:
896          * <ul>
897          * <li>None contains a speed range - implies full range; or
898          * <li>Combination covers range [0 - MAX_SPEED]
899          * </ul>
900          *
901          * Throws exception on invalidate input.
902          *
903          * @param restrictions Restrictions to be checked. Must be sorted.
904          */
validateRangeOfSpeed(List<RestrictionsPerSpeedRange> restrictions)905         private void validateRangeOfSpeed(List<RestrictionsPerSpeedRange> restrictions) {
906             if (restrictions.size() == 1) {
907                 SpeedRange speedRange = restrictions.get(0).mSpeedRange;
908                 if (speedRange == null) {
909                     // Single restriction with null speed range implies that
910                     // it applies to the entire driving state.
911                     return;
912                 }
913             }
914             if (Float.compare(restrictions.get(0).mSpeedRange.mMinSpeed, 0) != 0) {
915                 throw new IllegalStateException(
916                         "Speed range min speed should start at 0.");
917             }
918             float lastMaxSpeed = restrictions.get(restrictions.size() - 1).mSpeedRange.mMaxSpeed;
919             if (Float.compare(lastMaxSpeed, SpeedRange.MAX_SPEED) != 0) {
920                 throw new IllegalStateException(
921                         "Max speed of last restriction should be MAX_SPEED.");
922             }
923         }
924 
925         /**
926          * Validates if combined speed ranges of given restrictions are continuous, meaning they:
927          * <ul>
928          * <li>Do not overlap; and
929          * <li>Do not contain gap
930          * </ul>
931          *
932          * <p>Namely the max speed of current range equals the min speed of next range.
933          *
934          * Throws exception on invalidate input.
935          *
936          * @param restrictions Restrictions to be checked. Must be sorted.
937          */
validateContinuousSpeedRange(List<RestrictionsPerSpeedRange> restrictions)938         private void validateContinuousSpeedRange(List<RestrictionsPerSpeedRange> restrictions) {
939             for (int i = 1; i < restrictions.size(); i++) {
940                 RestrictionsPerSpeedRange prev = restrictions.get(i - 1);
941                 RestrictionsPerSpeedRange curr = restrictions.get(i);
942                 // If current min != prev.max, there's either an overlap or a gap in speed range.
943                 if (Float.compare(curr.mSpeedRange.mMinSpeed, prev.mSpeedRange.mMaxSpeed) != 0) {
944                     throw new IllegalArgumentException(
945                             "Mis-configured speed range. Possibly speed range overlap or gap.");
946                 }
947             }
948         }
949 
950         /**
951          * Speed range is defined by min and max speed. When there is no upper bound for max speed,
952          * set it to {@link SpeedRange#MAX_SPEED}.
953          */
954         public static final class SpeedRange implements Comparable<SpeedRange> {
955             public static final float MAX_SPEED = Float.POSITIVE_INFINITY;
956 
957             private float mMinSpeed;
958             private float mMaxSpeed;
959 
960             /**
961              * Defaults max speed to {@link SpeedRange#MAX_SPEED}.
962              */
SpeedRange(@loatRangefrom = 0.0) float minSpeed)963             public SpeedRange(@FloatRange(from = 0.0) float minSpeed) {
964                 this(minSpeed, MAX_SPEED);
965             }
966 
SpeedRange(@loatRangefrom = 0.0) float minSpeed, @FloatRange(from = 0.0) float maxSpeed)967             public SpeedRange(@FloatRange(from = 0.0) float minSpeed,
968                     @FloatRange(from = 0.0) float maxSpeed) {
969                 if (Float.compare(minSpeed, 0) < 0 || Float.compare(maxSpeed, 0) < 0) {
970                     throw new IllegalArgumentException("Speed cannot be negative.");
971                 }
972                 if (minSpeed == MAX_SPEED) {
973                     throw new IllegalArgumentException("Min speed cannot be MAX_SPEED.");
974                 }
975                 if (minSpeed > maxSpeed) {
976                     throw new IllegalArgumentException("Min speed " + minSpeed
977                             + " should not be greater than max speed " + maxSpeed);
978                 }
979                 mMinSpeed = minSpeed;
980                 mMaxSpeed = maxSpeed;
981             }
982 
983             /**
984              * Return if the given speed is in the range of [minSpeed, maxSpeed).
985              *
986              * @param speed Speed to check
987              * @return {@code true} if in range; {@code false} otherwise.
988              */
includes(float speed)989             public boolean includes(float speed) {
990                 return mMinSpeed <= speed && speed < mMaxSpeed;
991             }
992 
993             @Override
compareTo(SpeedRange other)994             public int compareTo(SpeedRange other) {
995                 // First compare min speed; then max speed.
996                 int minSpeedComparison = Float.compare(mMinSpeed, other.mMinSpeed);
997                 if (minSpeedComparison != 0) {
998                     return minSpeedComparison;
999                 }
1000 
1001                 return Float.compare(mMaxSpeed, other.mMaxSpeed);
1002             }
1003 
1004             @Override
hashCode()1005             public int hashCode() {
1006                 return Objects.hash(mMinSpeed, mMaxSpeed);
1007             }
1008 
1009             @Override
equals(Object obj)1010             public boolean equals(Object obj) {
1011                 if (this == obj) {
1012                     return true;
1013                 }
1014                 if (!(obj instanceof SpeedRange)) {
1015                     return false;
1016                 }
1017                 SpeedRange other = (SpeedRange) obj;
1018 
1019                 return compareTo(other) == 0;
1020             }
1021 
1022             @Override
toString()1023             public String toString() {
1024                 return new StringBuilder()
1025                         .append("[min: ").append(mMinSpeed)
1026                         .append("; max: ").append(mMaxSpeed == MAX_SPEED ? "max_speed" : mMaxSpeed)
1027                         .append("]")
1028                         .toString();
1029             }
1030         }
1031     }
1032 
1033     /**
1034      * UX restrictions to be applied to a driving state through {@link
1035      * Builder#setUxRestrictions(int, CarUxRestrictionsConfiguration.DrivingStateRestrictions)}.
1036      * These UX restrictions can also specified to be only applicable to certain speed range and
1037      * restriction mode.
1038      *
1039      * @hide
1040      * @see Builder.SpeedRange
1041      */
1042     public static final class DrivingStateRestrictions {
1043         private String mMode = UX_RESTRICTION_MODE_BASELINE;
1044         private boolean mReqOpt = true;
1045         private int mRestrictions = CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED;
1046         @Nullable
1047         private Builder.SpeedRange mSpeedRange;
1048 
1049         /**
1050          * Sets whether Distraction Optimization (DO) is required. Defaults to {@code true}.
1051          */
setDistractionOptimizationRequired( boolean distractionOptimizationRequired)1052         public DrivingStateRestrictions setDistractionOptimizationRequired(
1053                 boolean distractionOptimizationRequired) {
1054             mReqOpt = distractionOptimizationRequired;
1055             return this;
1056         }
1057 
1058         /**
1059          * Sets active restrictions.
1060          * Defaults to {@link CarUxRestrictions#UX_RESTRICTIONS_FULLY_RESTRICTED}.
1061          */
setRestrictions( @arUxRestrictions.CarUxRestrictionsInfo int restrictions)1062         public DrivingStateRestrictions setRestrictions(
1063                 @CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
1064             mRestrictions = restrictions;
1065             return this;
1066         }
1067 
1068         /**
1069          * Sets restriction mode to apply to.
1070          * Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
1071          */
setMode(@onNull String mode)1072         public DrivingStateRestrictions setMode(@NonNull String mode) {
1073             mMode = Objects.requireNonNull(mode, "mode must not be null");
1074             return this;
1075         }
1076 
1077         /**
1078          * Sets speed range to apply to. Optional value. Not setting one means the restrictions
1079          * apply to full speed range, namely {@code 0} to {@link Builder.SpeedRange#MAX_SPEED}.
1080          */
setSpeedRange(@onNull Builder.SpeedRange speedRange)1081         public DrivingStateRestrictions setSpeedRange(@NonNull Builder.SpeedRange speedRange) {
1082             mSpeedRange = speedRange;
1083             return this;
1084         }
1085 
1086         @Override
toString()1087         public String toString() {
1088             return new StringBuilder()
1089                     .append("Mode: ").append(mMode)
1090                     .append(". Requires DO? ").append(mReqOpt)
1091                     .append(". Restrictions: ").append(Integer.toBinaryString(mRestrictions))
1092                     .append(". SpeedRange: ")
1093                     .append(mSpeedRange == null ? "null" : mSpeedRange.toString())
1094                     .toString();
1095         }
1096     }
1097 
1098     /**
1099      * Container for UX restrictions for a speed range.
1100      * Speed range is valid only for the {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}.
1101      */
1102     private static final class RestrictionsPerSpeedRange implements Parcelable {
1103         final String mMode;
1104         final boolean mReqOpt;
1105         final int mRestrictions;
1106         @Nullable
1107         final Builder.SpeedRange mSpeedRange;
1108 
RestrictionsPerSpeedRange(boolean reqOpt, int restrictions)1109         RestrictionsPerSpeedRange(boolean reqOpt, int restrictions) {
1110             this(UX_RESTRICTION_MODE_BASELINE, reqOpt, restrictions, null);
1111         }
1112 
RestrictionsPerSpeedRange(@onNull String mode, boolean reqOpt, int restrictions, @Nullable Builder.SpeedRange speedRange)1113         RestrictionsPerSpeedRange(@NonNull String mode, boolean reqOpt, int restrictions,
1114                 @Nullable Builder.SpeedRange speedRange) {
1115             if (!reqOpt && restrictions != CarUxRestrictions.UX_RESTRICTIONS_BASELINE) {
1116                 throw new IllegalArgumentException(
1117                         "Driving optimization is not required but UX restrictions is required.");
1118             }
1119             mMode = Objects.requireNonNull(mode, "mode must not be null");
1120             mReqOpt = reqOpt;
1121             mRestrictions = restrictions;
1122             mSpeedRange = speedRange;
1123         }
1124 
getSpeedRange()1125         public Builder.SpeedRange getSpeedRange() {
1126             return mSpeedRange;
1127         }
1128 
1129         @Override
hashCode()1130         public int hashCode() {
1131             return Objects.hash(mMode, mReqOpt, mRestrictions, mSpeedRange);
1132         }
1133 
1134         @Override
equals(Object obj)1135         public boolean equals(Object obj) {
1136             if (this == obj) {
1137                 return true;
1138             }
1139             if (obj == null || !(obj instanceof RestrictionsPerSpeedRange)) {
1140                 return false;
1141             }
1142             RestrictionsPerSpeedRange other = (RestrictionsPerSpeedRange) obj;
1143             return Objects.equals(mMode, other.mMode)
1144                     && mReqOpt == other.mReqOpt
1145                     && mRestrictions == other.mRestrictions
1146                     && Objects.equals(mSpeedRange, other.mSpeedRange);
1147         }
1148 
1149         @Override
toString()1150         public String toString() {
1151             return new StringBuilder()
1152                     .append("[Mode is ").append(mMode)
1153                     .append("; Requires DO? ").append(mReqOpt)
1154                     .append("; Restrictions: ").append(Integer.toBinaryString(mRestrictions))
1155                     .append("; Speed range: ")
1156                     .append(mSpeedRange == null ? "null" : mSpeedRange.toString())
1157                     .append(']')
1158                     .toString();
1159         }
1160 
1161         // Parcelable methods/fields.
1162 
1163         public static final Creator<RestrictionsPerSpeedRange> CREATOR =
1164                 new Creator<RestrictionsPerSpeedRange>() {
1165                     @Override
1166                     public RestrictionsPerSpeedRange createFromParcel(Parcel in) {
1167                         return new RestrictionsPerSpeedRange(in);
1168                     }
1169 
1170                     @Override
1171                     public RestrictionsPerSpeedRange[] newArray(int size) {
1172                         return new RestrictionsPerSpeedRange[size];
1173                     }
1174                 };
1175 
1176         @Override
describeContents()1177         public int describeContents() {
1178             return 0;
1179         }
1180 
RestrictionsPerSpeedRange(Parcel in)1181         protected RestrictionsPerSpeedRange(Parcel in) {
1182             mMode = in.readString();
1183             mReqOpt = in.readBoolean();
1184             mRestrictions = in.readInt();
1185             // Whether speed range is specified.
1186             Builder.SpeedRange speedRange = null;
1187             if (in.readBoolean()) {
1188                 float minSpeed = in.readFloat();
1189                 float maxSpeed = in.readFloat();
1190                 speedRange = new Builder.SpeedRange(minSpeed, maxSpeed);
1191             }
1192             mSpeedRange = speedRange;
1193         }
1194 
1195         @Override
writeToParcel(Parcel dest, int flags)1196         public void writeToParcel(Parcel dest, int flags) {
1197             dest.writeString(mMode);
1198             dest.writeBoolean(mReqOpt);
1199             dest.writeInt(mRestrictions);
1200             // Whether speed range is specified.
1201             dest.writeBoolean(mSpeedRange != null);
1202             if (mSpeedRange != null) {
1203                 dest.writeFloat(mSpeedRange.mMinSpeed);
1204                 dest.writeFloat(mSpeedRange.mMaxSpeed);
1205             }
1206         }
1207     }
1208 
1209     /**
1210      * All the restriction configurations for a particular mode.
1211      */
1212     private static final class RestrictionModeContainer {
1213         /**
1214          * Mapping from drive state to the list of applicable restrictions.
1215          */
1216         private final Map<Integer, List<RestrictionsPerSpeedRange>> mDriveStateUxRestrictions =
1217                 new ArrayMap<>(DRIVING_STATES.length);
1218 
RestrictionModeContainer()1219         RestrictionModeContainer() {
1220             for (int drivingState : DRIVING_STATES) {
1221                 mDriveStateUxRestrictions.put(drivingState, new ArrayList<>());
1222             }
1223         }
1224 
1225         /**
1226          * Returns the restrictions for a particular drive state.
1227          */
1228         @NonNull
getRestrictionsForDriveState( @arDrivingState int driveState)1229         List<RestrictionsPerSpeedRange> getRestrictionsForDriveState(
1230                 @CarDrivingState int driveState) {
1231             // Guaranteed not to be null since a container is initialized with empty lists for
1232             // each drive state in the constructor.
1233             return mDriveStateUxRestrictions.get(driveState);
1234         }
1235 
setRestrictionsForDriveState(@arDrivingState int driveState, @NonNull List<RestrictionsPerSpeedRange> restrictions)1236         void setRestrictionsForDriveState(@CarDrivingState int driveState,
1237                 @NonNull List<RestrictionsPerSpeedRange> restrictions) {
1238             Objects.requireNonNull(restrictions, "null restrictions are not allows");
1239             mDriveStateUxRestrictions.put(driveState, restrictions);
1240         }
1241 
1242         @Override
equals(Object obj)1243         public boolean equals(Object obj) {
1244             if (this == obj) {
1245                 return true;
1246             }
1247             if (!(obj instanceof RestrictionModeContainer)) {
1248                 return false;
1249             }
1250             RestrictionModeContainer container = (RestrictionModeContainer) obj;
1251             return Objects.equals(mDriveStateUxRestrictions, container.mDriveStateUxRestrictions);
1252         }
1253 
1254         @Override
hashCode()1255         public int hashCode() {
1256             return Objects.hash(mDriveStateUxRestrictions);
1257         }
1258     }
1259 }
1260