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
17package android.car.hardware;
18
19import android.Manifest;
20import android.annotation.IntDef;
21import android.annotation.RequiresPermission;
22import android.car.Car;
23import android.car.CarApiUtil;
24import android.car.CarLibLog;
25import android.car.CarManagerBase;
26import android.car.CarNotConnectedException;
27import android.car.VehiclePropertyType;
28import android.car.hardware.property.CarPropertyManager;
29import android.content.Context;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.IBinder;
33import android.os.RemoteException;
34import android.util.ArraySet;
35import android.util.Log;
36
37import java.lang.annotation.Retention;
38import java.lang.annotation.RetentionPolicy;
39import java.lang.ref.WeakReference;
40import java.util.Arrays;
41import java.util.HashMap;
42import java.util.List;
43
44
45/**
46 *  API for monitoring car sensor data.
47 */
48public 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