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.content.pm.PackageManager; 23 import android.location.Location; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.support.car.CarNotConnectedException; 27 import android.support.car.hardware.CarSensorConfig; 28 import android.support.car.hardware.CarSensorEvent; 29 import android.support.car.hardware.CarSensorManager; 30 import android.support.v4.app.Fragment; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.view.LayoutInflater; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.widget.TextView; 37 38 import com.google.android.car.kitchensink.KitchenSinkActivity; 39 import com.google.android.car.kitchensink.R; 40 41 import java.text.DateFormat; 42 import java.text.SimpleDateFormat; 43 import java.util.ArrayList; 44 import java.util.Date; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Set; 49 import java.util.concurrent.ConcurrentHashMap; 50 51 public class SensorsTestFragment extends Fragment { 52 private static final String TAG = "CAR.SENSOR.KS"; 53 private static final boolean DBG = true; 54 private static final boolean DBG_VERBOSE = false; 55 private static final int KS_PERMISSIONS_REQUEST = 1; 56 57 private final static String[] REQUIRED_PERMISSIONS = new String[]{ 58 Manifest.permission.ACCESS_FINE_LOCATION, 59 Manifest.permission.ACCESS_COARSE_LOCATION, 60 Car.PERMISSION_MILEAGE, 61 Car.PERMISSION_FUEL, 62 Car.PERMISSION_SPEED, 63 Car.PERMISSION_VEHICLE_DYNAMICS_STATE 64 }; 65 66 private final CarSensorManager.OnSensorChangedListener mOnSensorChangedListener = 67 new CarSensorManager.OnSensorChangedListener() { 68 @Override 69 public void onSensorChanged(CarSensorManager manager, CarSensorEvent event) { 70 if (DBG_VERBOSE) { 71 Log.v(TAG, "New car sensor event: " + event); 72 } 73 synchronized (SensorsTestFragment.this) { 74 mEventMap.put(event.sensorType, event); 75 } 76 refreshUi(); 77 } 78 }; 79 private final Handler mHandler = new Handler(); 80 private final Map<Integer, CarSensorEvent> mEventMap = new ConcurrentHashMap<>(); 81 private final DateFormat mDateFormat = SimpleDateFormat.getDateTimeInstance(); 82 83 private KitchenSinkActivity mActivity; 84 private TextView mSensorInfo; 85 private Car mCar; 86 private CarSensorManager mSensorManager; 87 private String mNaString; 88 private int[] supportedSensors = new int[0]; 89 private Set<String> mActivePermissions = new HashSet<String>(); 90 91 92 @Nullable 93 @Override 94 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 95 @Nullable Bundle savedInstanceState) { 96 if (DBG) { 97 Log.i(TAG, "onCreateView"); 98 } 99 100 View view = inflater.inflate(R.layout.sensors, container, false); 101 mActivity = (KitchenSinkActivity) getHost(); 102 103 mSensorInfo = (TextView) view.findViewById(R.id.sensor_info); 104 mNaString = getContext().getString(R.string.sensor_na); 105 106 return view; 107 } 108 109 @Override 110 public void onResume() { 111 super.onResume(); 112 initPermissions(); 113 } 114 115 @Override 116 public void onPause() { 117 super.onPause(); 118 if (mSensorManager != null) { 119 mSensorManager.removeListener(mOnSensorChangedListener); 120 } 121 } 122 123 private void initSensors() { 124 try { 125 mSensorManager = (CarSensorManager) 126 mActivity.getCar().getCarManager(Car.SENSOR_SERVICE); 127 supportedSensors = mSensorManager.getSupportedSensors(); 128 for (Integer sensor : supportedSensors) { 129 if ((sensor == CarSensorManager.SENSOR_TYPE_LOCATION 130 || sensor == CarSensorManager.SENSOR_TYPE_GPS_SATELLITE) 131 && !mActivePermissions.contains(Manifest.permission.ACCESS_FINE_LOCATION)) { 132 continue; 133 } 134 mSensorManager.addListener(mOnSensorChangedListener, sensor, 135 CarSensorManager.SENSOR_RATE_NORMAL); 136 } 137 } catch (CarNotConnectedException e) { 138 Log.e(TAG, "Car not connected or not supported", e); 139 } 140 } 141 142 private void initPermissions() { 143 Set<String> missingPermissions = checkExistingPermissions(); 144 if (!missingPermissions.isEmpty()) { 145 requestPermissions(missingPermissions); 146 } else { 147 initSensors(); 148 } 149 } 150 151 private Set<String> checkExistingPermissions() { 152 Set<String> missingPermissions = new HashSet<String>(); 153 for (String permission : REQUIRED_PERMISSIONS) { 154 if (mActivity.checkSelfPermission(permission) 155 == PackageManager.PERMISSION_GRANTED) { 156 mActivePermissions.add(permission); 157 } else { 158 missingPermissions.add(permission); 159 } 160 } 161 return missingPermissions; 162 } 163 164 private void requestPermissions(Set<String> permissions) { 165 Log.d(TAG, "requesting additional permissions=" + permissions); 166 167 requestPermissions(permissions.toArray(new String[permissions.size()]), 168 KS_PERMISSIONS_REQUEST); 169 } 170 171 @Override 172 public void onRequestPermissionsResult(int requestCode, String[] permissions, 173 int[] grantResults) { 174 Log.d(TAG, "onRequestPermissionsResult reqCode=" + requestCode); 175 if (KS_PERMISSIONS_REQUEST == requestCode) { 176 for (int i=0; i<permissions.length; i++) { 177 if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { 178 mActivePermissions.add(permissions[i]); 179 } 180 } 181 initSensors(); 182 } 183 } 184 185 private void refreshUi() { 186 String summaryString; 187 synchronized (this) { 188 List<String> summary = new ArrayList<>(); 189 for (Integer i : supportedSensors) { 190 CarSensorEvent event = mEventMap.get(i); 191 switch (i) { 192 case CarSensorManager.SENSOR_TYPE_COMPASS: 193 summary.add(getCompassString(event)); 194 break; 195 case CarSensorManager.SENSOR_TYPE_CAR_SPEED: 196 summary.add(getContext().getString(R.string.sensor_speed, 197 getTimestamp(event), 198 event == null ? mNaString : event.getCarSpeedData().carSpeed)); 199 break; 200 case CarSensorManager.SENSOR_TYPE_RPM: 201 summary.add(getContext().getString(R.string.sensor_rpm, 202 getTimestamp(event), 203 event == null ? mNaString : event.getRpmData().rpm)); 204 break; 205 case CarSensorManager.SENSOR_TYPE_ODOMETER: 206 summary.add(getContext().getString(R.string.sensor_odometer, 207 getTimestamp(event), 208 event == null ? mNaString : event.getOdometerData().kms)); 209 break; 210 case CarSensorManager.SENSOR_TYPE_FUEL_LEVEL: 211 String level = mNaString; 212 String range = mNaString; 213 String lowFuelWarning = mNaString; 214 if (event != null) { 215 CarSensorEvent.FuelLevelData fuelData = event.getFuelLevelData(); 216 level = fuelData.level == -1 ? level : String.valueOf(fuelData.level); 217 range = fuelData.range == -1 ? range : String.valueOf(fuelData.range); 218 lowFuelWarning = String.valueOf(fuelData.lowFuelWarning); 219 } 220 summary.add(getContext().getString(R.string.sensor_fuel_level, 221 getTimestamp(event), level, range, lowFuelWarning)); 222 break; 223 case CarSensorManager.SENSOR_TYPE_PARKING_BRAKE: 224 summary.add(getContext().getString(R.string.sensor_parking_brake, 225 getTimestamp(event), 226 event == null ? mNaString : 227 event.getParkingBrakeData().isEngaged)); 228 break; 229 case CarSensorManager.SENSOR_TYPE_GEAR: 230 summary.add(getContext().getString(R.string.sensor_gear, 231 getTimestamp(event), 232 event == null ? mNaString : event.getGearData().gear)); 233 break; 234 case CarSensorManager.SENSOR_TYPE_NIGHT: 235 summary.add(getContext().getString(R.string.sensor_night, 236 getTimestamp(event), 237 event == null ? mNaString : event.getNightData().isNightMode)); 238 break; 239 case CarSensorManager.SENSOR_TYPE_LOCATION: 240 summary.add(getLocationString(event)); 241 break; 242 case CarSensorManager.SENSOR_TYPE_DRIVING_STATUS: 243 String drivingStatus = mNaString; 244 String binDrivingStatus = mNaString; 245 if (event != null) { 246 CarSensorEvent.DrivingStatusData drivingStatusData = 247 event.getDrivingStatusData(); 248 drivingStatus = String.valueOf(drivingStatusData.status); 249 binDrivingStatus = Integer.toBinaryString(drivingStatusData.status); 250 } 251 summary.add(getContext().getString(R.string.sensor_driving_status, 252 getTimestamp(event), drivingStatus, binDrivingStatus)); 253 break; 254 case CarSensorManager.SENSOR_TYPE_ENVIRONMENT: 255 String temperature = mNaString; 256 String pressure = mNaString; 257 if (event != null) { 258 CarSensorEvent.EnvironmentData env = event.getEnvironmentData(); 259 temperature = Float.isNaN(env.temperature) ? temperature : 260 String.valueOf(env.temperature); 261 pressure = Float.isNaN(env.pressure) ? pressure : 262 String.valueOf(env.pressure); 263 } 264 summary.add(getContext().getString(R.string.sensor_environment, 265 getTimestamp(event), temperature, pressure)); 266 break; 267 case CarSensorManager.SENSOR_TYPE_ACCELEROMETER: 268 summary.add(getAccelerometerString(event)); 269 break; 270 case CarSensorManager.SENSOR_TYPE_GPS_SATELLITE: 271 summary.add(getGpsSatelliteString(event)); 272 break; 273 case CarSensorManager.SENSOR_TYPE_GYROSCOPE: 274 summary.add(getGyroscopeString(event)); 275 break; 276 case CarSensorManager.SENSOR_TYPE_WHEEL_TICK_DISTANCE: 277 if(event != null) { 278 CarSensorEvent.CarWheelTickDistanceData d = 279 event.getCarWheelTickDistanceData(); 280 summary.add(getContext().getString(R.string.sensor_wheel_ticks, 281 getTimestamp(event), d.sensorResetCount, d.frontLeftWheelDistanceMm, 282 d.frontRightWheelDistanceMm, d.rearLeftWheelDistanceMm, 283 d.rearRightWheelDistanceMm)); 284 } else { 285 summary.add(getContext().getString(R.string.sensor_wheel_ticks, 286 getTimestamp(event), mNaString, mNaString, mNaString, mNaString, 287 mNaString)); 288 } 289 // Get the config data 290 try { 291 CarSensorConfig c = mSensorManager.getSensorConfig( 292 CarSensorManager.SENSOR_TYPE_WHEEL_TICK_DISTANCE); 293 summary.add(getContext().getString(R.string.sensor_wheel_ticks_cfg, 294 c.getInt(CarSensorConfig.WHEEL_TICK_DISTANCE_SUPPORTED_WHEELS), 295 c.getInt(CarSensorConfig.WHEEL_TICK_DISTANCE_FRONT_LEFT_UM_PER_TICK), 296 c.getInt(CarSensorConfig.WHEEL_TICK_DISTANCE_FRONT_RIGHT_UM_PER_TICK), 297 c.getInt(CarSensorConfig.WHEEL_TICK_DISTANCE_REAR_LEFT_UM_PER_TICK), 298 c.getInt(CarSensorConfig.WHEEL_TICK_DISTANCE_REAR_RIGHT_UM_PER_TICK))); 299 } catch (CarNotConnectedException e) { 300 Log.e(TAG, "Car not connected or not supported", e); 301 } 302 break; 303 case CarSensorManager.SENSOR_TYPE_ABS_ACTIVE: 304 summary.add(getContext().getString(R.string.sensor_abs_is_active, 305 getTimestamp(event), event == null ? mNaString : 306 event.getCarAbsActiveData().absIsActive)); 307 break; 308 309 case CarSensorManager.SENSOR_TYPE_TRACTION_CONTROL_ACTIVE: 310 summary.add( 311 getContext().getString(R.string.sensor_traction_control_is_active, 312 getTimestamp(event), event == null ? mNaString : 313 event.getCarTractionControlActiveData().tractionControlIsActive)); 314 break; 315 default: 316 // Should never happen. 317 Log.w(TAG, "Unrecognized event type: " + i); 318 } 319 } 320 summaryString = TextUtils.join("\n", summary); 321 } 322 mHandler.post(new Runnable() { 323 @Override 324 public void run() { 325 mSensorInfo.setText(summaryString); 326 } 327 }); 328 } 329 330 private String getTimestamp(CarSensorEvent event) { 331 if (event == null) { 332 return mNaString; 333 } 334 return mDateFormat.format(new Date(event.timestamp / 1000L)); 335 } 336 337 private String getCompassString(CarSensorEvent event) { 338 String bear = mNaString; 339 String pitch = mNaString; 340 String roll = mNaString; 341 if (event != null) { 342 CarSensorEvent.CompassData compass = event.getCompassData(); 343 bear = Float.isNaN(compass.bearing) ? bear : String.valueOf(compass.bearing); 344 pitch = Float.isNaN(compass.pitch) ? pitch : String.valueOf(compass.pitch); 345 roll = Float.isNaN(compass.roll) ? roll : String.valueOf(compass.roll); 346 } 347 return getContext().getString(R.string.sensor_compass, 348 getTimestamp(event), bear, pitch, roll); 349 } 350 351 private String getGyroscopeString(CarSensorEvent event) { 352 String x = mNaString; 353 String y = mNaString; 354 String z = mNaString; 355 if (event != null) { 356 CarSensorEvent.GyroscopeData gyro = event.getGyroscopeData(); 357 x = Float.isNaN(gyro.x) ? x : String.valueOf(gyro.x); 358 y = Float.isNaN(gyro.y) ? y : String.valueOf(gyro.y); 359 z = Float.isNaN(gyro.z) ? z : String.valueOf(gyro.z); 360 } 361 return getContext().getString(R.string.sensor_gyroscope, 362 getTimestamp(event), x, y, z); 363 } 364 365 private String getAccelerometerString(CarSensorEvent event) { 366 String x = mNaString; 367 String y = mNaString; 368 String z = mNaString; 369 if (event != null) { 370 CarSensorEvent.AccelerometerData gyro = event.getAccelerometerData(); 371 x = Float.isNaN(gyro.x) ? x : String.valueOf(gyro.x); 372 y = Float.isNaN(gyro.y) ? y : String.valueOf(gyro.y); 373 z = Float.isNaN(gyro.z) ? z : String.valueOf(gyro.z); 374 } 375 return getContext().getString(R.string.sensor_accelerometer, 376 getTimestamp(event), x, y, z); 377 } 378 379 private String getLocationString(CarSensorEvent event) { 380 String lat = mNaString; 381 String lon = mNaString; 382 String accuracy = mNaString; 383 String alt = mNaString; 384 String speed = mNaString; 385 String bearing = mNaString; 386 if (event != null) { 387 Location location = event.getLocation(null); 388 lat = String.valueOf(location.getLatitude()); 389 lon = String.valueOf(location.getLongitude()); 390 accuracy = location.hasAccuracy() ? String.valueOf(location.getAccuracy()) : accuracy; 391 alt = location.hasAltitude() ? String.valueOf(location.getAltitude()) : alt; 392 speed = location.hasSpeed() ? String.valueOf(location.getSpeed()) : speed; 393 bearing = location.hasBearing() ? String.valueOf(location.getBearing()) : bearing; 394 } 395 return getContext().getString(R.string.sensor_location, 396 getTimestamp(event), lat, lon, accuracy, alt, speed, bearing); 397 } 398 399 private String getGpsSatelliteString(CarSensorEvent event) { 400 String inUse = mNaString; 401 String inView = mNaString; 402 String perSattelite = ""; 403 if (event != null) { 404 CarSensorEvent.GpsSatelliteData gpsData = event.getGpsSatelliteData(true); 405 inUse = gpsData.numberInUse != -1 ? String.valueOf(gpsData.numberInUse) : inUse; 406 inView = gpsData.numberInView != -1 ? String.valueOf(gpsData.numberInView) : inView; 407 List<String> perSatteliteList = new ArrayList<>(); 408 int num = gpsData.usedInFix.length; 409 for (int i=0; i<num; i++) { 410 perSatteliteList.add(getContext().getString(R.string.sensor_single_gps_satellite, 411 i+1, gpsData.usedInFix[i], gpsData.prn[i], gpsData.snr[i], 412 gpsData.azimuth[i], gpsData.elevation[i])); 413 } 414 perSattelite = TextUtils.join(", ", perSatteliteList); 415 } 416 return getContext().getString(R.string.sensor_gps, 417 getTimestamp(event), inView, inUse, perSattelite); 418 } 419 } 420