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