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