package com.cellbots.logger.localServer; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.BatteryManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Base64; import android.util.Log; import com.cellbots.logger.GpsManager; import com.cellbots.logger.LoggerApplication; import com.cellbots.logger.WapManager; import com.cellbots.logger.GpsManager.GpsManagerListener; import com.cellbots.logger.WapManager.ScanResults; import com.cellbots.logger.localServer.LocalHttpServer.HttpCommandServerListener; import com.cellbots.logger.localServer.Telemetry.DataPacket.Builder; import com.cellbots.logger.localServer.XmppManager.XmppMessageListener; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.List; /** * Background service that performs logging and serves up the results as an HTTP * server. * * @author clchen@google.com (Charles L. Chen) */ public class LoggingService extends Service implements HttpCommandServerListener { // FILL THESE OUT TO USE XMPP! private static final String XMPP_PROTOBUF_RECEIVER_BOT = ""; public static final String GMAIL_ACCOUNT = ""; public static final String GMAIL_PASSWORD = ""; public static final String EXTRA_COMMAND = "COMMAND"; public static final int EXTRA_COMMAND_STOP = 0; public static final int EXTRA_COMMAND_START = 1; private static final String TAG = "LoggingService"; private LoggerApplication application; // FLAGS // TODO: Make these configurable! private boolean mWriteToFile = true; // Switch this to true to log to files // in addition to displaying through // HTTP. private SensorManager mSensorManager; private List<Sensor> sensors; private volatile Boolean mIsLoggerRunning = false; private BufferedWriter mBatteryTempWriter; private BufferedWriter mBatteryLevelWriter; private BufferedWriter mBatteryVoltageWriter; private BufferedWriter mWifiWriter; private HashMap<String, BufferedWriter> sensorLogFileWriters; private HashMap<String, String> lastSeenValues; private BufferedWriter mGpsLocationWriter; private BufferedWriter mGpsStatusWriter; private BufferedWriter mGpsNmeaWriter; private GpsManager mGpsManager; private LocalHttpServer httpServer; private XmppManager xmppHandler; private long mLastXmppUpdateTime = 0; private TelemetrySnapshot mTelemetrySnapshot; private String mDirectoryName; @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); application = (LoggerApplication) getApplication(); mDirectoryName = application.getDataLoggerPath(); if (intent != null) { switch (intent.getIntExtra(EXTRA_COMMAND, EXTRA_COMMAND_STOP)) { case EXTRA_COMMAND_START: if (!mIsLoggerRunning) { runLoggerService(); httpServer = new LocalHttpServer("cellbots/httpserver/files", 8080, this); if (GMAIL_ACCOUNT.length() > 0) { xmppHandler = new XmppManager( this, mXmppMessageListener, GMAIL_ACCOUNT, GMAIL_PASSWORD); xmppHandler.connect(); } } break; default: stopSelf(); break; } } return START_STICKY; } XmppMessageListener mXmppMessageListener = new XmppMessageListener() { @Override public void onMessageReceived(String from, String message) { xmppHandler.sendMessage(from, getLoggerStatus()); } }; private Runnable sendUpdatesToXmppRunnable = new Runnable() { @Override public void run() { while (mIsLoggerRunning && (GMAIL_ACCOUNT.length() > 0)) { if ((xmppHandler != null) && (mTelemetrySnapshot != null) && (mLastXmppUpdateTime + 15000 < System.currentTimeMillis())) { Log.e("Message to bot", "Sending..."); xmppHandler.sendMessage(XMPP_PROTOBUF_RECEIVER_BOT, "/prot " + mTelemetrySnapshot.getBase64EncodedProtobufDataPacket()); mLastXmppUpdateTime = System.currentTimeMillis(); Log.e("Message to bot", "OK"); } } } }; private void runLoggerService() { mIsLoggerRunning = true; mTelemetrySnapshot = new TelemetrySnapshot(); lastSeenValues = new HashMap<String, String>(); Log.e(TAG, "Starting logging service"); mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL); initSensorLogFiles(); initGps(); new Thread(sendUpdatesToXmppRunnable).start(); } @Override public void onDestroy() { super.onDestroy(); mIsLoggerRunning = false; if ((sensors != null) && (mSensorManager != null)) { // Unregister sensor listeners for (Sensor s : sensors) { mSensorManager.unregisterListener(mSensorEventListener, s); } } if (mGpsManager != null){ mGpsManager.shutdown(); } if (xmppHandler != null) { xmppHandler.disconnect(); } } private SensorEventListener mSensorEventListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { mTelemetrySnapshot.updateSensor(event); Sensor sensor = event.sensor; if (sensor.getType() == Sensor.TYPE_GYROSCOPE) { // Gyroscope doesn't really have a notion of accuracy. // Due to a bug in Android, the gyroscope incorrectly returns // its status as unreliable. This can be safely ignored and does // not impact the accuracy of the readings. event.accuracy = SensorManager.SENSOR_STATUS_ACCURACY_HIGH; } synchronized (mIsLoggerRunning) { if (mIsLoggerRunning) { String valuesStr = ""; for (int i = 0; i < event.values.length; i++) { valuesStr = valuesStr + event.values[i] + ","; } final String sensorName = sensor.getName(); final String lastSeenValue = event.timestamp + "," + event.accuracy + "," + valuesStr; lastSeenValues.put(sensorName, lastSeenValue); // Log.d(TAG, sensorName + ":" + lastSeenValue); BufferedWriter writer = sensorLogFileWriters.get(sensorName); if (writer != null) { try { writer.write(lastSeenValue + "\n"); writer.flush(); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } } } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }; private BroadcastReceiver batteryBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { synchronized (mIsLoggerRunning) { if (!mIsLoggerRunning) { return; } String value = ""; long currentTime = System.currentTimeMillis(); int batteryTemp = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0); try { value = currentTime + "," + batteryTemp; lastSeenValues.put("BatteryTemp", value); if (mBatteryTempWriter != null) { mBatteryTempWriter.write(value + "\n"); } } catch (IOException e) { e.printStackTrace(); } // Log the battery level int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); try { value = currentTime + "," + batteryLevel; lastSeenValues.put("BatteryLevel", value); if (mBatteryLevelWriter != null) { mBatteryLevelWriter.write(value + "\n"); } } catch (IOException e) { e.printStackTrace(); } // Log the battery voltage level int batteryVoltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0); try { value = currentTime + "," + batteryVoltage; lastSeenValues.put("BatteryVoltage", value); if (mBatteryVoltageWriter != null) { mBatteryVoltageWriter.write(value + "\n"); } } catch (IOException e) { e.printStackTrace(); } } } }; private WapManager.WapManagerListener mWifiListener = new WapManager.WapManagerListener() { @Override public void onScanResults(long timestamp, ScanResults results) { synchronized (mIsLoggerRunning) { if (!mIsLoggerRunning) return; } try { // Convert results to a json object JSONObject obj = new JSONObject(); JSONObject resultsObj = new JSONObject(results); obj.put("timestamp", timestamp); obj.put("results", resultsObj); lastSeenValues.put("Wifi", timestamp + "," + resultsObj.toString()); if (mWifiWriter != null) { // Write that object to a file mWifiWriter.write(obj.toString()); mWifiWriter.write("\n"); } } catch (JSONException e) { Log.e("LoggerActivity", "Error logging wifi results. JSON Error"); e.printStackTrace(); } catch (IOException e) { Log.e("LoggerActivity", "Error logging wifi results. IO error"); e.printStackTrace(); } } }; private void initSensorLogFiles() { sensorLogFileWriters = new HashMap<String, BufferedWriter>(); if (mWriteToFile) { File directory = new File(mDirectoryName); if (!directory.exists() && !directory.mkdirs()) { try { throw new IOException( "Path to file could not be created. " + directory.getAbsolutePath()); } catch (IOException e) { Log.e(TAG, "Directory could not be created. " + e.toString()); } } } for (int i = 0; i < sensors.size(); i++) { Sensor s = sensors.get(i); if (mWriteToFile) { String sensorFilename = mDirectoryName + s.getName().replaceAll(" ", "_") + "_" + application.getFilePathUniqueIdentifier() + ".txt"; File file = new File(sensorFilename); try { BufferedWriter writer = new BufferedWriter(new FileWriter(file)); sensorLogFileWriters.put(s.getName(), writer); } catch (IOException e) { e.printStackTrace(); } // GPS is another special case since it is not a real sensor mGpsLocationWriter = createBufferedWriter("/GpsLocation_", mDirectoryName); mGpsStatusWriter = createBufferedWriter("/GpsStatus_", mDirectoryName); mGpsNmeaWriter = createBufferedWriter("/GpsNmea_", mDirectoryName); } mSensorManager.registerListener( mSensorEventListener, s, SensorManager.SENSOR_DELAY_GAME); } /* * // The battery is a special case since it is not a real sensor * mBatteryTempWriter = createBufferedWriter("/BatteryTemp_", * directoryName); mBatteryLevelWriter = * createBufferedWriter("/BatteryLevel_", directoryName); * mBatteryVoltageWriter = createBufferedWriter("/BatteryVoltage_", * directoryName); // GPS is another special case since it is not a real * sensor mGpsLocationWriter = createBufferedWriter("/GpsLocation_", * directoryName); mGpsStatusWriter = * createBufferedWriter("/GpsStatus_", directoryName); mGpsNmeaWriter = * createBufferedWriter("/GpsNmea_", directoryName); // Wifi is another * special case mWifiWriter = createBufferedWriter("/Wifi_", * directoryName); */ } /** * Creates a new BufferedWriter. * * @param prefix The prefix for the file that we're writing to. * @return A BufferedWriter for a file in the specified directory. Null if * creation failed. */ private BufferedWriter createBufferedWriter(String prefix, String directoryName) { String filename = directoryName + prefix + application.getFilePathUniqueIdentifier() + ".txt"; File file = new File(filename); BufferedWriter bufferedWriter = null; try { bufferedWriter = new BufferedWriter(new FileWriter(file)); } catch (IOException e) { e.printStackTrace(); } return bufferedWriter; } private void initGps() { mGpsManager = new GpsManager(this, new GpsManagerListener() { @Override public void onGpsLocationUpdate(long time, float accuracy, double latitude, double longitude, double altitude, float bearing, float speed) { try { mTelemetrySnapshot.updateLocation(latitude, longitude, altitude); if (mWriteToFile) { mGpsLocationWriter.write( time + "," + accuracy + "," + latitude + "," + longitude + "," + altitude + "," + bearing + "," + speed + "\n"); } } catch (IOException e) { e.printStackTrace(); } } @Override public void onGpsNmeaUpdate(long time, String nmeaString) { try { if (mWriteToFile) { mGpsNmeaWriter.write(time + "," + nmeaString + "\n"); } } catch (IOException e) { e.printStackTrace(); } } @Override public void onGpsStatusUpdate( long time, int maxSatellites, int actualSatellites, int timeToFirstFix) { try { if (mWriteToFile) { mGpsStatusWriter.write( time + "," + maxSatellites + "," + actualSatellites + "," + timeToFirstFix + "\n"); } } catch (IOException e) { e.printStackTrace(); } } }); } /* * (non-Javadoc) * @see * com.cellbots.logger.localServer.HttpCommandServer.HttpCommandServerListener * #onRequest(java.lang.String, java.lang.String[], java.lang.String[], * byte[]) */ @Override public void onRequest(String req, String[] keys, String[] values, byte[] data) { Log.e("Server debug", "Request received:" + req); } @Override public String getLoggerStatus() { StringBuilder statusMessage = new StringBuilder(); Iterator<String> sensorNamesIt = lastSeenValues.keySet().iterator(); while (sensorNamesIt.hasNext()) { final String name = sensorNamesIt.next(); statusMessage.append(name); statusMessage.append(":"); statusMessage.append(lastSeenValues.get(name)); statusMessage.append("\n"); } return statusMessage.toString(); } public void addLogEntryToCustomSensor(final String sensorName, final String sensorReadings) { final String lastSeenValue = System.currentTimeMillis() + "," + sensorReadings; lastSeenValues.put(sensorName, lastSeenValue); BufferedWriter writer = sensorLogFileWriters.get(sensorName); if (mWriteToFile && (writer == null)) { String sensorFilename = mDirectoryName + sensorName + "_" + application.getFilePathUniqueIdentifier() + ".txt"; File file = new File(sensorFilename); try { writer = new BufferedWriter(new FileWriter(file)); sensorLogFileWriters.put(sensorName, writer); } catch (IOException e) { e.printStackTrace(); } } if (writer != null) { try { writer.write(lastSeenValue + "\n"); writer.flush(); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } } } /* * (non-Javadoc) * @see android.app.Service#onBind(android.content.Intent) */ @Override public IBinder onBind(Intent intent) { return mBinder; } public class Stub extends ILoggingService.Stub { @Override public void addLogEntry(String sensorName, String sensorReadings) throws RemoteException { addLogEntryToCustomSensor(sensorName, sensorReadings); } } private final Stub mBinder = new Stub(); }