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