Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🤖🛜 Android BLE Integration #219

Merged
merged 10 commits into from
Apr 14, 2024
Merged
17 changes: 15 additions & 2 deletions plugin.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
id="cordova-plugin-em-datacollection"
version="1.8.4">
version="1.8.5">

<name>DataCollection</name>
<description>Background data collection FTW! This is the part that I really
Expand Down Expand Up @@ -40,6 +40,7 @@
<hook type="after_plugin_install" src="hooks/android/addResourcesClassImport.js" />

<platform name="android">
<framework src="src/android/e-mission-datacollection.gradle" custom="true" type="gradleReference" />

<config-file target="res/xml/config.xml" parent="/*">
<feature name="DataCollection">
Expand Down Expand Up @@ -73,7 +74,7 @@
<receiver
android:name="edu.berkeley.eecs.emission.cordova.tracker.location.TripDiaryStateMachineReceiver"
android:enabled="true"
android:exported="false">
android:exported="true">
<intent-filter>
<action android:name="local.transition.initialize"></action>
<action android:name="local.transition.exited_geofence"></action>
Expand Down Expand Up @@ -126,6 +127,16 @@
android:enabled="true"
android:exported="false">
</service>
<service
android:name="edu.berkeley.eecs.emission.cordova.tracker.bluetooth.BluetoothService"
android:enabled="true"
android:exported="false">
</service>
<service
android:name="edu.berkeley.eecs.emission.cordova.tracker.bluetooth.BluetoothMonitoringService"
android:enabled="true"
android:exported="false">
</service>
</config-file>

<framework src="com.google.code.gson:gson:2.10.1"/>
Expand Down Expand Up @@ -175,6 +186,8 @@
<source-file src="src/android/wrapper/ConsentConfig.java" target-dir="src/edu/berkeley/eecs/emission/cordova/tracker/wrapper"/>
<source-file src="src/android/wrapper/Timer.java" target-dir="src/edu/berkeley/eecs/emission/cordova/tracker/wrapper"/>
<source-file src="src/android/wrapper/StatsEvent.java" target-dir="src/edu/berkeley/eecs/emission/cordova/tracker/wrapper"/>
<source-file src="src/android/bluetooth/BluetoothService.java" target-dir="src/edu/berkeley/eecs/emission/cordova/tracker/bluetooth"/>
<source-file src="src/android/bluetooth/BluetoothMonitoringService.java" target-dir="src/edu/berkeley/eecs/emission/cordova/tracker/bluetooth"/>
<resource-file src="res/android/statemachine.xml" target="res/values/statemachine.xml" />
<resource-file src="res/android/values/dc_strings.xml" target="res/values/dc_strings.xml"/>
</platform>
Expand Down
11 changes: 11 additions & 0 deletions src/android/DataCollectionPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import edu.berkeley.eecs.emission.cordova.tracker.wrapper.StatsEvent;
import edu.berkeley.eecs.emission.cordova.tracker.wrapper.BluetoothBLE;
import edu.berkeley.eecs.emission.cordova.tracker.verification.SensorControlForegroundDelegate;
import edu.berkeley.eecs.emission.cordova.tracker.bluetooth.BluetoothService;
import edu.berkeley.eecs.emission.cordova.unifiedlogger.Log;
import edu.berkeley.eecs.emission.cordova.usercache.BuiltinUserCache;
import edu.berkeley.eecs.emission.cordova.usercache.UserCacheFactory;
Expand All @@ -61,6 +62,10 @@ public void pluginInitialize() {
new StatsEvent(myActivity, R.string.app_launched));

TripDiaryStateMachineReceiver.initOnUpgrade(myActivity);

// Ask for bluetooth permissions
// We will change this with future releases, we just ran out of time implementing this into the front end
mControlDelegate.checkAndPromptBluetoothScanPermissions();
}

@Override
Expand Down Expand Up @@ -233,6 +238,12 @@ public void run() {
retVal.put("PRIORITY_NO_POWER", LocationRequest.PRIORITY_NO_POWER);
callbackContext.success(retVal);
return true;
} else if (action.equals("bluetoothScan")) {
Context ctxt = cordova.getActivity();
Log.d(ctxt, TAG, "JS requested scan for bluetooth!");
Intent bluetoothServiceIntent = new Intent(ctxt, BluetoothService.class);
ctxt.startService(bluetoothServiceIntent);
return true;
}
return false;
}
Expand Down
86 changes: 86 additions & 0 deletions src/android/bluetooth/BluetoothMonitoringService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package edu.berkeley.eecs.emission.cordova.tracker.bluetooth;

