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