From e45726970c547a5526d4f968b32b26ec83e612ff Mon Sep 17 00:00:00 2001 From: Kristin Muterspaw <kmmuterspaw@gmail.com> Date: Fri, 3 Jun 2016 15:39:56 -0400 Subject: [PATCH] SensorSampleActivity nows uses SensorSampleService to do all communication with the sensors and writing to the database. If it's a sample then the Service is started, contacts the sensor, writes to the database once, and then the service is told to stop sampling. If it's streaming, then a handler is started to continuously contact and write to the database. The Service now uses a 'Wake Lock' which means the CPU stays on even if the screen is off. Can stream with screen off now. --- app/src/main/AndroidManifest.xml | 19 +- .../edu/fieldday/BluetoothLeService.java | 11 +- .../earlham/edu/fieldday/BluetoothSensor.java | 13 +- .../edu/fieldday/BluetoothSensorFragment.java | 22 +- .../edu/fieldday/SensorSampleActivity.java | 242 ++++---- .../edu/fieldday/SensorSampleService.java | 552 ++++++++++++++++++ .../cs/earlham/edu/fieldday/aSensor.java | 46 +- .../main/res/layout/activity_sensorsample.xml | 21 +- 8 files changed, 772 insertions(+), 154 deletions(-) create mode 100644 app/src/main/java/fieldscience/cs/earlham/edu/fieldday/SensorSampleService.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ac55388..280f979 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,21 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="fieldscience.cs.earlham.edu.fieldday" > + package="fieldscience.cs.earlham.edu.fieldday"> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.CAMERA" /> - <uses-permission android:name="android.permission.INTERNET"/> - <uses-feature android:name="android.hardware.bluetooth_le"/> - <uses-feature android:name="android.hardware.camera2" android:required="false" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-feature android:name="android.hardware.bluetooth_le" /> + <uses-feature + android:name="android.hardware.camera2" + android:required="false" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:theme="@style/MainTheme" > + android:theme="@style/MainTheme"> <activity android:name=".MainScreenActivity" android:label="@string/app_name" @@ -55,9 +59,12 @@ android:label="My Documents Viewer" android:theme="@style/MainTheme"> </activity> + <service android:name=".BluetoothLeService" android:enabled="true" /> + <service + android:name=".SensorSampleService" + android:exported="false" /> </application> - </manifest> diff --git a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothLeService.java b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothLeService.java index d9dff8e..36ed160 100644 --- a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothLeService.java +++ b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothLeService.java @@ -10,8 +10,6 @@ import android.os.Binder; import android.os.IBinder; import android.util.Log; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; public class BluetoothLeService extends Service { @@ -46,15 +44,15 @@ public class BluetoothLeService extends Service { public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"; private BluetoothGattService gattService; - private Map<UUID, String> services = new HashMap<UUID, String>(); private final BluetoothSensor.Listener listener = new BluetoothSensor.Listener() { @Override public void onCharacteristicRead(UUID characteristicUUID, String data) { broadcastUpdate(ACTION_DATA_AVAILABLE, data); } + @Override - public void onServiceInformation(){ + public void onServiceInformation() { for (BluetoothGattService s : btSensor.getServices()){ if (s.getUuid().equals(UUID_BLE_SENSOR_SERVICE)) { gattService = btSensor.getService(UUID_BLE_SENSOR_SERVICE); @@ -88,6 +86,11 @@ public class BluetoothLeService extends Service { } } + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return Service.START_STICKY; + } + @Override public IBinder onBind(Intent intent) { return mBinder; diff --git a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothSensor.java b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothSensor.java index c592599..eb9858f 100644 --- a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothSensor.java +++ b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothSensor.java @@ -78,8 +78,11 @@ public class BluetoothSensor implements Parcelable { public boolean connect(Context context){ gattClient.connect(context, device); - // This listener is used when changes happen in the GattClient class. The GattClient class - // is used for communication with the actual bluetooth device. + // This listener is used when changes happen in the GattClient class. It is implemented in + // the GattClient class and anytime these functions are called in that class, the listener in + // this class are called as well. The GattClient class is used for communication with the + // actual bluetooth device. This is used for talking to the BluetoothService and + // BluetoothSensorFragment. GattClient.Listener listener = new GattClient.Listener() { @Override public void onMessageReceived(byte[] data, BluetoothGattCharacteristic characteristic) { @@ -112,18 +115,18 @@ public class BluetoothSensor implements Parcelable { this.mListener = listener; } - public static interface Listener { + public interface Listener { void onCharacteristicRead(UUID characteristicUUID, String data); void onServiceInformation(); } - // Required by android Parcelable class + // Required by Android Parcelable class @Override public int describeContents() { return 0; } - // Required by android Parcelable class + // Required by Android Parcelable class @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(device, 0); diff --git a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothSensorFragment.java b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothSensorFragment.java index 1c302e5..7b33995 100644 --- a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothSensorFragment.java +++ b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/BluetoothSensorFragment.java @@ -35,6 +35,8 @@ public class BluetoothSensorFragment extends ListFragment { private ArrayList<aSensor> ourSensors; SensorFragmentCommunication callback; + private static final String TAG = BluetoothSensorFragment.class.getSimpleName(); + private ServiceConnection btServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -44,7 +46,7 @@ public class BluetoothSensorFragment extends ListFragment { getActivity().finish(); } boolean connection = btService.connect(btSensor); - if (!connection){ + if (!connection) { Log.e("Sensor Sample Activity", "Unable to connect to device"); } } @@ -63,16 +65,22 @@ public class BluetoothSensorFragment extends ListFragment { } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { btService = null; - btHandler.removeCallbacks(btRunner); + btHandler.removeCallbacksAndMessages(btRunner); } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { btService.readMessage(); - btHandler.postDelayed(btRunner, 5000); + if (!registered) { + Log.d(TAG, "Services Discovered."); + btHandler.postDelayed(btRunner, 5000); + } } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { if (btService != null) { if (BluetoothLeService.current_request.equals(BluetoothLeService.LIST_OF_SENSORS)) { + Log.d(TAG, "Receiving List of Sensors"); parseResponse(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); + btHandler.removeCallbacksAndMessages(btRunner); } else if (BluetoothLeService.current_request.equals(BluetoothLeService.SENSOR_VALUES)){ parseResponse(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); + Log.d(TAG, "Receiving updated Sensor Values"); } } } @@ -108,7 +116,6 @@ public class BluetoothSensorFragment extends ListFragment { @Override public void run() { getSensorValues(); - btHandler.postDelayed(this, senseInterval); } }; @@ -156,7 +163,7 @@ public class BluetoothSensorFragment extends ListFragment { @Override public void onDetach(){ super.onDetach(); - btHandler.removeCallbacks(btRunner); + btHandler.removeCallbacksAndMessages(btRunner); if (btService != null) { btService.disconnect(); btService.close(); @@ -174,8 +181,9 @@ public class BluetoothSensorFragment extends ListFragment { for (int i = 1; i < tmp.length + 1; i++) { tx[i] = tmp[i - 1]; } + Log.d(TAG, "Writing message for sensor names."); btService.writeMessage(tx, BluetoothLeService.LIST_OF_SENSORS); - btHandler.removeCallbacks(btRunner); + btHandler.removeCallbacksAndMessages(btRunner); } public void getSensorValues() { @@ -187,11 +195,13 @@ public class BluetoothSensorFragment extends ListFragment { for (int i = 1; i < tmp.length + 1; i++) { tx[i] = tmp[i - 1]; } + Log.d(TAG, "Updating Sensor Values"); btService.writeMessage(tx, BluetoothLeService.SENSOR_VALUES); } public void displayData() { String sensor = ""; + Log.d(TAG, "Response: " + response); for (int i = 0; i < response.length(); i++) { char c = response.charAt(i); if (c == ';') { diff --git a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/SensorSampleActivity.java b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/SensorSampleActivity.java index 78904c3..774050a 100644 --- a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/SensorSampleActivity.java +++ b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/SensorSampleActivity.java @@ -1,9 +1,15 @@ package fieldscience.cs.earlham.edu.fieldday; import android.app.Activity; -import android.app.Fragment; -import android.app.FragmentManager; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -11,7 +17,7 @@ import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; -import android.os.Handler; +import android.os.IBinder; import android.preference.PreferenceManager; import android.text.Editable; import android.text.TextWatcher; @@ -22,18 +28,16 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageButton; +import android.widget.ListView; import android.widget.RadioGroup; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.List; -import java.util.Locale; -public class SensorSampleActivity extends Activity implements SensorFragmentCommunication { +public class SensorSampleActivity extends Activity { public LocationManager locationManager; public LocationListener locationListener; @@ -44,13 +48,11 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm public static Resources res; public static Spinner siteSpinner, sectorSpinner; public static EditText spotET; - public static String tripName, site, sector, spot, lastFragment; + public static String tripName, site, sector, spot, sensor; public ReadingsDatabase db; public RadioGroup logIntervalButton; public long logInterval = 5000; public ArrayList<aSensor> listSensors; - private Handler dbHandler; - private Runnable dbRunner; public Boolean streaming = false, remote_db; public Drawable streamOn, streamOff; public ImageButton exportButton, streamButton, sampleButton; @@ -58,6 +60,46 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm public static final String WHICH_SENSOR = "Which Sensor"; public static final String REMOTE_DB = "Remote DB Connected"; BluetoothSensor bluetoothSensor; + private SensorSampleService sampleService; + private Context context; + private ArrayAdapter<aSensor> adapter; + + private ServiceConnection sampleServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + sampleService = ((SensorSampleService.LocalBinder) service).getService(); + sampleService.connectSensor(sensor, bluetoothSensor); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + + } + }; + + private final BroadcastReceiver sampleServiceReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (SensorSampleService.ACTION_CONNECTED.equals(intent.getAction())){ + Log.d("SensorSampleActivity", "Received Connected Message!"); + enableButtons(); + ArrayList<aSensor> sensors = intent.getParcelableArrayListExtra(SensorSampleService.SENSOR_LIST); + for (aSensor s: sensors) { + Log.d("SensorSampleActivity", "Sensor: " + s.getName()); + Log.d("SensorSampleActivity", "Sensor Value: " + s.getLastValueString()); + } + adapter.clear(); + adapter.addAll(sensors); + adapter.notifyDataSetChanged(); + } else if (SensorSampleService.ACTION_UPDATED_LIST.equals(intent.getAction())) { + Log.d("SensorSampleActivity", "Received Updated List Message!"); + ArrayList<aSensor> sensors = intent.getParcelableArrayListExtra(SensorSampleService.SENSOR_LIST); + adapter.clear(); + adapter.addAll(sensors); + adapter.notifyDataSetChanged(); + } + } + }; @Override protected void onCreate(Bundle savedInstanceState) { @@ -65,8 +107,14 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm setContentView(R.layout.activity_sensorsample); remote_db = getIntent().getBooleanExtra(REMOTE_DB, false); initializeVariables(); + context = this; + listSensors = new ArrayList<aSensor>(); - String sensor = getIntent().getStringExtra(WHICH_SENSOR); + Intent sensorServiceIntent = new Intent(this, SensorSampleService.class); + bindService(sensorServiceIntent, sampleServiceConnection, Context.BIND_AUTO_CREATE); + registerReceiver(sampleServiceReceiver, makeSensorUpdateFilter()); + + sensor = getIntent().getStringExtra(WHICH_SENSOR); if (sensor.equals("bluetooth")){ bluetoothSensor = getIntent().getParcelableExtra(DEVICE); streamButton.setImageDrawable(res.getDrawable(R.drawable.stream_unavailable, null)); @@ -74,17 +122,11 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm sampleButton.setClickable(false); streamButton.setClickable(false); } - lastFragment = sensor; - displaySensor(sensor); - dbHandler = new Handler(); - dbRunner = new Runnable() { - @Override - public void run() { - writeToDB(); - dbHandler.postDelayed(this, logInterval); - } - }; + ListView mSensors = (ListView) findViewById(android.R.id.list); + adapter = new SensorListAdapter(this, listSensors); + mSensors.setAdapter(adapter); + Intent i = new Intent(context, SensorSampleService.class); logIntervalButton.setOnClickListener(new View.OnClickListener() { @Override @@ -99,22 +141,20 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm if (!streaming) { streamButton.setImageDrawable(streamOn); streaming = true; - writeToDB(); - if (lastFragment.equals("bluetooth")){ - BluetoothSensorFragment bt = (BluetoothSensorFragment) - getFragmentManager().findFragmentByTag("bluetooth"); - bt.sensingControl(true, logInterval); - } - dbHandler.postDelayed(dbRunner, logInterval); + + // Start the SensorSampleService supplying the type of sensor connected -- + // bluetooth or built-in and the logging interval to use. + Intent i = new Intent(context, SensorSampleService.class); + i.putExtra(SensorSampleService.LOG_INTERVAL, logInterval); + i.putExtra(SensorSampleService.SAMPLE_ONCE, false); + i.putExtra(SensorSampleService.TRIP_NAME, tripName); + startService(i); } else { streamButton.setImageDrawable(streamOff); - dbHandler.removeCallbacks(dbRunner); streaming = false; - if (lastFragment.equals("bluetooth")){ - BluetoothSensorFragment bt = (BluetoothSensorFragment) - getFragmentManager().findFragmentByTag("bluetooth"); - bt.sensingControl(false, 0); - } + sampleService.stopSampling(); + Intent i = new Intent(context, SensorSampleService.class); + stopService(i); } } }); @@ -138,11 +178,34 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm t.setGravity(Gravity.CENTER, 0, 0); t.show(); } else if (!db.addSpot(tripName, site, sector, spot)) { - Toast t = Toast.makeText(SensorSampleActivity.this, "The Spot number breaks the primary keys of the database. Please insert a new spot.", Toast.LENGTH_LONG); - t.setGravity(Gravity.CENTER, 0, 0); - t.show(); + Dialog d = new AlertDialog.Builder(SensorSampleActivity.this, AlertDialog.THEME_HOLO_LIGHT) + .setTitle("Are you sure?") + .setMessage("There's already a spot with this trip, site, and sector. Make sure you are at the same geolocation that is " + + "associated with this spot.") + .setNegativeButton("Change Spot", null) + .setPositiveButton("I'm sure.", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent i = new Intent(context, SensorSampleService.class); + i.putExtra(SensorSampleService.LOG_INTERVAL, logInterval); + i.putExtra(SensorSampleService.SAMPLE_ONCE, true); + i.putExtra(SensorSampleService.TRIP_NAME, tripName); + i.putExtra(SensorSampleService.SITE, site); + i.putExtra(SensorSampleService.SECTOR, sector); + i.putExtra(SensorSampleService.SPOT, spot); + startService(i); + } + }).create(); + d.show(); } else { - writeToDB(); + Intent i = new Intent(context, SensorSampleService.class); + i.putExtra(SensorSampleService.LOG_INTERVAL, logInterval); + i.putExtra(SensorSampleService.SAMPLE_ONCE, true); + i.putExtra(SensorSampleService.TRIP_NAME, tripName); + i.putExtra(SensorSampleService.SITE, site); + i.putExtra(SensorSampleService.SECTOR, sector); + i.putExtra(SensorSampleService.SPOT, spot); + startService(i); } } }); @@ -161,102 +224,43 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm } @Override - protected void onPause(){ + protected void onPause() { super.onPause(); } @Override - protected void onStop(){ + protected void onStop() { super.onStop(); - dbHandler.removeCallbacks(dbRunner); } @Override - protected void onDestroy(){ + protected void onDestroy() { super.onDestroy(); locationManager.removeUpdates(locationListener); - dbHandler.removeCallbacks(dbRunner); - } - - public void displaySensor(String sensor) { - FragmentManager fragMan = getFragmentManager(); - Fragment frag = fragMan.findFragmentById(R.id.sensor_container); - switch (sensor) { - case "built-in": - frag = new BuiltInSensorsFragment(); - fragMan.beginTransaction() - .add(R.id.sensor_container, frag, sensor) - .addToBackStack(null) - .commit(); - break; - case "bluetooth": - BluetoothSensorFragment btf = BluetoothSensorFragment.newInstance(bluetoothSensor); - fragMan.beginTransaction() - .add(R.id.sensor_container, btf, sensor) - .addToBackStack(null) - .commit(); - break; + if (sampleService != null) { + sampleService.stopSelf(); } - lastFragment = sensor; - } - - public String getTimestamp(){ - long seconds = System.currentTimeMillis() / 1000; - //return seconds; - - SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); - return s.format(new Date()); - } - - public void writeToDB() { - double[] geoInfo = {lastLatitude, lastLongitude, lastElevation}; - boolean success = false; - String message = ""; - // The user has connected and downloaded data from a remote database - if (remote_db) { - for (aSensor sensor : listSensors) { - if (sensor.getLastValues() != null) { - String table = ""; - if (streaming) { - table = "fieldday_streaming"; - db.addReading(sensor, sensor.getLastValues()[0], sensor.getLastValueQuality(), getTimestamp(), - site, sector, spot, geoInfo, tripName, lastAccuracy, lastSatellites, "", "", table); - } else { - table = "fieldday_reading"; - success = db.addReading(sensor, sensor.getLastValues()[0], sensor.getLastValueQuality(), getTimestamp(), - site, sector, spot, geoInfo, tripName, lastAccuracy, lastSatellites, "", "", table); - message = ""; - if (success) { - message = "Successfully took a sample"; - } else { - message = "Could not write to the database. Try again."; - } - } - } - } - if (success){ - Toast t = Toast.makeText(SensorSampleActivity.this, message, Toast.LENGTH_SHORT); - t.setGravity(Gravity.CENTER, 0, 0); - t.show(); - } - } else { - Toast t = Toast.makeText(SensorSampleActivity.this, "You can't write to the database without connecting a remote schema", Toast.LENGTH_SHORT); - t.setGravity(Gravity.CENTER, 0, 0); - t.show(); + if (sampleServiceConnection != null){ + unregisterReceiver(sampleServiceReceiver); + unbindService(sampleServiceConnection); } } - public void setSensorList(ArrayList<aSensor> sensorList){ - listSensors = sensorList; - } - - public void enableButtons(){ + public void enableButtons() { streamButton.setClickable(true); sampleButton.setClickable(true); streamButton.setImageDrawable(res.getDrawable(R.drawable.stream_button, null)); sampleButton.setImageDrawable(res.getDrawable(R.drawable.sample_button, null)); } + private static IntentFilter makeSensorUpdateFilter() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(SensorSampleService.ACTION_UPDATED_LIST); + intentFilter.addAction(SensorSampleService.ACTION_CONNECTED); + intentFilter.addAction(SensorSampleService.ACTION_DISCONNECTED); + return intentFilter; + } + public class myLocationListener implements LocationListener { @Override public void onLocationChanged(Location location) { @@ -294,14 +298,10 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm logInterval = 1000 * 60; break; } - dbHandler.removeCallbacks(dbRunner); - if (streaming) { - dbHandler.postDelayed(dbRunner, logInterval); - } Log.d("Changing Interval", "New Interval Time: " + String.valueOf(logInterval)); } - public void exportDatabase(){ + public void exportDatabase() { boolean success = db.copyDatabase(""); if (success){ Toast.makeText(this, "Successfully copied the database", Toast.LENGTH_LONG).show(); @@ -310,7 +310,7 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm } } - public void setLocation(Location location){ + public void setLocation(Location location) { if (location != null) { lastLongitude = location.getLongitude(); lastLatitude = location.getLatitude(); @@ -324,7 +324,7 @@ public class SensorSampleActivity extends Activity implements SensorFragmentComm } } - private void initializeVariables(){ + private void initializeVariables() { res = getResources(); longAndLat = (TextView) findViewById(R.id.longandlat); satellites = (TextView) findViewById(R.id.satellites); diff --git a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/SensorSampleService.java b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/SensorSampleService.java new file mode 100644 index 0000000..5d09997 --- /dev/null +++ b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/SensorSampleService.java @@ -0,0 +1,552 @@ +package fieldscience.cs.earlham.edu.fieldday; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Binder; +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.widget.Toast; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +public class SensorSampleService extends Service implements SensorEventListener { + + private BluetoothLeService btService; + private String sensorType, response; + private boolean sampleOnce, registered = false, writingToDB = false; + private long loggingInterval; + private Handler dbHandler, btHandler; + private Runnable dbRunner, btRunNames, builtinListRunner; + private BluetoothSensor bluetoothSensor; + private ArrayList<aSensor> sensorList; + public ReadingsDatabase db; + private SensorManager mSensorManager; + private LocationManager locationManager; + private LocationListener locationListener; + private List<Sensor> builtinList; + public static double lastLongitude, lastLatitude, lastElevation; + public static int lastSatellites; + public static float lastAccuracy; + public static String tripName, site, sector, spot; + private PowerManager.WakeLock mWakeLock; + + // These are used in the Intent that SensorSampleActivity creates to start the Service. + public static final String SENSOR_TYPE = "Sensor Type"; + public static final String LOG_INTERVAL = "Logging Interval"; + public static final String SAMPLE_ONCE = "Sample or Streaming"; + public static final String SENSOR_LIST = "List of Sensors"; + public static final String TRIP_NAME = "Name of Trip"; + public static final String SITE = "Name of Site"; + public static final String SECTOR = "Name of Sector"; + public static final String SPOT = "Spot Number"; + public static final String BT_SENSOR = "Bluetooth Sensor"; + + public static final String ACTION_CONNECTED = "Connected."; + public static final String ACTION_UPDATED_LIST = "Updated Sensor List."; + public static final String ACTION_DISCONNECTED = "Disconnect"; + + private static final String TAG = "SensorSampleService"; + + private ServiceConnection btServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + btService = ((BluetoothLeService.LocalBinder) service).getService(); + if (!btService.initialize()) { + Log.e(TAG, "Bluetooth Stopped Running"); + stopSelf(); + } + btService.connect(bluetoothSensor); + Log.d(TAG, "Bound to Bluetooth Service"); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + btService = null; + dbHandler.removeCallbacks(dbRunner); + } + }; + + + private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { + } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { + btService = null; + dbHandler.removeCallbacksAndMessages(dbRunner); + } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) { + btService.readMessage(); + btHandler.postDelayed(btRunNames, 5000); + btHandler.removeCallbacksAndMessages(btRunNames); + } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) { + if (btService != null) { + if (BluetoothLeService.current_request.equals(BluetoothLeService.LIST_OF_SENSORS)) { + Log.d(TAG, "List of Sensors from device."); + parseResponse(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); + } else if (BluetoothLeService.current_request.equals(BluetoothLeService.SENSOR_VALUES)){ + Log.d(TAG, "Receiving Message"); + parseResponse(intent.getStringExtra(BluetoothLeService.EXTRA_DATA)); + } + } + } + } + }; + + // Broadcasting updated to SensorSampleActivity + private void broadcastUpdate(final String action) { + final Intent intent = new Intent(action); + intent.putParcelableArrayListExtra(SENSOR_LIST, sensorList); + sendBroadcast(intent); + Log.d(TAG, "Sending Message Back to Activity."); + } + + public class LocalBinder extends Binder { + public SensorSampleService getService() { return SensorSampleService.this; } + } + + @Override + public void onCreate() { + + btHandler = new Handler(); + btRunNames = new Runnable() { + @Override + public void run() { + getSensorNames(); + } + }; + + builtinList = new ArrayList<Sensor>(); + + dbHandler = new Handler(); + dbRunner = new Runnable() { + @Override + public void run() { + getSensorValues(); + dbHandler.postDelayed(this, loggingInterval); + } + }; + + builtinListRunner = new Runnable() { + @Override + public void run() { + writeToDB(); + dbHandler.postDelayed(this, loggingInterval); + } + }; + + response = ""; + + sensorList = new ArrayList<aSensor>(); + + db = ReadingsDatabase.getInstance(this); + locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); + locationListener = new myLocationListener(); + setLocation(locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)); + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, locationListener); + } + + public class myLocationListener implements LocationListener { + @Override + public void onLocationChanged(Location location) { + setLocation(location); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + @Override + public void onProviderEnabled(String provider) { + } + + @Override + public void onProviderDisabled(String provider) { + } + } + + public void setLocation(Location location){ + if (location != null) { + lastLongitude = location.getLongitude(); + lastLatitude = location.getLatitude(); + lastElevation = location.getAltitude(); + lastAccuracy = location.getAccuracy(); + lastSatellites = location.getExtras().getInt("satellites"); + } + } + + // This is called when another activity or component called startService with the class as the + // intent + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + super.onStartCommand(intent, flags, startId); + Log.d(TAG, "Starting service."); + + // Set the sensor type and the logging interval from the activity that started the service + loggingInterval = intent.getExtras().getLong(LOG_INTERVAL); + sampleOnce = intent.getExtras().getBoolean(SAMPLE_ONCE); + tripName = intent.getExtras().getString(TRIP_NAME); + site = intent.getExtras().getString(SITE); + sector = intent.getExtras().getString(SECTOR); + spot = intent.getExtras().getString(SPOT); + dbHandler = new Handler(); + + writingToDB = true; + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Sampling wake lock"); + mWakeLock.acquire(); + + switch (sensorType){ + case "bluetooth": + if (sampleOnce){ + getSensorValues(); + } else { + dbHandler.postDelayed(dbRunner, loggingInterval); + } + break; + case "built-in": + if (sampleOnce){ + writeToDB(); + } else { + dbHandler.postDelayed(builtinListRunner, loggingInterval); + } + break; + } + + // START_STICKY means that the service continues running until something outside tells it + // to stop. START_NOT_STICKY the service would stop when there's no more work to do. + return Service.START_STICKY; + } + + private static IntentFilter makeGattUpdateIntentFilter() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); + intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED); + intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE); + return intentFilter; + } + + @Override + public IBinder onBind(Intent intent){ return mBinder; } + + @Override + public boolean onUnbind(Intent intent){ + return super.onUnbind(intent); + } + + @Override + public void onDestroy() { + if (btService != null) { + unregisterReceiver(mGattUpdateReceiver); + btService.disconnect(); + btHandler.removeCallbacksAndMessages(btRunNames); + btService.close(); + unbindService(btServiceConnection); + } + if (dbHandler != null) { + dbHandler.removeCallbacksAndMessages(dbRunner); + } + for (Sensor s : builtinList) { + mSensorManager.unregisterListener(this, s); + } + } + + public void stopSampling() { + dbHandler.removeMessages(0); + dbHandler.removeCallbacksAndMessages(dbRunner); + dbHandler = null; + mWakeLock.release(); + } + + private final IBinder mBinder = new LocalBinder(); + + public void parseResponse(String d){ + response += d; + if (response.endsWith("done")) { + if (BluetoothLeService.current_request.equals(BluetoothLeService.LIST_OF_SENSORS)) { + updateSensorList(); + } else { + updateSensorValues(); + } + } + } + + public void updateSensorList() { + String sensor = ""; + if (!registered) { + for (int i = 0; i < response.length(); i++) { + char c = response.charAt(i); + if (c == ';') { + String delims = "[,]"; + String[] tokens = sensor.split(delims); + aSensor sens = new aSensor(tokens[1], tokens[0]); + sens.setPlatform(bluetoothSensor.getDeviceName()); + Log.d(TAG, sens.getName()); + sensorList.add(sens); + sensor = ""; + } else { + sensor += c; + } + } + btHandler.removeCallbacksAndMessages(btRunNames); + getSensorValues(); + } + response = ""; + } + + public void updateSensorValues() { + String sensor = ""; + for (int i = 0; i < response.length(); i++) { + char c = response.charAt(i); + if (c == ';') { + String delims = "[,]"; + String[] tokens = sensor.split(delims); + aSensor sens = getSensor(tokens[1], null); + float[] value = {0, 0}; + value[0] = Float.parseFloat(tokens[2]); + sens.setLastValue(value, 1); + sens.setLastValueQuality(0); + Log.d(TAG, "Sensor Value: " + sens.getLastValueString()); + Log.d(TAG, "Sensor Name: " + sens.getName()); + sensor = ""; + } else { + sensor += c; + } + } + if (registered){ + if (writingToDB) { + writeToDB(); + } + broadcastUpdate(ACTION_UPDATED_LIST); + } else { + registered = true; + broadcastUpdate(ACTION_CONNECTED); + } + response = ""; + } + + public void connectSensor(String sensor, BluetoothSensor btSensor) { + sensorType = sensor; + if (sensor.equals("bluetooth")){ + bluetoothSensor = btSensor; + Intent i = new Intent(this, BluetoothLeService.class); + bindService(i, btServiceConnection, Context.BIND_AUTO_CREATE); + registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter()); + Log.d(TAG, "Starting Bluetooth Service"); + } else { + mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); + builtinList = mSensorManager.getSensorList(Sensor.TYPE_ALL); + + for (Sensor s : builtinList){ + mSensorManager.registerListener(this, s, 100000000); + } + + for (int i=0; i<builtinList.size(); i++) { + aSensor sens = new aSensor(builtinList.get(i)); + // This is to figure out which sensors are uncalibrated + String delims = "[ .]+"; + String[] tokens = sens.getStringType().split(delims); + sens.setName(tokens[2]); + sensorList.add(sens); + } + } + } + + public void getSensorNames() { + String getSensors = "00"; + byte b = 0x00; + byte[] tmp = getSensors.getBytes(); + byte[] tx = new byte[tmp.length + 1]; + tx[0] = b; + for (int i = 1; i < tmp.length + 1; i++) { + tx[i] = tmp[i - 1]; + } + Log.d(TAG, "Writing message for sensor names."); + btService.writeMessage(tx, BluetoothLeService.LIST_OF_SENSORS); + btHandler.removeCallbacksAndMessages(btRunNames); + } + + public aSensor getSensor(String sensorID, Sensor sensor) { + for (aSensor s : sensorList) { + if (sensorType.equals("bluetooth")) { + String aSensorID = s.getID(); + if (sensorID.equals(aSensorID)) { + return s; + } + } else { + String sensorType = sensor.getStringType(); + String aSensorType = s.getStringType(); + if (sensorType.equals(aSensorType)) { + return s; + } + } + } + return null; + } + + public void writeToDB(){ + double[] geoInfo = {lastLatitude, lastLongitude, lastElevation}; + boolean success = false; + String message = ""; + // The user has connected and downloaded data from a remote database + for (aSensor sensor : sensorList) { + if (sensor.getLastValues() != null) { + String table = ""; + if (!sampleOnce) { + table = "fieldday_streaming"; + db.addReading(sensor, sensor.getLastValues()[0], sensor.getLastValueQuality(), getTimestamp(), + site, sector, spot, geoInfo, tripName, lastAccuracy, lastSatellites, "", "", table); + } else { + table = "fieldday_reading"; + success = db.addReading(sensor, sensor.getLastValues()[0], sensor.getLastValueQuality(), getTimestamp(), + site, sector, spot, geoInfo, tripName, lastAccuracy, lastSatellites, "", "", table); + message = ""; + if (success) { + message = "Successfully took a sample"; + } else { + message = "Could not write to the database. Try again."; + } + stopSelf(); + dbHandler.removeCallbacksAndMessages(dbRunner); + } + } + if (success) { + Toast t = Toast.makeText(SensorSampleService.this, message, Toast.LENGTH_SHORT); + t.setGravity(Gravity.CENTER, 0, 0); + t.show(); + } + } + } + + public String getTimestamp(){ + SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + return s.format(new Date()); + } + + public void getSensorValues() { + String getSensors = "vals"; + byte b = 0x00; + byte[] tmp = getSensors.getBytes(); + byte[] tx = new byte[tmp.length + 1]; + tx[0] = b; + for (int i = 1; i < tmp.length + 1; i++) { + tx[i] = tmp[i - 1]; + } + btService.writeMessage(tx, BluetoothLeService.SENSOR_VALUES); + Log.d(TAG, "Writing Sensor Values"); + } + + @Override + public final void onAccuracyChanged(Sensor sensor, int accuracy) { + // Do something here if sensor accuracy changes. + } + + @Override + public final void onSensorChanged(SensorEvent event) { + Sensor sensor = event.sensor; + aSensor sens = getSensor(null, sensor); + float[] values = event.values; + switch (sensor.getType()) { + case Sensor.TYPE_ACCELEROMETER: + sens.setLastValue(values, 3); + sens.setID("and1"); + break; + case Sensor.TYPE_AMBIENT_TEMPERATURE: + sens.setLastValue(values, 3); + sens.setID("and2"); + break; + case Sensor.TYPE_GAME_ROTATION_VECTOR: + sens.setLastValue(values, 3); + sens.setID("and3"); + break; + case Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR: + sens.setLastValue(values, 3); + sens.setID("and4"); + break; + case Sensor.TYPE_GRAVITY: + sens.setLastValue(values, 3); + sens.setUnits("r"); + sens.setID("and5"); + break; + case Sensor.TYPE_GYROSCOPE: + sens.setLastValue(values, 3); + sens.setID("and6"); + break; + case Sensor.TYPE_GYROSCOPE_UNCALIBRATED: + sens.setLastValue(values, 3); + sens.setID("and7"); + break; + case Sensor.TYPE_HEART_RATE: + sens.setLastValue(values, 3); + sens.setID("and8"); + break; + case Sensor.TYPE_LIGHT: + sens.setLastValue(values, 1); + sens.setID("and9"); + break; + case Sensor.TYPE_LINEAR_ACCELERATION: + sens.setLastValue(values, 3); + sens.setID("and10"); + break; + case Sensor.TYPE_MAGNETIC_FIELD: + sens.setLastValue(values, 3); + sens.setID("and11"); + break; + case Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED: + sens.setLastValue(values, 3); + sens.setID("and12"); + break; + case Sensor.TYPE_PRESSURE: + sens.setLastValue(values, 3); + sens.setID("and13"); + break; + case Sensor.TYPE_PROXIMITY: + sens.setLastValue(values, 3); + sens.setID("and14"); + break; + case Sensor.TYPE_RELATIVE_HUMIDITY: + sens.setLastValue(values, 3); + sens.setID("and15"); + break; + case Sensor.TYPE_ROTATION_VECTOR: + sens.setLastValue(values, 3); + sens.setID("and16"); + break; + case Sensor.TYPE_SIGNIFICANT_MOTION: + sens.setLastValue(values, 3); + sens.setID("and17"); + break; + case Sensor.TYPE_STEP_COUNTER: + sens.setLastValue(values, 3); + sens.setID("and18"); + break; + case Sensor.TYPE_STEP_DETECTOR: + sens.setLastValue(values, 3); + sens.setID("and19"); + break; + } + sens.setLastValueQuality(0); + broadcastUpdate(ACTION_UPDATED_LIST); + } +} diff --git a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/aSensor.java b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/aSensor.java index ace716b..305b643 100644 --- a/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/aSensor.java +++ b/app/src/main/java/fieldscience/cs/earlham/edu/fieldday/aSensor.java @@ -1,15 +1,16 @@ package fieldscience.cs.earlham.edu.fieldday; import android.hardware.Sensor; +import android.os.Parcel; +import android.os.Parcelable; -public class aSensor { +public class aSensor implements Parcelable { private float[] lastValues; private float quality; private Integer numberValues; private String type, platform, name, id, units; private Sensor sensor; - private ReadingsDatabase db; // Using Built In sensors public aSensor(Sensor s) { @@ -30,6 +31,17 @@ public class aSensor { numberValues = 1; } + // Created from Parcel + public aSensor(String sensorID, String name, float[] values, float quality, String platform){ + id = sensorID; + this.name = name; + type = name; + lastValues = values; + this.quality = quality; + numberValues = 1; + this.platform = platform; + } + public String toString() { return sensor.toString(); } @@ -90,5 +102,35 @@ public class aSensor { name = sensorName; } + // Required by Android Parcelable class + @Override + public int describeContents() { + return 0; + } + + // Required by Android Parcelable class + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(name); + dest.writeFloatArray(lastValues); + dest.writeFloat(quality); + dest.writeString(platform); + } + + public static final Parcelable.Creator<aSensor> CREATOR = new Parcelable.Creator<aSensor>() { + public aSensor createFromParcel(Parcel in) { + String sensorID = in.readString(); + String sensorName = in.readString(); + float[] values = in.createFloatArray(); + float quality = in.readFloat(); + String platform = in.readString(); + return new aSensor(sensorID, sensorName, values, quality, platform); + } + public aSensor[] newArray(int size) { + return new aSensor[size]; + } + }; + } diff --git a/app/src/main/res/layout/activity_sensorsample.xml b/app/src/main/res/layout/activity_sensorsample.xml index 81dabcc..bf375ee 100644 --- a/app/src/main/res/layout/activity_sensorsample.xml +++ b/app/src/main/res/layout/activity_sensorsample.xml @@ -133,15 +133,16 @@ android:text="@string/sixtySeconds" /> </RadioGroup> - <FrameLayout - android:layout_width="match_parent" - android:id="@+id/sensor_container" - android:paddingTop="10dp" - android:layout_height="550dp" - android:layout_marginTop="65dp" - android:layout_alignParentStart="true" - android:layout_below="@id/site"> - </FrameLayout> + <ListView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="330dp" + android:layout_marginStart="4dp" + android:layout_height="match_parent" + android:id="@android:id/list" + android:layout_weight="0.90" + android:layout_gravity="start" + android:layout_below="@+id/interval" + android:layout_above="@+id/streamButton"> + </ListView> <ImageButton android:id="@+id/exportDbButton" @@ -151,7 +152,7 @@ android:contentDescription="@string/export" android:src="@drawable/export_button" android:layout_marginTop="48dp" - android:layout_below="@+id/sensor_container" + android:layout_below="@android:id/list" android:layout_alignEnd="@+id/accuracy" /> <ImageButton -- GitLab