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