├── .gitattributes ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── android ├── README.md ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── aidl │ │ └── org │ │ │ └── itoapp │ │ │ ├── DistanceCallback.aidl │ │ │ ├── PublishUUIDsCallback.aidl │ │ │ └── TracingServiceInterface.aidl │ └── java │ │ ├── com │ │ └── reactlibrary │ │ │ ├── ItoBluetoothModule.java │ │ │ └── ItoBluetoothPackage.java │ │ └── org │ │ └── itoapp │ │ └── strict │ │ ├── Constants.java │ │ ├── Helper.java │ │ ├── Preconditions.java │ │ ├── database │ │ ├── Converters.java │ │ ├── ItoDBHelper.java │ │ ├── RoomDB.java │ │ ├── dao │ │ │ ├── LastReportDao.java │ │ │ ├── LocalKeyDao.java │ │ │ └── SeenTCNDao.java │ │ └── entities │ │ │ ├── LastReport.java │ │ │ ├── LocalKey.java │ │ │ └── SeenTCN.java │ │ ├── network │ │ ├── BlockingInputStream.java │ │ └── NetworkHelper.java │ │ └── service │ │ ├── BleAdvertiser.java │ │ ├── BleScanner.java │ │ ├── CheckServerTask.java │ │ ├── ContactCache.java │ │ ├── INextTCNCallback.java │ │ ├── PublishBeaconsTask.java │ │ ├── StartupListener.java │ │ ├── TCNProtoGen.java │ │ ├── TCNProtoUtil.java │ │ └── TracingService.java │ └── test │ └── java │ └── org │ └── itoapp │ └── strict │ └── service │ ├── TCNProtoGenTest.java │ └── TCNProtoUtilTest.java ├── index.js ├── ios ├── ItoBluetooth-Bridging-Header.h ├── ItoBluetooth.m ├── ItoBluetooth.swift ├── ItoBluetooth.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── ItoBluetooth.xcscheme ├── ItoBluetooth.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Podfile ├── Podfile.lock └── SwItoBluetooth.swift ├── package-lock.json ├── package.json └── react-native-ito.podspec /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules/ 8 | npm-debug.log 9 | yarn-error.log 10 | 11 | # Xcode 12 | # 13 | build/ 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | *.xccheckout 24 | *.moved-aside 25 | DerivedData 26 | *.hmap 27 | *.ipa 28 | *.xcuserstate 29 | project.xcworkspace 30 | 31 | # Android/IntelliJ 32 | # 33 | build/ 34 | .idea 35 | .gradle 36 | local.properties 37 | *.iml 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ito-org/react-native-ito/a97daa0810a4e020d16331eef0f5aff37e7142df/.npmignore -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We accept pull requests. 2 | 3 | - Please make sure you understand the concepts behind the [TCN protocol](https://github.com/TCNCoalition/TCN). 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Clear BSD License 2 | 3 | Copyright (c) 2020, ito 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS 21 | LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ito library for React Native apps 2 | 3 | [![Documentation](https://img.shields.io/website?label=documentation&url=https%3A%2F%2Fdocs.ito-app.org%2Freact-native-ito)](https://docs.ito-app.org/react-native-ito/) 4 | [![License](https://img.shields.io/badge/license-BSD--3--Clause--Clear-brightgreen)](LICENSE) 5 | 6 | ## What it does 7 | 8 | This library is the native backend for the ito app. It does several things 9 | - generate TCNs (Temporary Contact Numbers) as per the STRICT protocol 10 | - handle bluetooth communication 11 | - store the data received over bluetooth 12 | - store the data used to generate the TCNs 13 | - regularly poll the server for TCN reports and compare with the local database 14 | - upload TCN generation data from specified timeframes 15 | - provide some feedback for better UX 16 | 17 | ## Getting started 18 | 19 | `$ npm install https://github.com/ito-org/react-native-ito --save` 20 | 21 | ### Mostly automatic installation 22 | 23 | `$ react-native link react-native-ito` 24 | 25 | ## Usage 26 | Retrieve the native module. 27 | ```js 28 | import {NativeModules, NativeEventEmitter} from 'react-native'; 29 | 30 | const { ItoBluetooth } = NativeModules; 31 | ``` 32 | 33 | Start tracing (after getting location permission for Bluetooth advertising and scanning): 34 | ```javascript 35 | ItoBluetooth.restartTracing(); 36 | ``` 37 | Get the current and recent distances of ito devices in the vicinity. 38 | ```js 39 | const eventEmitter = new NativeEventEmitter(ItoBluetooth); 40 | this.eventListener = eventEmitter.addListener('onDistancesChanged', (distances) => { 41 | //get notified about the distances to nearby devices 42 | }); 43 | ``` 44 | Find out whether the user is likely to have been in contact with an infected user 45 | ```js 46 | ItoBluetooth.isPossiblyInfected() // boolean 47 | ``` 48 | 49 | Upload the user's own TCNs of the last 7 days 50 | ```js 51 | const nowSeconds = Date.now() / 1000; 52 | const sevenDaysAgo = nowSeconds - 7 * 24 * 60 * 60; 53 | ItoBluetooth.publishBeaconUUIDs( 54 | sevenDaysAgo, 55 | now, 56 | (success) => { 57 | console.log('upload ' + success ? 'succeeded' : 'failed'); 58 | }, 59 | ) 60 | ``` 61 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | 4 | If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm: 5 | 6 | 1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed 7 | 2. Be sure to have a `local.properties` file in this folder that points to the Android SDK and NDK 8 | ``` 9 | ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle 10 | sdk.dir=/Users/{username}/Library/Android/sdk 11 | ``` 12 | 3. Delete the `maven` folder 13 | 4. Run `./gradlew installArchives` 14 | 5. Verify that latest set of generated files is in the maven folder with the correct version number 15 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // android/build.gradle 2 | 3 | // based on: 4 | // 5 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle 6 | // original location: 7 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle 8 | // 9 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle 10 | // original location: 11 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | 17 | } 18 | } 19 | 20 | def DEFAULT_COMPILE_SDK_VERSION = 28 21 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3' 22 | def DEFAULT_MIN_SDK_VERSION = 21 23 | def DEFAULT_TARGET_SDK_VERSION = 28 24 | 25 | def safeExtGet(prop, fallback) { 26 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 27 | } 28 | 29 | apply plugin: 'com.android.library' 30 | apply plugin: 'maven' 31 | 32 | buildscript { 33 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 34 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 35 | // module dependency in an application project. 36 | // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies 37 | if (project == rootProject) { 38 | repositories { 39 | google() 40 | jcenter() 41 | } 42 | dependencies { 43 | classpath 'com.android.tools.build:gradle:3.4.1' 44 | } 45 | } 46 | } 47 | 48 | apply plugin: 'com.android.library' 49 | apply plugin: 'maven' 50 | 51 | android { 52 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) 53 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION) 54 | compileOptions { 55 | sourceCompatibility = 1.8 56 | targetCompatibility = 1.8 57 | } 58 | defaultConfig { 59 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) 60 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) 61 | versionCode 1 62 | versionName "1.0" 63 | } 64 | lintOptions { 65 | abortOnError false 66 | } 67 | } 68 | 69 | repositories { 70 | // ref: https://www.baeldung.com/maven-local-repository 71 | mavenLocal() 72 | maven { 73 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 74 | url "$rootDir/../node_modules/react-native/android" 75 | } 76 | maven { 77 | // Android JSC is installed from npm 78 | url "$rootDir/../node_modules/jsc-android/dist" 79 | } 80 | google() 81 | jcenter() 82 | } 83 | 84 | dependencies { 85 | //noinspection GradleDynamicVersion 86 | implementation 'com.facebook.react:react-native:+' 87 | 88 | // Room 89 | def room_version = "2.2.5" 90 | 91 | implementation "androidx.room:room-runtime:$room_version" 92 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 93 | implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' 94 | annotationProcessor "androidx.room:room-compiler:$room_version" 95 | 96 | implementation 'com.squareup.retrofit2:retrofit:2.7.2' 97 | implementation 'com.squareup.retrofit2:converter-gson:2.7.2' 98 | implementation 'commons-codec:commons-codec:1.14' 99 | implementation 'cafe.cryptography:ed25519-elisabeth:0.1.0' 100 | implementation 'cafe.cryptography:curve25519-elisabeth:0.1.0' 101 | implementation 'androidx.room:room-runtime:2.2.5' 102 | annotationProcessor 'androidx.room:room-compiler:2.2.5' 103 | } 104 | 105 | def configureReactNativePom(def pom) { 106 | def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text) 107 | 108 | pom.project { 109 | name packageJson.title 110 | artifactId packageJson.name 111 | version = packageJson.version 112 | group = "com.reactlibrary" 113 | description packageJson.description 114 | url packageJson.repository.baseUrl 115 | 116 | licenses { 117 | license { 118 | name packageJson.license 119 | url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename 120 | distribution 'repo' 121 | } 122 | } 123 | 124 | developers { 125 | developer { 126 | id packageJson.author.username 127 | name packageJson.author.name 128 | } 129 | } 130 | } 131 | } 132 | 133 | afterEvaluate { project -> 134 | // some Gradle build hooks ref: 135 | // https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html 136 | task androidJavadoc(type: Javadoc) { 137 | source = android.sourceSets.main.java.srcDirs 138 | classpath += files(android.bootClasspath) 139 | classpath += files(project.getConfigurations().getByName('compile').asList()) 140 | include '**/*.java' 141 | } 142 | 143 | task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { 144 | classifier = 'javadoc' 145 | from androidJavadoc.destinationDir 146 | } 147 | 148 | task androidSourcesJar(type: Jar) { 149 | classifier = 'sources' 150 | from android.sourceSets.main.java.srcDirs 151 | include '**/*.java' 152 | } 153 | 154 | android.libraryVariants.all { variant -> 155 | def name = variant.name.capitalize() 156 | def javaCompileTask = variant.javaCompileProvider.get() 157 | 158 | task "jar${name}"(type: Jar, dependsOn: javaCompileTask) { 159 | from javaCompileTask.destinationDir 160 | } 161 | } 162 | 163 | artifacts { 164 | archives androidSourcesJar 165 | archives androidJavadocJar 166 | } 167 | 168 | task installArchives(type: Upload) { 169 | configuration = configurations.archives 170 | repositories.mavenDeployer { 171 | // Deploy to react-native-event-bridge/maven, ready to publish to npm 172 | repository url: "file://${projectDir}/../android/maven" 173 | configureReactNativePom pom 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /android/src/main/aidl/org/itoapp/DistanceCallback.aidl: -------------------------------------------------------------------------------- 1 | // DistanceCallback.aidl 2 | package org.itoapp; 3 | 4 | // Declare any non-default types here with import statements 5 | 6 | interface DistanceCallback { 7 | 8 | void onDistanceMeasurements(in float[] distance); 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/aidl/org/itoapp/PublishUUIDsCallback.aidl: -------------------------------------------------------------------------------- 1 | // PublishUUIDsCallback.aidl 2 | package org.itoapp; 3 | 4 | interface PublishUUIDsCallback { 5 | void onSuccess(); 6 | void onFailure(); 7 | } 8 | -------------------------------------------------------------------------------- /android/src/main/aidl/org/itoapp/TracingServiceInterface.aidl: -------------------------------------------------------------------------------- 1 | // TracingServiceInterface.aidl 2 | package org.itoapp; 3 | 4 | import org.itoapp.DistanceCallback; 5 | import org.itoapp.PublishUUIDsCallback; 6 | 7 | interface TracingServiceInterface { 8 | void setDistanceCallback(DistanceCallback distanceCallback); 9 | 10 | void publishBeaconUUIDs(long from, long to, PublishUUIDsCallback callback); 11 | 12 | boolean isPossiblyInfected(); 13 | 14 | void restartTracingService(); 15 | 16 | int getLatestFetchTime(); 17 | } 18 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactlibrary/ItoBluetoothModule.java: -------------------------------------------------------------------------------- 1 | package com.reactlibrary; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.ServiceConnection; 7 | import android.os.IBinder; 8 | import android.os.RemoteException; 9 | import android.util.Log; 10 | import androidx.room.Room; 11 | 12 | import com.facebook.react.bridge.Arguments; 13 | import com.facebook.react.bridge.Callback; 14 | import com.facebook.react.bridge.ReactApplicationContext; 15 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 16 | import com.facebook.react.bridge.ReactMethod; 17 | import com.facebook.react.modules.core.DeviceEventManagerModule; 18 | 19 | import org.itoapp.DistanceCallback; 20 | import org.itoapp.PublishUUIDsCallback; 21 | import org.itoapp.TracingServiceInterface; 22 | import org.itoapp.strict.service.TracingService; 23 | import org.itoapp.strict.database.RoomDB; 24 | 25 | public class ItoBluetoothModule extends ReactContextBaseJavaModule { 26 | 27 | private static final String LOG_TAG = "ItoBluetoothModule"; 28 | private final ReactApplicationContext reactContext; 29 | private TracingServiceInterface tracingServiceInterface; 30 | private DistanceCallback.Stub nativeContactCallback = new DistanceCallback.Stub() { 31 | @Override 32 | public void onDistanceMeasurements(float[] distances) { 33 | Log.d(LOG_TAG, "emitting onDistancesChanged"); 34 | reactContext. 35 | getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 36 | .emit("onDistancesChanged", Arguments.fromArray(distances)); 37 | } 38 | }; 39 | private ServiceConnection serviceConnection = new ServiceConnection() { 40 | public void onServiceConnected(ComponentName className, 41 | IBinder service) { 42 | Log.d(LOG_TAG, "Service connected"); 43 | tracingServiceInterface = TracingServiceInterface.Stub.asInterface(service); 44 | try { 45 | Log.d(LOG_TAG, "Registering callback"); 46 | tracingServiceInterface.setDistanceCallback(nativeContactCallback); 47 | } catch (RemoteException e) { 48 | Log.e(LOG_TAG, "looks like the service already crashed!", e); 49 | } 50 | } 51 | 52 | public void onServiceDisconnected(ComponentName className) { 53 | Log.d(LOG_TAG, "Service disconnected"); 54 | tracingServiceInterface = null; 55 | } 56 | }; 57 | 58 | public ItoBluetoothModule(ReactApplicationContext reactContext) { 59 | super(reactContext); 60 | Log.d(LOG_TAG, "Creating ItoBluetoothModule"); 61 | if (RoomDB.db == null) 62 | RoomDB.db = Room.databaseBuilder(reactContext, 63 | RoomDB.class, "ito.room.db").allowMainThreadQueries().build(); 64 | this.reactContext = reactContext; 65 | Intent intent = new Intent(reactContext, TracingService.class); 66 | reactContext.startService(intent); 67 | bindService(); 68 | } 69 | 70 | //make this method synchronous because it has to return a boolean 71 | @ReactMethod(isBlockingSynchronousMethod = true) 72 | public boolean isPossiblyInfected() { 73 | try { 74 | return tracingServiceInterface.isPossiblyInfected(); 75 | } catch (RemoteException e) { 76 | Log.e(LOG_TAG, "Could not get infected status", e); 77 | return false; 78 | } 79 | } 80 | 81 | //make this method synchronous because it has to return a boolean 82 | @ReactMethod(isBlockingSynchronousMethod = true) 83 | public int getLatestFetchTime() { 84 | try { 85 | return tracingServiceInterface.getLatestFetchTime(); 86 | } catch (RemoteException e) { 87 | Log.e(LOG_TAG, "Could not get latest fetch time", e); 88 | return -1; 89 | } 90 | } 91 | 92 | @ReactMethod 93 | public void publishBeaconUUIDs(int from, int to, Callback callback) { 94 | try { 95 | tracingServiceInterface.publishBeaconUUIDs(from * 1000L, to * 1000L, new PublishUUIDsCallback.Stub() { 96 | @Override 97 | public void onSuccess() { 98 | callback.invoke(true); 99 | } 100 | 101 | @Override 102 | public void onFailure() { 103 | callback.invoke(false); 104 | } 105 | }); 106 | } catch (RemoteException e) { 107 | e.printStackTrace(); 108 | } 109 | } 110 | 111 | @ReactMethod 112 | public void restartTracing() { 113 | try { 114 | tracingServiceInterface.restartTracingService(); 115 | } catch (RemoteException e) { 116 | Log.e(LOG_TAG, "Could not get TracingService", e); 117 | } 118 | } 119 | 120 | private void bindService() { 121 | Log.d(LOG_TAG, "binding service"); 122 | Intent intent = new Intent(reactContext, TracingService.class); 123 | reactContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); 124 | } 125 | 126 | @Override 127 | public String getName() { 128 | return "ItoBluetooth"; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /android/src/main/java/com/reactlibrary/ItoBluetoothPackage.java: -------------------------------------------------------------------------------- 1 | package com.reactlibrary; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import com.facebook.react.ReactPackage; 7 | import com.facebook.react.bridge.NativeModule; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.uimanager.ViewManager; 10 | import com.facebook.react.bridge.JavaScriptModule; 11 | 12 | public class ItoBluetoothPackage implements ReactPackage { 13 | @Override 14 | public List createNativeModules(ReactApplicationContext reactContext) { 15 | return Collections.singletonList(new ItoBluetoothModule(reactContext)); 16 | } 17 | 18 | // @Override 19 | public List> createJSModules() { 20 | return Collections.emptyList(); 21 | } 22 | 23 | @Override 24 | public List createViewManagers(ReactApplicationContext reactContext) { 25 | return Collections.emptyList(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/Constants.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict; 2 | 3 | public class Constants { 4 | public static final int BLUETOOTH_COMPANY_ID = 65535; // TODO get a real company ID! 5 | public static final int UUID_LENGTH = 16; 6 | public static final int HASH_LENGTH = 16; 7 | public static final int BROADCAST_LENGTH = HASH_LENGTH ; 8 | public static final int TCN_VALID_INTERVAL = 1000 * 60 * 15; //ms * sec * 15 min 9 | public static final int CHECK_SERVER_INTERVAL = 1000 * 10; //ms * 10 sec 10 | public static final int DISTANCE_SMOOTHING_MA_LENGTH = 7; 11 | public static final int CACHE_FLUSH_TIME = 1000 * 30; // 30 seconds timeout 12 | public static final long MIN_CONTACT_DURATION = 1000 * 60 * 1; //discard all contacts less than 1 minutes 13 | public static final float MIN_SCANNING_DISTANCE = 20; 14 | public static final long MIN_EXPOSURE_DURATION = 1000 * 60 * 2; // 2 minutes 15 | public static final int RATCHET_EXCHANGE_INTERVAL = (4 * 24); // 1day? if beacon gets changed every 15min 16 | } 17 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/Helper.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict; 2 | 3 | import android.util.Log; 4 | 5 | import java.security.MessageDigest; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.util.Arrays; 8 | 9 | import static org.itoapp.strict.Constants.HASH_LENGTH; 10 | 11 | public class Helper { 12 | 13 | private static final String LOG_TAG = "ITOHelper"; 14 | private static MessageDigest sha256MessageDigest; 15 | 16 | private Helper() { 17 | } 18 | 19 | public static byte[] calculateTruncatedSHA256(byte[] uuid) { 20 | if(sha256MessageDigest == null) { 21 | try { 22 | sha256MessageDigest = MessageDigest.getInstance("SHA-256"); 23 | } catch (NoSuchAlgorithmException e) { 24 | Log.wtf(LOG_TAG, "Algorithm not found", e); 25 | throw new RuntimeException(e); 26 | } 27 | } 28 | 29 | byte[] sha256Hash = sha256MessageDigest.digest(uuid); 30 | return Arrays.copyOf(sha256Hash, HASH_LENGTH); 31 | } 32 | 33 | public static byte[] hex2Byte(String s) { 34 | int len = s.length(); 35 | byte[] data = new byte[len / 2]; 36 | for (int i = 0; i < len; i += 2) { 37 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 38 | + Character.digit(s.charAt(i+1), 16)); 39 | } 40 | return data; 41 | } 42 | 43 | public static String byte2Hex(byte[] in) { 44 | String s = ""; 45 | for (byte b : in) { 46 | String st = String.format("%02X", b).toLowerCase(); 47 | s += st; 48 | } 49 | return s; 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/Preconditions.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict; 2 | 3 | import android.Manifest; 4 | import android.bluetooth.BluetoothAdapter; 5 | import android.bluetooth.BluetoothManager; 6 | import android.content.Context; 7 | import android.content.pm.PackageManager; 8 | import android.location.LocationManager; 9 | import android.os.Build; 10 | import android.util.Log; 11 | 12 | import androidx.core.content.ContextCompat; 13 | 14 | 15 | public class Preconditions { 16 | private static final String LOG_TAG = "ITOPreconditions"; 17 | 18 | public static boolean isLocationServiceEnabled(Context context) { 19 | LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 20 | assert locationManager != null; 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 22 | //I have read this is not actually required on all devices, but I have not found a way 23 | //to check if it is required. 24 | //If location is not enabled the BLE scan fails silently (scan callback is never called) 25 | if (!locationManager.isLocationEnabled()) { 26 | Log.i(LOG_TAG, "Location not enabled (API>=P check)"); 27 | return false; 28 | } 29 | } else { 30 | //Not sure if this is the correct check, gps is not really required, but passive provider 31 | //does not seem to be enough 32 | if (!locationManager.getProviders(true).contains(LocationManager.GPS_PROVIDER)) { 33 | Log.i(LOG_TAG, "Location not enabled (API

