Skip to content

Commit b4f36a1

Browse files
committed
implement SpO2 and Heart Rate readings
1 parent 5f87005 commit b4f36a1

22 files changed

+1062
-12
lines changed

.idea/gradle.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,19 @@ android {
2828
}
2929

3030
dependencies {
31+
// Samsung SDK
32+
implementation fileTree(dir: 'libs', include: '*.aar')
3133

34+
// Standards
3235
implementation 'androidx.core:core-ktx:1.7.0'
3336
implementation 'com.google.android.gms:play-services-wearable:18.0.0'
3437
implementation 'androidx.percentlayout:percentlayout:1.0.0'
3538
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
3639
implementation 'androidx.recyclerview:recyclerview:1.2.1'
40+
41+
// Wear
3742
implementation 'androidx.wear:wear:1.2.0'
43+
implementation 'androidx.wear:wear-input:1.1.0'
44+
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
45+
implementation 'com.google.android.material:material:1.7.0'
3846
}

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
33

44
<uses-permission android:name="android.permission.WAKE_LOCK" />
5+
<uses-permission android:name="android.permission.BODY_SENSORS"/>
6+
<uses-permission android:name="android.permission.INTERNET"/>
57

68
<uses-feature android:name="android.hardware.type.watch" />
79

@@ -10,7 +12,7 @@
1012
android:icon="@mipmap/ic_launcher"
1113
android:label="@string/app_name"
1214
android:supportsRtl="true"
13-
android:theme="@android:style/Theme.DeviceDefault">
15+
android:theme="@style/Theme.MainTheme">
1416
<uses-library
1517
android:name="com.google.android.wearable"
1618
android:required="true" />
@@ -33,6 +35,7 @@
3335
<category android:name="android.intent.category.LAUNCHER" />
3436
</intent-filter>
3537
</activity>
38+
<activity android:name=".MonitoringActivity" />
3639
</application>
3740

