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