getAll(); 17 | 18 | @Insert(onConflict = OnConflictStrategy.REPLACE) 19 | public void saveOrUpdate(LocalKey localKey); 20 | 21 | @Query("DELETE FROM LocalKey") 22 | public void deleteAll(); 23 | } 24 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/database/dao/SeenTCNDao.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.database.dao; 2 | 3 | import org.itoapp.strict.database.entities.SeenTCN; 4 | 5 | import java.util.List; 6 | 7 | import androidx.room.Dao; 8 | import androidx.room.Insert; 9 | import androidx.room.Query; 10 | import androidx.room.Update; 11 | 12 | @Dao 13 | public interface SeenTCNDao { 14 | 15 | @Query("SELECT * FROM SeenTCN WHERE reportedSick=1") 16 | public List findSickTCNs(); 17 | 18 | @Query("SELECT * FROM SeenTCN WHERE tcn=:tcn") 19 | public SeenTCN findSeenTCNByHash(String tcn); 20 | 21 | @Insert 22 | public void insert(SeenTCN seenTCN); 23 | 24 | @Update 25 | public void update(SeenTCN seenTCN); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/database/entities/LastReport.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.database.entities; 2 | 3 | import java.util.Date; 4 | 5 | import androidx.room.ColumnInfo; 6 | import androidx.room.Entity; 7 | import androidx.room.Index; 8 | import androidx.room.PrimaryKey; 9 | 10 | 11 | @Entity(indices = {@Index(value = {"server_url"}, 12 | unique = true)}) 13 | public class LastReport { 14 | @PrimaryKey(autoGenerate = true) 15 | public long id; 16 | 17 | @ColumnInfo(name="server_url") 18 | public String serverUrl; 19 | 20 | @ColumnInfo(name="lastcheck") 21 | public Date lastcheck; 22 | 23 | @ColumnInfo(name="lastReportHash") 24 | public String lastReportHash; 25 | 26 | @Override 27 | public String toString() { 28 | return "LastReport{" + 29 | "id=" + id + 30 | ", serverUrl='" + serverUrl + '\'' + 31 | ", lastcheck=" + lastcheck + 32 | ", lastReportHash='" + lastReportHash + '\'' + 33 | '}'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/database/entities/LocalKey.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.database.entities; 2 | 3 | import java.util.Date; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.room.ColumnInfo; 7 | import androidx.room.Entity; 8 | import androidx.room.PrimaryKey; 9 | 10 | @Entity 11 | public class LocalKey { 12 | 13 | @PrimaryKey() 14 | @NonNull 15 | public String rak; 16 | 17 | @ColumnInfo(name = "currentTCKpos") 18 | public int currentTCKpos; 19 | 20 | @ColumnInfo(name = "last_generated") 21 | public Date lastGenerated; 22 | 23 | @Override 24 | public String toString() { 25 | return "LocalKey{" + 26 | "rak='" + rak + '\'' + 27 | ", currentTCKpos=" + currentTCKpos + 28 | ", lastGenerated=" + lastGenerated + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/database/entities/SeenTCN.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.database.entities; 2 | 3 | import java.util.Date; 4 | 5 | import androidx.room.ColumnInfo; 6 | import androidx.room.Entity; 7 | import androidx.room.Index; 8 | import androidx.room.PrimaryKey; 9 | 10 | @Entity(indices = {@Index(value = {"tcn"}, 11 | unique = true)}) 12 | public class SeenTCN { 13 | @PrimaryKey(autoGenerate = true) 14 | public long id; 15 | 16 | @ColumnInfo(name = "tcn") 17 | public String tcn; 18 | 19 | @ColumnInfo(name = "last_seen") 20 | public Date lastSeen; 21 | 22 | @ColumnInfo(name = "total_duration") 23 | public long duration; 24 | 25 | @ColumnInfo(name = "proximity") 26 | public long proximity; 27 | 28 | @ColumnInfo(name = "reportedSick") 29 | public boolean reportedSick = false; 30 | 31 | public SeenTCN() { 32 | } 33 | 34 | public SeenTCN(String tcn, Date lastSeen, long proximity, long duration) { 35 | this.tcn = tcn; 36 | this.lastSeen = lastSeen; 37 | this.duration = duration; 38 | this.proximity = proximity; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return "SeenTCN{" + 44 | "id=" + id + 45 | ", tcn='" + tcn + '\'' + 46 | ", lastSeen=" + lastSeen + 47 | ", duration=" + duration + 48 | ", proximity=" + proximity + 49 | ", reportedSick=" + reportedSick + 50 | '}'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/network/BlockingInputStream.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.network; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | class BlockingInputStream extends InputStream { 7 | private final InputStream stream; 8 | 9 | public BlockingInputStream(InputStream content) { 10 | stream = content; 11 | } 12 | 13 | /* 14 | uses the implementation from .read(byte b[], int off, int len) 15 | this will only return if array was filled to request or -1 or exception occurred 16 | */ 17 | 18 | @Override 19 | public int read() throws IOException { 20 | return stream.read(); 21 | } 22 | 23 | @Override 24 | public int available() throws IOException { 25 | return stream.available(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/network/NetworkHelper.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.network; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import org.itoapp.strict.database.RoomDB; 7 | import org.itoapp.strict.database.entities.LastReport; 8 | 9 | import java.io.BufferedOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.net.HttpURLConnection; 14 | import java.net.MalformedURLException; 15 | import java.net.URL; 16 | import java.nio.ByteBuffer; 17 | import java.util.Arrays; 18 | import java.util.Date; 19 | import java.util.LinkedList; 20 | import java.util.List; 21 | 22 | import static org.itoapp.strict.Helper.byte2Hex; 23 | 24 | public class NetworkHelper { 25 | 26 | private static final String LOG_TAG = "ITOInfectedUUIDRepository"; 27 | public static final String BASE_URL = "https://tcn.ito-app.org/tcnreport"; 28 | 29 | private static final int SIGNATURELENGTH = 64; 30 | private static final int BASELENGTH = 70; 31 | 32 | public static List refreshInfectedUUIDs() { 33 | LastReport lastReportHashForServer = RoomDB.db.lastReportDao().getLastReportHashForServer(BASE_URL); 34 | if (lastReportHashForServer == null) { 35 | lastReportHashForServer = new LastReport(); 36 | lastReportHashForServer.serverUrl = BASE_URL; 37 | } 38 | List reports = new LinkedList<>(); 39 | HttpURLConnection urlConnection = null; 40 | try { 41 | //TODO use a more sophisticated library 42 | URL url; 43 | if (TextUtils.isEmpty(lastReportHashForServer.lastReportHash)) 44 | url = new URL(BASE_URL); 45 | else 46 | url = new URL(BASE_URL + "?from=" + lastReportHashForServer.lastReportHash); 47 | urlConnection = (HttpURLConnection) url.openConnection(); 48 | urlConnection.addRequestProperty("Accept", "application/octet-stream"); 49 | InputStream in = new BlockingInputStream(urlConnection.getInputStream()); 50 | byte[] base = new byte[BASELENGTH]; 51 | byte[] memo; 52 | int readBytes; 53 | while ((readBytes = in.read(base, 0, BASELENGTH)) == BASELENGTH) { 54 | int memolength = (int) base[BASELENGTH - 1] & 0xFF; 55 | memo = new byte[memolength]; 56 | if (in.read(memo, 0, memolength) < memolength) { 57 | throw new RuntimeException("Parsing from Server failed"); 58 | } 59 | byte[] signature = new byte[SIGNATURELENGTH]; 60 | if (in.read(signature, 0, SIGNATURELENGTH) < SIGNATURELENGTH) { 61 | throw new RuntimeException("Parsing from Server failed"); 62 | } 63 | // use PushbackInputstream and get rid of BB? 64 | ByteBuffer report = ByteBuffer.allocate(BASELENGTH + memolength + SIGNATURELENGTH); 65 | report.put(base); 66 | report.put(memo); 67 | report.put(signature); 68 | reports.add(report.array()); 69 | } 70 | if (readBytes > 0) 71 | throw new RuntimeException("Parsing from Server failed"); 72 | } catch (MalformedURLException e) { 73 | Log.wtf(LOG_TAG, "Malformed URL?!", e); 74 | throw new RuntimeException(e); 75 | } catch (IOException e) { 76 | e.printStackTrace(); 77 | } finally { 78 | if (urlConnection != null) { 79 | urlConnection.disconnect(); 80 | } 81 | } 82 | if (reports.size() > 0) { 83 | byte[] lastreport = reports.get(reports.size() - 1); 84 | 85 | lastReportHashForServer.lastcheck = new Date(); 86 | 87 | lastReportHashForServer.lastReportHash = byte2Hex(Arrays.copyOfRange(lastreport, 0, lastreport.length - SIGNATURELENGTH)); 88 | RoomDB.db.lastReportDao().saveOrUpdate(lastReportHashForServer); 89 | } 90 | return reports; 91 | } 92 | 93 | 94 | public static void publishReports(List reports) throws IOException { 95 | 96 | HttpURLConnection urlConnection = null; 97 | for (byte[] report : reports) // FIXME: validate return code 98 | try { 99 | URL url = new URL(BASE_URL); 100 | urlConnection = (HttpURLConnection) url.openConnection(); 101 | urlConnection.setDoOutput(true); 102 | urlConnection.addRequestProperty("Content-Type", "application/octet-stream"); 103 | OutputStream outputStream = new BufferedOutputStream(urlConnection.getOutputStream()); 104 | outputStream.write(report); 105 | outputStream.close(); 106 | 107 | InputStream inputStream = urlConnection.getInputStream(); 108 | inputStream.read(); 109 | inputStream.close(); 110 | } catch (MalformedURLException e) { 111 | Log.wtf(LOG_TAG, "Malformed URL?!", e); 112 | throw new RuntimeException(e); 113 | } finally { 114 | if (urlConnection != null) 115 | urlConnection.disconnect(); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/BleAdvertiser.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.le.AdvertiseCallback; 5 | import android.bluetooth.le.AdvertiseData; 6 | import android.bluetooth.le.AdvertiseSettings; 7 | import android.bluetooth.le.BluetoothLeAdvertiser; 8 | import android.os.Handler; 9 | import android.util.Log; 10 | 11 | import static org.itoapp.strict.Constants.BLUETOOTH_COMPANY_ID; 12 | 13 | public class BleAdvertiser { 14 | private static final String LOG_TAG = "ITOBleAdvertiser"; 15 | private final Handler serviceHandler; 16 | 17 | private byte[] broadcastData; 18 | private BluetoothLeAdvertiser bluetoothLeAdvertiser; 19 | private AdvertiseCallback bluetoothAdvertiseCallback; 20 | 21 | public BleAdvertiser(BluetoothAdapter bluetoothAdapter, Handler serviceHandler) { 22 | this.bluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser(); 23 | this.serviceHandler = serviceHandler; 24 | } 25 | 26 | public void setBroadcastData(byte[] broadcastData) { 27 | this.broadcastData = broadcastData; 28 | if(bluetoothAdvertiseCallback != null) { 29 | restartAdvertising(); 30 | } 31 | } 32 | 33 | private void restartAdvertising() { 34 | stopAdvertising(); 35 | startAdvertising(); 36 | } 37 | 38 | public void startAdvertising() { 39 | AdvertiseSettings settings = new AdvertiseSettings.Builder() 40 | .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) 41 | .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) 42 | .setConnectable(false) 43 | .setTimeout(0) 44 | .build(); 45 | 46 | 47 | AdvertiseData data = new AdvertiseData.Builder() 48 | .setIncludeTxPowerLevel(false) 49 | .setIncludeDeviceName(false) 50 | .addManufacturerData(BLUETOOTH_COMPANY_ID, broadcastData) 51 | .build(); 52 | 53 | bluetoothAdvertiseCallback = new AdvertiseCallback() { 54 | @Override 55 | public void onStartSuccess(AdvertiseSettings settingsInEffect) { 56 | super.onStartSuccess(settingsInEffect); 57 | Log.i(LOG_TAG, "Advertising onStartSuccess"); 58 | 59 | // when the timeout expires, restart advertising 60 | if (settingsInEffect.getTimeout() > 0) 61 | serviceHandler.postDelayed(() -> restartAdvertising(), 62 | settingsInEffect.getTimeout()); 63 | } 64 | 65 | @Override 66 | public void onStartFailure(int errorCode) { 67 | super.onStartFailure(errorCode); 68 | Log.e(LOG_TAG, "Advertising onStartFailure: " + errorCode); 69 | // TODO 70 | } 71 | }; 72 | 73 | // TODO: check if null when launching with Bluetooth disabled 74 | bluetoothLeAdvertiser.startAdvertising(settings, data, bluetoothAdvertiseCallback); 75 | } 76 | 77 | public void stopAdvertising() { 78 | Log.d(LOG_TAG, "Stopping advertising"); 79 | if (bluetoothAdvertiseCallback != null) { 80 | bluetoothLeAdvertiser.stopAdvertising(bluetoothAdvertiseCallback); 81 | bluetoothAdvertiseCallback = null; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/BleScanner.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import android.bluetooth.BluetoothAdapter; 4 | import android.bluetooth.le.BluetoothLeScanner; 5 | import android.bluetooth.le.ScanCallback; 6 | import android.bluetooth.le.ScanFilter; 7 | import android.bluetooth.le.ScanRecord; 8 | import android.bluetooth.le.ScanResult; 9 | import android.bluetooth.le.ScanSettings; 10 | import android.os.Build; 11 | import android.util.Log; 12 | 13 | import org.itoapp.strict.Constants; 14 | 15 | import java.util.Arrays; 16 | import java.util.Collections; 17 | 18 | import static org.itoapp.strict.Constants.BLUETOOTH_COMPANY_ID; 19 | import static org.itoapp.strict.Constants.BROADCAST_LENGTH; 20 | import static org.itoapp.strict.Constants.HASH_LENGTH; 21 | 22 | public class BleScanner { 23 | private static final String LOG_TAG = "ITOBleScanner"; 24 | 25 | private BluetoothLeScanner bluetoothLeScanner; 26 | private ScanCallback bluetoothScanCallback; 27 | private ContactCache contactCache; 28 | 29 | public BleScanner(BluetoothAdapter bluetoothAdapter, ContactCache contactCache) { 30 | bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); 31 | this.contactCache = contactCache; 32 | } 33 | 34 | public void startScanning() { 35 | Log.d(LOG_TAG, "Starting scan"); 36 | bluetoothScanCallback = new ScanCallback() { 37 | public void onScanResult(int callbackType, ScanResult result) { 38 | 39 | Log.d(LOG_TAG, "onScanResult"); 40 | 41 | ScanRecord record = result.getScanRecord(); 42 | 43 | // if there is no record, discard this packet 44 | if (record == null) { 45 | return; 46 | } 47 | 48 | byte[] receivedHash = record.getManufacturerSpecificData(BLUETOOTH_COMPANY_ID); 49 | 50 | // if there is no data, discard 51 | if (receivedHash == null) { 52 | return; 53 | } 54 | 55 | receivedHash = Arrays.copyOf(receivedHash, HASH_LENGTH); 56 | 57 | int rssi = result.getRssi(); 58 | 59 | // TODO take antenna attenuation into account 60 | float distance = (float) Math.pow(10F, (-65 - rssi) / (10 * 2)); 61 | 62 | if(distance < Constants.MIN_SCANNING_DISTANCE) 63 | contactCache.addReceivedBroadcast(receivedHash, distance); 64 | 65 | Log.d(LOG_TAG, Arrays.toString(receivedHash) + ":" + distance); 66 | 67 | } 68 | }; 69 | 70 | ScanSettings.Builder settingsBuilder = new ScanSettings.Builder() 71 | .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) 72 | .setReportDelay(0); 73 | 74 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 75 | settingsBuilder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) 76 | .setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT) 77 | .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE); 78 | } 79 | 80 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 81 | settingsBuilder.setLegacy(true); 82 | } 83 | 84 | byte[] manufacturerDataMask = new byte[BROADCAST_LENGTH]; 85 | 86 | ScanFilter filter = new ScanFilter.Builder() 87 | .setManufacturerData(BLUETOOTH_COMPANY_ID, manufacturerDataMask, manufacturerDataMask) 88 | .build(); 89 | 90 | bluetoothLeScanner.startScan(Collections.singletonList(filter), settingsBuilder.build(), bluetoothScanCallback); 91 | } 92 | 93 | public void stopScanning() { 94 | Log.d(LOG_TAG, "Stopping scanning"); 95 | if(bluetoothScanCallback != null) { 96 | bluetoothLeScanner.stopScan(bluetoothScanCallback); 97 | bluetoothScanCallback = null; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/CheckServerTask.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import android.os.AsyncTask; 4 | import android.util.Log; 5 | 6 | import org.itoapp.strict.database.ItoDBHelper; 7 | import org.itoapp.strict.database.RoomDB; 8 | import org.itoapp.strict.database.entities.SeenTCN; 9 | import org.itoapp.strict.network.NetworkHelper; 10 | 11 | import java.util.List; 12 | 13 | import androidx.annotation.RequiresApi; 14 | 15 | import static org.itoapp.strict.Helper.byte2Hex; 16 | 17 | public class CheckServerTask extends AsyncTask { 18 | private static final String LOG_TAG = "ITOCheckServerTask"; 19 | private ItoDBHelper dbHelper; 20 | 21 | public CheckServerTask(ItoDBHelper itoDBHelper) { 22 | this.dbHelper = itoDBHelper; 23 | } 24 | 25 | @RequiresApi(api = 24) 26 | @Override 27 | protected Void doInBackground(Void... voids) { 28 | try { 29 | List reports = NetworkHelper.refreshInfectedUUIDs(); 30 | reports.stream().filter(x -> TCNProtoUtil.verifySignatureOfReportCorrect(x)).forEach(x -> TCNProtoUtil.generateAllTCNsFromReport(x, tcn -> this.checkInfection(tcn))); 31 | } catch (Exception ex){ 32 | ex.printStackTrace(); // FIXME: Notify user of failed update 33 | } 34 | 35 | /* List contactResults = dbHelper.selectInfectedContacts(); 36 | if (!contactResults.isEmpty()) { 37 | Log.w(LOG_TAG, "Possibly encountered UUIDs: " + contactResults.size()); 38 | } */ 39 | return null; 40 | } 41 | 42 | private void checkInfection(byte[] tcn) { 43 | Log.d(LOG_TAG, "Test if following TCN was seen: " + byte2Hex(tcn)); 44 | final SeenTCN seenTCN = RoomDB.db.seenTCNDao().findSeenTCNByHash(byte2Hex(tcn)); 45 | if (seenTCN != null && !seenTCN.reportedSick) { 46 | seenTCN.reportedSick = true; 47 | RoomDB.db.seenTCNDao().update(seenTCN); 48 | Log.d(LOG_TAG, "Updated " + seenTCN); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/ContactCache.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import android.os.Handler; 4 | import android.os.RemoteException; 5 | import android.util.Log; 6 | 7 | import org.itoapp.DistanceCallback; 8 | import org.itoapp.strict.Constants; 9 | import org.itoapp.strict.Helper; 10 | import org.itoapp.strict.database.ItoDBHelper; 11 | 12 | import java.nio.ByteBuffer; 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | 17 | import androidx.collection.CircularArray; 18 | 19 | public class ContactCache { 20 | private static final String LOG_TAG = "ContactCache"; 21 | 22 | private ItoDBHelper dbHelper; 23 | private Handler serviceHandler; 24 | private HashMap cache = new HashMap<>(); 25 | private DistanceCallback distanceCallback; 26 | 27 | public ContactCache(ItoDBHelper dbHelper, Handler serviceHandler) { 28 | this.dbHelper = dbHelper; 29 | this.serviceHandler = serviceHandler; 30 | } 31 | 32 | private void flush(ByteBuffer hash) { 33 | Log.d(LOG_TAG, "Flushing distance to DB"); 34 | CacheEntry entry = cache.get(hash); 35 | entry.lowestDistance = Math.min(calculateDistance(entry), entry.lowestDistance); 36 | long contactDuration = entry.lastReceived - entry.firstReceived; 37 | if (contactDuration > Constants.MIN_CONTACT_DURATION) { 38 | dbHelper.insertContact(entry.hash, (int) entry.lowestDistance, contactDuration); 39 | Log.d(LOG_TAG, "Flushing " + Helper.byte2Hex(hash.array()) + " to DB"); 40 | } 41 | cache.remove(hash); 42 | 43 | reportDistances(); 44 | } 45 | 46 | public void flush() { 47 | for (ByteBuffer hash : cache.keySet()) { 48 | flush(hash); 49 | } 50 | } 51 | 52 | public void addReceivedBroadcast(byte[] hash, float distance) { 53 | ByteBuffer hashString = ByteBuffer.wrap(hash); 54 | CacheEntry entry = cache.get(hashString); 55 | 56 | if (entry == null) { 57 | // new unknown broadcast 58 | entry = new CacheEntry(); 59 | cache.put(hashString, entry); 60 | entry.hash = hash; 61 | entry.firstReceived = System.currentTimeMillis(); 62 | } 63 | 64 | entry.lastReceived = System.currentTimeMillis(); 65 | 66 | // postpone flushing 67 | serviceHandler.removeCallbacks(entry.flushRunnable); 68 | serviceHandler.postDelayed(entry.flushRunnable, Constants.CACHE_FLUSH_TIME); 69 | 70 | CircularArray distances = entry.distances; 71 | distances.addFirst(distance); 72 | if (distances.size() == Constants.DISTANCE_SMOOTHING_MA_LENGTH) { 73 | entry.lowestDistance = Math.min(calculateDistance(entry), entry.lowestDistance); 74 | distances.popLast(); 75 | distances.clear(); 76 | distances.addFirst(entry.lowestDistance); 77 | //int contactDuration = (int) (entry.lastReceived - entry.firstReceived); 78 | //dbHelper.insertContact(entry.hash, (int) entry.lowestDistance, contactDuration); 79 | } 80 | reportDistances(); 81 | } 82 | 83 | private void reportDistances() { 84 | 85 | if (distanceCallback != null) { 86 | try { 87 | distanceCallback.onDistanceMeasurements(calculateDistances()); 88 | } catch (RemoteException e) { 89 | distanceCallback = null; 90 | } 91 | } 92 | } 93 | 94 | private float calculateDistance(CacheEntry cacheEntry) { 95 | CircularArray measuredDistances = cacheEntry.distances; 96 | float distance = 0; 97 | 98 | for (int j = 0; j < measuredDistances.size(); j++) { 99 | distance += measuredDistances.get(j) / measuredDistances.size(); 100 | } 101 | return distance; 102 | } 103 | 104 | private float[] calculateDistances() { 105 | float[] distances = new float[cache.size()]; 106 | List cacheEntries = new ArrayList<>(cache.values()); 107 | for (int i = 0; i < distances.length; i++) { 108 | CacheEntry cacheEntry = cacheEntries.get(i); 109 | distances[i] = calculateDistance(cacheEntry); 110 | } 111 | 112 | return distances; 113 | } 114 | 115 | public void setDistanceCallback(DistanceCallback distanceCallback) { 116 | this.distanceCallback = distanceCallback; 117 | } 118 | 119 | private class CacheEntry { 120 | long firstReceived; 121 | long lastReceived; 122 | byte[] hash; 123 | CircularArray distances = new CircularArray<>(Constants.DISTANCE_SMOOTHING_MA_LENGTH); 124 | float lowestDistance = Float.MAX_VALUE; 125 | Runnable flushRunnable = () -> flush(ByteBuffer.wrap(hash)); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/INextTCNCallback.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | public interface INextTCNCallback { 4 | 5 | public void next(byte [] tcn); 6 | } 7 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/PublishBeaconsTask.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.RemoteException; 5 | import android.util.Log; 6 | 7 | import org.itoapp.PublishUUIDsCallback; 8 | import org.itoapp.strict.database.RoomDB; 9 | import org.itoapp.strict.network.NetworkHelper; 10 | 11 | import java.io.IOException; 12 | import java.util.List; 13 | 14 | class PublishBeaconsTask extends AsyncTask { 15 | private static final String LOG_TAG = "PublishBeaconsTask"; 16 | private List report; 17 | private long from; 18 | private long to; 19 | private PublishUUIDsCallback callback; 20 | 21 | public PublishBeaconsTask(List report, PublishUUIDsCallback callback) { 22 | this.report = report; 23 | this.callback = callback; 24 | } 25 | 26 | @Override 27 | protected Void doInBackground(Void... voids) { 28 | try { 29 | NetworkHelper.publishReports(report); 30 | try { 31 | RoomDB.db.localKeyDao().deleteAll(); // remove all Keys that we have sent 32 | callback.onSuccess(); 33 | } catch (RemoteException e) { 34 | Log.e(LOG_TAG, "._.", e); 35 | } 36 | } catch (IOException e) { 37 | Log.e(LOG_TAG, "Could not publish UUIDs!", e); 38 | try { 39 | callback.onFailure(); 40 | } catch (RemoteException ex) { 41 | Log.e(LOG_TAG, "._.", e); 42 | } 43 | } 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/StartupListener.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | /* 8 | This BroadcastReceiver starts the tracing service when the system boots 9 | */ 10 | public class StartupListener extends BroadcastReceiver { 11 | @Override 12 | public void onReceive(Context context, Intent intent) { 13 | context.startService(new Intent(context, TracingService.class)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/TCNProtoGen.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | 4 | import java.nio.ByteBuffer; 5 | import java.nio.ByteOrder; 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.SecureRandom; 9 | 10 | import cafe.cryptography.ed25519.Ed25519PrivateKey; 11 | 12 | public class TCNProtoGen { 13 | 14 | private static final SecureRandom RANDOM = new SecureRandom(); 15 | private static final String SHA256 = "SHA-256"; 16 | private static final byte[] H_TCK = "H_TCK".getBytes(); // Pin charset? 17 | private static final byte[] H_TCN = "H_TCN".getBytes(); // Pin charset? 18 | public static final byte TCN_ID_ITO = 0x2; 19 | public static final byte TCN_ID_COEPI = 0x0; 20 | 21 | byte memotype = TCN_ID_ITO; 22 | 23 | byte[] rak = new byte[32]; 24 | byte[] rvk = new byte[32]; 25 | 26 | byte[] startTCK; 27 | byte[] currentTCK; 28 | int currentTCKpos = 0; 29 | 30 | public TCNProtoGen() { 31 | RANDOM.nextBytes(rak); 32 | genRVKandTck0(); 33 | } 34 | 35 | public TCNProtoGen(byte[] rak, int currentTCKpos) { 36 | this.rak = rak; 37 | genRVKandTck0(); 38 | this.currentTCKpos = currentTCKpos; 39 | } 40 | 41 | public TCNProtoGen(byte memotype) { 42 | RANDOM.nextBytes(rak); 43 | genRVKandTck0(); 44 | this.memotype = memotype; 45 | } 46 | 47 | public TCNProtoGen(byte memotype, byte[] rvk, byte[] startTCK, int startTCKpos) { 48 | this.rvk = rvk; 49 | this.startTCK = startTCK; 50 | currentTCK = startTCK; 51 | currentTCKpos = startTCKpos; 52 | this.memotype = memotype; 53 | } 54 | 55 | void genRVKandTck0() { 56 | Ed25519PrivateKey sk = Ed25519PrivateKey.fromByteArray(rak); 57 | rvk = sk.derivePublic().toByteArray(); 58 | MessageDigest h_tck0 = getSHA256(); 59 | h_tck0.update(H_TCK); 60 | h_tck0.update(rak); // why do we use this ??? 61 | startTCK = h_tck0.digest(); 62 | currentTCK = startTCK; 63 | currentTCKpos = 0; 64 | } 65 | 66 | public synchronized byte[] getNewTCN() { 67 | currentTCK = genNextTCK(currentTCK); 68 | currentTCKpos++; 69 | return getCurrentTCN(); 70 | } 71 | 72 | byte[] getCurrentTCN() { 73 | MessageDigest h_tcnj = getSHA256(); 74 | h_tcnj.update(H_TCN); 75 | ByteBuffer length = ByteBuffer.allocate(2); 76 | length.order(ByteOrder.LITTLE_ENDIAN); 77 | length.putShort((short) (currentTCKpos)); 78 | h_tcnj.update(length.array()); 79 | h_tcnj.update(currentTCK); 80 | 81 | byte[] ret = new byte[16]; 82 | System.arraycopy(h_tcnj.digest(), 0, ret, 0, 16); 83 | return ret; 84 | } 85 | 86 | private byte[] genNextTCK(byte[] current) { 87 | MessageDigest h_tckj = getSHA256(); 88 | h_tckj.update(H_TCK); 89 | h_tckj.update(rvk); 90 | h_tckj.update(current); 91 | return h_tckj.digest(); 92 | } 93 | 94 | public synchronized byte[] generateReport(int previousRatchetTicks) { 95 | // todo fail if no rak present 96 | int end = currentTCKpos; 97 | int start = currentTCKpos - previousRatchetTicks - 1; 98 | if (currentTCKpos <= 0) { // have we got more than only tck_0? 99 | throw new RuntimeException("no Keys to report about"); 100 | } 101 | if (previousRatchetTicks < 0) 102 | throw new RuntimeException("daysBefore can not be negative"); 103 | if (start < 1) { // give em everything we've got (except tck_0) 104 | start = 1; 105 | } 106 | byte[] memo = createMemo(); 107 | final int totalPayloadbytes = 32 + 32 + 4 + memo.length; 108 | 109 | ByteBuffer payload = ByteBuffer.allocate(totalPayloadbytes); 110 | payload.put(rvk); 111 | payload.put(generateTCKAtPosition(start)); 112 | 113 | ByteBuffer beginAndEnd = ByteBuffer.allocate(4); 114 | beginAndEnd.order(ByteOrder.LITTLE_ENDIAN); 115 | beginAndEnd.putShort((short) (start + 1)); 116 | beginAndEnd.putShort((short) (end + 1)); 117 | payload.put(beginAndEnd.array()); 118 | 119 | payload.put(memo); 120 | 121 | byte[] sig = Ed25519PrivateKey.fromByteArray(rak).expand().sign(payload.array(), Ed25519PrivateKey.fromByteArray(rak).derivePublic()).toByteArray(); 122 | ByteBuffer ret = ByteBuffer.allocate(totalPayloadbytes + sig.length); 123 | ret.put(payload.array()); 124 | ret.put(sig); 125 | return ret.array(); 126 | } 127 | 128 | private byte[] generateTCKAtPosition(int start) { 129 | byte[] tmp = startTCK; 130 | for (int i = 0; i < start; i++) { 131 | tmp = genNextTCK(tmp); 132 | } 133 | return tmp; 134 | } 135 | 136 | public int getRatchetTickCount() { 137 | return currentTCKpos; 138 | } 139 | 140 | private byte[] createMemo() { 141 | byte[] symptomData = "symptom data".getBytes(); 142 | ByteBuffer memo = ByteBuffer.allocate(2 + symptomData.length); 143 | memo.order(ByteOrder.LITTLE_ENDIAN); 144 | memo.put((byte) this.memotype); // 0x2: ITO symptom report v1; 145 | memo.put((byte) symptomData.length); 146 | memo.put(symptomData); 147 | return memo.array(); 148 | } 149 | 150 | 151 | private MessageDigest getSHA256() { 152 | try { 153 | return MessageDigest.getInstance(SHA256); 154 | } catch (NoSuchAlgorithmException ex) { 155 | throw new RuntimeException(ex); 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/TCNProtoUtil.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import org.itoapp.strict.database.RoomDB; 4 | import org.itoapp.strict.database.entities.LocalKey; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.nio.ByteOrder; 8 | import java.util.Arrays; 9 | import java.util.Date; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | import androidx.annotation.RequiresApi; 14 | import cafe.cryptography.ed25519.Ed25519PublicKey; 15 | import cafe.cryptography.ed25519.Ed25519Signature; 16 | 17 | import static org.itoapp.strict.Helper.byte2Hex; 18 | import static org.itoapp.strict.Helper.hex2Byte; 19 | 20 | public class TCNProtoUtil { 21 | 22 | public static boolean verifySignatureOfReportCorrect(byte[] report) { 23 | byte[] bsignature = Arrays.copyOfRange(report, report.length - 64, report.length); 24 | byte[] brvk = getRvkfromReport(report); 25 | byte[] breport = Arrays.copyOfRange(report, 0, report.length - 64); 26 | try { 27 | Ed25519PublicKey rvk = Ed25519PublicKey.fromByteArray(brvk); 28 | Ed25519Signature signature = Ed25519Signature.fromByteArray(bsignature); 29 | return rvk.verify(breport, signature); 30 | 31 | } catch (Exception e) { 32 | return false; 33 | } 34 | } 35 | 36 | 37 | public static void generateAllTCNsFromReport(byte[] report, INextTCNCallback callback) { 38 | int from = readUShort(report, 64); 39 | byte[] bstartTCK = Arrays.copyOfRange(report, 32, 64); 40 | TCNProtoGen ratchet = new TCNProtoGen(report[68], getRvkfromReport(report), bstartTCK, from - 1); 41 | int to = readUShort(report, 66); 42 | callback.next(ratchet.getCurrentTCN()); 43 | for (int i = from; i < to; i++) { 44 | callback.next(ratchet.getNewTCN()); 45 | } 46 | 47 | } 48 | 49 | 50 | public static void persistRatchet(TCNProtoGen ratchet) { 51 | LocalKey lk = new LocalKey(); 52 | lk.lastGenerated = new Date(); 53 | lk.rak = byte2Hex(ratchet.rak); 54 | lk.currentTCKpos = ratchet.currentTCKpos; 55 | RoomDB.db.localKeyDao().saveOrUpdate(lk); 56 | } 57 | 58 | @RequiresApi(api = 24) 59 | public static List loadAllRatchets() { 60 | return RoomDB.db.localKeyDao().getAll().stream().map(x -> new TCNProtoGen(hex2Byte(x.rak), x.currentTCKpos)).collect(Collectors.toList()); 61 | } 62 | 63 | 64 | private static byte[] getRvkfromReport(byte[] report) { 65 | 66 | return Arrays.copyOfRange(report, 0, 32); 67 | } 68 | 69 | static int readUShort(byte[] report, int index) { 70 | final ByteBuffer bb = ByteBuffer.wrap(report); 71 | bb.order(ByteOrder.LITTLE_ENDIAN); 72 | int i = bb.getShort(index); 73 | if (i < 0) { 74 | i = i - Short.MIN_VALUE * 2; 75 | } 76 | return i; 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /android/src/main/java/org/itoapp/strict/service/TracingService.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Notification; 5 | import android.app.NotificationChannel; 6 | import android.app.NotificationManager; 7 | import android.app.PendingIntent; 8 | import android.app.Service; 9 | import android.bluetooth.BluetoothAdapter; 10 | import android.bluetooth.BluetoothManager; 11 | import android.content.BroadcastReceiver; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.content.IntentFilter; 15 | import android.graphics.Color; 16 | import android.location.LocationManager; 17 | import android.os.AsyncTask; 18 | import android.os.Build; 19 | import android.os.Handler; 20 | import android.os.HandlerThread; 21 | import android.os.IBinder; 22 | import android.os.Looper; 23 | import android.util.Log; 24 | 25 | import org.itoapp.DistanceCallback; 26 | import org.itoapp.PublishUUIDsCallback; 27 | import org.itoapp.TracingServiceInterface; 28 | import org.itoapp.strict.Constants; 29 | import org.itoapp.strict.Helper; 30 | import org.itoapp.strict.Preconditions; 31 | import org.itoapp.strict.database.ItoDBHelper; 32 | import org.itoapp.strict.database.RoomDB; 33 | 34 | import java.security.SecureRandom; 35 | import java.util.List; 36 | import java.util.stream.Collectors; 37 | 38 | import androidx.annotation.RequiresApi; 39 | import androidx.core.app.NotificationCompat; 40 | 41 | import static org.itoapp.strict.Constants.RATCHET_EXCHANGE_INTERVAL; 42 | 43 | public class TracingService extends Service { 44 | private static final String LOG_TAG = "ITOTracingService"; 45 | private static final String DEFAULT_NOTIFICATION_CHANNEL = "ContactTracing"; 46 | private static final int NOTIFICATION_ID = 1; 47 | private TCNProtoGen tcnProto; 48 | private SecureRandom uuidGenerator; 49 | private Looper serviceLooper; 50 | private Handler serviceHandler; 51 | private BleScanner bleScanner; 52 | private BleAdvertiser bleAdvertiser; 53 | private ContactCache contactCache; 54 | private ItoDBHelper dbHelper; 55 | 56 | private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { 57 | 58 | @Override 59 | public void onReceive(Context context, android.content.Intent intent) { 60 | if (!isBluetoothRunning()) { 61 | startBluetooth(); 62 | } else if (!Preconditions.canScanBluetooth(context)) { 63 | stopBluetooth(); 64 | } 65 | } 66 | }; 67 | 68 | private TracingServiceInterface.Stub binder = new TracingServiceInterface.Stub() { 69 | @Override 70 | public void setDistanceCallback(DistanceCallback distanceCallback) { 71 | contactCache.setDistanceCallback(distanceCallback); 72 | } 73 | 74 | 75 | @RequiresApi(api = 24) 76 | @Override 77 | public void publishBeaconUUIDs(long from, long to, PublishUUIDsCallback callback) { 78 | // todo use from & to ? 79 | List reports = TCNProtoUtil.loadAllRatchets().stream().map(ratchet -> ratchet.generateReport(ratchet.getRatchetTickCount())).collect(Collectors.toList()); 80 | 81 | new PublishBeaconsTask(reports, callback).execute(); 82 | } 83 | 84 | @RequiresApi(api = 24) 85 | @Override 86 | public boolean isPossiblyInfected() { 87 | //TODO do async 88 | Long totalExposureDuration = RoomDB.db.seenTCNDao().findSickTCNs().stream().map(x -> x.duration).reduce(0L, (a, b) -> a + b); 89 | return totalExposureDuration > Constants.MIN_EXPOSURE_DURATION; 90 | } 91 | 92 | @Override 93 | public void restartTracingService() { 94 | stopBluetooth(); 95 | startBluetooth(); 96 | } 97 | 98 | @Override 99 | public int getLatestFetchTime() { 100 | return dbHelper.getLatestFetchTime(); 101 | } 102 | }; 103 | 104 | private Runnable regenerateUUID = () -> { 105 | Log.i(LOG_TAG, "Regenerating TCN"); 106 | 107 | /* byte[] uuid = new byte[Constants.UUID_LENGTH]; 108 | uuidGenerator.nextBytes(uuid); 109 | byte[] hashedUUID = Helper.calculateTruncatedSHA256(uuid); 110 | 111 | dbHelper.insertBeacon(uuid); 112 | 113 | byte[] broadcastData = new byte[Constants.BROADCAST_LENGTH]; 114 | broadcastData[Constants.BROADCAST_LENGTH - 1] = getTransmitPower(); 115 | System.arraycopy(hashedUUID, 0, broadcastData, 0, Constants.HASH_LENGTH); 116 | */ 117 | 118 | 119 | if (tcnProto != null && tcnProto.currentTCKpos == RATCHET_EXCHANGE_INTERVAL) { 120 | tcnProto = null; 121 | } 122 | if (tcnProto == null) { 123 | Log.i(LOG_TAG, "Regenerating Ratchet"); 124 | tcnProto = new TCNProtoGen(); 125 | } 126 | byte[] tcn = tcnProto.getNewTCN(); 127 | Log.i(LOG_TAG, "Advertising " + Helper.byte2Hex(tcn)); 128 | bleAdvertiser.setBroadcastData(tcn); 129 | 130 | 131 | AsyncTask.execute(new Runnable() { // FIXME make everything async and get aligned with sendReport etc. 132 | @Override 133 | public void run() { 134 | TCNProtoUtil.persistRatchet(tcnProto); 135 | } 136 | }); 137 | 138 | serviceHandler.postDelayed(this.regenerateUUID, Constants.TCN_VALID_INTERVAL); 139 | }; 140 | //TODO move this to some alarmManager governed section. 141 | // Also ideally check the server when connected to WIFI and charger 142 | private Runnable checkServer = () -> { 143 | new CheckServerTask(dbHelper).execute(); 144 | serviceHandler.postDelayed(this.checkServer, Constants.CHECK_SERVER_INTERVAL); 145 | }; 146 | 147 | 148 | private boolean isBluetoothRunning() { 149 | return bleScanner != null; 150 | } 151 | 152 | private void stopBluetooth() { 153 | Log.i(LOG_TAG, "Stopping Bluetooth"); 154 | contactCache.flush(); 155 | if (bleScanner != null) 156 | try { 157 | bleScanner.stopScanning(); 158 | } catch (Exception ignored) { 159 | } 160 | if (bleAdvertiser != null) 161 | try { 162 | bleAdvertiser.stopAdvertising(); 163 | } catch (Exception ignored) { 164 | } 165 | 166 | serviceHandler.removeCallbacks(regenerateUUID); 167 | 168 | bleScanner = null; 169 | bleAdvertiser = null; 170 | } 171 | 172 | private void startBluetooth() { 173 | Log.i(LOG_TAG, "Starting Bluetooth"); 174 | if (!Preconditions.canScanBluetooth(this)) { 175 | Log.w(LOG_TAG, "Preconditions for starting Bluetooth not met"); 176 | return; 177 | } 178 | BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 179 | assert bluetoothManager != null; 180 | BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); 181 | 182 | bleScanner = new BleScanner(bluetoothAdapter, contactCache); 183 | bleAdvertiser = new BleAdvertiser(bluetoothAdapter, serviceHandler); 184 | 185 | regenerateUUID.run(); 186 | bleAdvertiser.startAdvertising(); 187 | bleScanner.startScanning(); 188 | } 189 | 190 | @Override 191 | public void onCreate() { 192 | super.onCreate(); 193 | uuidGenerator = new SecureRandom(); 194 | dbHelper = new ItoDBHelper(); 195 | HandlerThread thread = new HandlerThread("TracingServiceHandler", Thread.NORM_PRIORITY); 196 | thread.start(); 197 | 198 | // Get the HandlerThread's Looper and use it for our Handler 199 | serviceLooper = thread.getLooper(); 200 | serviceHandler = new Handler(serviceLooper); 201 | serviceHandler.post(this.checkServer); 202 | contactCache = new ContactCache(dbHelper, serviceHandler); 203 | 204 | startBluetooth(); 205 | 206 | IntentFilter filter = new IntentFilter(); 207 | filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 208 | filter.addAction(LocationManager.MODE_CHANGED_ACTION); 209 | registerReceiver(broadcastReceiver, filter); 210 | } 211 | 212 | @TargetApi(26) 213 | private void createNotificationChannel(NotificationManager notificationManager) { 214 | int importance = NotificationManager.IMPORTANCE_DEFAULT; 215 | 216 | NotificationChannel mChannel = new NotificationChannel(DEFAULT_NOTIFICATION_CHANNEL, DEFAULT_NOTIFICATION_CHANNEL, importance); 217 | mChannel.enableLights(true); 218 | mChannel.setLightColor(Color.BLUE); 219 | mChannel.setImportance(NotificationManager.IMPORTANCE_LOW); 220 | notificationManager.createNotificationChannel(mChannel); 221 | } 222 | 223 | private void runAsForgroundService() { 224 | NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); 225 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 226 | createNotificationChannel(notificationManager); 227 | 228 | Intent notificationIntent = new Intent(); 229 | 230 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, 231 | notificationIntent, 0); 232 | 233 | Notification notification = new NotificationCompat.Builder(this, 234 | DEFAULT_NOTIFICATION_CHANNEL) 235 | .setContentIntent(pendingIntent) 236 | .setPriority(NotificationManager.IMPORTANCE_LOW) 237 | .setVibrate(null) 238 | .build(); 239 | 240 | startForeground(NOTIFICATION_ID, notification); 241 | } 242 | 243 | @Override 244 | public void onDestroy() { 245 | bleAdvertiser.stopAdvertising(); 246 | bleScanner.stopScanning(); 247 | contactCache.flush(); 248 | unregisterReceiver(broadcastReceiver); 249 | super.onDestroy(); 250 | } 251 | 252 | @Override 253 | public int onStartCommand(Intent intent, int flags, int startId) { 254 | runAsForgroundService(); 255 | return START_STICKY; 256 | } 257 | 258 | /* 259 | Don't do anything here, because the service doesn't have to communicate to other apps 260 | */ 261 | @Override 262 | public IBinder onBind(Intent intent) { 263 | return (IBinder) binder; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /android/src/test/java/org/itoapp/strict/service/TCNProtoGenTest.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.stream.IntStream; 8 | 9 | import static org.itoapp.strict.Helper.byte2Hex; 10 | import static org.itoapp.strict.Helper.hex2Byte; 11 | import static org.junit.Assert.assertArrayEquals; 12 | import static org.junit.Assert.assertEquals; 13 | 14 | public class TCNProtoGenTest { 15 | 16 | 17 | public static final String VALIDREPORT = "fd8deb9d91a13e144ca5b0ce14e289532e040fe0bf922c6e3dadb1e4e2333c78df535b90ac99bec8be3a8add45ce77897b1e7cb1906b5cff1097d3cb142fd9d002000a00000c73796d70746f6d206461746131078ec5367b67a8c793b740626d81ba904789363137b5a313419c0f50b180d8226ecc984bf073ff89cbd9c88fea06bda1f0f368b0e7e88bbe68f15574482904"; 18 | 19 | 20 | public static final String[] REPORTTCNS = new String[]{ 21 | "f4350a4a33e30f2f568898fbe4c4cf34", 22 | "135eeaa6482b8852fea3544edf6eabf0", 23 | "d713ce68cf4127bcebde6874c4991e4b", 24 | "5174e6514d2086565e4ea09a45995191", 25 | "ccae4f2c3144ad1ed0c2a39613ef0342", 26 | "3b9e600991369bba3944b6e9d8fda370", 27 | "dc06a8625c08e946317ad4c89e6ee8a1", 28 | "9d671457835f2c254722bfd0de76dffc", 29 | "8b454d28430d3153a500359d9a49ec88"}; 30 | 31 | /** 32 | * Test Generate Report ~henry's vector from rust 33 | */ 34 | @Test 35 | public void testReportVector() throws Exception { 36 | String rak = "577cfdae21fee71579211ab02c418ee0948bacab613cf69d0a4a5ae5a1557dbb"; 37 | TCNProtoGen demo = new TCNProtoGen(TCNProtoGen.TCN_ID_COEPI); 38 | demo.rak = hex2Byte(rak); 39 | demo.genRVKandTck0(); 40 | List tcns = new LinkedList<>(); 41 | IntStream.range(0, 9).forEach(i 42 | -> tcns.add(byte2Hex(demo.getNewTCN())) 43 | ); 44 | assertArrayEquals(REPORTTCNS, tcns.toArray()); 45 | assertEquals(VALIDREPORT, byte2Hex(demo.generateReport(8))); 46 | } 47 | 48 | 49 | @Test 50 | public void testReport() { 51 | TCNProtoGen tcnGen = new TCNProtoGen(); 52 | tcnGen.getNewTCN(); 53 | tcnGen.generateReport(0); 54 | tcnGen.generateReport(1); 55 | tcnGen.generateReport(2); 56 | } 57 | 58 | @Test(expected = RuntimeException.class) 59 | public void testReportNotNegative() { 60 | TCNProtoGen tcnGen = new TCNProtoGen(); 61 | tcnGen.getNewTCN(); 62 | 63 | tcnGen.generateReport(-1); 64 | 65 | } 66 | 67 | @Test 68 | public void testReportSizing() { 69 | // "public report" tck start at 0x2 (first tck to follow tck0) in count = 2 70 | 71 | String rak = "577cfdae21fee71579211ab02c418ee0948bacab613cf69d0a4a5ae5a1557dbb"; 72 | TCNProtoGen tcnGen = new TCNProtoGen(TCNProtoGen.TCN_ID_COEPI); 73 | tcnGen.rak = hex2Byte(rak); 74 | tcnGen.genRVKandTck0(); 75 | 76 | 77 | String r0 = "fd8deb9d91a13e144ca5b0ce14e289532e040fe0bf922c6e3dadb1e4e2333c78df535b90ac99bec8be3a8add45ce77897b1e7cb1906b5cff1097d3cb142fd9d002000200000c73796d70746f6d2064617461400c2b0049c2345c2f91385ab053db5605cf9e8910348efbcfcc67dc505454d93579792742aab9a4343243bb6595c1d2ae6b824daa18eb4e7b64ce45e3b1260b"; 78 | String r1 = "fd8deb9d91a13e144ca5b0ce14e289532e040fe0bf922c6e3dadb1e4e2333c78df535b90ac99bec8be3a8add45ce77897b1e7cb1906b5cff1097d3cb142fd9d002000300000c73796d70746f6d206461746110c6d4c0ed3033c41ba85315758aa9aed18db9d142fc40d408f3df3095357f3daacb36cc76da8da841ef22cd785b4ab9f4e2277013e88738d253ef7ea9965509"; 79 | String r2 = "fd8deb9d91a13e144ca5b0ce14e289532e040fe0bf922c6e3dadb1e4e2333c78df535b90ac99bec8be3a8add45ce77897b1e7cb1906b5cff1097d3cb142fd9d002000400000c73796d70746f6d2064617461046abf5b87d61b29c498b0cf7976a9132ed14046656f36c14c7336c3f9130fc4267015560c3a6564d24c56cfa5c1350690026818e36d6fba20771f9e41954c03"; 80 | String r3 = "fd8deb9d91a13e144ca5b0ce14e289532e040fe0bf922c6e3dadb1e4e2333c78df535b90ac99bec8be3a8add45ce77897b1e7cb1906b5cff1097d3cb142fd9d002000500000c73796d70746f6d20646174614f82be44f2b6dcf98732ff05a302c0da13e35a14ad610c4fd8fbbfa33d7f969ac744c85a9adc223749bafb5ab2db4030042ef4c9b599a358050d5c2cce49e905"; 81 | 82 | 83 | tcnGen.getNewTCN(); 84 | assertEquals(tcnGen.getRatchetTickCount(), 1); 85 | assertEquals(r0, byte2Hex(tcnGen.generateReport(tcnGen.getRatchetTickCount()))); 86 | tcnGen.getNewTCN(); 87 | assertEquals(tcnGen.getRatchetTickCount(), 2); 88 | assertEquals(r1, byte2Hex(tcnGen.generateReport(tcnGen.getRatchetTickCount()))); 89 | tcnGen.getNewTCN(); 90 | assertEquals(tcnGen.getRatchetTickCount(), 3); 91 | assertEquals(r2, byte2Hex(tcnGen.generateReport(tcnGen.getRatchetTickCount()))); 92 | tcnGen.getNewTCN(); 93 | //getRatchetTickCount() 94 | // fixme 95 | assertEquals(r3, byte2Hex(tcnGen.generateReport(tcnGen.getRatchetTickCount()))); 96 | 97 | } 98 | 99 | 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /android/src/test/java/org/itoapp/strict/service/TCNProtoUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.itoapp.strict.service; 2 | 3 | import org.itoapp.strict.database.RoomDB; 4 | import org.itoapp.strict.database.dao.LastReportDao; 5 | import org.itoapp.strict.database.dao.LocalKeyDao; 6 | import org.itoapp.strict.database.dao.SeenTCNDao; 7 | import org.itoapp.strict.database.entities.LocalKey; 8 | import org.junit.Test; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.HashSet; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.Set; 17 | import java.util.stream.IntStream; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.room.DatabaseConfiguration; 21 | import androidx.room.InvalidationTracker; 22 | import androidx.sqlite.db.SupportSQLiteOpenHelper; 23 | 24 | import static org.itoapp.strict.service.TCNProtoGenTest.REPORTTCNS; 25 | import static org.itoapp.strict.Helper.byte2Hex; 26 | import static org.itoapp.strict.Helper.hex2Byte; 27 | import static org.junit.Assert.assertArrayEquals; 28 | import static org.junit.Assert.assertEquals; 29 | 30 | public class TCNProtoUtilTest { 31 | 32 | 33 | @Test 34 | public void testVerifySig() throws Exception { 35 | byte[] report = hex2Byte(TCNProtoGenTest.VALIDREPORT); 36 | assertEquals(true, TCNProtoUtil.verifySignatureOfReportCorrect(report)); 37 | 38 | } 39 | 40 | @Test 41 | public void generateFromTo() throws Exception { 42 | byte[] report = hex2Byte(TCNProtoGenTest.VALIDREPORT); 43 | List tcns = new LinkedList<>(); 44 | TCNProtoUtil.generateAllTCNsFromReport(report, x -> 45 | tcns.add(byte2Hex(x)) 46 | ); 47 | assertArrayEquals(REPORTTCNS, tcns.toArray()); 48 | } 49 | 50 | @Test // not too much of a unit test 51 | public void testBounds() throws Exception { 52 | TCNProtoGen tcnGen = new TCNProtoGen(); 53 | int ulimit = Short.MAX_VALUE * 2; 54 | List tcnsSource = new LinkedList<>(); 55 | IntStream.range(0, ulimit).forEach(i 56 | -> tcnsSource.add(byte2Hex(tcnGen.getNewTCN()))); 57 | 58 | List tcnRegenerated = new LinkedList<>(); 59 | TCNProtoUtil.generateAllTCNsFromReport(tcnGen.generateReport(tcnGen.getRatchetTickCount()), x -> 60 | tcnRegenerated.add(byte2Hex(x)) 61 | ); 62 | assertEquals(ulimit, tcnRegenerated.size()); 63 | assertArrayEquals(tcnsSource.toArray(), tcnRegenerated.toArray()); 64 | } 65 | 66 | 67 | @Test 68 | public void testSaveAndLoad() throws Exception { 69 | 70 | // 0 Mock DB 71 | Map lk = new HashMap<>(); 72 | RoomDB.db = new RoomDB() { 73 | @Override 74 | public LastReportDao lastReportDao() { 75 | return null; 76 | } 77 | 78 | @Override 79 | public SeenTCNDao seenTCNDao() { 80 | return null; 81 | } 82 | 83 | @Override 84 | public LocalKeyDao localKeyDao() { 85 | return new LocalKeyDao() { 86 | 87 | 88 | @Override 89 | public List getAll() { 90 | return new ArrayList(lk.values()); 91 | } 92 | 93 | @Override 94 | public void saveOrUpdate(LocalKey localKey) { 95 | lk.put(localKey.rak, localKey); 96 | } 97 | 98 | @Override 99 | public void deleteAll() { 100 | lk.clear(); 101 | } 102 | }; 103 | } 104 | 105 | @NonNull 106 | @Override 107 | protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config) { 108 | return null; 109 | } 110 | 111 | @NonNull 112 | @Override 113 | protected InvalidationTracker createInvalidationTracker() { 114 | return null; 115 | } 116 | 117 | @Override 118 | public void clearAllTables() { 119 | 120 | } 121 | }; 122 | 123 | 124 | // 1) Setup Sample data 125 | // 10 Days with 96 TCN's per Day 126 | Set tcnsSource = new HashSet<>(); 127 | IntStream.range(0, 10).forEach(i 128 | -> { 129 | TCNProtoGen tcnGen = new TCNProtoGen(); 130 | IntStream.range(0, 96).forEach(i2 131 | -> { 132 | tcnsSource.add(byte2Hex(tcnGen.getNewTCN())); 133 | TCNProtoUtil.persistRatchet(tcnGen); 134 | }); 135 | }); 136 | 137 | // 2) verify every TCN is found in the reports 138 | TCNProtoUtil.loadAllRatchets().stream().map(ratchet -> 139 | ratchet.generateReport(ratchet.getRatchetTickCount())) 140 | .filter(x -> TCNProtoUtil.verifySignatureOfReportCorrect(x)).forEach(x -> TCNProtoUtil.generateAllTCNsFromReport(x, tcn -> tcnsSource.remove(byte2Hex(tcn)))); 141 | assertEquals(0, tcnsSource.size()); 142 | 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { NativeModules } from 'react-native'; 2 | 3 | const { ItoBluetooth } = NativeModules; 4 | 5 | export default ItoBluetooth; 6 | -------------------------------------------------------------------------------- /ios/ItoBluetooth-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /ios/ItoBluetooth.m: -------------------------------------------------------------------------------- 1 | #import 2 | // #import 3 | 4 | 5 | // #import "ItoBluetooth-Bridging-Header.h" 6 | // @interface RCT_EXTERN_MODULE(ItoBluetooth, NSObject) 7 | 8 | // RCT_EXPORT_METHOD(callback:(RCTResponseSenderBlock)onDistanceMeasurements) 9 | // { 10 | // onDistanceMeasurements([NSNumber new]); 11 | // } 12 | 13 | // RCT_EXPORT_METHOD(publishBeaconUUIDs:(NSNumber *) from:(NSNumber*) to: callback:(RCTResponseSenderBlock)onSuccess){ 14 | 15 | // } 16 | 17 | // RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(isPossiblyInfected){ 18 | // SwItoBluetooth *sbt = [SwItoBluetooth new] ; 19 | // NSNumber *retval = sbt.isPossiblyInfected; 20 | // return retval; 21 | // } 22 | 23 | // RCT_EXPORT_METHOD(restartTracingService){ 24 | // SwItoBluetooth *sbt = [SwItoBluetooth new] ; 25 | // sbt.restartTracingService; 26 | // } 27 | 28 | // RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getLatestFetchTime){ 29 | // SwItoBluetooth *sbt = [SwItoBluetooth new] ; 30 | // NSNumber *retval = sbt.getLatestFetchTime; 31 | // return retval; 32 | // } 33 | // @end 34 | 35 | -------------------------------------------------------------------------------- /ios/ItoBluetooth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItoBluetooth.swift 3 | // ItoBluetooth 4 | // 5 | // Created by Dieder Timmers on 18/04/2020. 6 | // Copyright © 2020 Facebook. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | @objc(ItoBluetooth) 12 | public class ItoBluetooth: NSObject { 13 | @objc 14 | public func isPossiblyInfected() -> Bool{ 15 | return true; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ios/ItoBluetooth.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7BE90D4FB255B68DDBC03678 /* Pods_ItoBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7EC1987031BCF80FBAC73D9C /* Pods_ItoBluetooth.framework */; }; 11 | B3E7B58A1CC2AC0600A0062D /* ItoBluetooth.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* ItoBluetooth.m */; platformFilter = ios; }; 12 | C2FFF12D244B49A800067451 /* SwItoBluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2FFF12C244B49A800067451 /* SwItoBluetooth.swift */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = "include/$(PRODUCT_NAME)"; 20 | dstSubfolderSpec = 16; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 0; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 134814201AA4EA6300B7C361 /* libItoBluetooth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libItoBluetooth.a; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 742754495354FD816D4E8E4A /* Pods-ItoBluetooth.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ItoBluetooth.release.xcconfig"; path = "Target Support Files/Pods-ItoBluetooth/Pods-ItoBluetooth.release.xcconfig"; sourceTree = ""; }; 30 | 7EC1987031BCF80FBAC73D9C /* Pods_ItoBluetooth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ItoBluetooth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | B3E7B5891CC2AC0600A0062D /* ItoBluetooth.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItoBluetooth.m; sourceTree = ""; }; 32 | C2FFF12C244B49A800067451 /* SwItoBluetooth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwItoBluetooth.swift; sourceTree = ""; }; 33 | C2FFF12E244B59A100067451 /* ItoBluetooth-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ItoBluetooth-Bridging-Header.h"; sourceTree = ""; }; 34 | DD3AAB4ECDB5003D42DAF57C /* Pods-ItoBluetooth.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ItoBluetooth.debug.xcconfig"; path = "Target Support Files/Pods-ItoBluetooth/Pods-ItoBluetooth.debug.xcconfig"; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | 7BE90D4FB255B68DDBC03678 /* Pods_ItoBluetooth.framework in Frameworks */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 134814211AA4EA7D00B7C361 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 134814201AA4EA6300B7C361 /* libItoBluetooth.a */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 58B511D21A9E6C8500147676 = { 58 | isa = PBXGroup; 59 | children = ( 60 | C2FFF12E244B59A100067451 /* ItoBluetooth-Bridging-Header.h */, 61 | C2FFF12C244B49A800067451 /* SwItoBluetooth.swift */, 62 | B3E7B5891CC2AC0600A0062D /* ItoBluetooth.m */, 63 | 134814211AA4EA7D00B7C361 /* Products */, 64 | FCA88AA95B551F7F4223E6AE /* Pods */, 65 | AEB34772B03280170E4EF634 /* Frameworks */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | AEB34772B03280170E4EF634 /* Frameworks */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 7EC1987031BCF80FBAC73D9C /* Pods_ItoBluetooth.framework */, 73 | ); 74 | name = Frameworks; 75 | sourceTree = ""; 76 | }; 77 | FCA88AA95B551F7F4223E6AE /* Pods */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | DD3AAB4ECDB5003D42DAF57C /* Pods-ItoBluetooth.debug.xcconfig */, 81 | 742754495354FD816D4E8E4A /* Pods-ItoBluetooth.release.xcconfig */, 82 | ); 83 | path = Pods; 84 | sourceTree = ""; 85 | }; 86 | /* End PBXGroup section */ 87 | 88 | /* Begin PBXNativeTarget section */ 89 | 58B511DA1A9E6C8500147676 /* ItoBluetooth */ = { 90 | isa = PBXNativeTarget; 91 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "ItoBluetooth" */; 92 | buildPhases = ( 93 | 07AA11322CC909FC40CF9B9B /* [CP] Check Pods Manifest.lock */, 94 | 58B511D71A9E6C8500147676 /* Sources */, 95 | 58B511D81A9E6C8500147676 /* Frameworks */, 96 | 58B511D91A9E6C8500147676 /* CopyFiles */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = ItoBluetooth; 103 | productName = RCTDataManager; 104 | productReference = 134814201AA4EA6300B7C361 /* libItoBluetooth.a */; 105 | productType = "com.apple.product-type.library.static"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | 58B511D31A9E6C8500147676 /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | LastUpgradeCheck = 0920; 114 | ORGANIZATIONNAME = Facebook; 115 | TargetAttributes = { 116 | 58B511DA1A9E6C8500147676 = { 117 | CreatedOnToolsVersion = 6.1.1; 118 | LastSwiftMigration = 1120; 119 | }; 120 | }; 121 | }; 122 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "ItoBluetooth" */; 123 | compatibilityVersion = "Xcode 3.2"; 124 | developmentRegion = English; 125 | hasScannedForEncodings = 0; 126 | knownRegions = ( 127 | English, 128 | en, 129 | ); 130 | mainGroup = 58B511D21A9E6C8500147676; 131 | productRefGroup = 58B511D21A9E6C8500147676; 132 | projectDirPath = ""; 133 | projectRoot = ""; 134 | targets = ( 135 | 58B511DA1A9E6C8500147676 /* ItoBluetooth */, 136 | ); 137 | }; 138 | /* End PBXProject section */ 139 | 140 | /* Begin PBXShellScriptBuildPhase section */ 141 | 07AA11322CC909FC40CF9B9B /* [CP] Check Pods Manifest.lock */ = { 142 | isa = PBXShellScriptBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | ); 146 | inputFileListPaths = ( 147 | ); 148 | inputPaths = ( 149 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 150 | "${PODS_ROOT}/Manifest.lock", 151 | ); 152 | name = "[CP] Check Pods Manifest.lock"; 153 | outputFileListPaths = ( 154 | ); 155 | outputPaths = ( 156 | "$(DERIVED_FILE_DIR)/Pods-ItoBluetooth-checkManifestLockResult.txt", 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | shellPath = /bin/sh; 160 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 161 | showEnvVarsInLog = 0; 162 | }; 163 | /* End PBXShellScriptBuildPhase section */ 164 | 165 | /* Begin PBXSourcesBuildPhase section */ 166 | 58B511D71A9E6C8500147676 /* Sources */ = { 167 | isa = PBXSourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | C2FFF12D244B49A800067451 /* SwItoBluetooth.swift in Sources */, 171 | B3E7B58A1CC2AC0600A0062D /* ItoBluetooth.m in Sources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXSourcesBuildPhase section */ 176 | 177 | /* Begin XCBuildConfiguration section */ 178 | 58B511ED1A9E6C8500147676 /* Debug */ = { 179 | isa = XCBuildConfiguration; 180 | buildSettings = { 181 | ALWAYS_SEARCH_USER_PATHS = NO; 182 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 183 | CLANG_CXX_LIBRARY = "libc++"; 184 | CLANG_ENABLE_MODULES = YES; 185 | CLANG_ENABLE_OBJC_ARC = YES; 186 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 187 | CLANG_WARN_BOOL_CONVERSION = YES; 188 | CLANG_WARN_COMMA = YES; 189 | CLANG_WARN_CONSTANT_CONVERSION = YES; 190 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 191 | CLANG_WARN_EMPTY_BODY = YES; 192 | CLANG_WARN_ENUM_CONVERSION = YES; 193 | CLANG_WARN_INFINITE_RECURSION = YES; 194 | CLANG_WARN_INT_CONVERSION = YES; 195 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 196 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 197 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 198 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 199 | CLANG_WARN_STRICT_PROTOTYPES = YES; 200 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 201 | CLANG_WARN_UNREACHABLE_CODE = YES; 202 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 203 | COPY_PHASE_STRIP = NO; 204 | ENABLE_STRICT_OBJC_MSGSEND = YES; 205 | ENABLE_TESTABILITY = YES; 206 | GCC_C_LANGUAGE_STANDARD = gnu99; 207 | GCC_DYNAMIC_NO_PIC = NO; 208 | GCC_NO_COMMON_BLOCKS = YES; 209 | GCC_OPTIMIZATION_LEVEL = 0; 210 | GCC_PREPROCESSOR_DEFINITIONS = ( 211 | "DEBUG=1", 212 | "$(inherited)", 213 | ); 214 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 215 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 216 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 217 | GCC_WARN_UNDECLARED_SELECTOR = YES; 218 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 219 | GCC_WARN_UNUSED_FUNCTION = YES; 220 | GCC_WARN_UNUSED_VARIABLE = YES; 221 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 222 | MTL_ENABLE_DEBUG_INFO = YES; 223 | ONLY_ACTIVE_ARCH = YES; 224 | SDKROOT = iphoneos; 225 | }; 226 | name = Debug; 227 | }; 228 | 58B511EE1A9E6C8500147676 /* Release */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | ALWAYS_SEARCH_USER_PATHS = NO; 232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 233 | CLANG_CXX_LIBRARY = "libc++"; 234 | CLANG_ENABLE_MODULES = YES; 235 | CLANG_ENABLE_OBJC_ARC = YES; 236 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 237 | CLANG_WARN_BOOL_CONVERSION = YES; 238 | CLANG_WARN_COMMA = YES; 239 | CLANG_WARN_CONSTANT_CONVERSION = YES; 240 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 247 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 248 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 249 | CLANG_WARN_STRICT_PROTOTYPES = YES; 250 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 251 | CLANG_WARN_UNREACHABLE_CODE = YES; 252 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 253 | COPY_PHASE_STRIP = YES; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu99; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 265 | MTL_ENABLE_DEBUG_INFO = NO; 266 | SDKROOT = iphoneos; 267 | VALIDATE_PRODUCT = YES; 268 | }; 269 | name = Release; 270 | }; 271 | 58B511F01A9E6C8500147676 /* Debug */ = { 272 | isa = XCBuildConfiguration; 273 | baseConfigurationReference = DD3AAB4ECDB5003D42DAF57C /* Pods-ItoBluetooth.debug.xcconfig */; 274 | buildSettings = { 275 | CLANG_ENABLE_MODULES = YES; 276 | HEADER_SEARCH_PATHS = ( 277 | "$(inherited)", 278 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 279 | "$(SRCROOT)/../../../React/**", 280 | "$(SRCROOT)/../../react-native/React/**", 281 | "$(SRCROOT)/../../node_modules/react-native/React/**", 282 | ); 283 | LD_RUNPATH_SEARCH_PATHS = ( 284 | "$(inherited)", 285 | "@executable_path/Frameworks", 286 | "@loader_path/Frameworks", 287 | ); 288 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 289 | OTHER_LDFLAGS = "-ObjC"; 290 | PRODUCT_NAME = ItoBluetooth; 291 | SKIP_INSTALL = YES; 292 | SWIFT_OBJC_BRIDGING_HEADER = "ItoBluetooth-Bridging-Header.h"; 293 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 294 | SWIFT_VERSION = 5.0; 295 | }; 296 | name = Debug; 297 | }; 298 | 58B511F11A9E6C8500147676 /* Release */ = { 299 | isa = XCBuildConfiguration; 300 | baseConfigurationReference = 742754495354FD816D4E8E4A /* Pods-ItoBluetooth.release.xcconfig */; 301 | buildSettings = { 302 | CLANG_ENABLE_MODULES = YES; 303 | HEADER_SEARCH_PATHS = ( 304 | "$(inherited)", 305 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 306 | "$(SRCROOT)/../../../React/**", 307 | "$(SRCROOT)/../../react-native/React/**", 308 | "$(SRCROOT)/../../node_modules/react-native/React/**", 309 | ); 310 | LD_RUNPATH_SEARCH_PATHS = ( 311 | "$(inherited)", 312 | "@executable_path/Frameworks", 313 | "@loader_path/Frameworks", 314 | ); 315 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 316 | OTHER_LDFLAGS = "-ObjC"; 317 | PRODUCT_NAME = ItoBluetooth; 318 | SKIP_INSTALL = YES; 319 | SWIFT_OBJC_BRIDGING_HEADER = "ItoBluetooth-Bridging-Header.h"; 320 | SWIFT_VERSION = 5.0; 321 | }; 322 | name = Release; 323 | }; 324 | /* End XCBuildConfiguration section */ 325 | 326 | /* Begin XCConfigurationList section */ 327 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "ItoBluetooth" */ = { 328 | isa = XCConfigurationList; 329 | buildConfigurations = ( 330 | 58B511ED1A9E6C8500147676 /* Debug */, 331 | 58B511EE1A9E6C8500147676 /* Release */, 332 | ); 333 | defaultConfigurationIsVisible = 0; 334 | defaultConfigurationName = Release; 335 | }; 336 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "ItoBluetooth" */ = { 337 | isa = XCConfigurationList; 338 | buildConfigurations = ( 339 | 58B511F01A9E6C8500147676 /* Debug */, 340 | 58B511F11A9E6C8500147676 /* Release */, 341 | ); 342 | defaultConfigurationIsVisible = 0; 343 | defaultConfigurationName = Release; 344 | }; 345 | /* End XCConfigurationList section */ 346 | }; 347 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 348 | } 349 | -------------------------------------------------------------------------------- /ios/ItoBluetooth.xcodeproj/xcshareddata/xcschemes/ItoBluetooth.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /ios/ItoBluetooth.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/ItoBluetooth.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | use_frameworks! 3 | 4 | target 'ItoBluetooth' do 5 | pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector" 6 | pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec" 7 | pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired" 8 | pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety" 9 | pod 'React', :path => '../node_modules/react-native/' 10 | pod 'React-Core', :path => '../node_modules/react-native/' 11 | pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules' 12 | pod 'React-Core/DevSupport', :path => '../node_modules/react-native/' 13 | pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS' 14 | pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation' 15 | pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob' 16 | pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image' 17 | pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS' 18 | pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network' 19 | pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings' 20 | pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text' 21 | pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration' 22 | pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/' 23 | 24 | pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact' 25 | pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi' 26 | pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor' 27 | pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector' 28 | pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon" 29 | pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon" 30 | pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga' 31 | 32 | pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec' 33 | pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec' 34 | pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec' 35 | end 36 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - boost-for-react-native (1.63.0) 3 | - DoubleConversion (1.1.6) 4 | - FBLazyVector (0.62.2) 5 | - FBReactNativeSpec (0.62.2): 6 | - Folly (= 2018.10.22.00) 7 | - RCTRequired (= 0.62.2) 8 | - RCTTypeSafety (= 0.62.2) 9 | - React-Core (= 0.62.2) 10 | - React-jsi (= 0.62.2) 11 | - ReactCommon/turbomodule/core (= 0.62.2) 12 | - Folly (2018.10.22.00): 13 | - boost-for-react-native 14 | - DoubleConversion 15 | - Folly/Default (= 2018.10.22.00) 16 | - glog 17 | - Folly/Default (2018.10.22.00): 18 | - boost-for-react-native 19 | - DoubleConversion 20 | - glog 21 | - glog (0.3.5) 22 | - RCTRequired (0.62.2) 23 | - RCTTypeSafety (0.62.2): 24 | - FBLazyVector (= 0.62.2) 25 | - Folly (= 2018.10.22.00) 26 | - RCTRequired (= 0.62.2) 27 | - React-Core (= 0.62.2) 28 | - React (0.62.2): 29 | - React-Core (= 0.62.2) 30 | - React-Core/DevSupport (= 0.62.2) 31 | - React-Core/RCTWebSocket (= 0.62.2) 32 | - React-RCTActionSheet (= 0.62.2) 33 | - React-RCTAnimation (= 0.62.2) 34 | - React-RCTBlob (= 0.62.2) 35 | - React-RCTImage (= 0.62.2) 36 | - React-RCTLinking (= 0.62.2) 37 | - React-RCTNetwork (= 0.62.2) 38 | - React-RCTSettings (= 0.62.2) 39 | - React-RCTText (= 0.62.2) 40 | - React-RCTVibration (= 0.62.2) 41 | - React-Core (0.62.2): 42 | - Folly (= 2018.10.22.00) 43 | - glog 44 | - React-Core/Default (= 0.62.2) 45 | - React-cxxreact (= 0.62.2) 46 | - React-jsi (= 0.62.2) 47 | - React-jsiexecutor (= 0.62.2) 48 | - Yoga 49 | - React-Core/CoreModulesHeaders (0.62.2): 50 | - Folly (= 2018.10.22.00) 51 | - glog 52 | - React-Core/Default 53 | - React-cxxreact (= 0.62.2) 54 | - React-jsi (= 0.62.2) 55 | - React-jsiexecutor (= 0.62.2) 56 | - Yoga 57 | - React-Core/Default (0.62.2): 58 | - Folly (= 2018.10.22.00) 59 | - glog 60 | - React-cxxreact (= 0.62.2) 61 | - React-jsi (= 0.62.2) 62 | - React-jsiexecutor (= 0.62.2) 63 | - Yoga 64 | - React-Core/DevSupport (0.62.2): 65 | - Folly (= 2018.10.22.00) 66 | - glog 67 | - React-Core/Default (= 0.62.2) 68 | - React-Core/RCTWebSocket (= 0.62.2) 69 | - React-cxxreact (= 0.62.2) 70 | - React-jsi (= 0.62.2) 71 | - React-jsiexecutor (= 0.62.2) 72 | - React-jsinspector (= 0.62.2) 73 | - Yoga 74 | - React-Core/RCTActionSheetHeaders (0.62.2): 75 | - Folly (= 2018.10.22.00) 76 | - glog 77 | - React-Core/Default 78 | - React-cxxreact (= 0.62.2) 79 | - React-jsi (= 0.62.2) 80 | - React-jsiexecutor (= 0.62.2) 81 | - Yoga 82 | - React-Core/RCTAnimationHeaders (0.62.2): 83 | - Folly (= 2018.10.22.00) 84 | - glog 85 | - React-Core/Default 86 | - React-cxxreact (= 0.62.2) 87 | - React-jsi (= 0.62.2) 88 | - React-jsiexecutor (= 0.62.2) 89 | - Yoga 90 | - React-Core/RCTBlobHeaders (0.62.2): 91 | - Folly (= 2018.10.22.00) 92 | - glog 93 | - React-Core/Default 94 | - React-cxxreact (= 0.62.2) 95 | - React-jsi (= 0.62.2) 96 | - React-jsiexecutor (= 0.62.2) 97 | - Yoga 98 | - React-Core/RCTImageHeaders (0.62.2): 99 | - Folly (= 2018.10.22.00) 100 | - glog 101 | - React-Core/Default 102 | - React-cxxreact (= 0.62.2) 103 | - React-jsi (= 0.62.2) 104 | - React-jsiexecutor (= 0.62.2) 105 | - Yoga 106 | - React-Core/RCTLinkingHeaders (0.62.2): 107 | - Folly (= 2018.10.22.00) 108 | - glog 109 | - React-Core/Default 110 | - React-cxxreact (= 0.62.2) 111 | - React-jsi (= 0.62.2) 112 | - React-jsiexecutor (= 0.62.2) 113 | - Yoga 114 | - React-Core/RCTNetworkHeaders (0.62.2): 115 | - Folly (= 2018.10.22.00) 116 | - glog 117 | - React-Core/Default 118 | - React-cxxreact (= 0.62.2) 119 | - React-jsi (= 0.62.2) 120 | - React-jsiexecutor (= 0.62.2) 121 | - Yoga 122 | - React-Core/RCTSettingsHeaders (0.62.2): 123 | - Folly (= 2018.10.22.00) 124 | - glog 125 | - React-Core/Default 126 | - React-cxxreact (= 0.62.2) 127 | - React-jsi (= 0.62.2) 128 | - React-jsiexecutor (= 0.62.2) 129 | - Yoga 130 | - React-Core/RCTTextHeaders (0.62.2): 131 | - Folly (= 2018.10.22.00) 132 | - glog 133 | - React-Core/Default 134 | - React-cxxreact (= 0.62.2) 135 | - React-jsi (= 0.62.2) 136 | - React-jsiexecutor (= 0.62.2) 137 | - Yoga 138 | - React-Core/RCTVibrationHeaders (0.62.2): 139 | - Folly (= 2018.10.22.00) 140 | - glog 141 | - React-Core/Default 142 | - React-cxxreact (= 0.62.2) 143 | - React-jsi (= 0.62.2) 144 | - React-jsiexecutor (= 0.62.2) 145 | - Yoga 146 | - React-Core/RCTWebSocket (0.62.2): 147 | - Folly (= 2018.10.22.00) 148 | - glog 149 | - React-Core/Default (= 0.62.2) 150 | - React-cxxreact (= 0.62.2) 151 | - React-jsi (= 0.62.2) 152 | - React-jsiexecutor (= 0.62.2) 153 | - Yoga 154 | - React-CoreModules (0.62.2): 155 | - FBReactNativeSpec (= 0.62.2) 156 | - Folly (= 2018.10.22.00) 157 | - RCTTypeSafety (= 0.62.2) 158 | - React-Core/CoreModulesHeaders (= 0.62.2) 159 | - React-RCTImage (= 0.62.2) 160 | - ReactCommon/turbomodule/core (= 0.62.2) 161 | - React-cxxreact (0.62.2): 162 | - boost-for-react-native (= 1.63.0) 163 | - DoubleConversion 164 | - Folly (= 2018.10.22.00) 165 | - glog 166 | - React-jsinspector (= 0.62.2) 167 | - React-jsi (0.62.2): 168 | - boost-for-react-native (= 1.63.0) 169 | - DoubleConversion 170 | - Folly (= 2018.10.22.00) 171 | - glog 172 | - React-jsi/Default (= 0.62.2) 173 | - React-jsi/Default (0.62.2): 174 | - boost-for-react-native (= 1.63.0) 175 | - DoubleConversion 176 | - Folly (= 2018.10.22.00) 177 | - glog 178 | - React-jsiexecutor (0.62.2): 179 | - DoubleConversion 180 | - Folly (= 2018.10.22.00) 181 | - glog 182 | - React-cxxreact (= 0.62.2) 183 | - React-jsi (= 0.62.2) 184 | - React-jsinspector (0.62.2) 185 | - React-RCTActionSheet (0.62.2): 186 | - React-Core/RCTActionSheetHeaders (= 0.62.2) 187 | - React-RCTAnimation (0.62.2): 188 | - FBReactNativeSpec (= 0.62.2) 189 | - Folly (= 2018.10.22.00) 190 | - RCTTypeSafety (= 0.62.2) 191 | - React-Core/RCTAnimationHeaders (= 0.62.2) 192 | - ReactCommon/turbomodule/core (= 0.62.2) 193 | - React-RCTBlob (0.62.2): 194 | - FBReactNativeSpec (= 0.62.2) 195 | - Folly (= 2018.10.22.00) 196 | - React-Core/RCTBlobHeaders (= 0.62.2) 197 | - React-Core/RCTWebSocket (= 0.62.2) 198 | - React-jsi (= 0.62.2) 199 | - React-RCTNetwork (= 0.62.2) 200 | - ReactCommon/turbomodule/core (= 0.62.2) 201 | - React-RCTImage (0.62.2): 202 | - FBReactNativeSpec (= 0.62.2) 203 | - Folly (= 2018.10.22.00) 204 | - RCTTypeSafety (= 0.62.2) 205 | - React-Core/RCTImageHeaders (= 0.62.2) 206 | - React-RCTNetwork (= 0.62.2) 207 | - ReactCommon/turbomodule/core (= 0.62.2) 208 | - React-RCTLinking (0.62.2): 209 | - FBReactNativeSpec (= 0.62.2) 210 | - React-Core/RCTLinkingHeaders (= 0.62.2) 211 | - ReactCommon/turbomodule/core (= 0.62.2) 212 | - React-RCTNetwork (0.62.2): 213 | - FBReactNativeSpec (= 0.62.2) 214 | - Folly (= 2018.10.22.00) 215 | - RCTTypeSafety (= 0.62.2) 216 | - React-Core/RCTNetworkHeaders (= 0.62.2) 217 | - ReactCommon/turbomodule/core (= 0.62.2) 218 | - React-RCTSettings (0.62.2): 219 | - FBReactNativeSpec (= 0.62.2) 220 | - Folly (= 2018.10.22.00) 221 | - RCTTypeSafety (= 0.62.2) 222 | - React-Core/RCTSettingsHeaders (= 0.62.2) 223 | - ReactCommon/turbomodule/core (= 0.62.2) 224 | - React-RCTText (0.62.2): 225 | - React-Core/RCTTextHeaders (= 0.62.2) 226 | - React-RCTVibration (0.62.2): 227 | - FBReactNativeSpec (= 0.62.2) 228 | - Folly (= 2018.10.22.00) 229 | - React-Core/RCTVibrationHeaders (= 0.62.2) 230 | - ReactCommon/turbomodule/core (= 0.62.2) 231 | - ReactCommon/callinvoker (0.62.2): 232 | - DoubleConversion 233 | - Folly (= 2018.10.22.00) 234 | - glog 235 | - React-cxxreact (= 0.62.2) 236 | - ReactCommon/turbomodule/core (0.62.2): 237 | - DoubleConversion 238 | - Folly (= 2018.10.22.00) 239 | - glog 240 | - React-Core (= 0.62.2) 241 | - React-cxxreact (= 0.62.2) 242 | - React-jsi (= 0.62.2) 243 | - ReactCommon/callinvoker (= 0.62.2) 244 | - Yoga (1.14.0) 245 | 246 | DEPENDENCIES: 247 | - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) 248 | - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) 249 | - FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`) 250 | - Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`) 251 | - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) 252 | - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) 253 | - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) 254 | - React (from `../node_modules/react-native/`) 255 | - React-Core (from `../node_modules/react-native/`) 256 | - React-Core/DevSupport (from `../node_modules/react-native/`) 257 | - React-Core/RCTWebSocket (from `../node_modules/react-native/`) 258 | - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) 259 | - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) 260 | - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) 261 | - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) 262 | - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) 263 | - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) 264 | - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) 265 | - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) 266 | - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) 267 | - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) 268 | - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) 269 | - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) 270 | - React-RCTText (from `../node_modules/react-native/Libraries/Text`) 271 | - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) 272 | - ReactCommon/callinvoker (from `../node_modules/react-native/ReactCommon`) 273 | - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) 274 | - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) 275 | 276 | SPEC REPOS: 277 | https://github.com/cocoapods/specs.git: 278 | - boost-for-react-native 279 | 280 | EXTERNAL SOURCES: 281 | DoubleConversion: 282 | :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" 283 | FBLazyVector: 284 | :path: "../node_modules/react-native/Libraries/FBLazyVector" 285 | FBReactNativeSpec: 286 | :path: "../node_modules/react-native/Libraries/FBReactNativeSpec" 287 | Folly: 288 | :podspec: "../node_modules/react-native/third-party-podspecs/Folly.podspec" 289 | glog: 290 | :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" 291 | RCTRequired: 292 | :path: "../node_modules/react-native/Libraries/RCTRequired" 293 | RCTTypeSafety: 294 | :path: "../node_modules/react-native/Libraries/TypeSafety" 295 | React: 296 | :path: "../node_modules/react-native/" 297 | React-Core: 298 | :path: "../node_modules/react-native/" 299 | React-CoreModules: 300 | :path: "../node_modules/react-native/React/CoreModules" 301 | React-cxxreact: 302 | :path: "../node_modules/react-native/ReactCommon/cxxreact" 303 | React-jsi: 304 | :path: "../node_modules/react-native/ReactCommon/jsi" 305 | React-jsiexecutor: 306 | :path: "../node_modules/react-native/ReactCommon/jsiexecutor" 307 | React-jsinspector: 308 | :path: "../node_modules/react-native/ReactCommon/jsinspector" 309 | React-RCTActionSheet: 310 | :path: "../node_modules/react-native/Libraries/ActionSheetIOS" 311 | React-RCTAnimation: 312 | :path: "../node_modules/react-native/Libraries/NativeAnimation" 313 | React-RCTBlob: 314 | :path: "../node_modules/react-native/Libraries/Blob" 315 | React-RCTImage: 316 | :path: "../node_modules/react-native/Libraries/Image" 317 | React-RCTLinking: 318 | :path: "../node_modules/react-native/Libraries/LinkingIOS" 319 | React-RCTNetwork: 320 | :path: "../node_modules/react-native/Libraries/Network" 321 | React-RCTSettings: 322 | :path: "../node_modules/react-native/Libraries/Settings" 323 | React-RCTText: 324 | :path: "../node_modules/react-native/Libraries/Text" 325 | React-RCTVibration: 326 | :path: "../node_modules/react-native/Libraries/Vibration" 327 | ReactCommon: 328 | :path: "../node_modules/react-native/ReactCommon" 329 | Yoga: 330 | :path: "../node_modules/react-native/ReactCommon/yoga" 331 | 332 | SPEC CHECKSUMS: 333 | boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c 334 | DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2 335 | FBLazyVector: 4aab18c93cd9546e4bfed752b4084585eca8b245 336 | FBReactNativeSpec: 5465d51ccfeecb7faa12f9ae0024f2044ce4044e 337 | Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51 338 | glog: 1f3da668190260b06b429bb211bfbee5cd790c28 339 | RCTRequired: cec6a34b3ac8a9915c37e7e4ad3aa74726ce4035 340 | RCTTypeSafety: 93006131180074cffa227a1075802c89a49dd4ce 341 | React: 29a8b1a02bd764fb7644ef04019270849b9a7ac3 342 | React-Core: b12bffb3f567fdf99510acb716ef1abd426e0e05 343 | React-CoreModules: 4a9b87bbe669d6c3173c0132c3328e3b000783d0 344 | React-cxxreact: e65f9c2ba0ac5be946f53548c1aaaee5873a8103 345 | React-jsi: b6dc94a6a12ff98e8877287a0b7620d365201161 346 | React-jsiexecutor: 1540d1c01bb493ae3124ed83351b1b6a155db7da 347 | React-jsinspector: 512e560d0e985d0e8c479a54a4e5c147a9c83493 348 | React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c 349 | React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0 350 | React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71 351 | React-RCTImage: e70be9b9c74fe4e42d0005f42cace7981c994ac3 352 | React-RCTLinking: c1b9739a88d56ecbec23b7f63650e44672ab2ad2 353 | React-RCTNetwork: 73138b6f45e5a2768ad93f3d57873c2a18d14b44 354 | React-RCTSettings: 6e3738a87e21b39a8cb08d627e68c44acf1e325a 355 | React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d 356 | React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256 357 | ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3 358 | Yoga: 3ebccbdd559724312790e7742142d062476b698e 359 | 360 | PODFILE CHECKSUM: 2268b8c40ebfbed7816bf4a820665453ec09fecc 361 | 362 | COCOAPODS: 1.6.1 363 | -------------------------------------------------------------------------------- /ios/SwItoBluetooth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwItoBluetooth.swift 3 | // 4 | // 5 | // Created by Dieder Timmers on 18/04/2020. 6 | // 7 | 8 | import Foundation 9 | @objc 10 | public class SwItoBluetooth :NSObject { 11 | @objc 12 | func isPossiblyInfected() -> NSNumber{ 13 | return true; 14 | } 15 | 16 | @objc 17 | func restartTracingService(){ 18 | 19 | } 20 | 21 | @objc 22 | func getLatestFetchTime() -> NSNumber{ 23 | return 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-ito", 3 | "title": "React Native Ito Bluetooth", 4 | "version": "1.0.0", 5 | "description": "TODO", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/ito-org/react-native-ito.git", 13 | "baseUrl": "https://github.com/ito-org/react-native-ito" 14 | }, 15 | "keywords": [ 16 | "react-native" 17 | ], 18 | "author": { 19 | "name": "Christian Romberg", 20 | "email": "info@ito-app.org" 21 | }, 22 | "readmeFilename": "README.md", 23 | "peerDependencies": { 24 | "react": "^16.8.1", 25 | "react-native": ">=0.60.0-rc.0 <1.0.x" 26 | }, 27 | "devDependencies": { 28 | "react": "^16.9.0", 29 | "react-native": "^0.62.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /react-native-ito.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "react-native-ito" 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | s.description = <<-DESC 10 | react-native-ito 11 | DESC 12 | s.homepage = "https://github.com/github_account/react-native-ito" 13 | s.license = "MIT" 14 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 15 | s.authors = { "Your Name" => "yourname@email.com" } 16 | s.platforms = { :ios => "9.0" } 17 | s.source = { :git => "https://github.com/github_account/react-native-ito.git", :tag => "#{s.version}" } 18 | 19 | s.source_files = "ios/**/*.{h,m,swift}" 20 | s.requires_arc = true 21 | 22 | s.dependency "React" 23 | # ... 24 | # s.dependency "..." 25 | end 26 | 27 | --------------------------------------------------------------------------------