Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 26 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Android Hypertrace - Kotlin implementation of OpenTrace by Hyperjump

Kotlin OpenTrace implementation based on [BlueTrace specification](https://bluetrace.io/static/bluetrace_whitepaper-938063656596c104632def383eb33b3c.pdf).
Kotlin OpenTrace implementation based on [BlueTrace specification](https://bluetrace.io/static/bluetrace_whitepaper-938063656596c104632def383eb33b3c.pdf). Example app is available in [example module](https://github.com/hyperjumptech/hypertrace-android-sdk/tree/main/example/src/main).

### Table of Content

Expand Down Expand Up @@ -66,32 +66,31 @@ HypertraceSdk.startService(config)

##### Available configuration

| Field | Type | Description | Mandatory | Default |
| :--------------------------------- | :------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :-------- | :------------ |
| notificationChannelCreator | Function | Kotlin higher-order function for SDK to create android notification channel. (Required for API level 26 / Android O). | **YES** | - |
| foregroundNotificationCreator | Function | Kotlin higher-order function for SDK to create notification when service is actively running. | **YES** | - |
| bluetoothFailedNotificationCreator | Function | Kotlin higher-order function for SDK to create notification when service fails to access bluetooth and location permissions. | **YES** | - |
| userId | string | Main application's user ID. **Must be** 21 characters. | **YES** | - |
| organization | string | Application's organization name. Typically, this is a combination of `COUNTRY_CODE` and short organization name. | **YES** | - |
| baseUrl | string | URL for [Hypertrace server](https://github.com/hyperjumptech/hypertrace) implementation. **Must end** with slash `/` character. | **YES** | - |
| bleServiceUuid | string | BLE service UUID. **Must be** a valid UUID. | **YES** | - |
| bleCharacteristicUuid | string | BLE characteristic UUID. **Must be** a valid UUID. | **YES** | - |
| debug | boolean | Enable showing street pass list and bluetooth scanning activity | **NO** | - |
| keepAliveService | boolean | If `true`, Hypertrace will try to restart service everytime it is killed. | **NO** | `false` |
| scanDuration | long | Duration of each bluetooth scan action in **miliseconds.** Default to 10 seconds. | **NO** | 10_000 |
| minScanInterval | long | Minimum scan interval in **miliseconds.** Randomized between minScanInterval and maxScanInterval. Default to 30 seconds. | **NO** | 30_000 |
| maxScanInterval | long | Maximum scan interval in **miliseconds.** Randomized between minScanInterval and maxScanInterval. Default to 40 seconds. | **NO** | 40_000 |
| advertisingDuration | long | Duration of each bluetooth advertising action in **miliseconds.** Default to 30 minutes. | **NO** | 180_000 |
| advertisingInterval | long | Interval between bluetooth advertising action in **miliseconds.** Default to 6 seconds. | **NO** | 6_000 |
| purgeRecordInterval | long | Interval between purge action of encounter's records in **miliseconds.** Default to 24 hours. | **NO** | 86_400_000 |
| recordTTL | long | The lifetime of encounter's records in **miliseconds.** Default to 21 days. | **NO** | 1_814_400_000 |
| maxPeripheralQueueTime | long | Maximum time of a peripheral to wait to be processed in **miliseconds.** Default to 10 seconds. | **NO** | 10_000 |
| deviceConnectionTimeout | long | Maximum time of a peripheral read-write action in **miliseconds.** Default to 6 seconds. | **NO** | 6_000 |
| deviceBlacklistDuration | long | Maximum time of a device to be blacklisted time in **miliseconds.** Default to 1.5 minutes. | **NO** | 90_000 |
| temporaryIdCheckInterval | long | Interval between temporary IDs' supply check **miliseconds.** Default to 10 minutes. | **NO** | 600_000 |
| bluetoothServiceHeartBeat | long | Interval between OpenTrace bluetooth service check **miliseconds.** Default to 15 minutes. | **NO** | 900_000 |
| certificatePinner | CertificatePinner | Helper for certificate pinning provided by OkHttp. See [**Security Enhancements.**](#security-enhancements) | **NO** | null |
| okHttpConfig | OkHttpClient.Builder | For a complete control of SDK's OkHttpClient. | **NO** | null |
| Field | Type | Description | Mandatory | Default |
| :--------------------------------- | :------------------- | :------------------------------------------------------------------------------------------------------------------------------ | :-------- | :--------------------------------------------------------- |
| notificationChannelCreator | Function | Kotlin higher-order function for SDK to create android notification channel. (Required for API level 26 / Android O). | **YES** | - |
| foregroundNotificationCreator | Function | Kotlin higher-order function for SDK to create notification when service is actively running. | **YES** | - |
| bluetoothFailedNotificationCreator | Function | Kotlin higher-order function for SDK to create notification when service fails to access bluetooth and location permissions. | **YES** | - |
| userId | string | Main application's user ID. **Must be** 21 characters. | **YES** | - |
| organization | string | Application's organization name. Typically, this is a combination of `COUNTRY_CODE` and short organization name. | **YES** | - |
| baseUrl | string | URL for [Hypertrace server](https://github.com/hyperjumptech/hypertrace) implementation. **Must end** with slash `/` character. | **YES** | - |
| bleServiceUuid | string | BLE service UUID. **Must be** a valid UUID. | **YES** | - |
| bleCharacteristicUuid | string | BLE characteristic UUID. **Must be** a valid UUID. | **YES** | - |
| debug | boolean | Enable showing street pass list and bluetooth scanning activity | **NO** | - |
| keepAliveService | boolean | If `true`, Hypertrace will try to restart service everytime it is killed. | **NO** | `false` |
| scanDuration | long | Duration of each bluetooth scan action in **miliseconds.** Default to 10 seconds. | **NO** | 10_000 |
| minScanInterval | long | Minimum scan interval in **miliseconds.** Randomized between minScanInterval and maxScanInterval. Default to 30 seconds. | **NO** | 30_000 |
| maxScanInterval | long | Maximum scan interval in **miliseconds.** Randomized between minScanInterval and maxScanInterval. Default to 40 seconds. | **NO** | 40_000 |
| advertising | Config.Advertising | Hypertrace configuration for advertising's duration and interval. Default to `Advertising.Enable` | **NO** | `Advertising.Enable(duration = 180_000, interval = 6_000)` |
| purgeRecordInterval | long | Interval between purge action of encounter's records in **miliseconds.** Default to 24 hours. | **NO** | 86_400_000 |
| recordTTL | long | The lifetime of encounter's records in **miliseconds.** Default to 21 days. | **NO** | 1_814_400_000 |
| maxPeripheralQueueTime | long | Maximum time of a peripheral to wait to be processed in **miliseconds.** Default to 10 seconds. | **NO** | 10_000 |
| deviceConnectionTimeout | long | Maximum time of a peripheral read-write action in **miliseconds.** Default to 6 seconds. | **NO** | 6_000 |
| deviceBlacklistDuration | long | Maximum time of a device to be blacklisted time in **miliseconds.** Default to 1.5 minutes. | **NO** | 90_000 |
| temporaryIdCheckInterval | long | Interval between temporary IDs' supply check **miliseconds.** Default to 10 minutes. | **NO** | 600_000 |
| bluetoothServiceHeartBeat | long | Interval between OpenTrace bluetooth service check **miliseconds.** Default to 15 minutes. | **NO** | 900_000 |
| certificatePinner | CertificatePinner | Helper for certificate pinning provided by OkHttp. See [**Security Enhancements.**](#security-enhancements) | **NO** | null |
| okHttpConfig | OkHttpClient.Builder | For a complete control of SDK's OkHttpClient. | **NO** | null |

#### Stop background service

Expand Down Expand Up @@ -185,4 +184,3 @@ For more information visit [https://android-developers.googleblog.com/2020/04/go
**0.9.0**

- First release.

3 changes: 2 additions & 1 deletion example/src/main/java/tech/hyperjump/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.lifecycleScope
import io.bluetrace.opentrace.streetpassdebug.StreetPassDebugActivity
import kotlinx.android.synthetic.main.activity_main.*
import pub.devrel.easypermissions.EasyPermissions
import tech.hyperjump.hypertrace.HyperTraceSdk
import tech.hyperjump.hypertrace.scandebug.ScanDebugActivity
import io.bluetrace.opentrace.streetpassdebug.StreetPassDebugActivity
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
Expand Down Expand Up @@ -121,6 +121,7 @@ class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks {
}

private fun buildConfig(): HyperTraceSdk.Config {
// hypertrace server requires uid to be 21 character length
userId = generateUserId(21)
// FIXME change to hypertrace server implementation
val baseUrl = "https://192.108.0.0/"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ import io.bluetrace.opentrace.bluetooth.gatt.STREET_PASS
import io.bluetrace.opentrace.idmanager.TempIDManager
import io.bluetrace.opentrace.idmanager.TemporaryID
import io.bluetrace.opentrace.logging.CentralLog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pub.devrel.easypermissions.EasyPermissions
import tech.hyperjump.hypertrace.*
import io.bluetrace.opentrace.status.Status
import io.bluetrace.opentrace.status.persistence.StatusRecord
import io.bluetrace.opentrace.status.persistence.StatusRecordStorage
Expand All @@ -36,6 +30,14 @@ import io.bluetrace.opentrace.streetpass.StreetPassServer
import io.bluetrace.opentrace.streetpass.StreetPassWorker
import io.bluetrace.opentrace.streetpass.persistence.StreetPassRecord
import io.bluetrace.opentrace.streetpass.persistence.StreetPassRecordStorage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pub.devrel.easypermissions.EasyPermissions
import tech.hyperjump.hypertrace.BuildConfig
import tech.hyperjump.hypertrace.HyperTraceSdk
import tech.hyperjump.hypertrace.R
import java.lang.ref.WeakReference
import kotlin.coroutines.CoroutineContext

Expand Down Expand Up @@ -325,7 +327,10 @@ class BluetoothMonitoringService : Service(), CoroutineScope {
private fun actionAdvertise() {
setupAdvertiser()
if (isBluetoothEnabled()) {
advertiser?.startAdvertising(advertisingDuration)
val duration = HyperTraceSdk.CONFIG.advertising.let {
if (it is HyperTraceSdk.Config.Advertising.Enable) it.duration else 0
}
advertiser?.startAdvertising(duration)
} else {
CentralLog.w(TAG, "Unable to start advertising, bluetooth is off")
}
Expand Down Expand Up @@ -357,6 +362,8 @@ class BluetoothMonitoringService : Service(), CoroutineScope {
}

private fun setupAdvertisingCycles() {
// ignore if disabled
if (HyperTraceSdk.CONFIG.advertising is HyperTraceSdk.Config.Advertising.Disable) return
commandHandler.scheduleNextAdvertise(0)
}

Expand All @@ -377,8 +384,12 @@ class BluetoothMonitoringService : Service(), CoroutineScope {
}

private fun scheduleAdvertisement() {
if (!infiniteAdvertising) {
commandHandler.scheduleNextAdvertise(advertisingDuration + advertisingGap)
// ignore if disabled
if (HyperTraceSdk.CONFIG.advertising is HyperTraceSdk.Config.Advertising.Disable) return
val advertisingConfig = HyperTraceSdk.CONFIG.advertising
if (!infiniteAdvertising && advertisingConfig is HyperTraceSdk.Config.Advertising.Enable) {
val delay = advertisingConfig.duration + advertisingConfig.interval
commandHandler.scheduleNextAdvertise(delay)
}
}

Expand Down Expand Up @@ -429,6 +440,8 @@ class BluetoothMonitoringService : Service(), CoroutineScope {
}

if (!infiniteAdvertising) {
// ignore if disabled
if (HyperTraceSdk.CONFIG.advertising is HyperTraceSdk.Config.Advertising.Disable) return
if (!commandHandler.hasAdvertiseScheduled()) {
CentralLog.w(TAG, "Missing Advertise Schedule - rectifying")
// setupAdvertisingCycles()
Expand Down Expand Up @@ -637,14 +650,10 @@ class BluetoothMonitoringService : Service(), CoroutineScope {

var broadcastMessage: TemporaryID? = null

//should be more than advertising gap?
//should be more than advertising interval
val scanDuration: Long = HyperTraceSdk.CONFIG.scanDuration
val minScanInterval: Long = HyperTraceSdk.CONFIG.minScanInterval
val maxScanInterval: Long = HyperTraceSdk.CONFIG.maxScanInterval

val advertisingDuration: Long = HyperTraceSdk.CONFIG.advertisingDuration
val advertisingGap: Long = HyperTraceSdk.CONFIG.advertisingInterval

val maxQueueTime: Long = HyperTraceSdk.CONFIG.maxPeripheralQueueTime
val bmCheckInterval: Long = HyperTraceSdk.CONFIG.temporaryIdCheckInterval
val healthCheckInterval: Long = HyperTraceSdk.CONFIG.bluetoothServiceHeartBeat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ object HyperTraceSdk {
val scanDuration: Long = 10_000,
val minScanInterval: Long = 30_000,
val maxScanInterval: Long = 40_000,
val advertisingDuration: Long = 180_000, // 30 minutes
val advertisingInterval: Long = 6_000,
val advertising: Advertising = Advertising.Enable(
duration = 180_000, // 30 minutes
interval = 6_000,
),
val purgeRecordInterval: Long = 86_400_000, // 24 hours
val recordTTL: Long = 1_814_400_000, // 21 days
val maxPeripheralQueueTime: Long = 10_000,
Expand All @@ -144,8 +146,16 @@ object HyperTraceSdk {
val okHttpConfig: (OkHttpClient.Builder.() -> Unit)? = null
) {

sealed class Advertising {

@Keep
object Disable : Advertising()

@Keep
class Enable(val duration: Long, val interval: Long) : Advertising()
}

fun validateConfig() {
if (userId.length < 21) throw Exception("User ID must have exactly 21 characters.")
if (organization.isEmpty()) throw Exception("Organization Code cannot be empty.")
if (baseUrl.isEmpty()) throw Exception("Base URL cannot be empty.")
if (baseUrl.last() != '/') throw Exception("Base URL must end with slash '/'.")
Expand Down