package com.github.pires.obd.reader.activity; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.bluetooth.BluetoothAdapter; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.util.Log; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; import android.widget.LinearLayout; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TextView; import android.widget.Toast; import com.github.pires.obd.commands.ObdCommand; import com.github.pires.obd.commands.SpeedCommand; import com.github.pires.obd.commands.engine.RPMCommand; import com.github.pires.obd.commands.engine.RuntimeCommand; import com.github.pires.obd.enums.AvailableCommandNames; import com.github.pires.obd.reader.R; import com.github.pires.obd.reader.config.ObdConfig; import com.github.pires.obd.reader.io.AbstractGatewayService; import com.github.pires.obd.reader.io.LogCSVWriter; import com.github.pires.obd.reader.io.MockObdGatewayService; import com.github.pires.obd.reader.io.ObdCommandJob; import com.github.pires.obd.reader.io.ObdGatewayService; import com.github.pires.obd.reader.io.ObdProgressListener; import com.github.pires.obd.reader.net.ObdReading; import com.github.pires.obd.reader.net.ObdService; import com.github.pires.obd.reader.trips.TripLog; import com.github.pires.obd.reader.trips.TripRecord; import com.google.inject.Inject; import java.io.FileNotFoundException; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import retrofit.RestAdapter; import retrofit.RetrofitError; import retrofit.client.Response; import roboguice.RoboGuice; import roboguice.activity.RoboActivity; import roboguice.inject.ContentView; import roboguice.inject.InjectView; import static com.github.pires.obd.reader.activity.ConfigActivity.getGpsDistanceUpdatePeriod; import static com.github.pires.obd.reader.activity.ConfigActivity.getGpsUpdatePeriod; // Some code taken from https://github.com/barbeau/gpstest @ContentView(R.layout.main) public class MainActivity extends RoboActivity implements ObdProgressListener, LocationListener, GpsStatus.Listener { private static final String TAG = MainActivity.class.getName(); private static final int NO_BLUETOOTH_ID = 0; private static final int BLUETOOTH_DISABLED = 1; private static final int START_LIVE_DATA = 2; private static final int STOP_LIVE_DATA = 3; private static final int SETTINGS = 4; private static final int GET_DTC = 5; private static final int TABLE_ROW_MARGIN = 7; private static final int NO_ORIENTATION_SENSOR = 8; private static final int NO_GPS_SUPPORT = 9; private static final int TRIPS_LIST = 10; private static final int SAVE_TRIP_NOT_AVAILABLE = 11; private static final int REQUEST_ENABLE_BT = 1234; private static boolean bluetoothDefaultIsEnable = false; static { RoboGuice.setUseAnnotationDatabases(false); } public Map<String, String> commandResult = new HashMap<String, String>(); boolean mGpsIsStarted = false; private LocationManager mLocService; private LocationProvider mLocProvider; private LogCSVWriter myCSVWriter; private Location mLastLocation; /// the trip log private TripLog triplog; private TripRecord currentTrip; @InjectView(R.id.compass_text) private TextView compass; private final SensorEventListener orientListener = new SensorEventListener() { public void onSensorChanged(SensorEvent event) { float x = event.values[0]; String dir = ""; if (x >= 337.5 || x < 22.5) { dir = "N"; } else if (x >= 22.5 && x < 67.5) { dir = "NE"; } else if (x >= 67.5 && x < 112.5) { dir = "E"; } else if (x >= 112.5 && x < 157.5) { dir = "SE"; } else if (x >= 157.5 && x < 202.5) { dir = "S"; } else if (x >= 202.5 && x < 247.5) { dir = "SW"; } else if (x >= 247.5 && x < 292.5) { dir = "W"; } else if (x >= 292.5 && x < 337.5) { dir = "NW"; } updateTextView(compass, dir); } public void onAccuracyChanged(Sensor sensor, int accuracy) { // do nothing } }; @InjectView(R.id.BT_STATUS) private TextView btStatusTextView; @InjectView(R.id.OBD_STATUS) private TextView obdStatusTextView; @InjectView(R.id.GPS_POS) private TextView gpsStatusTextView; @InjectView(R.id.vehicle_view) private LinearLayout vv; @InjectView(R.id.data_table) private TableLayout tl; @Inject private SensorManager sensorManager; @Inject private PowerManager powerManager; @Inject private SharedPreferences prefs; private boolean isServiceBound; private AbstractGatewayService service; private final Runnable mQueueCommands = new Runnable() { public void run() { if (service != null && service.isRunning() && service.queueEmpty()) { queueCommands(); double lat = 0; double lon = 0; double alt = 0; final int posLen = 7; if (mGpsIsStarted && mLastLocation != null) { lat = mLastLocation.getLatitude(); lon = mLastLocation.getLongitude(); alt = mLastLocation.getAltitude(); StringBuilder sb = new StringBuilder(); sb.append("Lat: "); sb.append(String.valueOf(mLastLocation.getLatitude()).substring(0, posLen)); sb.append(" Lon: "); sb.append(String.valueOf(mLastLocation.getLongitude()).substring(0, posLen)); sb.append(" Alt: "); sb.append(String.valueOf(mLastLocation.getAltitude())); gpsStatusTextView.setText(sb.toString()); } if (prefs.getBoolean(ConfigActivity.UPLOAD_DATA_KEY, false)) { // Upload the current reading by http final String vin = prefs.getString(ConfigActivity.VEHICLE_ID_KEY, "UNDEFINED_VIN"); Map<String, String> temp = new HashMap<String, String>(); temp.putAll(commandResult); ObdReading reading = new ObdReading(lat, lon, alt, System.currentTimeMillis(), vin, temp); new UploadAsyncTask().execute(reading); } else if (prefs.getBoolean(ConfigActivity.ENABLE_FULL_LOGGING_KEY, false)) { // Write the current reading to CSV final String vin = prefs.getString(ConfigActivity.VEHICLE_ID_KEY, "UNDEFINED_VIN"); Map<String, String> temp = new HashMap<String, String>(); temp.putAll(commandResult); ObdReading reading = new ObdReading(lat, lon, alt, System.currentTimeMillis(), vin, temp); if(reading != null) myCSVWriter.writeLineCSV(reading); } commandResult.clear(); } // run again in period defined in preferences new Handler().postDelayed(mQueueCommands, ConfigActivity.getObdUpdatePeriod(prefs)); } }; private Sensor orientSensor = null; private PowerManager.WakeLock wakeLock = null; private boolean preRequisites = true; private ServiceConnection serviceConn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder binder) { Log.d(TAG, className.toString() + " service is bound"); isServiceBound = true; service = ((AbstractGatewayService.AbstractGatewayServiceBinder) binder).getService(); service.setContext(MainActivity.this); Log.d(TAG, "Starting live data"); try { service.startService(); if (preRequisites) btStatusTextView.setText(getString(R.string.status_bluetooth_connected)); } catch (IOException ioe) { Log.e(TAG, "Failure Starting live data"); btStatusTextView.setText(getString(R.string.status_bluetooth_error_connecting)); doUnbindService(); } } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } // This method is *only* called when the connection to the service is lost unexpectedly // and *not* when the client unbinds (http://developer.android.com/guide/components/bound-services.html) // So the isServiceBound attribute should also be set to false when we unbind from the service. @Override public void onServiceDisconnected(ComponentName className) { Log.d(TAG, className.toString() + " service is unbound"); isServiceBound = false; } }; public static String LookUpCommand(String txt) { for (AvailableCommandNames item : AvailableCommandNames.values()) { if (item.getValue().equals(txt)) return item.name(); } return txt; } public void updateTextView(final TextView view, final String txt) { new Handler().post(new Runnable() { public void run() { view.setText(txt); } }); } public void stateUpdate(final ObdCommandJob job) { final String cmdName = job.getCommand().getName(); String cmdResult = ""; final String cmdID = LookUpCommand(cmdName); if (job.getState().equals(ObdCommandJob.ObdCommandJobState.EXECUTION_ERROR)) { cmdResult = job.getCommand().getResult(); if (cmdResult != null && isServiceBound) { obdStatusTextView.setText(cmdResult.toLowerCase()); } } else if (job.getState().equals(ObdCommandJob.ObdCommandJobState.BROKEN_PIPE)) { if (isServiceBound) stopLiveData(); } else if (job.getState().equals(ObdCommandJob.ObdCommandJobState.NOT_SUPPORTED)) { cmdResult = getString(R.string.status_obd_no_support); } else { cmdResult = job.getCommand().getFormattedResult(); if(isServiceBound) obdStatusTextView.setText(getString(R.string.status_obd_data)); } if (vv.findViewWithTag(cmdID) != null) { TextView existingTV = (TextView) vv.findViewWithTag(cmdID); existingTV.setText(cmdResult); } else addTableRow(cmdID, cmdName, cmdResult); commandResult.put(cmdID, cmdResult); updateTripStatistic(job, cmdID); } private boolean gpsInit() { mLocService = (LocationManager) getSystemService(Context.LOCATION_SERVICE); if (mLocService != null) { mLocProvider = mLocService.getProvider(LocationManager.GPS_PROVIDER); if (mLocProvider != null) { mLocService.addGpsStatusListener(this); if (mLocService.isProviderEnabled(LocationManager.GPS_PROVIDER)) { gpsStatusTextView.setText(getString(R.string.status_gps_ready)); return true; } } } gpsStatusTextView.setText(getString(R.string.status_gps_no_support)); showDialog(NO_GPS_SUPPORT); Log.e(TAG, "Unable to get GPS PROVIDER"); // todo disable gps controls into Preferences return false; } private void updateTripStatistic(final ObdCommandJob job, final String cmdID) { if (currentTrip != null) { if (cmdID.equals(AvailableCommandNames.SPEED.toString())) { SpeedCommand command = (SpeedCommand) job.getCommand(); currentTrip.setSpeedMax(command.getMetricSpeed()); } else if (cmdID.equals(AvailableCommandNames.ENGINE_RPM.toString())) { RPMCommand command = (RPMCommand) job.getCommand(); currentTrip.setEngineRpmMax(command.getRPM()); } else if (cmdID.endsWith(AvailableCommandNames.ENGINE_RUNTIME.toString())) { RuntimeCommand command = (RuntimeCommand) job.getCommand(); currentTrip.setEngineRuntime(command.getFormattedResult()); } } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); if (btAdapter != null) bluetoothDefaultIsEnable = btAdapter.isEnabled(); // get Orientation sensor List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ORIENTATION); if (sensors.size() > 0) orientSensor = sensors.get(0); else showDialog(NO_ORIENTATION_SENSOR); // create a log instance for use by this application triplog = TripLog.getInstance(this.getApplicationContext()); obdStatusTextView.setText(getString(R.string.status_obd_disconnected)); } @Override protected void onStart() { super.onStart(); Log.d(TAG, "Entered onStart..."); } @Override protected void onDestroy() { super.onDestroy(); if (mLocService != null) { mLocService.removeGpsStatusListener(this); mLocService.removeUpdates(this); } releaseWakeLockIfHeld(); if (isServiceBound) { doUnbindService(); } endTrip(); final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); if (btAdapter != null && btAdapter.isEnabled() && !bluetoothDefaultIsEnable) btAdapter.disable(); } @Override protected void onPause() { super.onPause(); Log.d(TAG, "Pausing.."); releaseWakeLockIfHeld(); } /** * If lock is held, release. Lock will be held when the service is running. */ private void releaseWakeLockIfHeld() { if (wakeLock.isHeld()) wakeLock.release(); } protected void onResume() { super.onResume(); Log.d(TAG, "Resuming.."); sensorManager.registerListener(orientListener, orientSensor, SensorManager.SENSOR_DELAY_UI); wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "ObdReader"); // get Bluetooth device final BluetoothAdapter btAdapter = BluetoothAdapter .getDefaultAdapter(); preRequisites = btAdapter != null && btAdapter.isEnabled(); if (!preRequisites && prefs.getBoolean(ConfigActivity.ENABLE_BT_KEY, false)) { preRequisites = btAdapter != null && btAdapter.enable(); } gpsInit(); if (!preRequisites) { showDialog(BLUETOOTH_DISABLED); btStatusTextView.setText(getString(R.string.status_bluetooth_disabled)); } else { btStatusTextView.setText(getString(R.string.status_bluetooth_ok)); } } private void updateConfig() { startActivity(new Intent(this, ConfigActivity.class)); } public boolean onCreateOptionsMenu(Menu menu) { menu.add(0, START_LIVE_DATA, 0, getString(R.string.menu_start_live_data)); menu.add(0, STOP_LIVE_DATA, 0, getString(R.string.menu_stop_live_data)); menu.add(0, GET_DTC, 0, getString(R.string.menu_get_dtc)); menu.add(0, TRIPS_LIST, 0, getString(R.string.menu_trip_list)); menu.add(0, SETTINGS, 0, getString(R.string.menu_settings)); return true; } public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case START_LIVE_DATA: startLiveData(); return true; case STOP_LIVE_DATA: stopLiveData(); return true; case SETTINGS: updateConfig(); return true; case GET_DTC: getTroubleCodes(); return true; case TRIPS_LIST: startActivity(new Intent(this, TripListActivity.class)); return true; } return false; } private void getTroubleCodes() { startActivity(new Intent(this, TroubleCodesActivity.class)); } private void startLiveData() { Log.d(TAG, "Starting live data.."); tl.removeAllViews(); //start fresh doBindService(); currentTrip = triplog.startTrip(); if (currentTrip == null) showDialog(SAVE_TRIP_NOT_AVAILABLE); // start command execution new Handler().post(mQueueCommands); if (prefs.getBoolean(ConfigActivity.ENABLE_GPS_KEY, false)) gpsStart(); else gpsStatusTextView.setText(getString(R.string.status_gps_not_used)); // screen won't turn off until wakeLock.release() wakeLock.acquire(); if (prefs.getBoolean(ConfigActivity.ENABLE_FULL_LOGGING_KEY, false)) { // Create the CSV Logger long mils = System.currentTimeMillis(); SimpleDateFormat sdf = new SimpleDateFormat("_dd_MM_yyyy_HH_mm_ss"); try { myCSVWriter = new LogCSVWriter("Log" + sdf.format(new Date(mils)).toString() + ".csv", prefs.getString(ConfigActivity.DIRECTORY_FULL_LOGGING_KEY, getString(R.string.default_dirname_full_logging)) ); } catch (FileNotFoundException | RuntimeException e) { Log.e(TAG, "Can't enable logging to file.", e); } } } private void stopLiveData() { Log.d(TAG, "Stopping live data.."); gpsStop(); doUnbindService(); endTrip(); releaseWakeLockIfHeld(); final String devemail = prefs.getString(ConfigActivity.DEV_EMAIL_KEY, null); if (devemail != null && !devemail.isEmpty()) { DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_POSITIVE: ObdGatewayService.saveLogcatToFile(getApplicationContext(), devemail); break; case DialogInterface.BUTTON_NEGATIVE: //No button clicked break; } } }; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("Where there issues?\nThen please send us the logs.\nSend Logs?").setPositiveButton("Yes", dialogClickListener) .setNegativeButton("No", dialogClickListener).show(); } if (myCSVWriter != null) { myCSVWriter.closeLogCSVWriter(); } } protected void endTrip() { if (currentTrip != null) { currentTrip.setEndDate(new Date()); triplog.updateRecord(currentTrip); } } protected Dialog onCreateDialog(int id) { AlertDialog.Builder build = new AlertDialog.Builder(this); switch (id) { case NO_BLUETOOTH_ID: build.setMessage(getString(R.string.text_no_bluetooth_id)); return build.create(); case BLUETOOTH_DISABLED: Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); return build.create(); case NO_ORIENTATION_SENSOR: build.setMessage(getString(R.string.text_no_orientation_sensor)); return build.create(); case NO_GPS_SUPPORT: build.setMessage(getString(R.string.text_no_gps_support)); return build.create(); case SAVE_TRIP_NOT_AVAILABLE: build.setMessage(getString(R.string.text_save_trip_not_available)); return build.create(); } return null; } public boolean onPrepareOptionsMenu(Menu menu) { MenuItem startItem = menu.findItem(START_LIVE_DATA); MenuItem stopItem = menu.findItem(STOP_LIVE_DATA); MenuItem settingsItem = menu.findItem(SETTINGS); MenuItem getDTCItem = menu.findItem(GET_DTC); if (service != null && service.isRunning()) { getDTCItem.setEnabled(false); startItem.setEnabled(false); stopItem.setEnabled(true); settingsItem.setEnabled(false); } else { getDTCItem.setEnabled(true); stopItem.setEnabled(false); startItem.setEnabled(true); settingsItem.setEnabled(true); } return true; } private void addTableRow(String id, String key, String val) { TableRow tr = new TableRow(this); MarginLayoutParams params = new ViewGroup.MarginLayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); params.setMargins(TABLE_ROW_MARGIN, TABLE_ROW_MARGIN, TABLE_ROW_MARGIN, TABLE_ROW_MARGIN); tr.setLayoutParams(params); TextView name = new TextView(this); name.setGravity(Gravity.RIGHT); name.setText(key + ": "); TextView value = new TextView(this); value.setGravity(Gravity.LEFT); value.setText(val); value.setTag(id); tr.addView(name); tr.addView(value); tl.addView(tr, params); } /** * */ private void queueCommands() { if (isServiceBound) { for (ObdCommand Command : ObdConfig.getCommands()) { if (prefs.getBoolean(Command.getName(), true)) service.queueJob(new ObdCommandJob(Command)); } } } private void doBindService() { if (!isServiceBound) { Log.d(TAG, "Binding OBD service.."); if (preRequisites) { btStatusTextView.setText(getString(R.string.status_bluetooth_connecting)); Intent serviceIntent = new Intent(this, ObdGatewayService.class); bindService(serviceIntent, serviceConn, Context.BIND_AUTO_CREATE); } else { btStatusTextView.setText(getString(R.string.status_bluetooth_disabled)); Intent serviceIntent = new Intent(this, MockObdGatewayService.class); bindService(serviceIntent, serviceConn, Context.BIND_AUTO_CREATE); } } } private void doUnbindService() { if (isServiceBound) { if (service.isRunning()) { service.stopService(); if (preRequisites) btStatusTextView.setText(getString(R.string.status_bluetooth_ok)); } Log.d(TAG, "Unbinding OBD service.."); unbindService(serviceConn); isServiceBound = false; obdStatusTextView.setText(getString(R.string.status_obd_disconnected)); } } public void onLocationChanged(Location location) { mLastLocation = location; } public void onStatusChanged(String provider, int status, Bundle extras) { } public void onProviderEnabled(String provider) { } public void onProviderDisabled(String provider) { } public void onGpsStatusChanged(int event) { switch (event) { case GpsStatus.GPS_EVENT_STARTED: gpsStatusTextView.setText(getString(R.string.status_gps_started)); break; case GpsStatus.GPS_EVENT_STOPPED: gpsStatusTextView.setText(getString(R.string.status_gps_stopped)); break; case GpsStatus.GPS_EVENT_FIRST_FIX: gpsStatusTextView.setText(getString(R.string.status_gps_fix)); break; case GpsStatus.GPS_EVENT_SATELLITE_STATUS: break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_ENABLE_BT) { if (resultCode == Activity.RESULT_OK) { btStatusTextView.setText(getString(R.string.status_bluetooth_connected)); } else { Toast.makeText(this, R.string.text_bluetooth_disabled, Toast.LENGTH_LONG).show(); } } super.onActivityResult(requestCode, resultCode, data); } private synchronized void gpsStart() { if (!mGpsIsStarted && mLocProvider != null && mLocService != null && mLocService.isProviderEnabled(LocationManager.GPS_PROVIDER)) { mLocService.requestLocationUpdates(mLocProvider.getName(), getGpsUpdatePeriod(prefs), getGpsDistanceUpdatePeriod(prefs), this); mGpsIsStarted = true; } else { gpsStatusTextView.setText(getString(R.string.status_gps_no_support)); } } private synchronized void gpsStop() { if (mGpsIsStarted) { mLocService.removeUpdates(this); mGpsIsStarted = false; gpsStatusTextView.setText(getString(R.string.status_gps_stopped)); } } /** * Uploading asynchronous task */ private class UploadAsyncTask extends AsyncTask<ObdReading, Void, Void> { @Override protected Void doInBackground(ObdReading... readings) { Log.d(TAG, "Uploading " + readings.length + " readings.."); // instantiate reading service client final String endpoint = prefs.getString(ConfigActivity.UPLOAD_URL_KEY, ""); RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(endpoint) .build(); ObdService service = restAdapter.create(ObdService.class); // upload readings for (ObdReading reading : readings) { try { Response response = service.uploadReading(reading); assert response.getStatus() == 200; } catch (RetrofitError re) { Log.e(TAG, re.toString()); } } Log.d(TAG, "Done"); return null; } } }