1 /* 2 * Copyright (C) 2015 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 17 package android.car.hardware; 18 19 import android.Manifest; 20 import android.annotation.IntDef; 21 import android.annotation.RequiresPermission; 22 import android.car.Car; 23 import android.car.CarApiUtil; 24 import android.car.CarLibLog; 25 import android.car.CarManagerBase; 26 import android.car.CarNotConnectedException; 27 import android.car.VehiclePropertyType; 28 import android.car.hardware.property.CarPropertyManager; 29 import android.content.Context; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.util.ArraySet; 35 import android.util.Log; 36 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 import java.lang.ref.WeakReference; 40 import java.util.Arrays; 41 import java.util.HashMap; 42 import java.util.List; 43 44 45 /** 46 * API for monitoring car sensor data. 47 */ 48 public final class CarSensorManager implements CarManagerBase { 49 private static final boolean DBG = false; 50 private static final String TAG = "CarSensorManager"; 51 private final CarPropertyManager mCarPropertyMgr; 52 /** @hide */ 53 public static final int SENSOR_TYPE_RESERVED1 = 1; 54 /** 55 * This sensor represents vehicle speed in m/s. 56 * Sensor data in {@link CarSensorEvent} is a float which will be >= 0. 57 * This requires {@link Car#PERMISSION_SPEED} permission. 58 */ 59 public static final int SENSOR_TYPE_CAR_SPEED = 0x11600207; 60 /** 61 * Represents engine RPM of the car. Sensor data in {@link CarSensorEvent} is a float. 62 */ 63 public static final int SENSOR_TYPE_RPM = 0x11600305; 64 /** 65 * Total travel distance of the car in Kilometer. Sensor data is a float. 66 * This requires {@link Car#PERMISSION_MILEAGE} permission. 67 */ 68 public static final int SENSOR_TYPE_ODOMETER = 0x11600204; 69 /** 70 * Indicates fuel level of the car. 71 * In {@link CarSensorEvent}, represents fuel level in milliliters. 72 * This requires {@link Car#PERMISSION_ENERGY} permission. 73 */ 74 public static final int SENSOR_TYPE_FUEL_LEVEL = 0x11600307; 75 /** 76 * Represents the current status of parking brake. Sensor data in {@link CarSensorEvent} is an 77 * intValues[0]. Value of 1 represents parking brake applied while 0 means the other way 78 * around. For this sensor, rate in {@link #registerListener(OnSensorChangedListener, int, int)} 79 * will be ignored and all changes will be notified. 80 */ 81 public static final int SENSOR_TYPE_PARKING_BRAKE = 0x11200402; 82 /** 83 * This represents the current position of transmission gear. Sensor data in 84 * {@link CarSensorEvent} is an intValues[0]. For the meaning of the value, check 85 * {@link CarSensorEvent#GEAR_NEUTRAL} and other GEAR_*. 86 */ 87 public static final int SENSOR_TYPE_GEAR = 0x11400400; 88 /** @hide */ 89 public static final int SENSOR_TYPE_RESERVED8 = 8; 90 /** 91 * Day/night sensor. Sensor data is intValues[0]. 92 */ 93 public static final int SENSOR_TYPE_NIGHT = 0x11200407; 94 /** @hide */ 95 public static final int SENSOR_TYPE_RESERVED10 = 10; 96 /** @hide */ 97 public static final int SENSOR_TYPE_RESERVED11 = 11; 98 /** 99 * Environment like temperature and pressure. 100 */ 101 public static final int SENSOR_TYPE_ENVIRONMENT = 12; 102 /** @hide */ 103 public static final int SENSOR_TYPE_RESERVED13 = 13; 104 /** @hide */ 105 public static final int SENSOR_TYPE_RESERVED14 = 14; 106 /** @hide */ 107 public static final int SENSOR_TYPE_RESERVED15 = 15; 108 /** @hide */ 109 public static final int SENSOR_TYPE_RESERVED16 = 16; 110 /** @hide */ 111 public static final int SENSOR_TYPE_RESERVED17 = 17; 112 /** @hide */ 113 public static final int SENSOR_TYPE_RESERVED18 = 18; 114 /** @hide */ 115 public static final int SENSOR_TYPE_RESERVED19 = 19; 116 /** @hide */ 117 public static final int SENSOR_TYPE_RESERVED20 = 20; 118 /** @hide */ 119 public static final int SENSOR_TYPE_RESERVED21 = 21; 120 /** 121 * Represents ignition state. The value should be one of the constants that starts with 122 * IGNITION_STATE_* in {@link CarSensorEvent}. 123 */ 124 public static final int SENSOR_TYPE_IGNITION_STATE = 0x11400409; 125 /** 126 * Represents wheel distance in millimeters. Some cars may not have individual sensors on each 127 * wheel. If a value is not available, Long.MAX_VALUE will be reported. The wheel distance 128 * accumulates over time. It increments on forward movement, and decrements on reverse. Wheel 129 * distance shall be reset to zero each time a vehicle is started by the user. 130 * This requires {@link Car#PERMISSION_SPEED} permission. 131 */ 132 public static final int SENSOR_TYPE_WHEEL_TICK_DISTANCE = 0x11510306; 133 /** 134 * Set to true when ABS is active. This sensor is event driven. 135 * This requires {@link Car#PERMISSION_CAR_DYNAMICS_STATE} permission. 136 */ 137 public static final int SENSOR_TYPE_ABS_ACTIVE = 0x1120040a; 138 /** 139 * Set to true when traction control is active. This sensor is event driven. 140 * This requires {@link Car#PERMISSION_CAR_DYNAMICS_STATE} permission. 141 */ 142 public static final int SENSOR_TYPE_TRACTION_CONTROL_ACTIVE = 0x1120040b; 143 /** @hide */ 144 public static final int SENSOR_TYPE_RESERVED26 = 26; 145 /** 146 * Set to true if the fuel door is open. 147 */ 148 public static final int SENSOR_TYPE_FUEL_DOOR_OPEN = 0x11200308; 149 150 /** 151 * Indicates battery level of the car. 152 * In {@link CarSensorEvent}, represents battery level in WH. floatValues[{@link 153 * CarSensorEvent#INDEX_EV_BATTERY_CAPACITY_ACTUAL}] represents the actual battery capacity in 154 * WH. The battery degrades over time, so this value is expected to drop slowly over the life 155 * of the vehicle. 156 * This requires {@link Car#PERMISSION_ENERGY} permission. 157 */ 158 public static final int SENSOR_TYPE_EV_BATTERY_LEVEL = 0x11600309; 159 /** 160 * Set to true if EV charging port is open. 161 */ 162 public static final int SENSOR_TYPE_EV_CHARGE_PORT_OPEN = 0x1120030a; 163 /** 164 * Set to true if EV charging port is connected. 165 */ 166 public static final int SENSOR_TYPE_EV_CHARGE_PORT_CONNECTED = 0x1120030b; 167 /** 168 * Indicates the instantaneous battery charging rate in mW. 169 * This requires {@link Car#PERMISSION_ENERGY} permission. 170 */ 171 public static final int SENSOR_TYPE_EV_BATTERY_CHARGE_RATE = 0x1160030c; 172 /** 173 * Oil level sensor. 174 * This requires {@link Car#PERMISSION_CAR_ENGINE_DETAILED} permission 175 * @hide 176 */ 177 public static final int SENSOR_TYPE_ENGINE_OIL_LEVEL = 0x11400303; 178 179 180 /** @hide */ 181 @IntDef({ 182 SENSOR_TYPE_CAR_SPEED, 183 SENSOR_TYPE_RPM, 184 SENSOR_TYPE_ODOMETER, 185 SENSOR_TYPE_FUEL_LEVEL, 186 SENSOR_TYPE_PARKING_BRAKE, 187 SENSOR_TYPE_GEAR, 188 SENSOR_TYPE_NIGHT, 189 SENSOR_TYPE_ENVIRONMENT, 190 SENSOR_TYPE_IGNITION_STATE, 191 SENSOR_TYPE_WHEEL_TICK_DISTANCE, 192 SENSOR_TYPE_ABS_ACTIVE, 193 SENSOR_TYPE_TRACTION_CONTROL_ACTIVE, 194 SENSOR_TYPE_FUEL_DOOR_OPEN, 195 SENSOR_TYPE_EV_BATTERY_LEVEL, 196 SENSOR_TYPE_EV_CHARGE_PORT_OPEN, 197 SENSOR_TYPE_EV_CHARGE_PORT_CONNECTED, 198 SENSOR_TYPE_EV_BATTERY_CHARGE_RATE, 199 SENSOR_TYPE_ENGINE_OIL_LEVEL, 200 }) 201 @Retention(RetentionPolicy.SOURCE) 202 public @interface SensorType {} 203 204 private final ArraySet<Integer> mSensorConfigIds = new ArraySet<>(Arrays.asList(new Integer[]{ 205 SENSOR_TYPE_CAR_SPEED, 206 SENSOR_TYPE_RPM, 207 SENSOR_TYPE_ODOMETER, 208 SENSOR_TYPE_FUEL_LEVEL, 209 SENSOR_TYPE_PARKING_BRAKE, 210 SENSOR_TYPE_GEAR, 211 SENSOR_TYPE_NIGHT, 212 SENSOR_TYPE_ENVIRONMENT, 213 SENSOR_TYPE_IGNITION_STATE, 214 SENSOR_TYPE_WHEEL_TICK_DISTANCE, 215 SENSOR_TYPE_ABS_ACTIVE, 216 SENSOR_TYPE_TRACTION_CONTROL_ACTIVE, 217 SENSOR_TYPE_FUEL_DOOR_OPEN, 218 SENSOR_TYPE_EV_BATTERY_LEVEL, 219 SENSOR_TYPE_EV_CHARGE_PORT_OPEN, 220 SENSOR_TYPE_EV_CHARGE_PORT_CONNECTED, 221 SENSOR_TYPE_EV_BATTERY_CHARGE_RATE, 222 SENSOR_TYPE_ENGINE_OIL_LEVEL, 223 })); 224 225 /** Read sensor in default normal rate set for each sensors. This is default rate. */ 226 public static final int SENSOR_RATE_NORMAL = 1; 227 public static final int SENSOR_RATE_UI = 5; 228 public static final int SENSOR_RATE_FAST = 10; 229 /** Read sensor at the maximum rate. Actual rate will be different depending on the sensor. */ 230 public static final int SENSOR_RATE_FASTEST = 100; 231 232 /** @hide */ 233 @IntDef({ 234 SENSOR_RATE_NORMAL, 235 SENSOR_RATE_UI, 236 SENSOR_RATE_FAST, 237 SENSOR_RATE_FASTEST 238 }) 239 @Retention(RetentionPolicy.SOURCE) 240 public @interface SensorRate {} 241 242 private CarPropertyEventListenerToBase mCarPropertyEventListener = null; 243 244 /** 245 * To keep record of CarPropertyEventListenerToBase 246 */ 247 private final HashMap<OnSensorChangedListener, CarPropertyEventListenerToBase> mListenerMap = 248 new HashMap<>(); 249 /** 250 * Listener for car sensor data change. 251 * Callbacks are called in the Looper context. 252 */ 253 public interface OnSensorChangedListener { 254 /** 255 * Called when there is a new sensor data from car. 256 * @param event Incoming sensor event for the given sensor type. 257 */ 258 void onSensorChanged(CarSensorEvent event); 259 } 260 261 private static class CarPropertyEventListenerToBase implements 262 CarPropertyManager.CarPropertyEventListener{ 263 private final WeakReference<CarSensorManager> mManager; 264 private final OnSensorChangedListener mListener; 265 CarPropertyEventListenerToBase(CarSensorManager manager, OnSensorChangedListener listener) { 266 mManager = new WeakReference<>(manager); 267 mListener = listener; 268 } 269 270 @Override 271 public void onChangeEvent(CarPropertyValue value) { 272 CarSensorManager manager = mManager.get(); 273 if (manager != null) { 274 manager.handleOnChangeEvent(value, mListener); 275 } 276 } 277 278 @Override 279 public void onErrorEvent(int propertyId, int zone) { 280 281 } 282 } 283 284 private void handleOnChangeEvent(CarPropertyValue value, OnSensorChangedListener listener) { 285 synchronized (mListenerMap) { 286 CarSensorEvent event = createCarSensorEvent(value); 287 listener.onSensorChanged(event); 288 } 289 } 290 291 private void handleOnErrorEvent(int propertyId, int zone) { 292 293 } 294 /** @hide */ 295 public CarSensorManager(IBinder service, Context context, Handler handler) { 296 mCarPropertyMgr = new CarPropertyManager(service, handler, DBG, TAG); 297 } 298 299 /** @hide */ 300 @Override 301 public void onCarDisconnected() { 302 synchronized (mListenerMap) { 303 mListenerMap.clear(); 304 } 305 mCarPropertyMgr.onCarDisconnected(); 306 } 307 308 /** 309 * Give the list of CarSensors available in the connected car. 310 * @return array of all sensor types supported. 311 * @throws CarNotConnectedException if the connection to the car service has been lost. 312 */ 313 public int[] getSupportedSensors() throws CarNotConnectedException { 314 try { 315 List<CarPropertyConfig> carPropertyConfigList = getPropertyList(); 316 int[] supportedSensors = new int[carPropertyConfigList.size()]; 317 for (int i = 0; i < supportedSensors.length; i++) { 318 supportedSensors[i] = carPropertyConfigList.get(i).getPropertyId(); 319 } 320 return supportedSensors; 321 } catch (IllegalStateException e) { 322 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 323 } 324 return new int[0]; 325 } 326 327 /** 328 * Get list of properties represented by CarSensorManager for this car. 329 * @return List of CarPropertyConfig objects available via Car Cabin Manager. 330 * @throws CarNotConnectedException if the connection to the car service has been lost. 331 */ 332 public List<CarPropertyConfig> getPropertyList() throws CarNotConnectedException { 333 return mCarPropertyMgr.getPropertyList(mSensorConfigIds); 334 } 335 336 /** 337 * Tells if given sensor is supported or not. 338 * @param sensorType 339 * @return true if the sensor is supported. 340 * @throws CarNotConnectedException if the connection to the car service has been lost. 341 */ 342 public boolean isSensorSupported(@SensorType int sensorType) throws CarNotConnectedException { 343 int[] sensors = getSupportedSensors(); 344 for (int sensorSupported: sensors) { 345 if (sensorType == sensorSupported) { 346 return true; 347 } 348 } 349 return false; 350 } 351 352 /** 353 * Check if given sensorList is including the sensorType. 354 * @param sensorList 355 * @param sensorType 356 * @return 357 */ 358 public static boolean isSensorSupported(int[] sensorList, @SensorType int sensorType) { 359 for (int sensorSupported: sensorList) { 360 if (sensorType == sensorSupported) { 361 return true; 362 } 363 } 364 return false; 365 } 366 367 /** 368 * Register {@link OnSensorChangedListener} to get repeated sensor updates. Multiple listeners 369 * can be registered for a single sensor or the same listener can be used for different sensors. 370 * If the same listener is registered again for the same sensor, it will be either ignored or 371 * updated depending on the rate. 372 * <p> 373 * Requires {@link Car#PERMISSION_SPEED} for {@link #SENSOR_TYPE_CAR_SPEED} and 374 * {@link #SENSOR_TYPE_WHEEL_TICK_DISTANCE}, {@link Car#PERMISSION_MILEAGE} for 375 * {@link #SENSOR_TYPE_ODOMETER}, {@link Car#PERMISSION_ENERGY} for 376 * {@link #SENSOR_TYPE_FUEL_LEVEL} and (@link #SENSOR_TYPE_EV_BATTERY_LEVEL and 377 * {@link #SENSOR_TYPE_EV_CHARGE_RATE}, {@link Car#PERMISSION_CAR_DYNAMICS_STATE} for 378 * {@link #SENSOR_TYPE_ABS_ACTIVE} and {@link #SENSOR_TYPE_TRACTION_CONTROL_ACTIVE} 379 * 380 * @param listener 381 * @param sensorType sensor type to subscribe. 382 * @param rate how fast the sensor events are delivered. It should be one of 383 * {@link #SENSOR_RATE_FASTEST}, {@link #SENSOR_RATE_FAST}, {@link #SENSOR_RATE_UI}, 384 * {@link #SENSOR_RATE_NORMAL}. Rate may not be respected especially when the same sensor 385 * is registered with different listener with different rates. Also, rate might be 386 * ignored when vehicle property raises events only when the value is actually changed, 387 * for example {@link #SENSOR_TYPE_PARKING_BRAKE} will raise an event only when parking 388 * brake was engaged or disengaged. 389 * @return if the sensor was successfully enabled. 390 * @throws CarNotConnectedException if the connection to the car service has been lost. 391 * @throws IllegalArgumentException for wrong argument like wrong rate 392 * @throws SecurityException if missing the appropriate permission 393 */ 394 @RequiresPermission(anyOf={Manifest.permission.ACCESS_FINE_LOCATION, Car.PERMISSION_SPEED, 395 Car.PERMISSION_MILEAGE, Car.PERMISSION_ENERGY, Car.PERMISSION_CAR_DYNAMICS_STATE}, 396 conditional=true) 397 public boolean registerListener(OnSensorChangedListener listener, @SensorType int sensorType, 398 @SensorRate int rate) throws CarNotConnectedException, IllegalArgumentException { 399 if (rate != SENSOR_RATE_FASTEST && rate != SENSOR_RATE_NORMAL 400 && rate != SENSOR_RATE_UI && rate != SENSOR_RATE_FAST) { 401 throw new IllegalArgumentException("wrong rate " + rate); 402 } 403 if (mListenerMap.get(listener) == null) { 404 mCarPropertyEventListener = new CarPropertyEventListenerToBase(this, listener); 405 } else { 406 mCarPropertyEventListener = mListenerMap.get(listener); 407 } 408 if (mCarPropertyMgr.registerListener(mCarPropertyEventListener, sensorType, rate)) { 409 mListenerMap.put(listener, mCarPropertyEventListener); 410 return true; 411 } else { 412 return false; 413 } 414 } 415 416 /** 417 * Stop getting sensor update for the given listener. If there are multiple registrations for 418 * this listener, all listening will be stopped. 419 * @param listener 420 */ 421 public void unregisterListener(OnSensorChangedListener listener) { 422 //TODO: removing listener should reset update rate, bug: 32060307 423 synchronized (mListenerMap) { 424 mCarPropertyEventListener = mListenerMap.get(listener); 425 mCarPropertyMgr.unregisterListener(mCarPropertyEventListener); 426 mListenerMap.remove(listener); 427 } 428 } 429 430 /** 431 * Stop getting sensor update for the given listener and sensor. If the same listener is used 432 * for other sensors, those subscriptions will not be affected. 433 * @param listener 434 * @param sensorType 435 */ 436 public void unregisterListener(OnSensorChangedListener listener, @SensorType int sensorType) { 437 synchronized (mListenerMap) { 438 mCarPropertyEventListener = mListenerMap.get(listener); 439 } 440 mCarPropertyMgr.unregisterListener(mCarPropertyEventListener, sensorType); 441 } 442 443 /** 444 * Get the most recent CarSensorEvent for the given type. Note that latest sensor data from car 445 * will not be available if it was never subscribed before. This call will return immediately 446 * with null if there is no data available. 447 * @param type A sensor to request 448 * @return null if there was no sensor update since connected to the car. 449 * @throws CarNotConnectedException if the connection to the car service has been lost. 450 */ 451 public CarSensorEvent getLatestSensorEvent(@SensorType int type) 452 throws CarNotConnectedException { 453 try { 454 CarPropertyValue propertyValue = mCarPropertyMgr.getProperty(type, 0); 455 return createCarSensorEvent(propertyValue); 456 } catch (IllegalStateException e) { 457 CarApiUtil.checkCarNotConnectedExceptionFromCarService(e); 458 } 459 return null; 460 } 461 462 private void handleCarServiceRemoteExceptionAndThrow(RemoteException e) 463 throws CarNotConnectedException { 464 if (Log.isLoggable(CarLibLog.TAG_SENSOR, Log.INFO)) { 465 Log.i(CarLibLog.TAG_SENSOR, "RemoteException from car service:" + e.getMessage()); 466 } 467 throw new CarNotConnectedException(); 468 } 469 470 private CarSensorEvent createCarSensorEvent(CarPropertyValue propertyValue) { 471 CarSensorEvent event = null; 472 switch (propertyValue.getPropertyId() & VehiclePropertyType.MASK) { 473 case VehiclePropertyType.FLOAT: 474 event = new CarSensorEvent(propertyValue.getPropertyId(), 475 propertyValue.getTimestamp(), 1, 0, 0); 476 event.floatValues[0] = (float) propertyValue.getValue(); 477 break; 478 case VehiclePropertyType.INT32: 479 event = new CarSensorEvent(propertyValue.getPropertyId(), 480 propertyValue.getTimestamp(), 0, 1, 0); 481 event.intValues[0] = (int) propertyValue.getValue(); 482 break; 483 case VehiclePropertyType.BOOLEAN: 484 event = new CarSensorEvent(propertyValue.getPropertyId(), 485 propertyValue.getTimestamp(), 0, 1, 0); 486 event.intValues[0] = (boolean) propertyValue.getValue() ? 1 : 0; 487 break; 488 case VehiclePropertyType.INT64_VEC: 489 Object[] value = (Object[]) propertyValue.getValue(); 490 event = new CarSensorEvent(propertyValue.getPropertyId(), 491 propertyValue.getTimestamp(), 0, 0, value.length); 492 for (int i = 0; i < value.length; i++) { 493 event.longValues[i] = (Long) value[i]; 494 } 495 break; 496 default: 497 Log.e(TAG, "unhandled VehiclePropertyType for propId=" 498 + propertyValue.getPropertyId()); 499 break; 500 } 501 return event; 502 } 503 504 /** 505 * Get the config data for the given type. 506 * 507 * A CarSensorConfig object is returned for every sensor type. However, if there is no 508 * config, the data will be empty. 509 * 510 * @param sensor type to request 511 * @return CarSensorConfig object 512 * @throws CarNotConnectedException if the connection to the car service has been lost. 513 * @hide 514 */ 515 public CarSensorConfig getSensorConfig(@SensorType int type) 516 throws CarNotConnectedException { 517 Bundle b = null; 518 switch (type) { 519 case SENSOR_TYPE_WHEEL_TICK_DISTANCE: 520 List<CarPropertyConfig> propertyConfigs = mCarPropertyMgr.getPropertyList(); 521 for (CarPropertyConfig p : propertyConfigs) { 522 if (p.getPropertyId() == type) { 523 b = createWheelDistanceTickBundle(p.getConfigArray()); 524 break; 525 } 526 } 527 break; 528 default: 529 b = Bundle.EMPTY; 530 break; 531 } 532 return new CarSensorConfig(type, b); 533 } 534 535 private static final int INDEX_WHEEL_DISTANCE_ENABLE_FLAG = 0; 536 private static final int INDEX_WHEEL_DISTANCE_FRONT_LEFT = 1; 537 private static final int INDEX_WHEEL_DISTANCE_FRONT_RIGHT = 2; 538 private static final int INDEX_WHEEL_DISTANCE_REAR_RIGHT = 3; 539 private static final int INDEX_WHEEL_DISTANCE_REAR_LEFT = 4; 540 private static final int WHEEL_TICK_DISTANCE_BUNDLE_SIZE = 6; 541 542 private Bundle createWheelDistanceTickBundle(List<Integer> configArray) { 543 Bundle b = new Bundle(WHEEL_TICK_DISTANCE_BUNDLE_SIZE); 544 b.putInt(CarSensorConfig.WHEEL_TICK_DISTANCE_SUPPORTED_WHEELS, 545 configArray.get(INDEX_WHEEL_DISTANCE_ENABLE_FLAG)); 546 b.putInt(CarSensorConfig.WHEEL_TICK_DISTANCE_FRONT_LEFT_UM_PER_TICK, 547 configArray.get(INDEX_WHEEL_DISTANCE_FRONT_LEFT)); 548 b.putInt(CarSensorConfig.WHEEL_TICK_DISTANCE_FRONT_RIGHT_UM_PER_TICK, 549 configArray.get(INDEX_WHEEL_DISTANCE_FRONT_RIGHT)); 550 b.putInt(CarSensorConfig.WHEEL_TICK_DISTANCE_REAR_RIGHT_UM_PER_TICK, 551 configArray.get(INDEX_WHEEL_DISTANCE_REAR_RIGHT)); 552 b.putInt(CarSensorConfig.WHEEL_TICK_DISTANCE_REAR_LEFT_UM_PER_TICK, 553 configArray.get(INDEX_WHEEL_DISTANCE_REAR_LEFT)); 554 return b; 555 } 556 } 557