package edu.fordham.cis.wisdm.actipebble; 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.PowerManager; import android.os.StrictMode; import android.os.Vibrator; import android.util.Log; import com.google.android.gms.wearable.DataEvent; import com.google.android.gms.wearable.DataEventBuffer; import com.google.android.gms.wearable.DataMap; import com.google.android.gms.wearable.DataMapItem; import com.google.android.gms.wearable.WearableListenerService; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.ObjectInputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; /** * Mananges the collection and saving of data from both the phone and smartwatch * @author Andrew H. Johnston <a href="mailto:ajohnston9@fordham.edu">ajohnston9@fordham.edu</a> * @version 1.0STABLE */ public class DataManagementService extends WearableListenerService implements SensorEventListener { /** * The debugging tag for the class */ private static final String TAG = "DataManagementService"; /** * The user's name */ private String userName; /** * The label for the activity being performed */ private char activity; /** * The sampling rate in microseconds to collect acceleration records at (this is 20Hz) */ private int SAMPLE_RATE = 50000; /** * Flag that instructs the class whether to save new acceleration records */ private boolean shouldSample = true; /** * I am used in determining and manipulating the state of the screen. */ private PowerManager powerManager = null; /** * I am responsible for keeping the device partly awake while collecting data. */ private PowerManager.WakeLock wakeLock = null; /** * Handles the instantiation of the accelerometer */ private SensorManager mSensorManager; /** * Represents the physical acclerometer */ private Sensor mAccelerometer; /** * Represents the gyroscope */ private Sensor mGyroscope; /** * The list of acceleration records from the watch */ private ArrayList<AccelerationRecord> mWatchAccelerationRecords = new ArrayList<AccelerationRecord>(); /** * The list of gyroscopic records from the watch */ private ArrayList<GyroscopeRecord> mWatchGyroRecords = new ArrayList<GyroscopeRecord>(); /** * The list of acceleration records from the phone */ private ArrayList<AccelerationRecord> mPhoneAccelerationRecords = new ArrayList<AccelerationRecord>(); /** * The list of gyroscopic records from the phone */ private ArrayList<GyroscopeRecord> mPhoneGyroRecords = new ArrayList<GyroscopeRecord>(); /** * The email used to send the data */ public static final String EMAIL_SENDER = "wisdm.gaitlab@gmail.com"; /** * The password for the sender's email */ public static final String EMAIL_PASSWORD = "WiSdM403!"; /** * The email to send the data to */ private static final String EMAIL_RECIPIENT = "wisdm.gaitlab@gmail.com"; /** * Flag that signals the end of data transmission from the watch */ private static final String DATA_COLLECTION_DONE = "/thats-all-folks"; /** * This is the equivalent of onCreate() but for Services. Allows for instantiating a service with arguments * @param intent The intent that carries all arguments * @param flags Any special flags for the class * @param startId The ID of the service * @return A code used by Android internals */ @Override public int onStartCommand(Intent intent, int flags, int startId) { userName = intent.getStringExtra("NAME"); activity = intent.getCharExtra("ACTIVITY", 'A'); powerManager = (PowerManager)getApplicationContext().getSystemService(Context.POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); //wakeLock.acquire(); mSensorManager.registerListener(this, mAccelerometer, SAMPLE_RATE); mSensorManager.registerListener(this, mGyroscope, SAMPLE_RATE); shouldSample = true; //Don't restart when app is shut down and reopened return START_NOT_STICKY; } /** * Service clean up. * * Release the wake lock and un registers the accelerometer and sensor listeners when * collection is over. */ @Override public void onDestroy() { if(wakeLock.isHeld()) { wakeLock.release(); } mSensorManager.unregisterListener(this); Log.i(TAG, "Un registering phone sensor listeners."); super.onDestroy(); } /** * This method acquires the wake lock and registers the accelerometer and sensor listeners at * the chosen SAMPLE_RATE. * */ private void registerSensorListeners(){ wakeLock.acquire(); mSensorManager.registerListener(this, mAccelerometer, SAMPLE_RATE); mSensorManager.registerListener(this, mGyroscope, SAMPLE_RATE); shouldSample = true; } /** * This method is called each time new sensor data is available. Checks for the sensor type and * then adds the data to the appropriate list of records * * @param event - data structure containing the sensor data */ @Override public void onSensorChanged(SensorEvent event) { if (shouldSample) { long time = System.currentTimeMillis(); float x = event.values[0]; float y = event.values[1]; float z = event.values[2]; switch(event.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: mPhoneAccelerationRecords.add(new AccelerationRecord(x,y,z,time)); break; case Sensor.TYPE_GYROSCOPE: Log.wtf(TAG, "Gyro data"); mPhoneGyroRecords.add(new GyroscopeRecord(x,y,z,time)); } } } /** * This method is called once the phone receives a message from the watch. * The data is added to the appropriate lists and then finalizeDataCollection is called. * * @param dataEvents - the sensor data from the watch */ @Override public void onDataChanged(DataEventBuffer dataEvents) { //Once the watch starts sending data its time to stop collecting shouldSample = false; try { for (DataEvent event: dataEvents) { String path = event.getDataItem().getUri().getPath(); if (path.matches("/accel-data")) { DataMap map = DataMapItem.fromDataItem(event.getDataItem()).getDataMap(); ArrayList<AccelerationRecord> accelTmp = (ArrayList<AccelerationRecord>) (new ObjectInputStream( new ByteArrayInputStream(map.getByteArray("/accel")) ) ).readObject(); mWatchAccelerationRecords.addAll(accelTmp); Log.i(TAG, "Received acceleration list is of size: " + accelTmp.size()); } else if (path.matches("/gyro-data")) { DataMap map = DataMapItem.fromDataItem(event.getDataItem()).getDataMap(); ArrayList<GyroscopeRecord> gyroTmp = (ArrayList<GyroscopeRecord>) (new ObjectInputStream( new ByteArrayInputStream(map.getByteArray("/gyro")) ) ).readObject(); mWatchGyroRecords.addAll(gyroTmp); Log.i(TAG, "Received gyroscope list is of size: " + gyroTmp.size()); if (map.getString("/done").matches(DATA_COLLECTION_DONE)) { finalizeDataCollection(); } } else { Log.e(TAG, "Received unexpected data with path " + path); } } } catch (Exception e) { Log.e(TAG, "Exception in onDataChanged: " + e.getClass().getName() + ": " + e.getMessage()); } } /** * This method is called after the gyroscope sensor data from the watch is received and added * to the appropriate data structure in memory. */ private void finalizeDataCollection() { // Sort the lists in ascending order of timestamp Collections.sort(mWatchAccelerationRecords); Collections.sort(mWatchGyroRecords); Log.i(TAG, "Watch Acceleration List size is " + mWatchAccelerationRecords.size()); Log.i(TAG, "Watch Gyro List size is " + mWatchGyroRecords.size()); String filename = userName + "_accel_" + activity; String gyFilename = userName + "_gyro_" + activity; final String watchFile = "_watch.txt"; final String phoneFile = "_phone.txt"; // Write the sensor records to files on the phone's disk writeToFile(mWatchAccelerationRecords, filename+watchFile); writeToFile(mPhoneAccelerationRecords, filename+phoneFile); writeToFileGyro(mWatchGyroRecords, gyFilename + watchFile); writeToFileGyro(mPhoneGyroRecords, gyFilename+phoneFile); // Email all 4 files as attachments new Thread( new SendData(EMAIL_SENDER, EMAIL_PASSWORD, filename+watchFile, filename+phoneFile, gyFilename + watchFile, gyFilename+phoneFile)).start(); // Vibrate half a second for the user's sake Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); vibrator.vibrate(500l); } /** * This method write the acceleration records to a file on the phone. * * @param accelerationRecords * @param filename */ private void writeToFile(ArrayList<AccelerationRecord> accelerationRecords, String filename) { File file = new File(getFilesDir(), filename); PrintWriter writer = null; try { writer = new PrintWriter(file); for (AccelerationRecord record : accelerationRecords) { writer.println(record.toString()); } writer.flush(); writer.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } Log.i(TAG, "File location is: " + file.getAbsolutePath()); } /** * This method writes the gyroscope records to a file on the phone. * * @param gyroscopeRecords * @param filename */ private void writeToFileGyro(ArrayList<GyroscopeRecord> gyroscopeRecords, String filename) { File file = new File(getFilesDir(), filename); PrintWriter writer = null; try { writer = new PrintWriter(file); for (GyroscopeRecord record : gyroscopeRecords) { writer.println(record.toString()); } writer.flush(); writer.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } Log.i(TAG, "File location is: " + file.getAbsolutePath()); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { //Not actually needed but must be overridden } /** * Takes the data files and sends them to the appropriate emails */ class SendData implements Runnable { private String user; private String pass; private File watchAccel; private File phoneAccel; private File watchGyro; private File phoneGyro; /** * Provides arguments so the thread can send email appropriately * @param u The email of the sender * @param p The password to the sender's email * @param wA The filename for the watch accel file * @param fA The filename for the phone accel file * @param wG The filename for the watch gyro file * @param pG The filename for the phone accel file */ public SendData(String u, String p, String wA, String fA, String wG, String pG) { user = u; pass = p; watchAccel = new File(getFilesDir(), wA); phoneAccel = new File(getFilesDir(), fA); watchGyro = new File(getFilesDir(), wG); phoneGyro = new File(getFilesDir(), pG); } /** * Method called when the runnable is initiated in the thread. This sends an email * containing the sensor data in another Thread. * */ @Override public void run() { StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); StrictMode.setThreadPolicy(policy); GMailSender sender = new GMailSender(user, pass); try { File[] attach = {watchAccel, phoneAccel, watchGyro,phoneGyro}; sender.sendMail("Data for " + userName, "This is the data", user, EMAIL_RECIPIENT, attach); } catch (Exception e) { Log.e(TAG, e.getMessage()); } } } }