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