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