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
14 changes: 13 additions & 1 deletion packages/health/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ version '1.2'

buildscript {
ext.kotlin_version = '1.9.22'
ext.objectboxVersion = "4.0.3"
repositories {
google()
mavenCentral()
Expand All @@ -11,6 +12,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath("io.objectbox:objectbox-gradle-plugin:$objectboxVersion")
}
}

Expand All @@ -22,7 +24,10 @@ rootProject.allprojects {
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: "kotlin-android"
apply plugin: "kotlin-kapt"
apply plugin: "io.objectbox"


android {
compileSdkVersion 34
Expand All @@ -47,9 +52,16 @@ android {
lintOptions {
disable 'InvalidPackage'
}

namespace "cachet.plugins.health"
}

kapt {
arguments {
arg("objectbox.modelPath", "$projectDir/schemas/objectbox.json")
}
}

dependencies {
def composeBom = platform('androidx.compose:compose-bom:2022.10.00')
implementation(composeBom)
Expand Down
47 changes: 47 additions & 0 deletions packages/health/android/schemas/objectbox.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "1:594514540867167445",
"lastPropertyId": "4:7527286885730290448",
"name": "SensorStep",
"properties": [
{
"id": "1:998565355754980530",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:874476595718356043",
"name": "startTime",
"type": 12
},
{
"id": "3:8917239542853691220",
"name": "endTime",
"type": 12
},
{
"id": "4:7527286885730290448",
"name": "count",
"type": 8
}
],
"relations": []
}
],
"lastEntityId": "1:594514540867167445",
"lastIndexId": "0:0",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],
"retiredIndexUids": [],
"retiredPropertyUids": [],
"retiredRelationUids": [],
"version": 1
}
7 changes: 7 additions & 0 deletions packages/health/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<application>
<service android:foregroundServiceType="health" android:name=".StepCounterService" />
</application>
</manifest>
7,836 changes: 4,026 additions & 3,810 deletions packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cachet.plugins.health

import io.objectbox.annotation.DatabaseType
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import io.objectbox.annotation.Type

@Entity
class SensorStep {
@Id
var id: Long = 0
@Type(DatabaseType.DateNano)
var startTime: Long? = 0
@Type(DatabaseType.DateNano)
var endTime: Long? = 0
var count: Double = 0.0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package cachet.plugins.health

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Build
import android.os.IBinder
import android.os.SystemClock
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import io.objectbox.BoxStore


class StepCounterService : Service(), SensorEventListener {

private lateinit var sensorManager: SensorManager
private lateinit var sensor: Sensor

private var previousStepCount = 0
private val maxStepsThreshold = Int.MAX_VALUE / 2

companion object {
lateinit var box: BoxStore;
const val LOG = "StepCounterService"
const val SENSOR_NAME = Sensor.TYPE_STEP_COUNTER

const val CHANNEL_ID = "StepCounterServiceChannel"
const val CHANNEL_NAME = "Step Counter"
const val NOTIFICATION_ID = 1

fun initiated(): Boolean {
return this::box.isInitialized;
}
}

override fun onBind(p0: Intent?): IBinder? {
return null
}

override fun onCreate() {
super.onCreate()
if (!initiated()) {
box = MyObjectBox.builder()
.androidContext(applicationContext)
.build();
}

sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(SENSOR_NAME)!!
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForegroundService()

sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME)

return START_STICKY
}

private fun createNotificationChannel() {
val notificationChannel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT)
val notificationManager = applicationContext.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(notificationChannel)
}

private fun startForegroundService() {
createNotificationChannel()

ServiceCompat.startForeground(
this,
NOTIFICATION_ID,
NotificationCompat.Builder(this, CHANNEL_ID).build(),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_HEALTH
} else {
0
}
)
}

override fun onDestroy() {
super.onDestroy()
sensorManager.unregisterListener(this)
}

override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type == SENSOR_NAME) {
val steps = event.values[0].toInt()

if (previousStepCount == 0) {
previousStepCount = steps
}

if (steps < previousStepCount && previousStepCount > maxStepsThreshold) {
Log.d(LOG, "Overflow detected. Resetting previousStepCount.")
previousStepCount = steps // Reset the previous count to avoid overflow issues
return
}

val newSteps = steps - previousStepCount
previousStepCount = steps


val eventHappened =
System.currentTimeMillis() + ((event.timestamp - SystemClock.elapsedRealtimeNanos()) / 1000000L)

if (newSteps > 0) {
val step = SensorStep().apply {
startTime = eventHappened
endTime = System.currentTimeMillis()
count = newSteps.toDouble()
}

box.boxFor(SensorStep::class.java).put(step)
Log.d(
LOG,
"Steps: $newSteps at time: $eventHappened}-${System.currentTimeMillis()}"
)
}
}
}

override fun onAccuracyChanged(p0: Sensor?, p1: Int) {
Log.d(LOG, "onAccuracyChanged")
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/health/ios/Classes/SwiftHealthPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin {
samplesCategory = samplesCategory.filter { $0.value == 2 }
}
if dataTypeKey == self.SLEEP_ASLEEP {
samplesCategory = samplesCategory.filter { $0.value == 3 }
samplesCategory = samplesCategory.filter { $0.value == 3 || $0.value == 1 }
}
if dataTypeKey == self.SLEEP_DEEP {
samplesCategory = samplesCategory.filter { $0.value == 4 }
Expand Down
1 change: 1 addition & 0 deletions packages/health/lib/health.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions packages/health/lib/src/health_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,41 @@ class Health {
return <HealthDataPoint>[];
}

Future<bool?> doseStepSensorIsAvailable() async {
final fetchedDataPoints =
await _channel.invokeMethod<bool?>('doseStepSensorIsAvailable');

return fetchedDataPoints;
}

Future<bool?> startStepSensorBackgroundService() async {
final fetchedDataPoints =
await _channel.invokeMethod<bool?>('startStepSensorBackgroundService');

return fetchedDataPoints;
}

Future<bool?> stopStepSensorBackgroundService() async {
final fetchedDataPoints =
await _channel.invokeMethod<bool?>('stopStepSensorBackgroundService');

return fetchedDataPoints;
}

Future<bool?> isStepSensorRunning() async {
final fetchedDataPoints =
await _channel.invokeMethod<bool?>('isStepSensorRunning');

return fetchedDataPoints;
}

Future<bool?> clearStepSensorData() async {
final clearStepSensorData =
await _channel.invokeMethod<bool?>('clearStepSensorData');

return clearStepSensorData;
}

/// function for fetching statistic health data
Future<List<HealthDataPoint>> _dataAggregateQuery(
DateTime startDate,
Expand Down
Loading