blob: 06d19dfcdb974fe4632458205b0ee0cb913b321c [file] [log] [blame]
Yao, Yuxing24f07fc2018-11-26 11:09:45 -08001/*
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 */
16package android.car.drivingstate;
17
18import android.annotation.FloatRange;
19import android.annotation.Nullable;
20import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
21import android.os.Build;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.os.SystemClock;
Michal Palczewski9b6ee5d2018-11-28 11:26:17 -080025import android.util.ArrayMap;
Yao, Yuxing24f07fc2018-11-26 11:09:45 -080026import android.util.JsonReader;
27import android.util.JsonWriter;
28import android.util.Log;
29
30import java.io.IOException;
31import java.io.PrintWriter;
32import java.util.ArrayList;
33import java.util.Collections;
Yao, Yuxing24f07fc2018-11-26 11:09:45 -080034import java.util.List;
35import java.util.Map;
36
37/**
38 * Configuration for Car UX Restrictions service.
39 *
40 * @hide
41 */
42public final class CarUxRestrictionsConfiguration implements Parcelable {
43 private static final String TAG = "CarUxRConfig";
44
45 // Constants used by json de/serialization.
46 private static final String JSON_NAME_MAX_CONTENT_DEPTH = "max_content_depth";
47 private static final String JSON_NAME_MAX_CUMULATIVE_CONTENT_ITEMS =
48 "max_cumulative_content_items";
49 private static final String JSON_NAME_MAX_STRING_LENGTH = "max_string_length";
50 private static final String JSON_NAME_MOVING_RESTRICTIONS = "moving_restrictions";
51 private static final String JSON_NAME_IDLING_RESTRICTIONS = "idling_restrictions";
52 private static final String JSON_NAME_PARKED_RESTRICTIONS = "parked_restrictions";
53 private static final String JSON_NAME_UNKNOWN_RESTRICTIONS = "unknown_restrictions";
54 private static final String JSON_NAME_REQ_OPT = "req_opt";
55 private static final String JSON_NAME_RESTRICTIONS = "restrictions";
56 private static final String JSON_NAME_SPEED_RANGE = "speed_range";
57 private static final String JSON_NAME_MIN_SPEED = "min_speed";
58 private static final String JSON_NAME_MAX_SPEED = "max_speed";
59
60 private final int mMaxContentDepth;
61 private final int mMaxCumulativeContentItems;
62 private final int mMaxStringLength;
Michal Palczewski9b6ee5d2018-11-28 11:26:17 -080063 private final Map<Integer, List<RestrictionsPerSpeedRange>> mUxRestrictions =
64 new ArrayMap<>(DRIVING_STATES.length);
Yao, Yuxing24f07fc2018-11-26 11:09:45 -080065
66 private CarUxRestrictionsConfiguration(CarUxRestrictionsConfiguration.Builder builder) {
67 mMaxContentDepth = builder.mMaxContentDepth;
68 mMaxCumulativeContentItems = builder.mMaxCumulativeContentItems;
69 mMaxStringLength = builder.mMaxStringLength;
70
71 for (int drivingState : DRIVING_STATES) {
72 List<RestrictionsPerSpeedRange> list = new ArrayList<>();
73 for (RestrictionsPerSpeedRange r : builder.mUxRestrictions.get(drivingState)) {
74 list.add(r);
75 }
76 mUxRestrictions.put(drivingState, list);
77 }
78 }
79
80 /**
81 * Returns the restrictions based on current driving state and speed.
82 */
83 public CarUxRestrictions getUxRestrictions(@CarDrivingState int drivingState,
84 float currentSpeed) {
85 List<RestrictionsPerSpeedRange> restrictions = mUxRestrictions.get(drivingState);
86 if (restrictions.isEmpty()) {
87 if (Build.IS_ENG || Build.IS_USERDEBUG) {
88 throw new IllegalStateException("No restrictions for driving state "
89 + getDrivingStateName(drivingState));
90 }
91 return createDefaultUxRestrictionsEvent();
92 }
93
94 RestrictionsPerSpeedRange restriction = null;
95 if (restrictions.size() == 1) {
96 restriction = restrictions.get(0);
97 } else {
98 for (RestrictionsPerSpeedRange r : restrictions) {
99 if (r.mSpeedRange != null && r.mSpeedRange.includes(currentSpeed)) {
100 restriction = r;
101 break;
102 }
103 }
104 }
105
106 if (restriction == null) {
107 if (Build.IS_ENG || Build.IS_USERDEBUG) {
108 throw new IllegalStateException(
109 "No restrictions found for driving state " + drivingState
110 + " at speed " + currentSpeed);
111 }
112 return createDefaultUxRestrictionsEvent();
113 }
114 return createUxRestrictionsEvent(restriction.mReqOpt, restriction.mRestrictions);
115 }
116
117 private CarUxRestrictions createDefaultUxRestrictionsEvent() {
118 return createUxRestrictionsEvent(true,
119 CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED);
120 }
121
122 /**
123 * Creates CarUxRestrictions with restrictions parameters from current configuration.
124 */
125 private CarUxRestrictions createUxRestrictionsEvent(boolean requiresOpt,
126 @CarUxRestrictions.CarUxRestrictionsInfo int uxr) {
127 // In case the UXR is not baseline, set requiresDistractionOptimization to true since it
128 // doesn't make sense to have an active non baseline restrictions without
129 // requiresDistractionOptimization set to true.
130 if (uxr != CarUxRestrictions.UX_RESTRICTIONS_BASELINE) {
131 requiresOpt = true;
132 }
133 CarUxRestrictions.Builder builder = new CarUxRestrictions.Builder(requiresOpt, uxr,
134 SystemClock.elapsedRealtimeNanos());
135 if (mMaxStringLength != Builder.UX_RESTRICTIONS_UNKNOWN) {
136 builder.setMaxStringLength(mMaxStringLength);
137 }
138 if (mMaxCumulativeContentItems != Builder.UX_RESTRICTIONS_UNKNOWN) {
139 builder.setMaxCumulativeContentItems(mMaxCumulativeContentItems);
140 }
141 if (mMaxContentDepth != Builder.UX_RESTRICTIONS_UNKNOWN) {
142 builder.setMaxContentDepth(mMaxContentDepth);
143 }
144 return builder.build();
145 }
146
147 // Json de/serialization methods.
148
149 /**
150 * Writes current configuration as Json.
151 */
152 public void writeJson(JsonWriter writer) throws IOException {
153 // We need to be lenient to accept infinity number (as max speed).
154 writer.setLenient(true);
155
156 writer.beginObject();
157
158 writer.name(JSON_NAME_MAX_CONTENT_DEPTH).value(mMaxContentDepth);
159 writer.name(JSON_NAME_MAX_CUMULATIVE_CONTENT_ITEMS).value(
160 mMaxCumulativeContentItems);
161 writer.name(JSON_NAME_MAX_STRING_LENGTH).value(mMaxStringLength);
162
163 writer.name(JSON_NAME_PARKED_RESTRICTIONS);
164 writeRestrictionsList(writer,
165 mUxRestrictions.get(CarDrivingStateEvent.DRIVING_STATE_PARKED));
166
167 writer.name(JSON_NAME_IDLING_RESTRICTIONS);
168 writeRestrictionsList(writer,
169 mUxRestrictions.get(CarDrivingStateEvent.DRIVING_STATE_IDLING));
170
171 writer.name(JSON_NAME_MOVING_RESTRICTIONS);
172 writeRestrictionsList(writer,
173 mUxRestrictions.get(CarDrivingStateEvent.DRIVING_STATE_MOVING));
174
175 writer.name(JSON_NAME_UNKNOWN_RESTRICTIONS);
176 writeRestrictionsList(writer,
177 mUxRestrictions.get(CarDrivingStateEvent.DRIVING_STATE_UNKNOWN));
178
179 writer.endObject();
180 }
181
182 private void writeRestrictionsList(JsonWriter writer, List<RestrictionsPerSpeedRange> messages)
183 throws IOException {
184 writer.beginArray();
185 for (RestrictionsPerSpeedRange restrictions : messages) {
186 writeRestrictions(writer, restrictions);
187 }
188 writer.endArray();
189 }
190
191 private void writeRestrictions(JsonWriter writer, RestrictionsPerSpeedRange restrictions)
192 throws IOException {
193 writer.beginObject();
194 writer.name(JSON_NAME_REQ_OPT).value(restrictions.mReqOpt);
195 writer.name(JSON_NAME_RESTRICTIONS).value(restrictions.mRestrictions);
196 if (restrictions.mSpeedRange != null) {
197 writer.name(JSON_NAME_SPEED_RANGE);
198 writer.beginObject();
199 writer.name(JSON_NAME_MIN_SPEED).value(restrictions.mSpeedRange.mMinSpeed);
200 writer.name(JSON_NAME_MAX_SPEED).value(restrictions.mSpeedRange.mMaxSpeed);
201 writer.endObject();
202 }
203 writer.endObject();
204 }
205
206 /**
207 * Reads Json as UX restriction configuration.
208 */
209 public static CarUxRestrictionsConfiguration readJson(JsonReader reader) throws IOException {
210 // We need to be lenient to accept infinity number (as max speed).
211 reader.setLenient(true);
212
213 Builder builder = new Builder();
214 reader.beginObject();
215 while (reader.hasNext()) {
216 String name = reader.nextName();
217 if (name.equals(JSON_NAME_MAX_CONTENT_DEPTH)) {
218 builder.setMaxContentDepth(reader.nextInt());
219 } else if (name.equals(JSON_NAME_MAX_CUMULATIVE_CONTENT_ITEMS)) {
220 builder.setMaxCumulativeContentItems(reader.nextInt());
221 } else if (name.equals(JSON_NAME_MAX_STRING_LENGTH)) {
222 builder.setMaxStringLength(reader.nextInt());
223 } else if (name.equals(JSON_NAME_PARKED_RESTRICTIONS)) {
224 readRestrictionsList(reader, CarDrivingStateEvent.DRIVING_STATE_PARKED, builder);
225 } else if (name.equals(JSON_NAME_IDLING_RESTRICTIONS)) {
226 readRestrictionsList(reader, CarDrivingStateEvent.DRIVING_STATE_IDLING, builder);
227 } else if (name.equals(JSON_NAME_MOVING_RESTRICTIONS)) {
228 readRestrictionsList(reader, CarDrivingStateEvent.DRIVING_STATE_MOVING, builder);
229 } else if (name.equals(JSON_NAME_UNKNOWN_RESTRICTIONS)) {
230 readRestrictionsList(reader, CarDrivingStateEvent.DRIVING_STATE_UNKNOWN, builder);
231 } else {
232 Log.e(TAG, "Unknown name parsing json config: " + name);
233 reader.skipValue();
234 }
235 }
236 reader.endObject();
237 return builder.build();
238 }
239
240 private static void readRestrictionsList(JsonReader reader, @CarDrivingState int drivingState,
241 Builder builder) throws IOException {
242 reader.beginArray();
243 while (reader.hasNext()) {
244 readRestrictions(reader, drivingState, builder);
245 }
246 reader.endArray();
247 }
248
249 private static void readRestrictions(JsonReader reader, @CarDrivingState int drivingState,
250 Builder builder) throws IOException {
251 reader.beginObject();
252 boolean reqOpt = false;
253 int restrictions = CarUxRestrictions.UX_RESTRICTIONS_BASELINE;
254 Builder.SpeedRange speedRange = null;
255 while (reader.hasNext()) {
256 String name = reader.nextName();
257 if (name.equals(JSON_NAME_REQ_OPT)) {
258 reqOpt = reader.nextBoolean();
259 } else if (name.equals(JSON_NAME_RESTRICTIONS)) {
260 restrictions = reader.nextInt();
261 } else if (name.equals(JSON_NAME_SPEED_RANGE)) {
262 reader.beginObject();
263 // Okay to set min initial value as MAX_SPEED because SpeedRange() won't allow it.
264 float minSpeed = Builder.SpeedRange.MAX_SPEED;
265 float maxSpeed = Builder.SpeedRange.MAX_SPEED;
266
267 while (reader.hasNext()) {
268 String n = reader.nextName();
269 if (n.equals(JSON_NAME_MIN_SPEED)) {
270 minSpeed = Double.valueOf(reader.nextDouble()).floatValue();
271 } else if (n.equals(JSON_NAME_MAX_SPEED)) {
272 maxSpeed = Double.valueOf(reader.nextDouble()).floatValue();
273 } else {
274 Log.e(TAG, "Unknown name parsing json config: " + n);
275 reader.skipValue();
276 }
277 }
278 speedRange = new Builder.SpeedRange(minSpeed, maxSpeed);
279 reader.endObject();
280 }
281 }
282 reader.endObject();
283 builder.setUxRestrictions(drivingState, speedRange, reqOpt, restrictions);
284 }
285
286 @Override
287 public boolean equals(Object obj) {
288 if (this == obj) {
289 return true;
290 }
291 if (obj == null || !(obj instanceof CarUxRestrictionsConfiguration)) {
292 return false;
293 }
294
295 CarUxRestrictionsConfiguration other = (CarUxRestrictionsConfiguration) obj;
296
297 // Compare UXR parameters.
298 if (mMaxContentDepth != other.mMaxContentDepth
299 || mMaxCumulativeContentItems != other.mMaxCumulativeContentItems
300 || mMaxStringLength != other.mMaxStringLength) {
301 return false;
302 }
303
304 // Compare UXR by driving state.
305 if (!mUxRestrictions.keySet().equals(other.mUxRestrictions.keySet())) {
306 return false;
307 }
308 for (int drivingState : mUxRestrictions.keySet()) {
309 List<RestrictionsPerSpeedRange> restrictions = mUxRestrictions.get(
310 drivingState);
311 List<RestrictionsPerSpeedRange> otherRestrictions = other.mUxRestrictions.get(
312 drivingState);
313 if (restrictions.size() != otherRestrictions.size()) {
314 return false;
315 }
316 // Assuming the restrictions are sorted.
317 for (int i = 0; i < restrictions.size(); i++) {
318 if (!restrictions.get(i).equals(otherRestrictions.get(i))) {
319 return false;
320 }
321 }
322 }
323 return true;
324 }
325
326 /**
327 * Dump the driving state to UX restrictions mapping.
328 */
329 public void dump(PrintWriter writer) {
330 for (Integer state : mUxRestrictions.keySet()) {
331 List<RestrictionsPerSpeedRange> list = mUxRestrictions.get(state);
332 writer.println("===========================================");
333 writer.println("Driving State to UXR");
334 if (list != null) {
335 writer.println("State:" + getDrivingStateName(state) + " num restrictions:"
336 + list.size());
337 for (RestrictionsPerSpeedRange r : list) {
338 writer.println("Requires DO? " + r.mReqOpt
339 + "\nRestrictions: 0x" + Integer.toHexString(r.mRestrictions)
Yao, Yuxingd7e9c492018-11-28 14:50:07 -0800340 + "\nSpeed Range: " + (r.mSpeedRange == null
Yao, Yuxing24f07fc2018-11-26 11:09:45 -0800341 ? "None"
Yao, Yuxingd7e9c492018-11-28 14:50:07 -0800342 : r.mSpeedRange.mMinSpeed + " - " + r.mSpeedRange.mMaxSpeed));
Yao, Yuxing24f07fc2018-11-26 11:09:45 -0800343 writer.println("===========================================");
344 }
345 }
346 }
347 writer.println("Max String length: " + mMaxStringLength);
348 writer.println("Max Cumulative Content Items: " + mMaxCumulativeContentItems);
349 writer.println("Max Content depth: " + mMaxContentDepth);
350 }
351
352 private static String getDrivingStateName(@CarDrivingState int state) {
353 switch (state) {
354 case 0:
355 return "parked";
356 case 1:
357 return "idling";
358 case 2:
359 return "moving";
360 default:
361 return "unknown";
362 }
363 }
364
365 // Parcelable methods/fields.
366
367 // Used by Parcel methods to ensure de/serialization order.
368 private static final int[] DRIVING_STATES = new int[]{
369 CarDrivingStateEvent.DRIVING_STATE_UNKNOWN,
370 CarDrivingStateEvent.DRIVING_STATE_PARKED,
371 CarDrivingStateEvent.DRIVING_STATE_IDLING,
372 CarDrivingStateEvent.DRIVING_STATE_MOVING
373 };
374
375 public static final Parcelable.Creator<CarUxRestrictionsConfiguration> CREATOR =
376 new Parcelable.Creator<CarUxRestrictionsConfiguration>() {
377
378 @Override
379 public CarUxRestrictionsConfiguration createFromParcel(Parcel source) {
380 return new CarUxRestrictionsConfiguration(source);
381 }
382
383 @Override
384 public CarUxRestrictionsConfiguration[] newArray(int size) {
385 return new CarUxRestrictionsConfiguration[size];
386 }
387 };
388
389 @Override
390 public int describeContents() {
391 return 0;
392 }
393
394 private CarUxRestrictionsConfiguration(Parcel in) {
395 for (int drivingState : DRIVING_STATES) {
396 List<RestrictionsPerSpeedRange> restrictions = new ArrayList<>();
397 in.readTypedList(restrictions, RestrictionsPerSpeedRange.CREATOR);
398 mUxRestrictions.put(drivingState, restrictions);
399 }
400 mMaxContentDepth = in.readInt();
401 mMaxCumulativeContentItems = in.readInt();
402 mMaxStringLength = in.readInt();
403 }
404
405 @Override
406 public void writeToParcel(Parcel dest, int flags) {
407 for (int drivingState : DRIVING_STATES) {
408 dest.writeTypedList(mUxRestrictions.get(drivingState), 0);
409 }
410 dest.writeInt(mMaxContentDepth);
411 dest.writeInt(mMaxCumulativeContentItems);
412 dest.writeInt(mMaxStringLength);
413 }
414
415 /**
416 * @hide
417 */
418 public static final class Builder {
419
420 private static final int UX_RESTRICTIONS_UNKNOWN = -1;
421
422 private int mMaxContentDepth = UX_RESTRICTIONS_UNKNOWN;
423 private int mMaxCumulativeContentItems = UX_RESTRICTIONS_UNKNOWN;
424 private int mMaxStringLength = UX_RESTRICTIONS_UNKNOWN;
425
Michal Palczewski9b6ee5d2018-11-28 11:26:17 -0800426 private Map<Integer, List<RestrictionsPerSpeedRange>> mUxRestrictions =
427 new ArrayMap<>(DRIVING_STATES.length);
Yao, Yuxing24f07fc2018-11-26 11:09:45 -0800428
429 public Builder() {
430 for (int drivingState : DRIVING_STATES) {
431 mUxRestrictions.put(drivingState, new ArrayList<>());
432 }
433 }
434
435 /**
436 * Sets ux restrictions for driving state.
437 */
438 public Builder setUxRestrictions(@CarDrivingState int drivingState,
439 boolean requiresOptimization,
440 @CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
441 return this.setUxRestrictions(drivingState, null, requiresOptimization, restrictions);
442 }
443
444 /**
445 * Sets ux restrictions with speed range.
446 *
447 * @param drivingState Restrictions will be set for this Driving state.
448 * See constants in {@link CarDrivingStateEvent}.
449 * @param speedRange If set, restrictions will only apply when current speed is within
450 * the range. Only {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}
451 * supports speed range. {@code null} implies the full speed range,
452 * i.e. zero to {@link SpeedRange#MAX_SPEED}.
453 * @param requiresOptimization Whether distraction optimization (DO) is required for this
454 * driving state.
455 * @param restrictions See constants in {@link CarUxRestrictions}.
456 */
457 public Builder setUxRestrictions(@CarDrivingState int drivingState,
458 SpeedRange speedRange, boolean requiresOptimization,
459 @CarUxRestrictions.CarUxRestrictionsInfo int restrictions) {
460 if (drivingState != CarDrivingStateEvent.DRIVING_STATE_MOVING) {
461 if (speedRange != null) {
462 throw new IllegalArgumentException(
463 "Non-moving driving state cannot specify speed range.");
464 }
465 if (mUxRestrictions.get(drivingState).size() > 0) {
466 throw new IllegalArgumentException("Non-moving driving state cannot have "
467 + "more than one set of restrictions.");
468 }
469 }
470
471 mUxRestrictions.get(drivingState).add(
472 new RestrictionsPerSpeedRange(requiresOptimization, restrictions, speedRange));
473 return this;
474 }
475
476 /**
477 * Sets max string length.
478 */
479 public Builder setMaxStringLength(int maxStringLength) {
480 mMaxStringLength = maxStringLength;
481 return this;
482 }
483
484 /**
485 * Sets max cumulative content items.
486 */
487 public Builder setMaxCumulativeContentItems(int maxCumulativeContentItems) {
488 mMaxCumulativeContentItems = maxCumulativeContentItems;
489 return this;
490 }
491
492 /**
493 * Sets max content depth.
494 */
495 public Builder setMaxContentDepth(int maxContentDepth) {
496 mMaxContentDepth = maxContentDepth;
497 return this;
498 }
499
500 /**
501 * @return CarUxRestrictionsConfiguration based on builder configuration.
502 */
503 public CarUxRestrictionsConfiguration build() {
504 // Create default restriction for unspecified driving state.
505 for (int drivingState : DRIVING_STATES) {
506 List<RestrictionsPerSpeedRange> restrictions = mUxRestrictions.get(drivingState);
507 if (restrictions.size() == 0) {
508 Log.i(TAG, "Using default restrictions for driving state: "
509 + getDrivingStateName(drivingState));
510 restrictions.add(new RestrictionsPerSpeedRange(
511 true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED));
512 }
513 }
514
515 // Configuration validation.
516 for (int drivingState : DRIVING_STATES) {
517 List<RestrictionsPerSpeedRange> restrictions = mUxRestrictions.get(drivingState);
518
519 if (drivingState == CarDrivingStateEvent.DRIVING_STATE_MOVING) {
520 // Sort restrictions based on speed range.
521 Collections.sort(restrictions,
522 (r1, r2) -> r1.mSpeedRange.compareTo(r2.mSpeedRange));
523
524 if (!isAllSpeedRangeCovered(restrictions)) {
525 throw new IllegalStateException(
526 "Moving state should cover full speed range.");
527 }
528 } else {
529 if (restrictions.size() != 1) {
530 throw new IllegalStateException("Non-moving driving state should contain "
531 + "one set of restriction rules.");
532 }
533 }
534 }
535 return new CarUxRestrictionsConfiguration(this);
536 }
537
538 /**
539 * restrictions should be sorted based on speed range.
540 */
541 private boolean isAllSpeedRangeCovered(List<RestrictionsPerSpeedRange> restrictions) {
542 if (restrictions.size() == 1) {
543 if (restrictions.get(0).mSpeedRange == null) {
544 // Single restriction with null speed range implies that
545 // it applies to the entire driving state.
546 return true;
547 }
548 return restrictions.get(0).mSpeedRange.mMinSpeed == 0
549 && Float.compare(restrictions.get(0).mSpeedRange.mMaxSpeed,
550 SpeedRange.MAX_SPEED) == 0;
551 }
552
553 if (restrictions.get(0).mSpeedRange.mMinSpeed != 0) {
554 Log.e(TAG, "Speed range min speed should start at 0.");
555 return false;
556 }
557 for (int i = 1; i < restrictions.size(); i++) {
558 RestrictionsPerSpeedRange prev = restrictions.get(i - 1);
559 RestrictionsPerSpeedRange curr = restrictions.get(i);
560 // If current min != prev.max, there's either an overlap or a gap in speed range.
561 if (Float.compare(curr.mSpeedRange.mMinSpeed, prev.mSpeedRange.mMaxSpeed) != 0) {
562 Log.e(TAG, "Mis-configured speed range. Possibly speed range overlap or gap.");
563 return false;
564 }
565 }
566 // The last speed range should have max speed.
567 float lastMaxSpeed = restrictions.get(restrictions.size() - 1).mSpeedRange.mMaxSpeed;
568 return lastMaxSpeed == SpeedRange.MAX_SPEED;
569 }
570
571 /**
572 * Speed range is defined by min and max speed. When there is no upper bound for max speed,
573 * set it to {@link SpeedRange#MAX_SPEED}.
574 */
575 public static final class SpeedRange implements Comparable<SpeedRange> {
576 public static final float MAX_SPEED = Float.POSITIVE_INFINITY;
577
578 private float mMinSpeed;
579 private float mMaxSpeed;
580
581 /**
582 * Defaults max speed to {@link SpeedRange#MAX_SPEED}.
583 */
584 public SpeedRange(@FloatRange(from = 0.0) float minSpeed) {
585 this(minSpeed, MAX_SPEED);
586 }
587
588 public SpeedRange(@FloatRange(from = 0.0) float minSpeed,
Yao, Yuxingd7e9c492018-11-28 14:50:07 -0800589 @FloatRange(from = 0.0) float maxSpeed) {
Yao, Yuxing24f07fc2018-11-26 11:09:45 -0800590 if (minSpeed == MAX_SPEED) {
591 throw new IllegalArgumentException("Min speed cannot be MAX_SPEED.");
592 }
593 if (maxSpeed < 0) {
594 throw new IllegalArgumentException("Max speed cannot be negative.");
595 }
596 if (minSpeed > maxSpeed) {
597 throw new IllegalArgumentException("Min speed " + minSpeed
598 + " should not be greater than max speed " + maxSpeed);
599 }
600 mMinSpeed = minSpeed;
601 mMaxSpeed = maxSpeed;
602 }
603
604 /**
605 * Return if the given speed is in the range of [minSpeed, maxSpeed).
606 *
607 * @param speed Speed to check
608 * @return {@code true} if in range; {@code false} otherwise.
609 */
610 public boolean includes(float speed) {
611 if (speed < mMinSpeed) {
612 return false;
613 }
614 if (mMaxSpeed == MAX_SPEED) {
615 return true;
616 }
617 return speed < mMaxSpeed;
618 }
619
620 @Override
621 public int compareTo(SpeedRange other) {
622 // First compare min speed; then max speed.
623 int minSpeedComparison = Float.compare(this.mMinSpeed, other.mMinSpeed);
624 if (minSpeedComparison != 0) {
625 return minSpeedComparison;
626 }
627
628 return Float.compare(this.mMaxSpeed, other.mMaxSpeed);
629 }
630
631 @Override
632 public boolean equals(Object obj) {
633 if (this == obj) {
634 return true;
635 }
636 if (obj == null || !(obj instanceof SpeedRange)) {
637 return false;
638 }
639 SpeedRange other = (SpeedRange) obj;
640
641 return this.compareTo(other) == 0;
642 }
643 }
644 }
645
646 /**
647 * Container for UX restrictions for a speed range.
648 * Speed range is valid only for the {@link CarDrivingStateEvent#DRIVING_STATE_MOVING}.
649 * @hide
650 */
651 public static final class RestrictionsPerSpeedRange implements Parcelable {
652 final boolean mReqOpt;
653 final int mRestrictions;
654 @Nullable
655 final Builder.SpeedRange mSpeedRange;
656
657 public RestrictionsPerSpeedRange(boolean reqOpt, int restrictions) {
658 this(reqOpt, restrictions, null);
659 }
660
661 public RestrictionsPerSpeedRange(boolean reqOpt, int restrictions,
662 @Nullable Builder.SpeedRange speedRange) {
663 if (!reqOpt && restrictions != CarUxRestrictions.UX_RESTRICTIONS_BASELINE) {
664 throw new IllegalArgumentException(
665 "Driving optimization is not required but UX restrictions is required.");
666 }
667 mReqOpt = reqOpt;
668 mRestrictions = restrictions;
669 mSpeedRange = speedRange;
670 }
671
672 @Override
673 public boolean equals(Object obj) {
674 if (this == obj) {
675 return true;
676 }
677 if (obj == null || !(obj instanceof RestrictionsPerSpeedRange)) {
678 return false;
679 }
680 RestrictionsPerSpeedRange other = (RestrictionsPerSpeedRange) obj;
681 return mReqOpt == other.mReqOpt
682 && mRestrictions == other.mRestrictions
683 && ((mSpeedRange == null && other.mSpeedRange == null) || mSpeedRange.equals(
684 other.mSpeedRange));
685 }
686
687 // Parcelable methods/fields.
688
689 public static final Creator<RestrictionsPerSpeedRange> CREATOR =
690 new Creator<RestrictionsPerSpeedRange>() {
691 @Override
692 public RestrictionsPerSpeedRange createFromParcel(Parcel in) {
693 return new RestrictionsPerSpeedRange(in);
694 }
695
696 @Override
697 public RestrictionsPerSpeedRange[] newArray(int size) {
698 return new RestrictionsPerSpeedRange[size];
699 }
700 };
701
702 @Override
703 public int describeContents() {
704 return 0;
705 }
706
707 protected RestrictionsPerSpeedRange(Parcel in) {
708 mReqOpt = in.readBoolean();
709 mRestrictions = in.readInt();
710 // Whether speed range is specified.
711 Builder.SpeedRange speedRange = null;
712 if (in.readBoolean()) {
713 float minSpeed = in.readFloat();
714 float maxSpeed = in.readFloat();
715 speedRange = new Builder.SpeedRange(minSpeed, maxSpeed);
716 }
717 mSpeedRange = speedRange;
718 }
719
720 @Override
721 public void writeToParcel(Parcel dest, int flags) {
722 dest.writeBoolean(mReqOpt);
723 dest.writeInt(mRestrictions);
724 // Whether speed range is specified.
725 dest.writeBoolean(mSpeedRange != null);
726 if (mSpeedRange != null) {
727 dest.writeFloat(mSpeedRange.mMinSpeed);
728 dest.writeFloat(mSpeedRange.mMaxSpeed);
729 }
730 }
731 }
732}