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