// Android imports
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

// Altbeacon imports
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.Beacon;

// Other plugin imports
import edu.berkeley.eecs.emission.R;
import edu.berkeley.eecs.emission.cordova.unifiedlogger.Log;
import edu.berkeley.eecs.emission.cordova.tracker.ExplicitIntent;
import edu.berkeley.eecs.emission.cordova.tracker.verification.SensorControlChecks;


public class BluetoothMonitoringService extends Service {
private static String TAG = "BluetoothMonitoringService";
private BeaconManager beaconManager;
private String uuid = "bf3df3b1-6e46-35fa-86e5-927c95dd096c";

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(this, TAG, "onStartCommand called!!!!");

// Instantiate variable
beaconManager = BeaconManager.getInstanceForApplication(this);
// This line will ensure that we always get a first callback if beacon is in region when we start monitoring
// https://github.com/AltBeacon/android-beacon-library/issues/708#issuecomment-399513853
beaconManager.setRegionStatePersistenceEnabled(false);

// Start monitoring for BLE beacons
startMonitoring();

// Start sticky
return 1;
}

@Override
public IBinder onBind(Intent intent) {
return null;
}

private void startMonitoring() {
Log.d(this, TAG, "Start monitoring has been called!");

// Code to start monitoring for BLE beacons using AltBeacon library
beaconManager.addMonitorNotifier(new MonitorNotifier() {
@Override
public void didEnterRegion(Region region) {
Log.d(BluetoothMonitoringService.this, TAG, "I just saw a beacon for the first time!");
}

@Override
public void didExitRegion(Region region) {
Log.d(BluetoothMonitoringService.this, TAG, "I no longer see an beacon");
}

@Override
public void didDetermineStateForRegion(int state, Region region) {
Log.d(BluetoothMonitoringService.this, TAG, "I have just switched from seeing/not seeing beacons: "+state);
}
});

// Define our region and start monitoring
Region region = new Region(uuid, null, null, null);
beaconManager.startMonitoring(region);
Log.d(this, TAG, "Starting to monitor for our region: " + region.toString());
}

@Override
public void onDestroy() {
stopMonitoring();
super.onDestroy();
}

private void stopMonitoring() {
Log.d(this, TAG, "Stopping monitoring for the beacon.");
beaconManager.stopMonitoring(new Region(uuid, null, null, null));
beaconManager.removeAllMonitorNotifiers();
}
}
137 changes: 137 additions & 0 deletions src/android/bluetooth/BluetoothService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package edu.berkeley.eecs.emission.cordova.tracker.bluetooth;

// Android imports
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;

// Altbeacon imports
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Beacon;

// Other plugin imports
import edu.berkeley.eecs.emission.R;
import edu.berkeley.eecs.emission.cordova.unifiedlogger.Log;
import edu.berkeley.eecs.emission.cordova.tracker.ExplicitIntent;
import edu.berkeley.eecs.emission.cordova.tracker.verification.SensorControlChecks;

// Saving data
import edu.berkeley.eecs.emission.cordova.usercache.UserCacheFactory;
import edu.berkeley.eecs.emission.cordova.tracker.wrapper.BluetoothBLE;

public class BluetoothService extends Service {
private static String TAG = "BluetoothService";
private BeaconManager beaconManager;
private Set<Beacon> scanned;
private String uuid = "bf3df3b1-6e46-35fa-86e5-927c95dd096c";

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(this, TAG, "onStartCommand called!!!!");

// Instantiate variables
beaconManager = BeaconManager.getInstanceForApplication(this);
scanned = new HashSet<>();

// Check to see if we even have permission to scan at all
boolean bluetoothPermissions = SensorControlChecks.checkBluetoothScanningPermissions(this);

if (!bluetoothPermissions) {
return 1;
}

// Start scanning for BLE beacons
startBeaconScan();

// Start sticky
return 1;
}

// @Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

