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