1 /*
2  * Copyright (C) 2016 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 com.google.android.car.kitchensink.sensor;
18 
19 import android.Manifest;
20 import android.annotation.Nullable;
21 import android.car.Car;
22 import android.car.VehiclePropertyIds;
23 import android.car.VehiclePropertyType;
24 import android.car.hardware.CarPropertyConfig;
25 import android.car.hardware.CarPropertyValue;
26 import android.car.hardware.property.CarPropertyManager;
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.text.TextUtils;
32 import android.text.method.ScrollingMovementMethod;
33 import android.util.ArraySet;
34 import android.util.Log;
35 import android.util.SparseIntArray;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.widget.TextView;
40 
41 import androidx.fragment.app.Fragment;
42 
43 import com.google.android.car.kitchensink.KitchenSinkHelper;
44 import com.google.android.car.kitchensink.R;
45 
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Set;
52 import java.util.concurrent.ConcurrentHashMap;
53 
54 public class SensorsTestFragment extends Fragment {
55     private static final String TAG = "CAR.SENSOR.KS";
56     private static final boolean DBG = true;
57     private static final boolean DBG_VERBOSE = true;
58     private static final int KS_PERMISSIONS_REQUEST = 1;
59 
60     private final static String[] REQUIRED_PERMISSIONS = new String[]{
61         Manifest.permission.ACCESS_FINE_LOCATION,
62         Manifest.permission.ACCESS_COARSE_LOCATION,
63         Car.PERMISSION_MILEAGE,
64         Car.PERMISSION_ENERGY,
65         Car.PERMISSION_SPEED,
66         Car.PERMISSION_CAR_DYNAMICS_STATE
67     };
68     private static final ArraySet<Integer> SENSORS_SET = new ArraySet<>(Arrays.asList(
69             VehiclePropertyIds.PERF_VEHICLE_SPEED,
70             VehiclePropertyIds.ENGINE_RPM,
71             VehiclePropertyIds.PERF_ODOMETER,
72             VehiclePropertyIds.FUEL_LEVEL,
73             VehiclePropertyIds.FUEL_DOOR_OPEN,
74             VehiclePropertyIds.IGNITION_STATE,
75             VehiclePropertyIds.PARKING_BRAKE_ON,
76             VehiclePropertyIds.GEAR_SELECTION,
77             VehiclePropertyIds.NIGHT_MODE,
78             VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE,
79             VehiclePropertyIds.WHEEL_TICK,
80             VehiclePropertyIds.ABS_ACTIVE,
81             VehiclePropertyIds.TRACTION_CONTROL_ACTIVE,
82             VehiclePropertyIds.EV_BATTERY_LEVEL,
83             VehiclePropertyIds.EV_CHARGE_PORT_OPEN,
84             VehiclePropertyIds.EV_CHARGE_PORT_CONNECTED,
85             VehiclePropertyIds.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE,
86             VehiclePropertyIds.ENGINE_OIL_LEVEL
87     ));
88     private static final SparseIntArray PROPERTY_TO_RESOURCE =
89             new SparseIntArray() {{
90                 put(VehiclePropertyIds.PERF_VEHICLE_SPEED, R.string.sensor_speed);
91                 put(VehiclePropertyIds.ENGINE_RPM, R.string.sensor_rpm);
92                 put(VehiclePropertyIds.PERF_ODOMETER, R.string.sensor_odometer);
93                 put(VehiclePropertyIds.FUEL_LEVEL, R.string.sensor_fuel_level);
94                 put(VehiclePropertyIds.FUEL_DOOR_OPEN, R.string.sensor_fuel_door_open);
95                 put(VehiclePropertyIds.IGNITION_STATE, R.string.sensor_ignition_status);
96                 put(VehiclePropertyIds.PARKING_BRAKE_ON, R.string.sensor_parking_brake);
97                 put(VehiclePropertyIds.GEAR_SELECTION, R.string.sensor_gear);
98                 put(VehiclePropertyIds.NIGHT_MODE, R.string.sensor_night);
99                 put(VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE, R.string.sensor_environment);
100                 put(VehiclePropertyIds.WHEEL_TICK, R.string.sensor_wheel_ticks);
101                 put(VehiclePropertyIds.ABS_ACTIVE, R.string.sensor_abs_is_active);
102                 put(VehiclePropertyIds.TRACTION_CONTROL_ACTIVE,
103                         R.string.sensor_traction_control_is_active);
104                 put(VehiclePropertyIds.EV_BATTERY_LEVEL, R.string.sensor_ev_battery_level);
105                 put(VehiclePropertyIds.EV_CHARGE_PORT_OPEN, R.string.sensor_ev_charge_port_is_open);
106                 put(VehiclePropertyIds.EV_CHARGE_PORT_CONNECTED,
107                         R.string.sensor_ev_charge_port_is_connected);
108                 put(VehiclePropertyIds.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE,
109                         R.string.sensor_ev_charge_rate);
110                 put(VehiclePropertyIds.ENGINE_OIL_LEVEL, R.string.sensor_engine_oil_level);
111             }};
112 
113     private final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
114             new CarPropertyManager.CarPropertyEventCallback() {
115                 @Override
116                 public void onChangeEvent(CarPropertyValue value) {
117                     if (DBG_VERBOSE) {
118                         Log.v(TAG, "New car property value: " + value);
119                     }
120                     mValueMap.put(value.getPropertyId(), value);
121                     refreshCarSensorInfoText();
122                 }
123                 @Override
124                 public void onErrorEvent(int propId, int zone) {
125                     Log.e(TAG, "propId: " + propId + " zone: " + zone);
126                 }
127             };
128 
129     private final Handler mHandler = new Handler();
130     private final Map<Integer, CarPropertyValue> mValueMap = new ConcurrentHashMap<>();
131     private Context mContext;
132     private KitchenSinkHelper mKitchenSinkHelper;
133     private CarPropertyManager mCarPropertyManager;
134     private LocationListeners mLocationListener;
135     private String mNaString;
136     private List<CarPropertyConfig> mCarPropertyConfigs;
137 
138     private TextView mCarSensorInfo;
139     private TextView mLocationInfo;
140     private TextView mAccelInfo;
141     private TextView mGyroInfo;
142     private TextView mMagInfo;
143     private TextView mAccelUncalInfo;
144     private TextView mGyroUncalInfo;
145     private TextView mAccelLimitedAxesInfo;
146     private TextView mGyroLimitedAxesInfo;
147     private TextView mAccelLimitedAxesUncalInfo;
148     private TextView mGyroLimitedAxesUncalInfo;
149     private TextView mHeadingInfo;
150 
151     @Nullable
152     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)153     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
154                              @Nullable Bundle savedInstanceState) {
155         if (DBG) {
156             Log.i(TAG, "onCreateView");
157         }
158 
159         View view = inflater.inflate(R.layout.sensors, container, false);
160         mContext = getActivity();
161         if (!(mContext instanceof KitchenSinkHelper)) {
162             throw new IllegalStateException(
163                     "context does not implement " + KitchenSinkHelper.class.getSimpleName());
164         }
165         mKitchenSinkHelper = (KitchenSinkHelper) mContext;
166         mCarSensorInfo = (TextView) view.findViewById(R.id.car_sensor_info);
167         mCarSensorInfo.setMovementMethod(new ScrollingMovementMethod());
168         mLocationInfo = (TextView) view.findViewById(R.id.location_info);
169         mLocationInfo.setMovementMethod(new ScrollingMovementMethod());
170         mAccelInfo = (TextView) view.findViewById(R.id.accel_info);
171         mGyroInfo = (TextView) view.findViewById(R.id.gyro_info);
172         mMagInfo = (TextView) view.findViewById(R.id.mag_info);
173         mAccelUncalInfo = (TextView) view.findViewById(R.id.accel_uncal_info);
174         mGyroUncalInfo = (TextView) view.findViewById(R.id.gyro_uncal_info);
175         mAccelLimitedAxesInfo = (TextView) view.findViewById(R.id.accel_limited_axes_info);
176         mGyroLimitedAxesInfo = (TextView) view.findViewById(R.id.gyro_limited_axes_info);
177         mAccelLimitedAxesUncalInfo =
178                 (TextView) view.findViewById(R.id.accel_limited_axes_uncal_info);
179         mGyroLimitedAxesUncalInfo = (TextView) view.findViewById(R.id.gyro_limited_axes_uncal_info);
180         mHeadingInfo = (TextView) view.findViewById(R.id.heading_info);
181 
182         mNaString = getContext().getString(R.string.sensor_na);
183         return view;
184     }
185 
186     @Override
onStart()187     public void onStart() {
188         super.onStart();
189         initPermissions();
190     }
191 
192     @Override
onResume()193     public void onResume() {
194         super.onResume();
195         Set<String> missingPermissions = checkExistingPermissions();
196         if (!missingPermissions.isEmpty()) {
197             Log.e(TAG, "Permissions not granted. Cannot initialize sensors. " + missingPermissions);
198             return;
199         }
200 
201         mKitchenSinkHelper.requestRefreshManager(
202                 this::initSensors, new Handler(getContext().getMainLooper()));
203     }
204 
205     @Override
onPause()206     public void onPause() {
207         super.onPause();
208         if (mCarPropertyManager != null) {
209             mCarPropertyManager.unregisterCallback(mCarPropertyEventCallback);
210         }
211         if (mLocationListener != null) {
212             mLocationListener.stopListening();
213         }
214     }
215 
initSensors()216     private void initSensors() {
217         try {
218             initCarSensor();
219             initLocationSensor();
220         } catch (Exception e) {
221             Log.e(TAG, "initSensors() exception caught ", e);
222         }
223 
224     }
225 
initCarSensor()226     private void initCarSensor() {
227         if (mCarPropertyManager == null) {
228             mCarPropertyManager = mKitchenSinkHelper.getPropertyManager();
229         }
230         mCarPropertyConfigs = mCarPropertyManager.getPropertyList(SENSORS_SET);
231 
232         for (CarPropertyConfig property : mCarPropertyConfigs) {
233             float rate = CarPropertyManager.SENSOR_RATE_NORMAL;
234             if (property.getChangeMode()
235                     == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
236                 rate = CarPropertyManager.SENSOR_RATE_ONCHANGE;
237             }
238             mCarPropertyManager.registerCallback(mCarPropertyEventCallback,
239                     property.getPropertyId(), rate);
240         }
241     }
242 
initLocationSensor()243     private void initLocationSensor() {
244         if (mLocationListener == null) {
245             mLocationListener = new LocationListeners(getContext(),
246                     new LocationInfoTextUpdateListener());
247         }
248         mLocationListener.startListening();
249     }
250 
initPermissions()251     private void initPermissions() {
252         Set<String> missingPermissions = checkExistingPermissions();
253         if (!missingPermissions.isEmpty()) {
254             requestPermissions(missingPermissions);
255         }
256     }
257 
checkExistingPermissions()258     private Set<String> checkExistingPermissions() {
259         Set<String> missingPermissions = new HashSet<String>();
260         for (String permission : REQUIRED_PERMISSIONS) {
261             if (mContext.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
262                 missingPermissions.add(permission);
263             }
264         }
265         return missingPermissions;
266     }
267 
requestPermissions(Set<String> permissions)268     private void requestPermissions(Set<String> permissions) {
269         Log.d(TAG, "requesting additional permissions=" + permissions);
270         requestPermissions(permissions.toArray(new String[permissions.size()]),
271                 KS_PERMISSIONS_REQUEST);
272     }
273 
274     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)275     public void onRequestPermissionsResult(int requestCode, String[] permissions,
276             int[] grantResults) {
277         Log.d(TAG, "onRequestPermissionsResult reqCode=" + requestCode);
278     }
279 
refreshCarSensorInfoText()280     private void refreshCarSensorInfoText() {
281         String summaryString;
282         List<String> summary = formSummary();
283         summaryString = TextUtils.join("\n", summary);
284         mHandler.post(() -> mCarSensorInfo.setText(summaryString));
285     }
286 
formSummary()287     private List<String> formSummary() {
288         List<String> summary = new ArrayList<>();
289         for (CarPropertyConfig propertyConfig : mCarPropertyConfigs) {
290             int propertyId = propertyConfig.getPropertyId();
291             CarPropertyValue propertyValue = mValueMap.get(propertyId);
292             if (propertyValue != null
293                     && propertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
294                 propertyValue = null;
295             }
296             int resourceId = PROPERTY_TO_RESOURCE.get(propertyId);
297             // for wheel_tick, add the configuration.
298             if (propertyId == VehiclePropertyIds.WHEEL_TICK) {
299                 if (propertyValue != null) {
300                     Long[] wheelTickData = (Long[]) propertyValue.getValue();
301                     summary.add(getContext().getString(R.string.sensor_wheel_ticks,
302                             getTimestamp(propertyValue),
303                             wheelTickData[0],
304                             wheelTickData[1],
305                             wheelTickData[2],
306                             wheelTickData[3],
307                             wheelTickData[4]));
308                 } else {
309                     summary.add(getContext().getString(R.string.sensor_wheel_ticks,
310                             getTimestamp(propertyValue),
311                             mNaString, mNaString, mNaString, mNaString, mNaString));
312                 }
313                 List<Integer> configArray = propertyConfig.getConfigArray();
314                 summary.add(getContext().getString(R.string.sensor_wheel_ticks_cfg,
315                         configArray.get(0),
316                         configArray.get(1),
317                         configArray.get(2),
318                         configArray.get(3),
319                         configArray.get(4)));
320             } else {
321                 summary.add(getContext().getString(
322                         resourceId, getTimestamp(propertyValue),
323                         getStringOfPropertyValue(propertyValue)));
324             }
325         }
326         return summary;
327     }
328 
getTimestamp(CarPropertyValue value)329     private String getTimestamp(CarPropertyValue value) {
330         if (value == null) {
331             return mNaString;
332         }
333         return Double.toString(value.getTimestamp() / (1000L * 1000L * 1000L)) + " sec";
334     }
335 
getTimestampNow()336     private String getTimestampNow() {
337         return Double.toString(System.nanoTime() / (1000L * 1000L * 1000L)) + " sec";
338     }
339 
getStringOfPropertyValue(CarPropertyValue value)340     private String getStringOfPropertyValue(CarPropertyValue value) {
341         String defaultString = mNaString;
342         if (value != null) {
343             if (isArrayType(value.getPropertyId())) {
344                 defaultString = Arrays.toString((Object[]) value.getValue());
345             } else {
346                 defaultString = value.getValue().toString();
347             }
348         }
349         return defaultString;
350     }
351 
isArrayType(int propertyId)352     private boolean isArrayType(int propertyId) {
353         int mask = propertyId & VehiclePropertyType.MASK;
354         return mask == VehiclePropertyType.FLOAT_VEC
355                 || mask == VehiclePropertyType.INT32_VEC
356                 || mask == VehiclePropertyType.INT64_VEC;
357     }
358 
359     public class LocationInfoTextUpdateListener {
setLocationField(String value)360         public void setLocationField(String value) {
361             setTimestampedTextField(mLocationInfo, value);
362         }
363 
setAccelField(String value)364         public void setAccelField(String value) {
365             setTimestampedTextField(mAccelInfo, value);
366         }
367 
setGyroField(String value)368         public void setGyroField(String value) {
369             setTimestampedTextField(mGyroInfo, value);
370         }
371 
setMagField(String value)372         public void setMagField(String value) {
373             setTimestampedTextField(mMagInfo, value);
374         }
375 
setAccelUncalField(String value)376         public void setAccelUncalField(String value) {
377             setTimestampedTextField(mAccelUncalInfo, value);
378         }
379 
setGyroUncalField(String value)380         public void setGyroUncalField(String value) {
381             setTimestampedTextField(mGyroUncalInfo, value);
382         }
383 
setAccelLimitedAxesField(String value)384         public void setAccelLimitedAxesField(String value) {
385             setTimestampedTextField(mAccelLimitedAxesInfo, value);
386         }
387 
setGyroLimitedAxesField(String value)388         public void setGyroLimitedAxesField(String value) {
389             setTimestampedTextField(mGyroLimitedAxesInfo, value);
390         }
391 
setAccelLimitedAxesUncalField(String value)392         public void setAccelLimitedAxesUncalField(String value) {
393             setTimestampedTextField(mAccelLimitedAxesUncalInfo, value);
394         }
395 
setGyroLimitedAxesUncalField(String value)396         public void setGyroLimitedAxesUncalField(String value) {
397             setTimestampedTextField(mGyroLimitedAxesUncalInfo, value);
398         }
399 
setHeadingField(String value)400         public void setHeadingField(String value) {
401             setTimestampedTextField(mHeadingInfo, value);
402         }
403 
setTimestampedTextField(TextView text, String value)404         private void setTimestampedTextField(TextView text, String value) {
405             synchronized (SensorsTestFragment.this) {
406                 text.setText(getTimestampNow() + ": " + value);
407                 Log.d(TAG, "setText: " + value);
408             }
409         }
410     }
411 }
412