package edu.fordham.cis.wisdm.actipebble; import android.app.Activity; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.os.PowerManager; import android.os.Vibrator; import android.support.wearable.view.WatchViewStub; import android.util.Log; import android.view.WindowManager; import android.widget.TextView; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.wearable.DataApi; import com.google.android.gms.wearable.PutDataMapRequest; import com.google.android.gms.wearable.PutDataRequest; import com.google.android.gms.wearable.Wearable; import com.google.common.collect.Lists; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.List; /** * Handles the collection of data and the transmission of the data back to the phone in batches. * @author Andrew H. Johnston <a href="mailto:ajohnston9@fordham.edu">ajohnston9@fordham.edu</a> * @version 1.0STABLE */ public class WearTrainingActivity extends Activity implements SensorEventListener { private SensorManager mSensorManager; private Sensor mAccelerometer; private Sensor mGyroscope; private TextView mPrompt; private TextView mProgress; private GoogleApiClient googleApiClient; private ArrayList<AccelerationRecord> mAccelerationRecords = new ArrayList<AccelerationRecord>(); private ArrayList<GyroscopeRecord> mGyroscopeRecords = new ArrayList<GyroscopeRecord>(); /** * Tells the Sensor if it should save the records it */ private boolean shouldCollect = false; /** * Debugging tag */ private static final String TAG = "WearTrainingActivity"; /** * One of the strategies to keep the watch screen on */ private PowerManager.WakeLock wakeLock; /** * Constant used to add clarity to formulae below */ private static final short MILLIS_IN_A_SECOND = 1000; /** * Change the second (i.e. 2nd) multiplicand to change the number of seconds of data collected */ private int delay = MILLIS_IN_A_SECOND * 120; /** * Expressed in a formula so that value makes more sense */ private int maxNumRecords = (delay / MILLIS_IN_A_SECOND) * 20; /** * Keeps track of the number of records obtained. This is more accurate than a time-based * approach. */ private int recordCount = 0; /** * Sample rate, expressed as number of microseconds between samplings */ private static final int SAMPLE_RATE = 50000; /** * Maximum number of records we can send to the phone in one transmission. */ private static final int MAX_RECORDS_SENT_AT_ONCE = 3500; /** * Flag that signals the end of data transmission to the phone */ private static final String DATA_COLLECTION_DONE = "/thats-all-folks"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_training); //Easy way to keep watch from sleeping on me getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); mProgress = (TextView) findViewById(R.id.txtProgress); mPrompt = (TextView) findViewById(R.id.txtPrompt); final WatchViewStub stub = (WatchViewStub) findViewById(R.id.watch_view_stub); stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() { @Override public void onLayoutInflated(WatchViewStub stub) { mProgress = (TextView) findViewById(R.id.txtProgress); mPrompt = (TextView) findViewById(R.id.txtPrompt); } }); mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); //Collect at 20Hz (Once every 50,000 microseconds) mSensorManager.registerListener(this, mAccelerometer, SAMPLE_RATE); mSensorManager.registerListener(this, mGyroscope, SAMPLE_RATE); googleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { @Override public void onConnected(Bundle bundle) { Log.d(TAG, "Connected to phone."); } @Override public void onConnectionSuspended(int i) { Log.d(TAG, "Connection to phone suspended. Code: " + i); } }) .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() { @Override public void onConnectionFailed(ConnectionResult connectionResult) { Log.d(TAG, "Fuck! Connection failed: " + connectionResult); } }) .addApi(Wearable.API) .build(); googleApiClient.connect(); shouldCollect = true; Log.d(TAG, "Started collecting"); PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "WearTrainingWakelock"); wakeLock.acquire(); } @Override protected void onPause() { super.onPause(); mSensorManager.unregisterListener(this); } @Override protected void onResume() { super.onResume(); mSensorManager.registerListener(this, mAccelerometer, SAMPLE_RATE); mSensorManager.registerListener(this, mGyroscope, SAMPLE_RATE); } @Override public void onSensorChanged(SensorEvent event) { if (shouldCollect) { long timestamp = 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: mAccelerationRecords.add(new AccelerationRecord(x,y,z,timestamp)); recordCount++; break; case Sensor.TYPE_GYROSCOPE: GyroscopeRecord gyro = new GyroscopeRecord(x,y,z,timestamp); //Clean up debugging output a little if (recordCount % 10 == 0) { Log.d(TAG, "Record is: " + gyro.toString()); } mGyroscopeRecords.add(gyro); } if (recordCount > maxNumRecords) { shouldCollect = false; new Thread(new SendDataToPhoneTask()).start(); } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { //Not used but must be overridden } class SendDataToPhoneTask implements Runnable { @Override public void run() { Log.d(TAG, "Ending stream"); try { List<List<AccelerationRecord>> accelLists = Lists.partition(mAccelerationRecords, MAX_RECORDS_SENT_AT_ONCE); List<List<GyroscopeRecord>> gyroLists = Lists.partition(mGyroscopeRecords, MAX_RECORDS_SENT_AT_ONCE); /* I know the following two for loops look like they could be * abstracted into a single generic method, but due to type erasure * of generics I can't do this with a single polymoprhic method. */ for (List<AccelerationRecord> list : accelLists) { Log.d(TAG, "Sending list of acceleration records..."); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); ArrayList<AccelerationRecord> tmp = new ArrayList<AccelerationRecord>(list); Log.d(TAG, "List is of size: " + tmp.size()); oos.writeObject(tmp); oos.flush(); oos.close(); byte[] data = baos.toByteArray(); PutDataMapRequest dataMapRequest = PutDataMapRequest.create("/accel-data"); dataMapRequest.getDataMap().putByteArray("/accel", data); PutDataRequest request = dataMapRequest.asPutDataRequest(); PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(googleApiClient, request); } //Having a bug where a "that's all" message actually beats the //data to the phone, so we'll just attach the message to the last //list of data going out for (int i = 0; i < gyroLists.size(); ++i) { List<GyroscopeRecord> list = gyroLists.get(i); Log.d(TAG, "Sending list of gyroscope records..."); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oosG = new ObjectOutputStream(baos); ArrayList<GyroscopeRecord> tmp = new ArrayList<GyroscopeRecord>(list); Log.d(TAG, "List is of size: " + tmp.size()); oosG.writeObject(tmp); oosG.flush(); oosG.close(); byte[] data = baos.toByteArray(); PutDataMapRequest dataMapRequest = PutDataMapRequest.create("/gyro-data"); dataMapRequest.getDataMap().putByteArray("/gyro", data); if ((i+1) == gyroLists.size()) { dataMapRequest.getDataMap().putString("/done", DATA_COLLECTION_DONE); } else { dataMapRequest.getDataMap().putString("/done", "/not-done"); } PutDataRequest request = dataMapRequest.asPutDataRequest(); PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(googleApiClient, request); } //Vibrate and tell the user to check their phone Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); vibrator.vibrate(500L); //Vibrate for half a second runOnUiThread(new Runnable() { @Override public void run() { mPrompt.setText("Please finish the training by opening your phone."); mProgress.setText(""); } }); } catch (IOException e) { Log.d(TAG, "Something fucky happened: " + e.getMessage()); } finally { //Screen can turn off now. wakeLock.release(); } } } }