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 static java.lang.Integer.toHexString;
20 
21 import android.Manifest;
22 import android.annotation.Nullable;
23 import android.car.Car;
24 import android.car.VehiclePropertyIds;
25 import android.car.hardware.CarPropertyConfig;
26 import android.car.hardware.CarPropertyValue;
27 import android.car.hardware.property.CarPropertyManager;
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.view.LayoutInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.TextView;
39 
40 import androidx.fragment.app.Fragment;
41 
42 import com.google.android.car.kitchensink.KitchenSinkActivity;
43 import com.google.android.car.kitchensink.R;
44 
45 import java.text.DateFormat;
46 import java.text.SimpleDateFormat;
47 import java.util.ArrayList;
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 
69     private final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
70             new CarPropertyManager.CarPropertyEventCallback() {
71                 @Override
72                 public void onChangeEvent(CarPropertyValue value) {
73                     if (DBG_VERBOSE) {
74                         Log.v(TAG, "New car property value: " + value);
75                     }
76                     if (value.getStatus() == CarPropertyValue.STATUS_AVAILABLE) {
77                         mValueMap.put(value.getPropertyId(), value);
78                     } else {
79                         mValueMap.put(value.getPropertyId(), null);
80                     }
81                     refreshSensorInfoText();
82                 }
83                 @Override
84                 public void onErrorEvent(int propId, int zone) {
85                     Log.e(TAG, "propId: " + propId + " zone: " + zone);
86                 }
87             };
88 
89     private final Handler mHandler = new Handler();
90     private final Map<Integer, CarPropertyValue> mValueMap = new ConcurrentHashMap<>();
91 
92 
93     private final DateFormat mDateFormat = SimpleDateFormat.getDateTimeInstance();
94 
95     private KitchenSinkActivity mActivity;
96     private Car mCar;
97 
98     private CarPropertyManager mCarPropertyManager;
99 
100     private LocationListeners mLocationListener;
101     private String mNaString;
102 
103     private List<CarPropertyConfig> mPropertyList;
104 
105     private Set<String> mActivePermissions = new HashSet<String>();
106 
107     private TextView mSensorInfo;
108     private TextView mLocationInfo;
109     private TextView mAccelInfo;
110     private TextView mGyroInfo;
111     private TextView mMagInfo;
112 
113     @Nullable
114     @Override
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)115     public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
116                              @Nullable Bundle savedInstanceState) {
117         if (DBG) {
118             Log.i(TAG, "onCreateView");
119         }
120 
121         View view = inflater.inflate(R.layout.sensors, container, false);
122         mActivity = (KitchenSinkActivity) getHost();
123         mSensorInfo = (TextView) view.findViewById(R.id.sensor_info);
124         mSensorInfo.setMovementMethod(new ScrollingMovementMethod());
125         mLocationInfo = (TextView) view.findViewById(R.id.location_info);
126         mLocationInfo.setMovementMethod(new ScrollingMovementMethod());
127         mAccelInfo = (TextView) view.findViewById(R.id.accel_info);
128         mGyroInfo = (TextView) view.findViewById(R.id.gyro_info);
129         mMagInfo = (TextView) view.findViewById(R.id.mag_info);
130 
131         mNaString = getContext().getString(R.string.sensor_na);
132         return view;
133     }
134 
135     @Override
onResume()136     public void onResume() {
137         super.onResume();
138         final Runnable r = () -> {
139             initPermissions();
140         };
141         ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
142                 new Handler(getContext().getMainLooper()));
143     }
144 
145     @Override
onPause()146     public void onPause() {
147         super.onPause();
148         if (mCarPropertyManager != null) {
149             mCarPropertyManager.unregisterCallback(mCarPropertyEventCallback);
150         }
151         if (mLocationListener != null) {
152             mLocationListener.stopListening();
153         }
154     }
155 
initSensors()156     private void initSensors() {
157         try {
158             if (mCarPropertyManager == null) {
159                 mCarPropertyManager =
160                     (CarPropertyManager) ((KitchenSinkActivity) getActivity()).getPropertyManager();
161             }
162             ArraySet<Integer> set = new ArraySet<>();
163             set.add(VehiclePropertyIds.PERF_VEHICLE_SPEED);
164             set.add(VehiclePropertyIds.ENGINE_RPM);
165             set.add(VehiclePropertyIds.PERF_ODOMETER);
166             set.add(VehiclePropertyIds.FUEL_LEVEL);
167             set.add(VehiclePropertyIds.FUEL_DOOR_OPEN);
168             set.add(VehiclePropertyIds.IGNITION_STATE);
169             set.add(VehiclePropertyIds.PARKING_BRAKE_ON);
170             set.add(VehiclePropertyIds.GEAR_SELECTION);
171             set.add(VehiclePropertyIds.NIGHT_MODE);
172             set.add(VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE);
173             set.add(VehiclePropertyIds.WHEEL_TICK);
174             set.add(VehiclePropertyIds.ABS_ACTIVE);
175             set.add(VehiclePropertyIds.TRACTION_CONTROL_ACTIVE);
176             set.add(VehiclePropertyIds.EV_BATTERY_LEVEL);
177             set.add(VehiclePropertyIds.EV_CHARGE_PORT_OPEN);
178             set.add(VehiclePropertyIds.EV_CHARGE_PORT_CONNECTED);
179             set.add(VehiclePropertyIds.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE);
180             set.add(VehiclePropertyIds.ENGINE_OIL_LEVEL);
181 
182             mPropertyList = mCarPropertyManager.getPropertyList(set);
183 
184             for (CarPropertyConfig property : mPropertyList) {
185                 float rate = CarPropertyManager.SENSOR_RATE_NORMAL;
186                 if (property.getChangeMode()
187                         == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
188                     rate = CarPropertyManager.SENSOR_RATE_ONCHANGE;
189                 }
190                 mCarPropertyManager.registerCallback(mCarPropertyEventCallback,
191                               property.getPropertyId(), rate);
192             }
193         } catch (Exception e) {
194             Log.e(TAG, "initSensors() exception caught SensorManager: ", e);
195         }
196         try {
197             if (mLocationListener == null) {
198                 mLocationListener = new LocationListeners(getContext(),
199                                                           new LocationInfoTextUpdateListener());
200             }
201             mLocationListener.startListening();
202         } catch (Exception e) {
203             Log.e(TAG, "initSensors() exception caught from LocationListeners: ", e);
204         }
205     }
206 
initPermissions()207     private void initPermissions() {
208         Set<String> missingPermissions = checkExistingPermissions();
209         if (!missingPermissions.isEmpty()) {
210             requestPermissions(missingPermissions);
211             // The callback with premission results will take care of calling initSensors for us
212         } else {
213             initSensors();
214         }
215     }
216 
checkExistingPermissions()217     private Set<String> checkExistingPermissions() {
218         Set<String> missingPermissions = new HashSet<String>();
219         for (String permission : REQUIRED_PERMISSIONS) {
220             if (mActivity.checkSelfPermission(permission)
221                 == PackageManager.PERMISSION_GRANTED) {
222                 mActivePermissions.add(permission);
223             } else {
224                 missingPermissions.add(permission);
225             }
226         }
227         return missingPermissions;
228     }
229 
requestPermissions(Set<String> permissions)230     private void requestPermissions(Set<String> permissions) {
231         Log.d(TAG, "requesting additional permissions=" + permissions);
232 
233         requestPermissions(permissions.toArray(new String[permissions.size()]),
234                 KS_PERMISSIONS_REQUEST);
235     }
236 
237     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)238     public void onRequestPermissionsResult(int requestCode, String[] permissions,
239             int[] grantResults) {
240         Log.d(TAG, "onRequestPermissionsResult reqCode=" + requestCode);
241         if (KS_PERMISSIONS_REQUEST == requestCode) {
242             for (int i=0; i<permissions.length; i++) {
243                 if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
244                     mActivePermissions.add(permissions[i]);
245                 }
246             }
247             initSensors();
248         }
249     }
250 
refreshSensorInfoText()251     private void refreshSensorInfoText() {
252         String summaryString;
253         synchronized (this) {
254             List<String> summary = new ArrayList<>();
255             for (CarPropertyConfig property : mPropertyList) {
256                 int propertyId = property.getPropertyId();
257                 CarPropertyValue value = mValueMap.get(propertyId);
258                 switch (propertyId) {
259                     case VehiclePropertyIds.PERF_VEHICLE_SPEED: //0x11600207  291504647
260                         summary.add(getContext().getString(R.string.sensor_speed,
261                                 getTimestamp(value),
262                                 value == null ? mNaString : (float) value.getValue()));
263                         break;
264                     case VehiclePropertyIds.ENGINE_RPM: //0x11600305  291504901
265                         summary.add(getContext().getString(R.string.sensor_rpm,
266                                 getTimestamp(value),
267                                 value == null ? mNaString : (float) value.getValue()));
268                         break;
269                     case VehiclePropertyIds.PERF_ODOMETER: //0x11600204 291504644
270                         summary.add(getContext().getString(R.string.sensor_odometer,
271                                 getTimestamp(value),
272                                 value == null ? mNaString : (float) value.getValue()));
273                         break;
274                     case VehiclePropertyIds.FUEL_LEVEL: //0x11600307 291504903
275                         summary.add(getFuelLevel(value));
276                         break;
277                     case VehiclePropertyIds.FUEL_DOOR_OPEN: //0x11200308 287310600
278                         summary.add(getFuelDoorOpen(value));
279                         break;
280                     case VehiclePropertyIds.IGNITION_STATE: //0x11400409 289408009
281                         summary.add(getContext().getString(R.string.sensor_ignition_status,
282                                 getTimestamp(value),
283                                 value == null ? mNaString :
284                                 (int) value.getValue()));
285                         break;
286                     case VehiclePropertyIds.PARKING_BRAKE_ON: //0x11200402 287310850
287                         summary.add(getContext().getString(R.string.sensor_parking_brake,
288                                 getTimestamp(value),
289                                 value == null ? mNaString :
290                                 value.getValue()));
291                         break;
292                     case VehiclePropertyIds.GEAR_SELECTION: //0x11400400 289408000
293                         summary.add(getContext().getString(R.string.sensor_gear,
294                                 getTimestamp(value),
295                                 value == null ? mNaString : (int) value.getValue()));
296                         break;
297                     case VehiclePropertyIds.NIGHT_MODE: //0x11200407 287310855
298                         summary.add(getContext().getString(R.string.sensor_night,
299                                 getTimestamp(value),
300                                 value == null ? mNaString : value.getValue()));
301                         break;
302                     case VehiclePropertyIds.ENV_OUTSIDE_TEMPERATURE: //0x11600703 291505923
303                         String temperature = mNaString;
304                         if (value != null) {
305                             temperature = String.valueOf((float) value.getValue());
306                         }
307                         summary.add(getContext().getString(R.string.sensor_environment,
308                                 getTimestamp(value), temperature));
309                         break;
310                     case VehiclePropertyIds.WHEEL_TICK: //0x11510306 290521862
311                         if (value != null) {
312                             Long[] wheelTickData = (Long[]) value.getValue();
313                             summary.add(getContext().getString(R.string.sensor_wheel_ticks,
314                                     getTimestamp(value),
315                                     wheelTickData[0],
316                                     wheelTickData[1],
317                                     wheelTickData[2],
318                                     wheelTickData[3],
319                                     wheelTickData[4]));
320                         } else {
321                             summary.add(getContext().getString(R.string.sensor_wheel_ticks,
322                                     getTimestamp(value), mNaString, mNaString, mNaString, mNaString,
323                                     mNaString));
324                         }
325                         List<Integer> wheelProperties = property.getConfigArray();
326                         summary.add(getContext().getString(R.string.sensor_wheel_ticks_cfg,
327                                     wheelProperties.get(0),
328                                     wheelProperties.get(1),
329                                     wheelProperties.get(2),
330                                     wheelProperties.get(3),
331                                     wheelProperties.get(4)));
332                         break;
333                     case VehiclePropertyIds.ABS_ACTIVE: //0x1120040a 287310858
334                         summary.add(getContext().getString(R.string.sensor_abs_is_active,
335                                 getTimestamp(value), value == null ? mNaString :
336                                     value.getValue()));
337                         break;
338 
339                     case VehiclePropertyIds.TRACTION_CONTROL_ACTIVE: //0x1120040b 287310859
340                         summary.add(
341                             getContext().getString(R.string.sensor_traction_control_is_active,
342                                 getTimestamp(value), value == null ? mNaString :
343                                     value.getValue()));
344                         break;
345                     case VehiclePropertyIds.EV_BATTERY_LEVEL: //0x11600309 291504905
346                         summary.add(getEvBatteryLevel(value));
347                         break;
348                     case VehiclePropertyIds.EV_CHARGE_PORT_OPEN: //0x1120030a 287310602
349                         summary.add(getEvChargePortOpen(value));
350                         break;
351                     case VehiclePropertyIds.EV_CHARGE_PORT_CONNECTED: //0x1120030b 287310603
352                         summary.add(getEvChargePortConnected(value));
353                         break;
354                     case VehiclePropertyIds.EV_BATTERY_INSTANTANEOUS_CHARGE_RATE:
355                         summary.add(getEvChargeRate(value));
356                         break;
357                     case VehiclePropertyIds.ENGINE_OIL_LEVEL: //0x11400303 289407747
358                         summary.add(getEngineOilLevel(value));
359                         break;
360                     default:
361                         // Should never happen.
362                         Log.w(TAG, "Unrecognized event type: " + toHexString(propertyId));
363                 }
364             }
365             summaryString = TextUtils.join("\n", summary);
366         }
367         mHandler.post(new Runnable() {
368             @Override
369             public void run() {
370                 mSensorInfo.setText(summaryString);
371             }
372         });
373     }
374 
getTimestamp(CarPropertyValue value)375     private String getTimestamp(CarPropertyValue value) {
376         if (value == null) {
377             return mNaString;
378         }
379         return Double.toString(value.getTimestamp() / (1000L * 1000L * 1000L)) + " seconds";
380     }
381 
getTimestampNow()382     private String getTimestampNow() {
383         return Double.toString(System.nanoTime() / (1000L * 1000L * 1000L)) + " seconds";
384     }
385 
getFuelLevel(CarPropertyValue value)386     private String getFuelLevel(CarPropertyValue value) {
387         String fuelLevel = mNaString;
388         if (value != null) {
389             fuelLevel = String.valueOf((float) value.getValue());
390         }
391         return getContext().getString(R.string.sensor_fuel_level, getTimestamp(value), fuelLevel);
392     }
393 
getFuelDoorOpen(CarPropertyValue value)394     private String getFuelDoorOpen(CarPropertyValue value) {
395         String fuelDoorOpen = mNaString;
396         if (value != null) {
397             fuelDoorOpen = String.valueOf(value.getValue());
398         }
399         return getContext().getString(R.string.sensor_fuel_door_open, getTimestamp(value),
400             fuelDoorOpen);
401     }
402 
getEvBatteryLevel(CarPropertyValue value)403     private String getEvBatteryLevel(CarPropertyValue value) {
404         String evBatteryLevel = mNaString;
405         if (value != null) {
406             evBatteryLevel = String.valueOf((float) value.getValue());
407         }
408         return getContext().getString(R.string.sensor_ev_battery_level, getTimestamp(value),
409             evBatteryLevel);
410     }
411 
getEvChargePortOpen(CarPropertyValue value)412     private String getEvChargePortOpen(CarPropertyValue value) {
413         String evChargePortOpen = mNaString;
414         if (value != null) {
415             evChargePortOpen = String.valueOf((float) value.getValue());
416         }
417         return getContext().getString(R.string.sensor_ev_charge_port_is_open, getTimestamp(value),
418             evChargePortOpen);
419     }
420 
getEvChargePortConnected(CarPropertyValue value)421     private String getEvChargePortConnected(CarPropertyValue value) {
422         String evChargePortConnected = mNaString;
423         if (value != null) {
424             evChargePortConnected = String.valueOf((float) value.getValue());
425         }
426         return getContext().getString(R.string.sensor_ev_charge_port_is_connected,
427             getTimestamp(value), evChargePortConnected);
428     }
429 
getEvChargeRate(CarPropertyValue value)430     private String getEvChargeRate(CarPropertyValue value) {
431         String evChargeRate = mNaString;
432         if (value != null) {
433             evChargeRate = String.valueOf((float) value.getValue());
434         }
435         return getContext().getString(R.string.sensor_ev_charge_rate, getTimestamp(value),
436             evChargeRate);
437     }
438 
getEngineOilLevel(CarPropertyValue value)439     private String getEngineOilLevel(CarPropertyValue value) {
440         String engineOilLevel = mNaString;
441         if (value != null) {
442             engineOilLevel = String.valueOf((float) value.getValue());
443         }
444         return  getContext().getString(R.string.sensor_oil_level, getTimestamp(value),
445            engineOilLevel);
446     }
447 
448     public class LocationInfoTextUpdateListener {
setLocationField(String value)449         public void setLocationField(String value) {
450             setTimestampedTextField(mLocationInfo, value);
451         }
452 
setAccelField(String value)453         public void setAccelField(String value) {
454             setTimestampedTextField(mAccelInfo, value);
455         }
456 
setGyroField(String value)457         public void setGyroField(String value) {
458             setTimestampedTextField(mGyroInfo, value);
459         }
460 
setMagField(String value)461         public void setMagField(String value) {
462             setTimestampedTextField(mMagInfo, value);
463         }
464 
setTimestampedTextField(TextView text, String value)465         private void setTimestampedTextField(TextView text, String value) {
466             synchronized (SensorsTestFragment.this) {
467                 text.setText(getTimestampNow() + ": " + value);
468                 Log.d(TAG, "setText: " + value);
469             }
470         }
471     }
472 }
473