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