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