diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6f2cfad --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "android/tcn-client-android"] + path = android/tcn-client-android + url = https://github.com/TCNCoalition/tcn-client-android diff --git a/android/build.gradle b/android/build.gradle index 0a0325c..e705e82 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -35,12 +35,15 @@ buildscript { // module dependency in an application project. // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies if (project == rootProject) { + ext.kotlin_version = '1.3.72' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:3.6.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' } } } @@ -84,18 +87,7 @@ repositories { dependencies { //noinspection GradleDynamicVersion implementation 'com.facebook.react:react-native:+' - - // Room - def room_version = "2.2.5" - - implementation "androidx.room:room-runtime:$room_version" - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' - annotationProcessor "androidx.room:room-compiler:$room_version" - - implementation 'com.squareup.retrofit2:retrofit:2.7.2' - implementation 'com.squareup.retrofit2:converter-gson:2.7.2' - implementation 'commons-codec:commons-codec:1.14' + implementation project(':tcn-client-android') } def configureReactNativePom(def pom) { diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..dbb7bf7 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..fabdbea --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,5 @@ +include ':tcn-client-android' + +project(":tcn-client-android").projectDir = file("tcn-client-android/tcn-client-android") + + diff --git a/android/src/main/java/com/reactlibrary/ItoBluetoothModule.java b/android/src/main/java/com/reactlibrary/ItoBluetoothModule.java index 4f9ff88..70672b0 100644 --- a/android/src/main/java/com/reactlibrary/ItoBluetoothModule.java +++ b/android/src/main/java/com/reactlibrary/ItoBluetoothModule.java @@ -63,7 +63,7 @@ public ItoBluetoothModule(ReactApplicationContext reactContext) { } //make this method synchronous because it has to return a boolean - @ReactMethod(isBlockingSynchronousMethod = true) + @ReactMethod() public boolean isPossiblyInfected() { try { return tracingServiceInterface.isPossiblyInfected(); @@ -74,7 +74,7 @@ public boolean isPossiblyInfected() { } //make this method synchronous because it has to return a boolean - @ReactMethod(isBlockingSynchronousMethod = true) + @ReactMethod() public int getLatestFetchTime() { try { return tracingServiceInterface.getLatestFetchTime(); diff --git a/android/src/main/java/org/itoapp/strict/service/BleAdvertiser.java b/android/src/main/java/org/itoapp/strict/service/BleAdvertiser.java deleted file mode 100644 index fdde5a1..0000000 --- a/android/src/main/java/org/itoapp/strict/service/BleAdvertiser.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.itoapp.strict.service; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.le.AdvertiseCallback; -import android.bluetooth.le.AdvertiseData; -import android.bluetooth.le.AdvertiseSettings; -import android.bluetooth.le.BluetoothLeAdvertiser; -import android.os.Handler; -import android.util.Log; - -import static org.itoapp.strict.Constants.BLUETOOTH_COMPANY_ID; - -public class BleAdvertiser { - private static final String LOG_TAG = "BleAdvertiser"; - private final Handler serviceHandler; - - private byte[] broadcastData; - private BluetoothLeAdvertiser bluetoothLeAdvertiser; - private AdvertiseCallback bluetoothAdvertiseCallback; - - public BleAdvertiser(BluetoothAdapter bluetoothAdapter, Handler serviceHandler) { - this.bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); - this.serviceHandler = serviceHandler; - } - - public void setBroadcastData(byte[] broadcastData) { - this.broadcastData = broadcastData; - if(bluetoothAdvertiseCallback != null) { - restartAdvertising(); - } - } - - private void restartAdvertising() { - stopAdvertising(); - startAdvertising(); - } - - public void startAdvertising() { - AdvertiseSettings settings = new AdvertiseSettings.Builder() - .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) - .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) - .setConnectable(false) - .setTimeout(0) - .build(); - - - AdvertiseData data = new AdvertiseData.Builder() - .setIncludeTxPowerLevel(false) - .setIncludeDeviceName(false) - .addManufacturerData(BLUETOOTH_COMPANY_ID, broadcastData) - .build(); - - bluetoothAdvertiseCallback = new AdvertiseCallback() { - @Override - public void onStartSuccess(AdvertiseSettings settingsInEffect) { - super.onStartSuccess(settingsInEffect); - Log.i(LOG_TAG, "Advertising onStartSuccess"); - - // when the timeout expires, restart advertising - if (settingsInEffect.getTimeout() > 0) - serviceHandler.postDelayed(() -> restartAdvertising(), - settingsInEffect.getTimeout()); - } - - @Override - public void onStartFailure(int errorCode) { - super.onStartFailure(errorCode); - Log.e(LOG_TAG, "Advertising onStartFailure: " + errorCode); - // TODO - } - }; - - // TODO: check if null when launching with Bluetooth disabled - bluetoothLeAdvertiser.startAdvertising(settings, data, bluetoothAdvertiseCallback); - } - - public void stopAdvertising() { - Log.d(LOG_TAG, "Stopping advertising"); - if (bluetoothAdvertiseCallback != null) { - bluetoothLeAdvertiser.stopAdvertising(bluetoothAdvertiseCallback); - bluetoothAdvertiseCallback = null; - } - } -} diff --git a/android/src/main/java/org/itoapp/strict/service/BleScanner.java b/android/src/main/java/org/itoapp/strict/service/BleScanner.java deleted file mode 100644 index 8b9457b..0000000 --- a/android/src/main/java/org/itoapp/strict/service/BleScanner.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.itoapp.strict.service; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.le.BluetoothLeScanner; -import android.bluetooth.le.ScanCallback; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanRecord; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; -import android.os.Build; -import android.util.Log; - -import org.itoapp.strict.Constants; - -import java.util.Arrays; -import java.util.Collections; - -import static org.itoapp.strict.Constants.BLUETOOTH_COMPANY_ID; -import static org.itoapp.strict.Constants.BROADCAST_LENGTH; -import static org.itoapp.strict.Constants.HASH_LENGTH; - -public class BleScanner { - private static final String LOG_TAG = "BleScanner"; - - private BluetoothLeScanner bluetoothLeScanner; - private ScanCallback bluetoothScanCallback; - private ContactCache contactCache; - - public BleScanner(BluetoothAdapter bluetoothAdapter, ContactCache contactCache) { - bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); - this.contactCache = contactCache; - } - - public void startScanning() { - Log.d(LOG_TAG, "Starting scan"); - bluetoothScanCallback = new ScanCallback() { - public void onScanResult(int callbackType, ScanResult result) { - - Log.d(LOG_TAG, "onScanResult"); - - ScanRecord record = result.getScanRecord(); - - // if there is no record, discard this packet - if (record == null) { - return; - } - - byte[] receivedHash = record.getManufacturerSpecificData(BLUETOOTH_COMPANY_ID); - - // if there is no data, discard - if (receivedHash == null) { - return; - } - - byte txPower = receivedHash[HASH_LENGTH]; - receivedHash = Arrays.copyOf(receivedHash, HASH_LENGTH); - - int rssi = result.getRssi(); - - // TODO take antenna attenuation into account - float distance = (float) Math.pow(10F, ((float) txPower - rssi) / (10 * 2)); - - if(distance < Constants.MIN_SCANNING_DISTANCE) - contactCache.addReceivedBroadcast(receivedHash, distance); - - Log.d(LOG_TAG, Arrays.toString(receivedHash) + ":" + distance); - - } - }; - - ScanSettings.Builder settingsBuilder = new ScanSettings.Builder() - .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) - .setReportDelay(0); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - settingsBuilder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) - .setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT) - .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - settingsBuilder.setLegacy(true); - } - - byte[] manufacturerDataMask = new byte[BROADCAST_LENGTH]; - - ScanFilter filter = new ScanFilter.Builder() - .setManufacturerData(BLUETOOTH_COMPANY_ID, manufacturerDataMask, manufacturerDataMask) - .build(); - - bluetoothLeScanner.startScan(Collections.singletonList(filter), settingsBuilder.build(), bluetoothScanCallback); - } - - public void stopScanning() { - Log.d(LOG_TAG, "Stopping scanning"); - if(bluetoothScanCallback != null) { - bluetoothLeScanner.stopScan(bluetoothScanCallback); - bluetoothScanCallback = null; - } - } -} diff --git a/android/src/main/java/org/itoapp/strict/service/TracingService.java b/android/src/main/java/org/itoapp/strict/service/TracingService.java index 1c5d644..1992b66 100644 --- a/android/src/main/java/org/itoapp/strict/service/TracingService.java +++ b/android/src/main/java/org/itoapp/strict/service/TracingService.java @@ -5,7 +5,6 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; @@ -30,18 +29,17 @@ import org.itoapp.strict.Helper; import org.itoapp.strict.Preconditions; import org.itoapp.strict.database.ItoDBHelper; +import org.tcncoalition.tcnclient.bluetooth.TcnBluetoothService; +import org.tcncoalition.tcnclient.bluetooth.TcnBluetoothServiceCallback; import java.security.SecureRandom; -public class TracingService extends Service { +public class TracingService extends TcnBluetoothService { private static final String LOG_TAG = "TracingService"; private static final String DEFAULT_NOTIFICATION_CHANNEL = "ContactTracing"; private static final int NOTIFICATION_ID = 1; - private SecureRandom uuidGenerator; private Looper serviceLooper; private Handler serviceHandler; - private BleScanner bleScanner; - private BleAdvertiser bleAdvertiser; private ContactCache contactCache; private ItoDBHelper dbHelper; @@ -49,11 +47,20 @@ public class TracingService extends Service { @Override public void onReceive(Context context, android.content.Intent intent) { - if(!isBluetoothRunning()) { - startBluetooth(); - } - else if(!Preconditions.canScanBluetooth(context)) { - stopBluetooth(); + if (!isBluetoothRunning()) { + startTcnExchange(new TcnBluetoothServiceCallback() { + @Override + public byte[] generateTcn() { + return new byte[0]; //TODO + } + + @Override + public void onTcnFound(byte[] tcn, Double estimatedDistance) { + contactCache.addReceivedBroadcast(tcn, (float) estimatedDistance.doubleValue()); + } + }); + } else if (!Preconditions.canScanBluetooth(context)) { + stopTcnExchange(); } } }; @@ -74,7 +81,7 @@ public void publishBeaconUUIDs(long from, long to, PublishUUIDsCallback callback public boolean isPossiblyInfected() { //TODO do async long totalExposureDuration = 0; - for(ItoDBHelper.ContactResult contact:dbHelper.selectInfectedContacts()) { + for (ItoDBHelper.ContactResult contact : dbHelper.selectInfectedContacts()) { totalExposureDuration += contact.duration; } return totalExposureDuration > Constants.MIN_EXPOSURE_DURATION; @@ -91,24 +98,6 @@ public int getLatestFetchTime() { return dbHelper.getLatestFetchTime(); } }; - - private Runnable regenerateUUID = () -> { - Log.i(LOG_TAG, "Regenerating UUID"); - - byte[] uuid = new byte[Constants.UUID_LENGTH]; - uuidGenerator.nextBytes(uuid); - byte[] hashedUUID = Helper.calculateTruncatedSHA256(uuid); - - dbHelper.insertBeacon(uuid); - - byte[] broadcastData = new byte[Constants.BROADCAST_LENGTH]; - broadcastData[Constants.BROADCAST_LENGTH - 1] = getTransmitPower(); - System.arraycopy(hashedUUID, 0, broadcastData, 0, Constants.HASH_LENGTH); - - bleAdvertiser.setBroadcastData(broadcastData); - - serviceHandler.postDelayed(this.regenerateUUID, Constants.UUID_VALID_INTERVAL); - }; //TODO move this to some alarmManager governed section. // Also ideally check the server when connected to WIFI and charger private Runnable checkServer = () -> { @@ -122,27 +111,11 @@ private byte getTransmitPower() { } private boolean isBluetoothRunning() { - return bleScanner != null; + return false; //TODO } private void stopBluetooth() { - Log.i(LOG_TAG, "Stopping Bluetooth"); - contactCache.flush(); - if (bleScanner != null) - try { - bleScanner.stopScanning(); - } - catch(Exception ignored) {} - if (bleAdvertiser != null) - try { - bleAdvertiser.stopAdvertising(); - } - catch(Exception ignored) {} - - serviceHandler.removeCallbacks(regenerateUUID); - - bleScanner = null; - bleAdvertiser = null; + //TODO } private void startBluetooth() { @@ -155,18 +128,11 @@ private void startBluetooth() { assert bluetoothManager != null; BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); - bleScanner = new BleScanner(bluetoothAdapter, contactCache); - bleAdvertiser = new BleAdvertiser(bluetoothAdapter, serviceHandler); - - regenerateUUID.run(); - bleAdvertiser.startAdvertising(); - bleScanner.startScanning(); } @Override public void onCreate() { super.onCreate(); - uuidGenerator = new SecureRandom(); dbHelper = new ItoDBHelper(this); HandlerThread thread = new HandlerThread("TracingServiceHandler", Thread.NORM_PRIORITY); thread.start(); @@ -218,8 +184,6 @@ private void runAsForgroundService() { @Override public void onDestroy() { - bleAdvertiser.stopAdvertising(); - bleScanner.stopScanning(); contactCache.flush(); unregisterReceiver(broadcastReceiver); super.onDestroy(); @@ -227,6 +191,7 @@ public void onDestroy() { @Override public int onStartCommand(Intent intent, int flags, int startId) { + super.onStartCommand(intent, flags, startId); runAsForgroundService(); return START_STICKY; } diff --git a/android/tcn-client-android b/android/tcn-client-android new file mode 160000 index 0000000..b0286fd --- /dev/null +++ b/android/tcn-client-android @@ -0,0 +1 @@ +Subproject commit b0286fda8904ad7d0a7ea95e6524e151c7667436