private void isInRange() {
Log.d(this, TAG, "Is in range has been called!");

stopBeaconScan();

Log.d(this, TAG, "Done waiting, results are... " + scanned.size());

if (scanned.size() > 0) {
Log.d(this, TAG, "Found something!");
Log.d(this, TAG, scanned.toString());
this.sendBroadcast(new ExplicitIntent(this, R.string.transition_ble_beacon_found));
}
onDestroy();
}

private void startBeaconScan() {
// Code to start scanning for BLE beacons using AltBeacon library
Log.d(this, TAG, "startBeaconScan called!!!!");

// Keep track of how many times we have scanned
beaconManager.addRangeNotifier(new RangeNotifier() {
int numScans = 0;

@Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
Log.d(BluetoothService.this, TAG, "Range notifier called...");

if (beacons.size() > 0) {
Log.d(BluetoothService.this, TAG, "Found beacons " + beacons.toString());

for (Beacon beacon : beacons) {
// Even though we are scanning for beacons in a certain region, beacons with different UUID's still come up.
// Until we figure out why that is the case, put this check in place so we only save the ones with the right UUID.
if (beacon.getId1().toString().equals(uuid)) {
scanned.add(beacon);
BluetoothBLE currWrapper = BluetoothBLE.initRangeUpdate(
beacon.getId1().toString(),
System.currentTimeMillis() / 1000, // timestamp in always in secs for us
beacon.getId2().toInt(),
beacon.getId3().toInt(),
// TODO: Figure out what to do with the distance calculations
"ProximityNear",
// accuracy = rough distance estimate limited to two decimal places (in metres)
// NO NOT ASSUME THIS IS ACCURATE - it is effected by radio interference and obstacles
// from https://github.com/petermetz/cordova-plugin-ibeacon
Math.round((beacon.getDistance() * 100)/100),
beacon.getRssi());
UserCacheFactory.getUserCache(BluetoothService.this)
.putSensorData(R.string.key_usercache_bluetooth_ble, currWrapper);
}
}
}

numScans++;

if (numScans >= 4) {
// Once we have hit certain number of scans, stop and determine if any beacons are in range
isInRange();
}
}
});

beaconManager.startRangingBeacons(new Region(uuid, null, null, null));
}

@Override
public void onDestroy() {
// stopBeaconScan();
super.onDestroy();
}

private void stopBeaconScan() {
Log.d(this, TAG, "Stopping monitoring for the beacon.");
beaconManager.stopRangingBeacons(new Region(uuid, null, null, null));
beaconManager.removeAllRangeNotifiers();
}
}
7 changes: 7 additions & 0 deletions src/android/e-mission-datacollection.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
repositories {
mavenCentral()
}

dependencies {
implementation 'org.altbeacon:android-beacon-library:2.19'
}
15 changes: 15 additions & 0 deletions src/android/location/TripDiaryStateMachineForegroundService.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import edu.berkeley.eecs.emission.cordova.tracker.verification.SensorControlBackgroundChecker;
import edu.berkeley.eecs.emission.cordova.tracker.ExplicitIntent;
import edu.berkeley.eecs.emission.cordova.tracker.wrapper.StatsEvent;
import edu.berkeley.eecs.emission.cordova.tracker.bluetooth.BluetoothService;
import edu.berkeley.eecs.emission.cordova.tracker.bluetooth.BluetoothMonitoringService;
import edu.berkeley.eecs.emission.cordova.usercache.BuiltinUserCache;

import edu.berkeley.eecs.emission.cordova.unifiedlogger.Log;
Expand Down Expand Up @@ -79,6 +81,19 @@ public static String humanizeState(Context ctxt, String state) {
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(this, TAG, "onStartCommand called with intent = "+intent+
" flags = " + flags + " and startId = " + startId);

if (intent != null && intent.getAction() != null) {
if (intent.getAction().equals("foreground_start_bluetooth")) {
Intent bluetoothService = new Intent(this, BluetoothService.class);
this.startService(bluetoothService);
return START_STICKY;
} else if (intent.getAction().equals("foreground_start_bluetooth_monitoring")) {
Intent bluetoothService = new Intent(this, BluetoothMonitoringService.class);
this.startService(bluetoothService);
return START_STICKY;
}
}

String message = humanizeState(this, TripDiaryStateMachineService.getState(this));
if (intent == null) {
SensorControlBackgroundChecker.checkAppState(this);
Expand Down
Loading