3841
</manifest>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.example.isowatch
2+
3+
import android.os.Handler
4+
import android.util.Log
5+
import com.samsung.android.service.health.tracking.HealthTracker
6+
import com.samsung.android.service.health.tracking.HealthTracker.TrackerEventListener
7+
8+
public open class BaseListener {
9+
private val tag = "BaseListener"
10+
11+
private lateinit var handler: Handler
12+
private lateinit var healthTracker: HealthTracker
13+
private var isHandlerRunning = false
14+
15+
private lateinit var trackerEventListener: TrackerEventListener
16+
17+
public fun setHealthTracker(tracker: HealthTracker) {
18+
healthTracker = tracker
19+
}
20+
21+
public fun setHandler(handler: Handler) {
22+
this.handler = handler
23+
}
24+
25+
public fun setHandlerRunning(handlerRunning: Boolean) {
26+
isHandlerRunning = handlerRunning
27+
}
28+
29+
public fun setTrackerEventListener(tracker: TrackerEventListener) {
30+
trackerEventListener = tracker
31+
}
32+
33+
public fun startTracker() {
34+
Log.i(tag, "startTracker called ")
35+
Log.d(tag, "healthTracker: $healthTracker")
36+
Log.d(tag, "trackerEventListener: $trackerEventListener")
37+
if (!isHandlerRunning) {
38+
handler.post {
39+
healthTracker.setEventListener(trackerEventListener)
40+
setHandlerRunning(true)
41+
}
42+
}
43+
}
44+
45+
public fun stopTracker() {
46+
Log.i(tag, "stopTracker called ")
47+
Log.d(tag, "healthTracker: $healthTracker")
48+
Log.d(tag, "trackerEventListener: $trackerEventListener")
49+
if (isHandlerRunning) {
50+
healthTracker.unsetEventListener()
51+
setHandlerRunning(false)
52+
53+
handler.removeCallbacksAndMessages(null)
54+
}
55+
}
56+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.example.isowatch
2+
3+
import android.content.Context
4+
import android.os.Handler
5+
import android.os.Looper
6+
import android.util.Log
7+
import com.samsung.android.service.health.tracking.ConnectionListener
8+
import com.samsung.android.service.health.tracking.HealthTrackerException
9+
import com.samsung.android.service.health.tracking.HealthTrackingService
10+
import com.samsung.android.service.health.tracking.data.HealthTrackerType
11+
12+
class ConnectionManager(observer: ConnectionObserver) {
13+
private val tag = "Connection Manager"
14+
private var connectionObserver: ConnectionObserver = observer
15+
private lateinit var healthTrackingService: HealthTrackingService
16+
private val connectionListener: ConnectionListener = object : ConnectionListener {
17+
override fun onConnectionSuccess() {
18+
Log.i(tag, "Connected")
19+
connectionObserver!!.onConnectionResult(R.string.ConnectedToHs)
20+
if (!isSpO2Available(healthTrackingService)) {
21+
Log.i(tag, "Device does not support SpO2 tracking")
22+
connectionObserver.onConnectionResult(R.string.NoSpo2Support)
23+
}
24+
if (!isHeartRateAvailable(healthTrackingService)) {
25+
Log.i(tag, "Device does not support Heart Rate tracking")
26+
connectionObserver.onConnectionResult(R.string.NoHrSupport)
27+
}
28+
}
29+
30+
override fun onConnectionEnded() {
31+
Log.i(tag, "Disconnected")
32+
}
33+
34+
override fun onConnectionFailed(e: HealthTrackerException) {
35+
connectionObserver!!.onError(e)
36+
}
37+
}
38+
39+
fun connect(context: Context?) {
40+
healthTrackingService = HealthTrackingService(connectionListener, context)
41+
healthTrackingService!!.connectService()
42+
}
43+
44+
fun disconnect() {
45+
healthTrackingService?.disconnectService()
46+
}
47+
48+
fun initSpO2(spO2Listener: SpO2Listener) {
49+
val healthTracker = healthTrackingService.getHealthTracker(HealthTrackerType.SPO2)
50+
spO2Listener.setHealthTracker(healthTracker)
51+
setHandlerForBaseListener(spO2Listener)
52+
}
53+
54+
fun initHeartRate(heartRateListener: HeartRateListener) {
55+
val healthTracker = healthTrackingService.getHealthTracker(HealthTrackerType.HEART_RATE)
56+
heartRateListener.setHealthTracker(healthTracker)
57+
setHandlerForBaseListener(heartRateListener)
58+
}
59+
60+
private fun setHandlerForBaseListener(baseListener: BaseListener) {
61+
baseListener.setHandler(Handler(Looper.getMainLooper()))
62+
}
63+
64+
private fun isSpO2Available(healthTrackingService: HealthTrackingService): Boolean {
65+
val availableTrackers = healthTrackingService.trackingCapability.supportHealthTrackerTypes
66+
return availableTrackers.contains(HealthTrackerType.SPO2)
67+
}
68+
69+
private fun isHeartRateAvailable(healthTrackingService: HealthTrackingService): Boolean {
70+
val availableTrackers = healthTrackingService.trackingCapability.supportHealthTrackerTypes
71+
return availableTrackers.contains(HealthTrackerType.HEART_RATE)
72+
}
73+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example.isowatch
2+
3+
import com.samsung.android.service.health.tracking.HealthTrackerException
4+
5+
public interface ConnectionObserver {
6+
abstract fun onConnectionResult(stringResourceId: Int)
7+
8+
abstract fun onError(e: HealthTrackerException?)
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.example.isowatch
2+
3+
class HeartRateData {
4+
var status = HeartRateStatus.HR_STATUS_NONE
5+
var hr = 0
6+
7+
internal constructor() {}
8+
internal constructor(status: Int, hr: Int) {
9+
this.status = status
10+
this.hr = hr
11+
}
12+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.example.isowatch
2+
3+
import android.util.Log
4+
import com.samsung.android.service.health.tracking.HealthTracker.TrackerError
5+
import com.samsung.android.service.health.tracking.HealthTracker.TrackerEventListener
6+
import com.samsung.android.service.health.tracking.data.DataPoint
7+
import com.samsung.android.service.health.tracking.data.ValueKey
8+
9+
class HeartRateListener internal constructor() : BaseListener() {
10+
private val tag = "Heart Rate Listener"
11+
12+
init {
13+
val trackerEventListener: TrackerEventListener = object : TrackerEventListener {
14+
override fun onDataReceived(list: List<DataPoint>) {
15+
for (dataPoint in list) {
16+
readValuesFromDataPoint(dataPoint)
17+
}
18+
}
19+
20+
override fun onFlushCompleted() {
21+
Log.i(tag, " onFlushCompleted called")
22+
}
23+
24+
override fun onError(trackerError: TrackerError) {
25+
Log.e(tag, " onError called: $trackerError")
26+
setHandlerRunning(false)
27+
if (trackerError == TrackerError.PERMISSION_ERROR) {
28+
TrackerDataNotifier.instance?.notifyError(R.string.NoPermission)
29+
}
30+
if (trackerError == TrackerError.SDK_POLICY_ERROR) {
31+
TrackerDataNotifier.instance?.notifyError(R.string.SdkPolicyError)
32+
}
33+
}
34+
}
35+
setTrackerEventListener(trackerEventListener)
36+
}
37+
38+
fun readValuesFromDataPoint(dataPoint: DataPoint) {
39+
val hrData = HeartRateData()
40+
hrData.status = dataPoint.getValue(ValueKey.HeartRateSet.STATUS)
41+
hrData.hr = dataPoint.getValue(ValueKey.HeartRateSet.HEART_RATE)
42+
TrackerDataNotifier.instance?.notifyHeartRateTrackerObservers(hrData)
43+
Log.d(tag, dataPoint.toString())
44+
}
45+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.example.isowatch
2+
3+
object HeartRateStatus {
4+
const val HR_STATUS_NONE = 0
5+
const val HR_STATUS_FIND_HR = 1
6+
const val HR_STATUS_ATTACHED = -1
7+
const val HR_STATUS_DETECT_MOVE = -2
8+
const val HR_STATUS_DETACHED = -3
9+
const val HR_STATUS_LOW_RELIABILITY = -8
10+
const val HR_STATUS_VERY_LOW_RELIABILITY = -10
11+
const val HR_STATUS_NO_DATA_FLUSH = -99
12+
}

0 commit comments

Comments
 (0)