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.pm.PackageManager; 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 mValueMap.put(value.getPropertyId(), value); 120 refreshCarSensorInfoText(); 121 } 122 @Override 123 public void onErrorEvent(int propId, int zone) { 124 Log.e(TAG, "propId: " + propId + " zone: " + zone); 125 } 126 }; 127 128 private final Handler mHandler = new Handler(); 129 private final Map<Integer, CarPropertyValue> mValueMap = new ConcurrentHashMap<>(); 130 private KitchenSinkActivity mActivity; 131 private CarPropertyManager mCarPropertyManager; 132 private LocationListeners mLocationListener; 133 private String mNaString; 134 private List<CarPropertyConfig> mCarPropertyConfigs; 135 136 private TextView mCarSensorInfo; 137 private TextView mLocationInfo; 138 private TextView mAccelInfo; 139 private TextView mGyroInfo; 140 private TextView mMagInfo; 141 private TextView mAccelUncalInfo; 142 private TextView mGyroUncalInfo; 143 private TextView mAccelLimitedAxesInfo; 144 private TextView mGyroLimitedAxesInfo; 145 private TextView mAccelLimitedAxesUncalInfo; 146 private TextView mGyroLimitedAxesUncalInfo; 147 148 @Nullable 149 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)150 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 151 @Nullable Bundle savedInstanceState) { 152 if (DBG) { 153 Log.i(TAG, "onCreateView"); 154 } 155 156 View view = inflater.inflate(R.layout.sensors, container, false); 157 mActivity = (KitchenSinkActivity) getHost(); 158 mCarSensorInfo = (TextView) view.findViewById(R.id.car_sensor_info); 159 mCarSensorInfo.setMovementMethod(new ScrollingMovementMethod()); 160 mLocationInfo = (TextView) view.findViewById(R.id.location_info); 161 mLocationInfo.setMovementMethod(new ScrollingMovementMethod()); 162 mAccelInfo = (TextView) view.findViewById(R.id.accel_info); 163 mGyroInfo = (TextView) view.findViewById(R.id.gyro_info); 164 mMagInfo = (TextView) view.findViewById(R.id.mag_info); 165 mAccelUncalInfo = (TextView) view.findViewById(R.id.accel_uncal_info); 166 mGyroUncalInfo = (TextView) view.findViewById(R.id.gyro_uncal_info); 167 mAccelLimitedAxesInfo = (TextView) view.findViewById(R.id.accel_limited_axes_info); 168 mGyroLimitedAxesInfo = (TextView) view.findViewById(R.id.gyro_limited_axes_info); 169 mAccelLimitedAxesUncalInfo = 170 (TextView) view.findViewById(R.id.accel_limited_axes_uncal_info); 171 mGyroLimitedAxesUncalInfo = (TextView) view.findViewById(R.id.gyro_limited_axes_uncal_info); 172 173 mNaString = getContext().getString(R.string.sensor_na); 174 return view; 175 } 176 177 @Override onStart()178 public void onStart() { 179 super.onStart(); 180 initPermissions(); 181 } 182 183 @Override onResume()184 public void onResume() { 185 super.onResume(); 186 Set<String> missingPermissions = checkExistingPermissions(); 187 if (!missingPermissions.isEmpty()) { 188 Log.e(TAG, "Permissions not granted. Cannot initialize sensors. " + missingPermissions); 189 return; 190 } 191 192 ((KitchenSinkActivity) getActivity()).requestRefreshManager( 193 this::initSensors, new Handler(getContext().getMainLooper())); 194 } 195 196 @Override onPause()197 public void onPause() { 198 super.onPause(); 199 if (mCarPropertyManager != null) { 200 mCarPropertyManager.unregisterCallback(mCarPropertyEventCallback); 201 } 202 if (mLocationListener != null) { 203 mLocationListener.stopListening(); 204 } 205 } 206 initSensors()207 private void initSensors() { 208 try { 209 initCarSensor(); 210 initLocationSensor(); 211 } catch (Exception e) { 212 Log.e(TAG, "initSensors() exception caught ", e); 213 } 214 215 } 216 initCarSensor()217 private void initCarSensor() { 218 if (mCarPropertyManager == null) { 219 mCarPropertyManager = ((KitchenSinkActivity) getActivity()).getPropertyManager(); 220 } 221 mCarPropertyConfigs = mCarPropertyManager.getPropertyList(SENSORS_SET); 222 223 for (CarPropertyConfig property : mCarPropertyConfigs) { 224 float rate = CarPropertyManager.SENSOR_RATE_NORMAL; 225 if (property.getChangeMode() 226 == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) { 227 rate = CarPropertyManager.SENSOR_RATE_ONCHANGE; 228 } 229 mCarPropertyManager.registerCallback(mCarPropertyEventCallback, 230 property.getPropertyId(), rate); 231 } 232 } 233 initLocationSensor()234 private void initLocationSensor() { 235 if (mLocationListener == null) { 236 mLocationListener = new LocationListeners(getContext(), 237 new LocationInfoTextUpdateListener()); 238 } 239 mLocationListener.startListening(); 240 } 241 initPermissions()242 private void initPermissions() { 243 Set<String> missingPermissions = checkExistingPermissions(); 244 if (!missingPermissions.isEmpty()) { 245 requestPermissions(missingPermissions); 246 } 247 } 248 checkExistingPermissions()249 private Set<String> checkExistingPermissions() { 250 Set<String> missingPermissions = new HashSet<String>(); 251 for (String permission : REQUIRED_PERMISSIONS) { 252 if (mActivity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { 253 missingPermissions.add(permission); 254 } 255 } 256 return missingPermissions; 257 } 258 requestPermissions(Set<String> permissions)259 private void requestPermissions(Set<String> permissions) { 260 Log.d(TAG, "requesting additional permissions=" + permissions); 261 requestPermissions(permissions.toArray(new String[permissions.size()]), 262 KS_PERMISSIONS_REQUEST); 263 } 264 265 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)266 public void onRequestPermissionsResult(int requestCode, String[] permissions, 267 int[] grantResults) { 268 Log.d(TAG, "onRequestPermissionsResult reqCode=" + requestCode); 269 } 270 refreshCarSensorInfoText()271 private void refreshCarSensorInfoText() { 272 String summaryString; 273 List<String> summary = formSummary(); 274 summaryString = TextUtils.join("\n", summary); 275 mHandler.post(() -> mCarSensorInfo.setText(summaryString)); 276 } 277 formSummary()278 private List<String> formSummary() { 279 List<String> summary = new ArrayList<>(); 280 for (CarPropertyConfig propertyConfig : mCarPropertyConfigs) { 281 int propertyId = propertyConfig.getPropertyId(); 282 CarPropertyValue propertyValue = mValueMap.get(propertyId); 283 if (propertyValue != null 284 && propertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE) { 285 propertyValue = null; 286 } 287 int resourceId = PROPERTY_TO_RESOURCE.get(propertyId); 288 // for wheel_tick, add the configuration. 289 if (propertyId == VehiclePropertyIds.WHEEL_TICK) { 290 if (propertyValue != null) { 291 Long[] wheelTickData = (Long[]) propertyValue.getValue(); 292 summary.add(getContext().getString(R.string.sensor_wheel_ticks, 293 getTimestamp(propertyValue), 294 wheelTickData[0], 295 wheelTickData[1], 296 wheelTickData[2], 297 wheelTickData[3], 298 wheelTickData[4])); 299 } else { 300 summary.add(getContext().getString(R.string.sensor_wheel_ticks, 301 getTimestamp(propertyValue), 302 mNaString, mNaString, mNaString, mNaString, mNaString)); 303 } 304 List<Integer> configArray = propertyConfig.getConfigArray(); 305 summary.add(getContext().getString(R.string.sensor_wheel_ticks_cfg, 306 configArray.get(0), 307 configArray.get(1), 308 configArray.get(2), 309 configArray.get(3), 310 configArray.get(4))); 311 } else { 312 summary.add(getContext().getString( 313 resourceId, getTimestamp(propertyValue), 314 getStringOfPropertyValue(propertyValue))); 315 } 316 } 317 return summary; 318 } 319 getTimestamp(CarPropertyValue value)320 private String getTimestamp(CarPropertyValue value) { 321 if (value == null) { 322 return mNaString; 323 } 324 return Double.toString(value.getTimestamp() / (1000L * 1000L * 1000L)) + " sec"; 325 } 326 getTimestampNow()327 private String getTimestampNow() { 328 return Double.toString(System.nanoTime() / (1000L * 1000L * 1000L)) + " sec"; 329 } 330 getStringOfPropertyValue(CarPropertyValue value)331 private String getStringOfPropertyValue(CarPropertyValue value) { 332 String defaultString = mNaString; 333 if (value != null) { 334 if (isArrayType(value.getPropertyId())) { 335 defaultString = Arrays.toString((Object[]) value.getValue()); 336 } else { 337 defaultString = value.getValue().toString(); 338 } 339 } 340 return defaultString; 341 } 342 isArrayType(int propertyId)343 private boolean isArrayType(int propertyId) { 344 int mask = propertyId & VehiclePropertyType.MASK; 345 return mask == VehiclePropertyType.FLOAT_VEC 346 || mask == VehiclePropertyType.INT32_VEC 347 || mask == VehiclePropertyType.INT64_VEC; 348 } 349 350 public class LocationInfoTextUpdateListener { setLocationField(String value)351 public void setLocationField(String value) { 352 setTimestampedTextField(mLocationInfo, value); 353 } 354 setAccelField(String value)355 public void setAccelField(String value) { 356 setTimestampedTextField(mAccelInfo, value); 357 } 358 setGyroField(String value)359 public void setGyroField(String value) { 360 setTimestampedTextField(mGyroInfo, value); 361 } 362 setMagField(String value)363 public void setMagField(String value) { 364 setTimestampedTextField(mMagInfo, value); 365 } 366 setAccelUncalField(String value)367 public void setAccelUncalField(String value) { 368 setTimestampedTextField(mAccelUncalInfo, value); 369 } 370 setGyroUncalField(String value)371 public void setGyroUncalField(String value) { 372 setTimestampedTextField(mGyroUncalInfo, value); 373 } 374 setAccelLimitedAxesField(String value)375 public void setAccelLimitedAxesField(String value) { 376 setTimestampedTextField(mAccelLimitedAxesInfo, value); 377 } 378 setGyroLimitedAxesField(String value)379 public void setGyroLimitedAxesField(String value) { 380 setTimestampedTextField(mGyroLimitedAxesInfo, value); 381 } 382 setAccelLimitedAxesUncalField(String value)383 public void setAccelLimitedAxesUncalField(String value) { 384 setTimestampedTextField(mAccelLimitedAxesUncalInfo, value); 385 } 386 setGyroLimitedAxesUncalField(String value)387 public void setGyroLimitedAxesUncalField(String value) { 388 setTimestampedTextField(mGyroLimitedAxesUncalInfo, value); 389 } 390 setTimestampedTextField(TextView text, String value)391 private void setTimestampedTextField(TextView text, String value) { 392 synchronized (SensorsTestFragment.this) { 393 text.setText(getTimestampNow() + ": " + value); 394 Log.d(TAG, "setText: " + value); 395 } 396 } 397 } 398 } 399