Yao, Yuxing | 24f07fc | 2018-11-26 11:09:45 -0800 | [diff] [blame] | 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 android.annotation.FloatRange; |
| 19 | import android.annotation.Nullable; |
| 20 | import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState; |
| 21 | import android.os.Build; |
| 22 | import android.os.Parcel; |
| 23 | import android.os.Parcelable; |
| 24 | import android.os.SystemClock; |
Michal Palczewski | 9b6ee5d | 2018-11-28 11:26:17 -0800 | [diff] [blame] | 25 | import android.util.ArrayMap; |
Yao, Yuxing | 24f07fc | 2018-11-26 11:09:45 -0800 | [diff] [blame] | 26 | import android.util.JsonReader; |
| 27 | import android.util.JsonWriter; |
| 28 | import android.util.Log; |
| 29 | |
| 30 | import java.io.IOException; |
| 31 | import java.io.PrintWriter; |
| 32 | import java.util.ArrayList; |
| 33 | import java.util.Collections; |
Yao, Yuxing | 24f07fc | 2018-11-26 11:09:45 -0800 | [diff] [blame] | 34 | import java.util.List; |
| 35 | import java.util.Map; |
| 36 | |
| 37 | /** |
| 38 | * Configuration for Car UX Restrictions service. |
| 39 | * |
| 40 | * @hide |
| 41 | */ |
| 42 | public 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 Palczewski | 9b6ee5d | 2018-11-28 11:26:17 -0800 | [diff] [blame] | 63 | private final Map<Integer, List<RestrictionsPerSpeedRange>> mUxRestrictions = |
| 64 | new ArrayMap<>(DRIVING_STATES.length); |
Yao, Yuxing | 24f07fc | 2018-11-26 11:09:45 -0800 | [diff] [blame] | 65 | |
| 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, Yuxing | d7e9c49 | 2018-11-28 14:50:07 -0800 | [diff] [blame] | 340 | + "\nSpeed Range: " + (r.mSpeedRange == null |
Yao, Yuxing | 24f07fc | 2018-11-26 11:09:45 -0800 | [diff] [blame] | 341 | ? "None" |
Yao, Yuxing | d7e9c49 | 2018-11-28 14:50:07 -0800 | [diff] [blame] | 342 | : r.mSpeedRange.mMinSpeed + " - " + r.mSpeedRange.mMaxSpeed)); |
Yao, Yuxing | 24f07fc | 2018-11-26 11:09:45 -0800 | [diff] [blame] | 343 | 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 Palczewski | 9b6ee5d | 2018-11-28 11:26:17 -0800 | [diff] [blame] | 426 | private Map<Integer, List<RestrictionsPerSpeedRange>> mUxRestrictions = |
| 427 | new ArrayMap<>(DRIVING_STATES.length); |
Yao, Yuxing | 24f07fc | 2018-11-26 11:09:45 -0800 | [diff] [blame] | 428 | |
| 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, Yuxing | d7e9c49 | 2018-11-28 14:50:07 -0800 | [diff] [blame] | 589 | @FloatRange(from = 0.0) float maxSpeed) { |
Yao, Yuxing | 24f07fc | 2018-11-26 11:09:45 -0800 | [diff] [blame] | 590 | 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 | } |