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