diff --git a/packages/health/android/build.gradle b/packages/health/android/build.gradle
index ee0b26c7f..cd5e3e2aa 100644
--- a/packages/health/android/build.gradle
+++ b/packages/health/android/build.gradle
@@ -3,6 +3,7 @@ version '1.2'
buildscript {
ext.kotlin_version = '1.9.22'
+ ext.objectboxVersion = "4.0.3"
repositories {
google()
mavenCentral()
@@ -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")
}
}
@@ -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
@@ -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)
diff --git a/packages/health/android/schemas/objectbox.json b/packages/health/android/schemas/objectbox.json
new file mode 100644
index 000000000..4934d20cf
--- /dev/null
+++ b/packages/health/android/schemas/objectbox.json
@@ -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
+}
\ No newline at end of file
diff --git a/packages/health/android/src/main/AndroidManifest.xml b/packages/health/android/src/main/AndroidManifest.xml
index a2f47b605..79b8699ff 100644
--- a/packages/health/android/src/main/AndroidManifest.xml
+++ b/packages/health/android/src/main/AndroidManifest.xml
@@ -1,2 +1,9 @@
+
+
+
+
+
+
+
diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt
index 323c157c4..a99d69ea0 100644
--- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt
+++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt
@@ -1,16 +1,23 @@
package cachet.plugins.health
+import android.Manifest
import android.app.Activity
+import android.app.ActivityManager
+import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.hardware.Sensor
+import android.hardware.SensorManager
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.NonNull
+import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.PermissionController
@@ -64,3995 +71,4204 @@ const val MMOLL_2_MGDL = 18.0 // 1 mmoll= 18 mgdl
const val MIN_SUPPORTED_SDK = Build.VERSION_CODES.O_MR1
class HealthPlugin(private var channel: MethodChannel? = null) :
- MethodCallHandler, ActivityResultListener, Result, ActivityAware, FlutterPlugin {
- private var mResult: Result? = null
- private var handler: Handler? = null
- private var activity: Activity? = null
- private var context: Context? = null
- private var threadPoolExecutor: ExecutorService? = null
- private var useHealthConnectIfAvailable: Boolean = false
- private var healthConnectRequestPermissionsLauncher: ActivityResultLauncher>? =
- null
- private lateinit var healthConnectClient: HealthConnectClient
- private lateinit var scope: CoroutineScope
-
- private var BODY_FAT_PERCENTAGE = "BODY_FAT_PERCENTAGE"
- private var HEIGHT = "HEIGHT"
- private var WEIGHT = "WEIGHT"
- private var STEPS = "STEPS"
- private var AGGREGATE_STEP_COUNT = "AGGREGATE_STEP_COUNT"
- private var ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED"
- private var HEART_RATE = "HEART_RATE"
- private var BODY_TEMPERATURE = "BODY_TEMPERATURE"
- private var BODY_WATER_MASS = "BODY_WATER_MASS"
- private var BLOOD_PRESSURE_SYSTOLIC = "BLOOD_PRESSURE_SYSTOLIC"
- private var BLOOD_PRESSURE_DIASTOLIC = "BLOOD_PRESSURE_DIASTOLIC"
- private var BLOOD_OXYGEN = "BLOOD_OXYGEN"
- private var BLOOD_GLUCOSE = "BLOOD_GLUCOSE"
- private var MOVE_MINUTES = "MOVE_MINUTES"
- private var DISTANCE_DELTA = "DISTANCE_DELTA"
- private var WATER = "WATER"
- private var RESTING_HEART_RATE = "RESTING_HEART_RATE"
- private var BASAL_ENERGY_BURNED = "BASAL_ENERGY_BURNED"
- private var FLIGHTS_CLIMBED = "FLIGHTS_CLIMBED"
- private var RESPIRATORY_RATE = "RESPIRATORY_RATE"
-
- // TODO support unknown?
- private var SLEEP_ASLEEP = "SLEEP_ASLEEP"
- private var SLEEP_AWAKE = "SLEEP_AWAKE"
- private var SLEEP_IN_BED = "SLEEP_IN_BED"
- private var SLEEP_SESSION = "SLEEP_SESSION"
- private var SLEEP_LIGHT = "SLEEP_LIGHT"
- private var SLEEP_DEEP = "SLEEP_DEEP"
- private var SLEEP_REM = "SLEEP_REM"
- private var SLEEP_OUT_OF_BED = "SLEEP_OUT_OF_BED"
- private var WORKOUT = "WORKOUT"
- private var NUTRITION = "NUTRITION"
- private var BREAKFAST = "BREAKFAST"
- private var LUNCH = "LUNCH"
- private var DINNER = "DINNER"
- private var SNACK = "SNACK"
- private var MEAL_UNKNOWN = "UNKNOWN"
-
- private var TOTAL_CALORIES_BURNED = "TOTAL_CALORIES_BURNED"
-
- val workoutTypeMap =
- mapOf(
- "AEROBICS" to FitnessActivities.AEROBICS,
- "AMERICAN_FOOTBALL" to FitnessActivities.FOOTBALL_AMERICAN,
- "ARCHERY" to FitnessActivities.ARCHERY,
- "AUSTRALIAN_FOOTBALL" to
- FitnessActivities.FOOTBALL_AUSTRALIAN,
- "BADMINTON" to FitnessActivities.BADMINTON,
- "BASEBALL" to FitnessActivities.BASEBALL,
- "BASKETBALL" to FitnessActivities.BASKETBALL,
- "BIATHLON" to FitnessActivities.BIATHLON,
- "BIKING" to FitnessActivities.BIKING,
- "BIKING_HAND" to FitnessActivities.BIKING_HAND,
- "BIKING_MOUNTAIN" to FitnessActivities.BIKING_MOUNTAIN,
- "BIKING_ROAD" to FitnessActivities.BIKING_ROAD,
- "BIKING_SPINNING" to FitnessActivities.BIKING_SPINNING,
- "BIKING_STATIONARY" to FitnessActivities.BIKING_STATIONARY,
- "BIKING_UTILITY" to FitnessActivities.BIKING_UTILITY,
- "BOXING" to FitnessActivities.BOXING,
- "CALISTHENICS" to FitnessActivities.CALISTHENICS,
- "CIRCUIT_TRAINING" to FitnessActivities.CIRCUIT_TRAINING,
- "CRICKET" to FitnessActivities.CRICKET,
- "CROSS_COUNTRY_SKIING" to
- FitnessActivities.SKIING_CROSS_COUNTRY,
- "CROSS_FIT" to FitnessActivities.CROSSFIT,
- "CURLING" to FitnessActivities.CURLING,
- "DANCING" to FitnessActivities.DANCING,
- "DIVING" to FitnessActivities.DIVING,
- "DOWNHILL_SKIING" to FitnessActivities.SKIING_DOWNHILL,
- "ELEVATOR" to FitnessActivities.ELEVATOR,
- "ELLIPTICAL" to FitnessActivities.ELLIPTICAL,
- "ERGOMETER" to FitnessActivities.ERGOMETER,
- "ESCALATOR" to FitnessActivities.ESCALATOR,
- "FENCING" to FitnessActivities.FENCING,
- "FRISBEE_DISC" to FitnessActivities.FRISBEE_DISC,
- "GARDENING" to FitnessActivities.GARDENING,
- "GOLF" to FitnessActivities.GOLF,
- "GUIDED_BREATHING" to FitnessActivities.GUIDED_BREATHING,
- "GYMNASTICS" to FitnessActivities.GYMNASTICS,
- "HANDBALL" to FitnessActivities.HANDBALL,
- "HIGH_INTENSITY_INTERVAL_TRAINING" to
- FitnessActivities
- .HIGH_INTENSITY_INTERVAL_TRAINING,
- "HIKING" to FitnessActivities.HIKING,
- "HOCKEY" to FitnessActivities.HOCKEY,
- "HORSEBACK_RIDING" to FitnessActivities.HORSEBACK_RIDING,
- "HOUSEWORK" to FitnessActivities.HOUSEWORK,
- "IN_VEHICLE" to FitnessActivities.IN_VEHICLE,
- "ICE_SKATING" to FitnessActivities.ICE_SKATING,
- "INTERVAL_TRAINING" to FitnessActivities.INTERVAL_TRAINING,
- "JUMP_ROPE" to FitnessActivities.JUMP_ROPE,
- "KAYAKING" to FitnessActivities.KAYAKING,
- "KETTLEBELL_TRAINING" to
- FitnessActivities.KETTLEBELL_TRAINING,
- "KICK_SCOOTER" to FitnessActivities.KICK_SCOOTER,
- "KICKBOXING" to FitnessActivities.KICKBOXING,
- "KITE_SURFING" to FitnessActivities.KITESURFING,
- "MARTIAL_ARTS" to FitnessActivities.MARTIAL_ARTS,
- "MEDITATION" to FitnessActivities.MEDITATION,
- "MIXED_MARTIAL_ARTS" to
- FitnessActivities.MIXED_MARTIAL_ARTS,
- "P90X" to FitnessActivities.P90X,
- "PARAGLIDING" to FitnessActivities.PARAGLIDING,
- "PILATES" to FitnessActivities.PILATES,
- "POLO" to FitnessActivities.POLO,
- "RACQUETBALL" to FitnessActivities.RACQUETBALL,
- "ROCK_CLIMBING" to FitnessActivities.ROCK_CLIMBING,
- "ROWING" to FitnessActivities.ROWING,
- "ROWING_MACHINE" to FitnessActivities.ROWING_MACHINE,
- "RUGBY" to FitnessActivities.RUGBY,
- "RUNNING_JOGGING" to FitnessActivities.RUNNING_JOGGING,
- "RUNNING_SAND" to FitnessActivities.RUNNING_SAND,
- "RUNNING_TREADMILL" to FitnessActivities.RUNNING_TREADMILL,
- "RUNNING" to FitnessActivities.RUNNING,
- "SAILING" to FitnessActivities.SAILING,
- "SCUBA_DIVING" to FitnessActivities.SCUBA_DIVING,
- "SKATING_CROSS" to FitnessActivities.SKATING_CROSS,
- "SKATING_INDOOR" to FitnessActivities.SKATING_INDOOR,
- "SKATING_INLINE" to FitnessActivities.SKATING_INLINE,
- "SKATING" to FitnessActivities.SKATING,
- "SKIING" to FitnessActivities.SKIING,
- "SKIING_BACK_COUNTRY" to
- FitnessActivities.SKIING_BACK_COUNTRY,
- "SKIING_KITE" to FitnessActivities.SKIING_KITE,
- "SKIING_ROLLER" to FitnessActivities.SKIING_ROLLER,
- "SLEDDING" to FitnessActivities.SLEDDING,
- "SNOWBOARDING" to FitnessActivities.SNOWBOARDING,
- "SNOWMOBILE" to FitnessActivities.SNOWMOBILE,
- "SNOWSHOEING" to FitnessActivities.SNOWSHOEING,
- "SOCCER" to FitnessActivities.FOOTBALL_SOCCER,
- "SOFTBALL" to FitnessActivities.SOFTBALL,
- "SQUASH" to FitnessActivities.SQUASH,
- "STAIR_CLIMBING_MACHINE" to
- FitnessActivities.STAIR_CLIMBING_MACHINE,
- "STAIR_CLIMBING" to FitnessActivities.STAIR_CLIMBING,
- "STANDUP_PADDLEBOARDING" to
- FitnessActivities.STANDUP_PADDLEBOARDING,
- "STILL" to FitnessActivities.STILL,
- "STRENGTH_TRAINING" to FitnessActivities.STRENGTH_TRAINING,
- "SURFING" to FitnessActivities.SURFING,
- "SWIMMING_OPEN_WATER" to
- FitnessActivities.SWIMMING_OPEN_WATER,
- "SWIMMING_POOL" to FitnessActivities.SWIMMING_POOL,
- "SWIMMING" to FitnessActivities.SWIMMING,
- "TABLE_TENNIS" to FitnessActivities.TABLE_TENNIS,
- "TEAM_SPORTS" to FitnessActivities.TEAM_SPORTS,
- "TENNIS" to FitnessActivities.TENNIS,
- "TILTING" to FitnessActivities.TILTING,
- "VOLLEYBALL_BEACH" to FitnessActivities.VOLLEYBALL_BEACH,
- "VOLLEYBALL_INDOOR" to FitnessActivities.VOLLEYBALL_INDOOR,
- "VOLLEYBALL" to FitnessActivities.VOLLEYBALL,
- "WAKEBOARDING" to FitnessActivities.WAKEBOARDING,
- "WALKING_FITNESS" to FitnessActivities.WALKING_FITNESS,
- "WALKING_PACED" to FitnessActivities.WALKING_PACED,
- "WALKING_NORDIC" to FitnessActivities.WALKING_NORDIC,
- "WALKING_STROLLER" to FitnessActivities.WALKING_STROLLER,
- "WALKING_TREADMILL" to FitnessActivities.WALKING_TREADMILL,
- "WALKING" to FitnessActivities.WALKING,
- "WATER_POLO" to FitnessActivities.WATER_POLO,
- "WEIGHTLIFTING" to FitnessActivities.WEIGHTLIFTING,
- "WHEELCHAIR" to FitnessActivities.WHEELCHAIR,
- "WINDSURFING" to FitnessActivities.WINDSURFING,
- "YOGA" to FitnessActivities.YOGA,
- "ZUMBA" to FitnessActivities.ZUMBA,
- "OTHER" to FitnessActivities.OTHER,
- )
+ MethodCallHandler, ActivityResultListener, Result, ActivityAware, FlutterPlugin {
+ private var mResult: Result? = null
+ private var handler: Handler? = null
+ private var activity: Activity? = null
+ private var context: Context? = null
+ private var threadPoolExecutor: ExecutorService? = null
+ private var useHealthConnectIfAvailable: Boolean = false
+ private var healthConnectRequestPermissionsLauncher: ActivityResultLauncher>? =
+ null
+ private var activityRecognitionPermissionLauncher: ActivityResultLauncher>? =
+ null
+ private lateinit var healthConnectClient: HealthConnectClient
+ private lateinit var scope: CoroutineScope
+
+ private var BODY_FAT_PERCENTAGE = "BODY_FAT_PERCENTAGE"
+ private var HEIGHT = "HEIGHT"
+ private var WEIGHT = "WEIGHT"
+ private var STEPS = "STEPS"
+ private var AGGREGATE_STEP_COUNT = "AGGREGATE_STEP_COUNT"
+ private var ACTIVE_ENERGY_BURNED = "ACTIVE_ENERGY_BURNED"
+ private var HEART_RATE = "HEART_RATE"
+ private var BODY_TEMPERATURE = "BODY_TEMPERATURE"
+ private var BODY_WATER_MASS = "BODY_WATER_MASS"
+ private var BLOOD_PRESSURE_SYSTOLIC = "BLOOD_PRESSURE_SYSTOLIC"
+ private var BLOOD_PRESSURE_DIASTOLIC = "BLOOD_PRESSURE_DIASTOLIC"
+ private var BLOOD_OXYGEN = "BLOOD_OXYGEN"
+ private var BLOOD_GLUCOSE = "BLOOD_GLUCOSE"
+ private var MOVE_MINUTES = "MOVE_MINUTES"
+ private var DISTANCE_DELTA = "DISTANCE_DELTA"
+ private var WATER = "WATER"
+ private var RESTING_HEART_RATE = "RESTING_HEART_RATE"
+ private var BASAL_ENERGY_BURNED = "BASAL_ENERGY_BURNED"
+ private var FLIGHTS_CLIMBED = "FLIGHTS_CLIMBED"
+ private var RESPIRATORY_RATE = "RESPIRATORY_RATE"
+
+ // TODO support unknown?
+ private var SLEEP_ASLEEP = "SLEEP_ASLEEP"
+ private var SLEEP_AWAKE = "SLEEP_AWAKE"
+ private var SLEEP_IN_BED = "SLEEP_IN_BED"
+ private var SLEEP_SESSION = "SLEEP_SESSION"
+ private var SLEEP_LIGHT = "SLEEP_LIGHT"
+ private var SLEEP_DEEP = "SLEEP_DEEP"
+ private var SLEEP_REM = "SLEEP_REM"
+ private var SLEEP_OUT_OF_BED = "SLEEP_OUT_OF_BED"
+ private var WORKOUT = "WORKOUT"
+ private var NUTRITION = "NUTRITION"
+ private var BREAKFAST = "BREAKFAST"
+ private var LUNCH = "LUNCH"
+ private var DINNER = "DINNER"
+ private var SNACK = "SNACK"
+ private var MEAL_UNKNOWN = "UNKNOWN"
+
+ private var TOTAL_CALORIES_BURNED = "TOTAL_CALORIES_BURNED"
+
+ val workoutTypeMap =
+ mapOf(
+ "AEROBICS" to FitnessActivities.AEROBICS,
+ "AMERICAN_FOOTBALL" to FitnessActivities.FOOTBALL_AMERICAN,
+ "ARCHERY" to FitnessActivities.ARCHERY,
+ "AUSTRALIAN_FOOTBALL" to
+ FitnessActivities.FOOTBALL_AUSTRALIAN,
+ "BADMINTON" to FitnessActivities.BADMINTON,
+ "BASEBALL" to FitnessActivities.BASEBALL,
+ "BASKETBALL" to FitnessActivities.BASKETBALL,
+ "BIATHLON" to FitnessActivities.BIATHLON,
+ "BIKING" to FitnessActivities.BIKING,
+ "BIKING_HAND" to FitnessActivities.BIKING_HAND,
+ "BIKING_MOUNTAIN" to FitnessActivities.BIKING_MOUNTAIN,
+ "BIKING_ROAD" to FitnessActivities.BIKING_ROAD,
+ "BIKING_SPINNING" to FitnessActivities.BIKING_SPINNING,
+ "BIKING_STATIONARY" to FitnessActivities.BIKING_STATIONARY,
+ "BIKING_UTILITY" to FitnessActivities.BIKING_UTILITY,
+ "BOXING" to FitnessActivities.BOXING,
+ "CALISTHENICS" to FitnessActivities.CALISTHENICS,
+ "CIRCUIT_TRAINING" to FitnessActivities.CIRCUIT_TRAINING,
+ "CRICKET" to FitnessActivities.CRICKET,
+ "CROSS_COUNTRY_SKIING" to
+ FitnessActivities.SKIING_CROSS_COUNTRY,
+ "CROSS_FIT" to FitnessActivities.CROSSFIT,
+ "CURLING" to FitnessActivities.CURLING,
+ "DANCING" to FitnessActivities.DANCING,
+ "DIVING" to FitnessActivities.DIVING,
+ "DOWNHILL_SKIING" to FitnessActivities.SKIING_DOWNHILL,
+ "ELEVATOR" to FitnessActivities.ELEVATOR,
+ "ELLIPTICAL" to FitnessActivities.ELLIPTICAL,
+ "ERGOMETER" to FitnessActivities.ERGOMETER,
+ "ESCALATOR" to FitnessActivities.ESCALATOR,
+ "FENCING" to FitnessActivities.FENCING,
+ "FRISBEE_DISC" to FitnessActivities.FRISBEE_DISC,
+ "GARDENING" to FitnessActivities.GARDENING,
+ "GOLF" to FitnessActivities.GOLF,
+ "GUIDED_BREATHING" to FitnessActivities.GUIDED_BREATHING,
+ "GYMNASTICS" to FitnessActivities.GYMNASTICS,
+ "HANDBALL" to FitnessActivities.HANDBALL,
+ "HIGH_INTENSITY_INTERVAL_TRAINING" to
+ FitnessActivities
+ .HIGH_INTENSITY_INTERVAL_TRAINING,
+ "HIKING" to FitnessActivities.HIKING,
+ "HOCKEY" to FitnessActivities.HOCKEY,
+ "HORSEBACK_RIDING" to FitnessActivities.HORSEBACK_RIDING,
+ "HOUSEWORK" to FitnessActivities.HOUSEWORK,
+ "IN_VEHICLE" to FitnessActivities.IN_VEHICLE,
+ "ICE_SKATING" to FitnessActivities.ICE_SKATING,
+ "INTERVAL_TRAINING" to FitnessActivities.INTERVAL_TRAINING,
+ "JUMP_ROPE" to FitnessActivities.JUMP_ROPE,
+ "KAYAKING" to FitnessActivities.KAYAKING,
+ "KETTLEBELL_TRAINING" to
+ FitnessActivities.KETTLEBELL_TRAINING,
+ "KICK_SCOOTER" to FitnessActivities.KICK_SCOOTER,
+ "KICKBOXING" to FitnessActivities.KICKBOXING,
+ "KITE_SURFING" to FitnessActivities.KITESURFING,
+ "MARTIAL_ARTS" to FitnessActivities.MARTIAL_ARTS,
+ "MEDITATION" to FitnessActivities.MEDITATION,
+ "MIXED_MARTIAL_ARTS" to
+ FitnessActivities.MIXED_MARTIAL_ARTS,
+ "P90X" to FitnessActivities.P90X,
+ "PARAGLIDING" to FitnessActivities.PARAGLIDING,
+ "PILATES" to FitnessActivities.PILATES,
+ "POLO" to FitnessActivities.POLO,
+ "RACQUETBALL" to FitnessActivities.RACQUETBALL,
+ "ROCK_CLIMBING" to FitnessActivities.ROCK_CLIMBING,
+ "ROWING" to FitnessActivities.ROWING,
+ "ROWING_MACHINE" to FitnessActivities.ROWING_MACHINE,
+ "RUGBY" to FitnessActivities.RUGBY,
+ "RUNNING_JOGGING" to FitnessActivities.RUNNING_JOGGING,
+ "RUNNING_SAND" to FitnessActivities.RUNNING_SAND,
+ "RUNNING_TREADMILL" to FitnessActivities.RUNNING_TREADMILL,
+ "RUNNING" to FitnessActivities.RUNNING,
+ "SAILING" to FitnessActivities.SAILING,
+ "SCUBA_DIVING" to FitnessActivities.SCUBA_DIVING,
+ "SKATING_CROSS" to FitnessActivities.SKATING_CROSS,
+ "SKATING_INDOOR" to FitnessActivities.SKATING_INDOOR,
+ "SKATING_INLINE" to FitnessActivities.SKATING_INLINE,
+ "SKATING" to FitnessActivities.SKATING,
+ "SKIING" to FitnessActivities.SKIING,
+ "SKIING_BACK_COUNTRY" to
+ FitnessActivities.SKIING_BACK_COUNTRY,
+ "SKIING_KITE" to FitnessActivities.SKIING_KITE,
+ "SKIING_ROLLER" to FitnessActivities.SKIING_ROLLER,
+ "SLEDDING" to FitnessActivities.SLEDDING,
+ "SNOWBOARDING" to FitnessActivities.SNOWBOARDING,
+ "SNOWMOBILE" to FitnessActivities.SNOWMOBILE,
+ "SNOWSHOEING" to FitnessActivities.SNOWSHOEING,
+ "SOCCER" to FitnessActivities.FOOTBALL_SOCCER,
+ "SOFTBALL" to FitnessActivities.SOFTBALL,
+ "SQUASH" to FitnessActivities.SQUASH,
+ "STAIR_CLIMBING_MACHINE" to
+ FitnessActivities.STAIR_CLIMBING_MACHINE,
+ "STAIR_CLIMBING" to FitnessActivities.STAIR_CLIMBING,
+ "STANDUP_PADDLEBOARDING" to
+ FitnessActivities.STANDUP_PADDLEBOARDING,
+ "STILL" to FitnessActivities.STILL,
+ "STRENGTH_TRAINING" to FitnessActivities.STRENGTH_TRAINING,
+ "SURFING" to FitnessActivities.SURFING,
+ "SWIMMING_OPEN_WATER" to
+ FitnessActivities.SWIMMING_OPEN_WATER,
+ "SWIMMING_POOL" to FitnessActivities.SWIMMING_POOL,
+ "SWIMMING" to FitnessActivities.SWIMMING,
+ "TABLE_TENNIS" to FitnessActivities.TABLE_TENNIS,
+ "TEAM_SPORTS" to FitnessActivities.TEAM_SPORTS,
+ "TENNIS" to FitnessActivities.TENNIS,
+ "TILTING" to FitnessActivities.TILTING,
+ "VOLLEYBALL_BEACH" to FitnessActivities.VOLLEYBALL_BEACH,
+ "VOLLEYBALL_INDOOR" to FitnessActivities.VOLLEYBALL_INDOOR,
+ "VOLLEYBALL" to FitnessActivities.VOLLEYBALL,
+ "WAKEBOARDING" to FitnessActivities.WAKEBOARDING,
+ "WALKING_FITNESS" to FitnessActivities.WALKING_FITNESS,
+ "WALKING_PACED" to FitnessActivities.WALKING_PACED,
+ "WALKING_NORDIC" to FitnessActivities.WALKING_NORDIC,
+ "WALKING_STROLLER" to FitnessActivities.WALKING_STROLLER,
+ "WALKING_TREADMILL" to FitnessActivities.WALKING_TREADMILL,
+ "WALKING" to FitnessActivities.WALKING,
+ "WATER_POLO" to FitnessActivities.WATER_POLO,
+ "WEIGHTLIFTING" to FitnessActivities.WEIGHTLIFTING,
+ "WHEELCHAIR" to FitnessActivities.WHEELCHAIR,
+ "WINDSURFING" to FitnessActivities.WINDSURFING,
+ "YOGA" to FitnessActivities.YOGA,
+ "ZUMBA" to FitnessActivities.ZUMBA,
+ "OTHER" to FitnessActivities.OTHER,
+ )
+
+ // TODO: Update with new workout types when Health Connect becomes the standard.
+ val workoutTypeMapHealthConnect =
+ mapOf(
+ // "AEROBICS" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_AEROBICS,
+ "AMERICAN_FOOTBALL" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_FOOTBALL_AMERICAN,
+ // "ARCHERY" to ExerciseSessionRecord.EXERCISE_TYPE_ARCHERY,
+ "AUSTRALIAN_FOOTBALL" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_FOOTBALL_AUSTRALIAN,
+ "BADMINTON" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_BADMINTON,
+ "BASEBALL" to ExerciseSessionRecord.EXERCISE_TYPE_BASEBALL,
+ "BASKETBALL" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_BASKETBALL,
+ // "BIATHLON" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_BIATHLON,
+ "BIKING" to ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
+ // "BIKING_HAND" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_HAND,
+ // "BIKING_MOUNTAIN" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_MOUNTAIN,
+ // "BIKING_ROAD" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_ROAD,
+ // "BIKING_SPINNING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_SPINNING,
+ // "BIKING_STATIONARY" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_STATIONARY,
+ // "BIKING_UTILITY" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_UTILITY,
+ "BOXING" to ExerciseSessionRecord.EXERCISE_TYPE_BOXING,
+ "CALISTHENICS" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_CALISTHENICS,
+ // "CIRCUIT_TRAINING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_CIRCUIT_TRAINING,
+ "CRICKET" to ExerciseSessionRecord.EXERCISE_TYPE_CRICKET,
+ // "CROSS_COUNTRY_SKIING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_CROSS_COUNTRY,
+ // "CROSS_FIT" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_CROSSFIT,
+ // "CURLING" to ExerciseSessionRecord.EXERCISE_TYPE_CURLING,
+ "DANCING" to ExerciseSessionRecord.EXERCISE_TYPE_DANCING,
+ // "DIVING" to ExerciseSessionRecord.EXERCISE_TYPE_DIVING,
+ // "DOWNHILL_SKIING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_DOWNHILL,
+ // "ELEVATOR" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_ELEVATOR,
+ "ELLIPTICAL" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_ELLIPTICAL,
+ // "ERGOMETER" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_ERGOMETER,
+ // "ESCALATOR" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_ESCALATOR,
+ "FENCING" to ExerciseSessionRecord.EXERCISE_TYPE_FENCING,
+ "FRISBEE_DISC" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_FRISBEE_DISC,
+ // "GARDENING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_GARDENING,
+ "GOLF" to ExerciseSessionRecord.EXERCISE_TYPE_GOLF,
+ "GUIDED_BREATHING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_GUIDED_BREATHING,
+ "GYMNASTICS" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_GYMNASTICS,
+ "HANDBALL" to ExerciseSessionRecord.EXERCISE_TYPE_HANDBALL,
+ "HIGH_INTENSITY_INTERVAL_TRAINING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING,
+ "HIKING" to ExerciseSessionRecord.EXERCISE_TYPE_HIKING,
+ // "HOCKEY" to ExerciseSessionRecord.EXERCISE_TYPE_HOCKEY,
+ // "HORSEBACK_RIDING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_HORSEBACK_RIDING,
+ // "HOUSEWORK" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_HOUSEWORK,
+ // "IN_VEHICLE" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_IN_VEHICLE,
+ "ICE_SKATING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_ICE_SKATING,
+ // "INTERVAL_TRAINING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_INTERVAL_TRAINING,
+ // "JUMP_ROPE" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_JUMP_ROPE,
+ // "KAYAKING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_KAYAKING,
+ // "KETTLEBELL_TRAINING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_KETTLEBELL_TRAINING,
+ // "KICK_SCOOTER" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_KICK_SCOOTER,
+ // "KICKBOXING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_KICKBOXING,
+ // "KITE_SURFING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_KITESURFING,
+ "MARTIAL_ARTS" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_MARTIAL_ARTS,
+ // "MEDITATION" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_MEDITATION,
+ // "MIXED_MARTIAL_ARTS" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_MIXED_MARTIAL_ARTS,
+ // "P90X" to ExerciseSessionRecord.EXERCISE_TYPE_P90X,
+ "PARAGLIDING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_PARAGLIDING,
+ "PILATES" to ExerciseSessionRecord.EXERCISE_TYPE_PILATES,
+ // "POLO" to ExerciseSessionRecord.EXERCISE_TYPE_POLO,
+ "RACQUETBALL" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_RACQUETBALL,
+ "ROCK_CLIMBING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_ROCK_CLIMBING,
+ "ROWING" to ExerciseSessionRecord.EXERCISE_TYPE_ROWING,
+ "ROWING_MACHINE" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_ROWING_MACHINE,
+ "RUGBY" to ExerciseSessionRecord.EXERCISE_TYPE_RUGBY,
+ // "RUNNING_JOGGING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_RUNNING_JOGGING,
+ // "RUNNING_SAND" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_RUNNING_SAND,
+ "RUNNING_TREADMILL" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_RUNNING_TREADMILL,
+ "RUNNING" to ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
+ "SAILING" to ExerciseSessionRecord.EXERCISE_TYPE_SAILING,
+ "SCUBA_DIVING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_SCUBA_DIVING,
+ // "SKATING_CROSS" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SKATING_CROSS,
+ // "SKATING_INDOOR" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SKATING_INDOOR,
+ // "SKATING_INLINE" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SKATING_INLINE,
+ "SKATING" to ExerciseSessionRecord.EXERCISE_TYPE_SKATING,
+ "SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING,
+ // "SKIING_BACK_COUNTRY" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_BACK_COUNTRY,
+ // "SKIING_KITE" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_KITE,
+ // "SKIING_ROLLER" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_ROLLER,
+ // "SLEDDING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SLEDDING,
+ "SNOWBOARDING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_SNOWBOARDING,
+ // "SNOWMOBILE" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SNOWMOBILE,
+ "SNOWSHOEING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_SNOWSHOEING,
+ // "SOCCER" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_SOCCER,
+ "SOFTBALL" to ExerciseSessionRecord.EXERCISE_TYPE_SOFTBALL,
+ "SQUASH" to ExerciseSessionRecord.EXERCISE_TYPE_SQUASH,
+ "STAIR_CLIMBING_MACHINE" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_STAIR_CLIMBING_MACHINE,
+ "STAIR_CLIMBING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_STAIR_CLIMBING,
+ // "STANDUP_PADDLEBOARDING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_STANDUP_PADDLEBOARDING,
+ // "STILL" to ExerciseSessionRecord.EXERCISE_TYPE_STILL,
+ "STRENGTH_TRAINING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_STRENGTH_TRAINING,
+ "SURFING" to ExerciseSessionRecord.EXERCISE_TYPE_SURFING,
+ "SWIMMING_OPEN_WATER" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_SWIMMING_OPEN_WATER,
+ "SWIMMING_POOL" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_SWIMMING_POOL,
+ // "SWIMMING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING,
+ "TABLE_TENNIS" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_TABLE_TENNIS,
+ // "TEAM_SPORTS" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_TEAM_SPORTS,
+ "TENNIS" to ExerciseSessionRecord.EXERCISE_TYPE_TENNIS,
+ // "TILTING" to ExerciseSessionRecord.EXERCISE_TYPE_TILTING,
+ // "VOLLEYBALL_BEACH" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_VOLLEYBALL_BEACH,
+ // "VOLLEYBALL_INDOOR" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_VOLLEYBALL_INDOOR,
+ "VOLLEYBALL" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_VOLLEYBALL,
+ // "WAKEBOARDING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_WAKEBOARDING,
+ // "WALKING_FITNESS" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_FITNESS,
+ // "WALKING_PACED" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_PACED,
+ // "WALKING_NORDIC" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_NORDIC,
+ // "WALKING_STROLLER" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_STROLLER,
+ // "WALKING_TREADMILL" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_TREADMILL,
+ "WALKING" to ExerciseSessionRecord.EXERCISE_TYPE_WALKING,
+ "WATER_POLO" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_WATER_POLO,
+ "WEIGHTLIFTING" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_WEIGHTLIFTING,
+ "WHEELCHAIR" to
+ ExerciseSessionRecord
+ .EXERCISE_TYPE_WHEELCHAIR,
+ // "WINDSURFING" to
+ // ExerciseSessionRecord.EXERCISE_TYPE_WINDSURFING,
+ "YOGA" to ExerciseSessionRecord.EXERCISE_TYPE_YOGA,
+ // "ZUMBA" to ExerciseSessionRecord.EXERCISE_TYPE_ZUMBA,
+ // "OTHER" to ExerciseSessionRecord.EXERCISE_TYPE_OTHER,
+ )
+
+ override fun onAttachedToEngine(
+ @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
+ ) {
+ scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
+ channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
+ channel?.setMethodCallHandler(this)
+ context = flutterPluginBinding.applicationContext
+ threadPoolExecutor = Executors.newFixedThreadPool(4)
+ checkAvailability()
+ if (healthConnectAvailable) {
+ healthConnectClient =
+ HealthConnectClient.getOrCreate(
+ flutterPluginBinding.applicationContext
+ )
+ }
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ channel = null
+ activity = null
+ threadPoolExecutor!!.shutdown()
+ threadPoolExecutor = null
+ }
+
+ // This static function is optional and equivalent to onAttachedToEngine. It supports the
+ // old
+ // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
+ // plugin registration via this function while apps migrate to use the new Android APIs
+ // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
+ //
+ // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
+ // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be
+ // called
+ // depending on the user's project. onAttachedToEngine or registerWith must both be defined
+ // in the same class.
+ companion object {
+ @Suppress("unused")
+ @JvmStatic
+ fun registerWith(registrar: Registrar) {
+ val channel = MethodChannel(registrar.messenger(), CHANNEL_NAME)
+ val plugin = HealthPlugin(channel)
+ registrar.addActivityResultListener(plugin)
+ channel.setMethodCallHandler(plugin)
+ }
- // TODO: Update with new workout types when Health Connect becomes the standard.
- val workoutTypeMapHealthConnect =
- mapOf(
- // "AEROBICS" to
- // ExerciseSessionRecord.EXERCISE_TYPE_AEROBICS,
- "AMERICAN_FOOTBALL" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_FOOTBALL_AMERICAN,
- // "ARCHERY" to ExerciseSessionRecord.EXERCISE_TYPE_ARCHERY,
- "AUSTRALIAN_FOOTBALL" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_FOOTBALL_AUSTRALIAN,
- "BADMINTON" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_BADMINTON,
- "BASEBALL" to ExerciseSessionRecord.EXERCISE_TYPE_BASEBALL,
- "BASKETBALL" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_BASKETBALL,
- // "BIATHLON" to
- // ExerciseSessionRecord.EXERCISE_TYPE_BIATHLON,
- "BIKING" to ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
- // "BIKING_HAND" to
- // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_HAND,
- // "BIKING_MOUNTAIN" to
- // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_MOUNTAIN,
- // "BIKING_ROAD" to
- // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_ROAD,
- // "BIKING_SPINNING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_SPINNING,
- // "BIKING_STATIONARY" to
- // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_STATIONARY,
- // "BIKING_UTILITY" to
- // ExerciseSessionRecord.EXERCISE_TYPE_BIKING_UTILITY,
- "BOXING" to ExerciseSessionRecord.EXERCISE_TYPE_BOXING,
- "CALISTHENICS" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_CALISTHENICS,
- // "CIRCUIT_TRAINING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_CIRCUIT_TRAINING,
- "CRICKET" to ExerciseSessionRecord.EXERCISE_TYPE_CRICKET,
- // "CROSS_COUNTRY_SKIING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_CROSS_COUNTRY,
- // "CROSS_FIT" to
- // ExerciseSessionRecord.EXERCISE_TYPE_CROSSFIT,
- // "CURLING" to ExerciseSessionRecord.EXERCISE_TYPE_CURLING,
- "DANCING" to ExerciseSessionRecord.EXERCISE_TYPE_DANCING,
- // "DIVING" to ExerciseSessionRecord.EXERCISE_TYPE_DIVING,
- // "DOWNHILL_SKIING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_DOWNHILL,
- // "ELEVATOR" to
- // ExerciseSessionRecord.EXERCISE_TYPE_ELEVATOR,
- "ELLIPTICAL" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_ELLIPTICAL,
- // "ERGOMETER" to
- // ExerciseSessionRecord.EXERCISE_TYPE_ERGOMETER,
- // "ESCALATOR" to
- // ExerciseSessionRecord.EXERCISE_TYPE_ESCALATOR,
- "FENCING" to ExerciseSessionRecord.EXERCISE_TYPE_FENCING,
- "FRISBEE_DISC" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_FRISBEE_DISC,
- // "GARDENING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_GARDENING,
- "GOLF" to ExerciseSessionRecord.EXERCISE_TYPE_GOLF,
- "GUIDED_BREATHING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_GUIDED_BREATHING,
- "GYMNASTICS" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_GYMNASTICS,
- "HANDBALL" to ExerciseSessionRecord.EXERCISE_TYPE_HANDBALL,
- "HIGH_INTENSITY_INTERVAL_TRAINING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_HIGH_INTENSITY_INTERVAL_TRAINING,
- "HIKING" to ExerciseSessionRecord.EXERCISE_TYPE_HIKING,
- // "HOCKEY" to ExerciseSessionRecord.EXERCISE_TYPE_HOCKEY,
- // "HORSEBACK_RIDING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_HORSEBACK_RIDING,
- // "HOUSEWORK" to
- // ExerciseSessionRecord.EXERCISE_TYPE_HOUSEWORK,
- // "IN_VEHICLE" to
- // ExerciseSessionRecord.EXERCISE_TYPE_IN_VEHICLE,
- "ICE_SKATING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_ICE_SKATING,
- // "INTERVAL_TRAINING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_INTERVAL_TRAINING,
- // "JUMP_ROPE" to
- // ExerciseSessionRecord.EXERCISE_TYPE_JUMP_ROPE,
- // "KAYAKING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_KAYAKING,
- // "KETTLEBELL_TRAINING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_KETTLEBELL_TRAINING,
- // "KICK_SCOOTER" to
- // ExerciseSessionRecord.EXERCISE_TYPE_KICK_SCOOTER,
- // "KICKBOXING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_KICKBOXING,
- // "KITE_SURFING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_KITESURFING,
- "MARTIAL_ARTS" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_MARTIAL_ARTS,
- // "MEDITATION" to
- // ExerciseSessionRecord.EXERCISE_TYPE_MEDITATION,
- // "MIXED_MARTIAL_ARTS" to
- // ExerciseSessionRecord.EXERCISE_TYPE_MIXED_MARTIAL_ARTS,
- // "P90X" to ExerciseSessionRecord.EXERCISE_TYPE_P90X,
- "PARAGLIDING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_PARAGLIDING,
- "PILATES" to ExerciseSessionRecord.EXERCISE_TYPE_PILATES,
- // "POLO" to ExerciseSessionRecord.EXERCISE_TYPE_POLO,
- "RACQUETBALL" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_RACQUETBALL,
- "ROCK_CLIMBING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_ROCK_CLIMBING,
- "ROWING" to ExerciseSessionRecord.EXERCISE_TYPE_ROWING,
- "ROWING_MACHINE" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_ROWING_MACHINE,
- "RUGBY" to ExerciseSessionRecord.EXERCISE_TYPE_RUGBY,
- // "RUNNING_JOGGING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_RUNNING_JOGGING,
- // "RUNNING_SAND" to
- // ExerciseSessionRecord.EXERCISE_TYPE_RUNNING_SAND,
- "RUNNING_TREADMILL" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_RUNNING_TREADMILL,
- "RUNNING" to ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
- "SAILING" to ExerciseSessionRecord.EXERCISE_TYPE_SAILING,
- "SCUBA_DIVING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_SCUBA_DIVING,
- // "SKATING_CROSS" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SKATING_CROSS,
- // "SKATING_INDOOR" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SKATING_INDOOR,
- // "SKATING_INLINE" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SKATING_INLINE,
- "SKATING" to ExerciseSessionRecord.EXERCISE_TYPE_SKATING,
- "SKIING" to ExerciseSessionRecord.EXERCISE_TYPE_SKIING,
- // "SKIING_BACK_COUNTRY" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_BACK_COUNTRY,
- // "SKIING_KITE" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_KITE,
- // "SKIING_ROLLER" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SKIING_ROLLER,
- // "SLEDDING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SLEDDING,
- "SNOWBOARDING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_SNOWBOARDING,
- // "SNOWMOBILE" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SNOWMOBILE,
- "SNOWSHOEING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_SNOWSHOEING,
- // "SOCCER" to
- // ExerciseSessionRecord.EXERCISE_TYPE_FOOTBALL_SOCCER,
- "SOFTBALL" to ExerciseSessionRecord.EXERCISE_TYPE_SOFTBALL,
- "SQUASH" to ExerciseSessionRecord.EXERCISE_TYPE_SQUASH,
- "STAIR_CLIMBING_MACHINE" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_STAIR_CLIMBING_MACHINE,
- "STAIR_CLIMBING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_STAIR_CLIMBING,
- // "STANDUP_PADDLEBOARDING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_STANDUP_PADDLEBOARDING,
- // "STILL" to ExerciseSessionRecord.EXERCISE_TYPE_STILL,
- "STRENGTH_TRAINING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_STRENGTH_TRAINING,
- "SURFING" to ExerciseSessionRecord.EXERCISE_TYPE_SURFING,
- "SWIMMING_OPEN_WATER" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_SWIMMING_OPEN_WATER,
- "SWIMMING_POOL" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_SWIMMING_POOL,
- // "SWIMMING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING,
- "TABLE_TENNIS" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_TABLE_TENNIS,
- // "TEAM_SPORTS" to
- // ExerciseSessionRecord.EXERCISE_TYPE_TEAM_SPORTS,
- "TENNIS" to ExerciseSessionRecord.EXERCISE_TYPE_TENNIS,
- // "TILTING" to ExerciseSessionRecord.EXERCISE_TYPE_TILTING,
- // "VOLLEYBALL_BEACH" to
- // ExerciseSessionRecord.EXERCISE_TYPE_VOLLEYBALL_BEACH,
- // "VOLLEYBALL_INDOOR" to
- // ExerciseSessionRecord.EXERCISE_TYPE_VOLLEYBALL_INDOOR,
- "VOLLEYBALL" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_VOLLEYBALL,
- // "WAKEBOARDING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_WAKEBOARDING,
- // "WALKING_FITNESS" to
- // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_FITNESS,
- // "WALKING_PACED" to
- // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_PACED,
- // "WALKING_NORDIC" to
- // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_NORDIC,
- // "WALKING_STROLLER" to
- // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_STROLLER,
- // "WALKING_TREADMILL" to
- // ExerciseSessionRecord.EXERCISE_TYPE_WALKING_TREADMILL,
- "WALKING" to ExerciseSessionRecord.EXERCISE_TYPE_WALKING,
- "WATER_POLO" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_WATER_POLO,
- "WEIGHTLIFTING" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_WEIGHTLIFTING,
- "WHEELCHAIR" to
- ExerciseSessionRecord
- .EXERCISE_TYPE_WHEELCHAIR,
- // "WINDSURFING" to
- // ExerciseSessionRecord.EXERCISE_TYPE_WINDSURFING,
- "YOGA" to ExerciseSessionRecord.EXERCISE_TYPE_YOGA,
- // "ZUMBA" to ExerciseSessionRecord.EXERCISE_TYPE_ZUMBA,
- // "OTHER" to ExerciseSessionRecord.EXERCISE_TYPE_OTHER,
- )
+ const val PERMISSIONS_REQUEST_ACTIVITY_RECOGNITION = 10
+ }
+
+ override fun success(p0: Any?) {
+ handler?.post { mResult?.success(p0) }
+ }
+
+ override fun notImplemented() {
+ handler?.post { mResult?.notImplemented() }
+ }
+
+ override fun error(
+ errorCode: String,
+ errorMessage: String?,
+ errorDetails: Any?,
+ ) {
+ handler?.post { mResult?.error(errorCode, errorMessage, errorDetails) }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
+ if (requestCode == GOOGLE_FIT_PERMISSIONS_REQUEST_CODE) {
+ if (resultCode == Activity.RESULT_OK) {
+ Log.i("FLUTTER_HEALTH", "Access Granted!")
+ mResult?.success(true)
+ } else if (resultCode == Activity.RESULT_CANCELED) {
+ Log.i("FLUTTER_HEALTH", "Access Denied!")
+ mResult?.success(false)
+ }
+ }
+ return false
+ }
+
+ private fun onHealthConnectPermissionCallback(permissionGranted: Set) {
+ if (permissionGranted.isEmpty()) {
+ mResult?.success(false)
+ Log.i("FLUTTER_HEALTH", "Access Denied (to Health Connect)!")
+ } else {
+ mResult?.success(true)
+ Log.i("FLUTTER_HEALTH", "Access Granted (to Health Connect)!")
+ }
+ }
+
+ private fun keyToHealthDataType(type: String): DataType {
+ return when (type) {
+ BODY_FAT_PERCENTAGE -> DataType.TYPE_BODY_FAT_PERCENTAGE
+ HEIGHT -> DataType.TYPE_HEIGHT
+ WEIGHT -> DataType.TYPE_WEIGHT
+ STEPS -> DataType.TYPE_STEP_COUNT_DELTA
+ AGGREGATE_STEP_COUNT -> DataType.AGGREGATE_STEP_COUNT_DELTA
+ ACTIVE_ENERGY_BURNED -> DataType.TYPE_CALORIES_EXPENDED
+ HEART_RATE -> DataType.TYPE_HEART_RATE_BPM
+ BODY_TEMPERATURE -> HealthDataTypes.TYPE_BODY_TEMPERATURE
+ BLOOD_PRESSURE_SYSTOLIC -> HealthDataTypes.TYPE_BLOOD_PRESSURE
+ BLOOD_PRESSURE_DIASTOLIC -> HealthDataTypes.TYPE_BLOOD_PRESSURE
+ BLOOD_OXYGEN -> HealthDataTypes.TYPE_OXYGEN_SATURATION
+ BLOOD_GLUCOSE -> HealthDataTypes.TYPE_BLOOD_GLUCOSE
+ MOVE_MINUTES -> DataType.TYPE_MOVE_MINUTES
+ DISTANCE_DELTA -> DataType.TYPE_DISTANCE_DELTA
+ WATER -> DataType.TYPE_HYDRATION
+ SLEEP_ASLEEP -> DataType.TYPE_SLEEP_SEGMENT
+ SLEEP_AWAKE -> DataType.TYPE_SLEEP_SEGMENT
+ SLEEP_IN_BED -> DataType.TYPE_SLEEP_SEGMENT
+ SLEEP_LIGHT -> DataType.TYPE_SLEEP_SEGMENT
+ SLEEP_REM -> DataType.TYPE_SLEEP_SEGMENT
+ SLEEP_DEEP -> DataType.TYPE_SLEEP_SEGMENT
+ WORKOUT -> DataType.TYPE_ACTIVITY_SEGMENT
+ NUTRITION -> DataType.TYPE_NUTRITION
+ else -> throw IllegalArgumentException("Unsupported dataType: $type")
+ }
+ }
+
+ private fun getField(type: String): Field {
+ return when (type) {
+ BODY_FAT_PERCENTAGE -> Field.FIELD_PERCENTAGE
+ HEIGHT -> Field.FIELD_HEIGHT
+ WEIGHT -> Field.FIELD_WEIGHT
+ STEPS -> Field.FIELD_STEPS
+ ACTIVE_ENERGY_BURNED -> Field.FIELD_CALORIES
+ HEART_RATE -> Field.FIELD_BPM
+ BODY_TEMPERATURE -> HealthFields.FIELD_BODY_TEMPERATURE
+ BLOOD_PRESSURE_SYSTOLIC -> HealthFields.FIELD_BLOOD_PRESSURE_SYSTOLIC
+ BLOOD_PRESSURE_DIASTOLIC -> HealthFields.FIELD_BLOOD_PRESSURE_DIASTOLIC
+ BLOOD_OXYGEN -> HealthFields.FIELD_OXYGEN_SATURATION
+ BLOOD_GLUCOSE -> HealthFields.FIELD_BLOOD_GLUCOSE_LEVEL
+ MOVE_MINUTES -> Field.FIELD_DURATION
+ DISTANCE_DELTA -> Field.FIELD_DISTANCE
+ WATER -> Field.FIELD_VOLUME
+ SLEEP_ASLEEP -> Field.FIELD_SLEEP_SEGMENT_TYPE
+ SLEEP_AWAKE -> Field.FIELD_SLEEP_SEGMENT_TYPE
+ SLEEP_IN_BED -> Field.FIELD_SLEEP_SEGMENT_TYPE
+ SLEEP_LIGHT -> Field.FIELD_SLEEP_SEGMENT_TYPE
+ SLEEP_REM -> Field.FIELD_SLEEP_SEGMENT_TYPE
+ SLEEP_DEEP -> Field.FIELD_SLEEP_SEGMENT_TYPE
+ WORKOUT -> Field.FIELD_ACTIVITY
+ NUTRITION -> Field.FIELD_NUTRIENTS
+ else -> throw IllegalArgumentException("Unsupported dataType: $type")
+ }
+ }
+
+ private fun isIntField(dataSource: DataSource, unit: Field): Boolean {
+ val dataPoint = DataPoint.builder(dataSource).build()
+ val value = dataPoint.getValue(unit)
+ return value.format == Field.FORMAT_INT32
+ }
+
+ // / Extracts the (numeric) value from a Health Data Point
+ private fun getHealthDataValue(dataPoint: DataPoint, field: Field): Any {
+ val value = dataPoint.getValue(field)
+ // Conversion is needed because glucose is stored as mmoll in Google Fit;
+ // while mgdl is used for glucose in this plugin.
+ val isGlucose = field == HealthFields.FIELD_BLOOD_GLUCOSE_LEVEL
+ return when (value.format) {
+ Field.FORMAT_FLOAT ->
+ if (!isGlucose) value.asFloat()
+ else value.asFloat() * MMOLL_2_MGDL
+
+ Field.FORMAT_INT32 -> value.asInt()
+ Field.FORMAT_STRING -> value.asString()
+ else -> Log.e("Unsupported format:", value.format.toString())
+ }
+ }
- override fun onAttachedToEngine(
- @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
- ) {
- scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
- channel = MethodChannel(flutterPluginBinding.binaryMessenger, CHANNEL_NAME)
- channel?.setMethodCallHandler(this)
- context = flutterPluginBinding.applicationContext
- threadPoolExecutor = Executors.newFixedThreadPool(4)
- checkAvailability()
- if (healthConnectAvailable) {
- healthConnectClient =
- HealthConnectClient.getOrCreate(
- flutterPluginBinding.applicationContext
- )
- }
+ /** Delete records of the given type in the time range */
+ private fun delete(call: MethodCall, result: Result) {
+ if (useHealthConnectIfAvailable && healthConnectAvailable) {
+ deleteHCData(call, result)
+ return
}
+ if (context == null) {
+ result.success(false)
+ return
+ }
+
+ val type = call.argument("dataTypeKey")!!
+ val startTime = call.argument("startTime")!!
+ val endTime = call.argument("endTime")!!
+
+ // Look up data type and unit for the type key
+ val dataType = keyToHealthDataType(type)
+ val field = getField(type)
+
+ val typesBuilder = FitnessOptions.builder()
+ typesBuilder.addDataType(dataType, FitnessOptions.ACCESS_WRITE)
- override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
- channel = null
- activity = null
- threadPoolExecutor!!.shutdown()
- threadPoolExecutor = null
- }
-
- // This static function is optional and equivalent to onAttachedToEngine. It supports the
- // old
- // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
- // plugin registration via this function while apps migrate to use the new Android APIs
- // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
- //
- // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
- // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be
- // called
- // depending on the user's project. onAttachedToEngine or registerWith must both be defined
- // in the same class.
- companion object {
- @Suppress("unused")
- @JvmStatic
- fun registerWith(registrar: Registrar) {
- val channel = MethodChannel(registrar.messenger(), CHANNEL_NAME)
- val plugin = HealthPlugin(channel)
- registrar.addActivityResultListener(plugin)
- channel.setMethodCallHandler(plugin)
+ val dataSource =
+ DataDeleteRequest.Builder()
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .addDataType(dataType)
+ .deleteAllSessions()
+ .build()
+
+ val fitnessOptions = typesBuilder.build()
+
+ try {
+ val googleSignInAccount =
+ GoogleSignIn.getAccountForExtension(
+ context!!.applicationContext,
+ fitnessOptions
+ )
+ Fitness.getHistoryClient(context!!.applicationContext, googleSignInAccount)
+ .deleteData(dataSource)
+ .addOnSuccessListener {
+ Log.i(
+ "FLUTTER_HEALTH::SUCCESS",
+ "Dataset deleted successfully!"
+ )
+ result.success(true)
}
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error deleting the dataset"
+ )
+ )
+ } catch (e3: Exception) {
+ result.success(false)
}
+ }
- override fun success(p0: Any?) {
- handler?.post { mResult?.success(p0) }
+ /** Save a Blood Pressure measurement with systolic and diastolic values */
+ private fun writeBloodPressure(call: MethodCall, result: Result) {
+ if (useHealthConnectIfAvailable && healthConnectAvailable) {
+ writeBloodPressureHC(call, result)
+ return
}
-
- override fun notImplemented() {
- handler?.post { mResult?.notImplemented() }
+ if (context == null) {
+ result.success(false)
+ return
}
- override fun error(
- errorCode: String,
- errorMessage: String?,
- errorDetails: Any?,
- ) {
- handler?.post { mResult?.error(errorCode, errorMessage, errorDetails) }
- }
+ val dataType = HealthDataTypes.TYPE_BLOOD_PRESSURE
+ val systolic = call.argument("systolic")!!
+ val diastolic = call.argument("diastolic")!!
+ val startTime = call.argument("startTime")!!
+ val endTime = call.argument("endTime")!!
+
+ val typesBuilder = FitnessOptions.builder()
+ typesBuilder.addDataType(dataType, FitnessOptions.ACCESS_WRITE)
+
+ val dataSource =
+ DataSource.Builder()
+ .setDataType(dataType)
+ .setType(DataSource.TYPE_RAW)
+ .setDevice(
+ Device.getLocalDevice(
+ context!!.applicationContext
+ )
+ )
+ .setAppPackageName(context!!.applicationContext)
+ .build()
+
+ val builder =
+ DataPoint.builder(dataSource)
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .setField(
+ HealthFields.FIELD_BLOOD_PRESSURE_SYSTOLIC,
+ systolic
+ )
+ .setField(
+ HealthFields.FIELD_BLOOD_PRESSURE_DIASTOLIC,
+ diastolic
+ )
+ .build()
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
- if (requestCode == GOOGLE_FIT_PERMISSIONS_REQUEST_CODE) {
- if (resultCode == Activity.RESULT_OK) {
- Log.i("FLUTTER_HEALTH", "Access Granted!")
- mResult?.success(true)
- } else if (resultCode == Activity.RESULT_CANCELED) {
- Log.i("FLUTTER_HEALTH", "Access Denied!")
- mResult?.success(false)
- }
+ val dataPoint = builder
+ val dataSet = DataSet.builder(dataSource).add(dataPoint).build()
+
+ val fitnessOptions = typesBuilder.build()
+ try {
+ val googleSignInAccount =
+ GoogleSignIn.getAccountForExtension(
+ context!!.applicationContext,
+ fitnessOptions
+ )
+ Fitness.getHistoryClient(context!!.applicationContext, googleSignInAccount)
+ .insertData(dataSet)
+ .addOnSuccessListener {
+ Log.i(
+ "FLUTTER_HEALTH::SUCCESS",
+ "Blood Pressure added successfully!"
+ )
+ result.success(true)
}
- return false
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error adding the blood pressure data!",
+ ),
+ )
+ } catch (e3: Exception) {
+ result.success(false)
+ }
+ }
+
+ private fun writeMealHC(call: MethodCall, result: Result) {
+ val startTime = Instant.ofEpochMilli(call.argument("startTime")!!)
+ val endTime = Instant.ofEpochMilli(call.argument("endTime")!!)
+ val calories = call.argument("caloriesConsumed")
+ val carbs = call.argument("carbohydrates") as Double?
+ val protein = call.argument("protein") as Double?
+ val fat = call.argument("fatTotal") as Double?
+ val caffeine = call.argument("caffeine") as Double?
+ val name = call.argument("name")
+ val mealType = call.argument("mealType")!!
+
+ scope.launch {
+ try {
+ val list = mutableListOf()
+ list.add(
+ NutritionRecord(
+ name = name,
+ energy = calories?.kilocalories,
+ totalCarbohydrate = carbs?.grams,
+ protein = protein?.grams,
+ totalFat = fat?.grams,
+ caffeine = caffeine?.grams,
+ startTime = startTime,
+ startZoneOffset = null,
+ endTime = endTime,
+ endZoneOffset = null,
+ mealType =
+ MapMealTypeToTypeHC[
+ mealType]
+ ?: MEAL_TYPE_UNKNOWN,
+ ),
+ )
+ healthConnectClient.insertRecords(
+ list,
+ )
+ result.success(true)
+ Log.i(
+ "FLUTTER_HEALTH::SUCCESS",
+ "[Health Connect] Meal was successfully added!"
+ )
+ } catch (e: Exception) {
+ Log.w(
+ "FLUTTER_HEALTH::ERROR",
+ "[Health Connect] There was an error adding the meal",
+ )
+ Log.w("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error")
+ Log.w("FLUTTER_HEALTH::ERROR", e.stackTrace.toString())
+ result.success(false)
+ }
}
+ }
- private fun onHealthConnectPermissionCallback(permissionGranted: Set) {
- if (permissionGranted.isEmpty()) {
- mResult?.success(false)
- Log.i("FLUTTER_HEALTH", "Access Denied (to Health Connect)!")
- } else {
- mResult?.success(true)
- Log.i("FLUTTER_HEALTH", "Access Granted (to Health Connect)!")
- }
+ /** Save a Nutrition measurement with calories, carbs, protein, fat, name and mealType */
+ private fun writeMeal(call: MethodCall, result: Result) {
+ if (useHealthConnectIfAvailable && healthConnectAvailable) {
+ writeMealHC(call, result)
+ return
}
- private fun keyToHealthDataType(type: String): DataType {
- return when (type) {
- BODY_FAT_PERCENTAGE -> DataType.TYPE_BODY_FAT_PERCENTAGE
- HEIGHT -> DataType.TYPE_HEIGHT
- WEIGHT -> DataType.TYPE_WEIGHT
- STEPS -> DataType.TYPE_STEP_COUNT_DELTA
- AGGREGATE_STEP_COUNT -> DataType.AGGREGATE_STEP_COUNT_DELTA
- ACTIVE_ENERGY_BURNED -> DataType.TYPE_CALORIES_EXPENDED
- HEART_RATE -> DataType.TYPE_HEART_RATE_BPM
- BODY_TEMPERATURE -> HealthDataTypes.TYPE_BODY_TEMPERATURE
- BLOOD_PRESSURE_SYSTOLIC -> HealthDataTypes.TYPE_BLOOD_PRESSURE
- BLOOD_PRESSURE_DIASTOLIC -> HealthDataTypes.TYPE_BLOOD_PRESSURE
- BLOOD_OXYGEN -> HealthDataTypes.TYPE_OXYGEN_SATURATION
- BLOOD_GLUCOSE -> HealthDataTypes.TYPE_BLOOD_GLUCOSE
- MOVE_MINUTES -> DataType.TYPE_MOVE_MINUTES
- DISTANCE_DELTA -> DataType.TYPE_DISTANCE_DELTA
- WATER -> DataType.TYPE_HYDRATION
- SLEEP_ASLEEP -> DataType.TYPE_SLEEP_SEGMENT
- SLEEP_AWAKE -> DataType.TYPE_SLEEP_SEGMENT
- SLEEP_IN_BED -> DataType.TYPE_SLEEP_SEGMENT
- SLEEP_LIGHT -> DataType.TYPE_SLEEP_SEGMENT
- SLEEP_REM -> DataType.TYPE_SLEEP_SEGMENT
- SLEEP_DEEP -> DataType.TYPE_SLEEP_SEGMENT
- WORKOUT -> DataType.TYPE_ACTIVITY_SEGMENT
- NUTRITION -> DataType.TYPE_NUTRITION
- else -> throw IllegalArgumentException("Unsupported dataType: $type")
- }
+ if (context == null) {
+ result.success(false)
+ return
}
- private fun getField(type: String): Field {
- return when (type) {
- BODY_FAT_PERCENTAGE -> Field.FIELD_PERCENTAGE
- HEIGHT -> Field.FIELD_HEIGHT
- WEIGHT -> Field.FIELD_WEIGHT
- STEPS -> Field.FIELD_STEPS
- ACTIVE_ENERGY_BURNED -> Field.FIELD_CALORIES
- HEART_RATE -> Field.FIELD_BPM
- BODY_TEMPERATURE -> HealthFields.FIELD_BODY_TEMPERATURE
- BLOOD_PRESSURE_SYSTOLIC -> HealthFields.FIELD_BLOOD_PRESSURE_SYSTOLIC
- BLOOD_PRESSURE_DIASTOLIC -> HealthFields.FIELD_BLOOD_PRESSURE_DIASTOLIC
- BLOOD_OXYGEN -> HealthFields.FIELD_OXYGEN_SATURATION
- BLOOD_GLUCOSE -> HealthFields.FIELD_BLOOD_GLUCOSE_LEVEL
- MOVE_MINUTES -> Field.FIELD_DURATION
- DISTANCE_DELTA -> Field.FIELD_DISTANCE
- WATER -> Field.FIELD_VOLUME
- SLEEP_ASLEEP -> Field.FIELD_SLEEP_SEGMENT_TYPE
- SLEEP_AWAKE -> Field.FIELD_SLEEP_SEGMENT_TYPE
- SLEEP_IN_BED -> Field.FIELD_SLEEP_SEGMENT_TYPE
- SLEEP_LIGHT -> Field.FIELD_SLEEP_SEGMENT_TYPE
- SLEEP_REM -> Field.FIELD_SLEEP_SEGMENT_TYPE
- SLEEP_DEEP -> Field.FIELD_SLEEP_SEGMENT_TYPE
- WORKOUT -> Field.FIELD_ACTIVITY
- NUTRITION -> Field.FIELD_NUTRIENTS
- else -> throw IllegalArgumentException("Unsupported dataType: $type")
- }
+ val startTime = call.argument("startTime")!!
+ val endTime = call.argument("endTime")!!
+ val calories = call.argument("caloriesConsumed")
+ val carbs = call.argument("carbohydrates") as Double?
+ val protein = call.argument("protein") as Double?
+ val fat = call.argument("fatTotal") as Double?
+ val name = call.argument("name")
+ val mealType = call.argument("mealType")!!
+
+ val dataType = DataType.TYPE_NUTRITION
+
+ val typesBuilder = FitnessOptions.builder()
+ typesBuilder.addDataType(dataType, FitnessOptions.ACCESS_WRITE)
+
+ val dataSource =
+ DataSource.Builder()
+ .setDataType(dataType)
+ .setType(DataSource.TYPE_RAW)
+ .setDevice(
+ Device.getLocalDevice(
+ context!!.applicationContext
+ )
+ )
+ .setAppPackageName(context!!.applicationContext)
+ .build()
+
+ val nutrients = mutableMapOf(Field.NUTRIENT_CALORIES to calories?.toFloat())
+
+ if (carbs != null) {
+ nutrients[Field.NUTRIENT_TOTAL_CARBS] = carbs.toFloat()
}
- private fun isIntField(dataSource: DataSource, unit: Field): Boolean {
- val dataPoint = DataPoint.builder(dataSource).build()
- val value = dataPoint.getValue(unit)
- return value.format == Field.FORMAT_INT32
- }
-
- // / Extracts the (numeric) value from a Health Data Point
- private fun getHealthDataValue(dataPoint: DataPoint, field: Field): Any {
- val value = dataPoint.getValue(field)
- // Conversion is needed because glucose is stored as mmoll in Google Fit;
- // while mgdl is used for glucose in this plugin.
- val isGlucose = field == HealthFields.FIELD_BLOOD_GLUCOSE_LEVEL
- return when (value.format) {
- Field.FORMAT_FLOAT ->
- if (!isGlucose) value.asFloat()
- else value.asFloat() * MMOLL_2_MGDL
- Field.FORMAT_INT32 -> value.asInt()
- Field.FORMAT_STRING -> value.asString()
- else -> Log.e("Unsupported format:", value.format.toString())
- }
+ if (protein != null) {
+ nutrients[Field.NUTRIENT_PROTEIN] = protein.toFloat()
}
- /** Delete records of the given type in the time range */
- private fun delete(call: MethodCall, result: Result) {
- if (useHealthConnectIfAvailable && healthConnectAvailable) {
- deleteHCData(call, result)
- return
- }
- if (context == null) {
- result.success(false)
- return
- }
+ if (fat != null) {
+ nutrients[Field.NUTRIENT_TOTAL_FAT] = fat.toFloat()
+ }
- val type = call.argument("dataTypeKey")!!
- val startTime = call.argument("startTime")!!
- val endTime = call.argument("endTime")!!
+ val dataBuilder =
+ DataPoint.builder(dataSource)
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .setField(Field.FIELD_NUTRIENTS, nutrients)
- // Look up data type and unit for the type key
- val dataType = keyToHealthDataType(type)
- val field = getField(type)
+ if (name != null) {
+ dataBuilder.setField(Field.FIELD_FOOD_ITEM, name as String)
+ }
- val typesBuilder = FitnessOptions.builder()
- typesBuilder.addDataType(dataType, FitnessOptions.ACCESS_WRITE)
+ dataBuilder.setField(
+ Field.FIELD_MEAL_TYPE,
+ MapMealTypeToType[mealType] ?: Field.MEAL_TYPE_UNKNOWN
+ )
- val dataSource =
- DataDeleteRequest.Builder()
- .setTimeInterval(
- startTime,
- endTime,
- TimeUnit.MILLISECONDS
- )
- .addDataType(dataType)
- .deleteAllSessions()
- .build()
+ val dataPoint = dataBuilder.build()
- val fitnessOptions = typesBuilder.build()
+ val dataSet = DataSet.builder(dataSource).add(dataPoint).build()
- try {
- val googleSignInAccount =
- GoogleSignIn.getAccountForExtension(
- context!!.applicationContext,
- fitnessOptions
- )
- Fitness.getHistoryClient(context!!.applicationContext, googleSignInAccount)
- .deleteData(dataSource)
- .addOnSuccessListener {
- Log.i(
- "FLUTTER_HEALTH::SUCCESS",
- "Dataset deleted successfully!"
- )
- result.success(true)
- }
- .addOnFailureListener(
- errHandler(
- result,
- "There was an error deleting the dataset"
- )
- )
- } catch (e3: Exception) {
- result.success(false)
+ val fitnessOptions = typesBuilder.build()
+ try {
+ val googleSignInAccount =
+ GoogleSignIn.getAccountForExtension(
+ context!!.applicationContext,
+ fitnessOptions
+ )
+ Fitness.getHistoryClient(context!!.applicationContext, googleSignInAccount)
+ .insertData(dataSet)
+ .addOnSuccessListener {
+ Log.i(
+ "FLUTTER_HEALTH::SUCCESS",
+ "Meal added successfully!"
+ )
+ result.success(true)
}
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error adding the meal data!"
+ )
+ )
+ } catch (e3: Exception) {
+ result.success(false)
}
+ }
- /** Save a Blood Pressure measurement with systolic and diastolic values */
- private fun writeBloodPressure(call: MethodCall, result: Result) {
- if (useHealthConnectIfAvailable && healthConnectAvailable) {
- writeBloodPressureHC(call, result)
- return
- }
- if (context == null) {
- result.success(false)
- return
- }
+ /** Save a data type in Google Fit */
+ private fun writeData(call: MethodCall, result: Result) {
+ if (useHealthConnectIfAvailable && healthConnectAvailable) {
+ writeHCData(call, result)
+ return
+ }
+ if (context == null) {
+ result.success(false)
+ return
+ }
- val dataType = HealthDataTypes.TYPE_BLOOD_PRESSURE
- val systolic = call.argument("systolic")!!
- val diastolic = call.argument("diastolic")!!
- val startTime = call.argument("startTime")!!
- val endTime = call.argument("endTime")!!
-
- val typesBuilder = FitnessOptions.builder()
- typesBuilder.addDataType(dataType, FitnessOptions.ACCESS_WRITE)
-
- val dataSource =
- DataSource.Builder()
- .setDataType(dataType)
- .setType(DataSource.TYPE_RAW)
- .setDevice(
- Device.getLocalDevice(
- context!!.applicationContext
- )
- )
- .setAppPackageName(context!!.applicationContext)
- .build()
-
- val builder =
- DataPoint.builder(dataSource)
- .setTimeInterval(
- startTime,
- endTime,
- TimeUnit.MILLISECONDS
- )
- .setField(
- HealthFields.FIELD_BLOOD_PRESSURE_SYSTOLIC,
- systolic
- )
- .setField(
- HealthFields.FIELD_BLOOD_PRESSURE_DIASTOLIC,
- diastolic
- )
- .build()
+ val type = call.argument("dataTypeKey")!!
+ val startTime = call.argument("startTime")!!
+ val endTime = call.argument("endTime")!!
+ val value = call.argument("value")!!
+
+ // Look up data type and unit for the type key
+ val dataType = keyToHealthDataType(type)
+ val field = getField(type)
+
+ val typesBuilder = FitnessOptions.builder()
+ typesBuilder.addDataType(dataType, FitnessOptions.ACCESS_WRITE)
+
+ val dataSource =
+ DataSource.Builder()
+ .setDataType(dataType)
+ .setType(DataSource.TYPE_RAW)
+ .setDevice(
+ Device.getLocalDevice(
+ context!!.applicationContext
+ )
+ )
+ .setAppPackageName(context!!.applicationContext)
+ .build()
+
+ val builder =
+ if (startTime == endTime) {
+ DataPoint.builder(dataSource)
+ .setTimestamp(
+ startTime,
+ TimeUnit.MILLISECONDS
+ )
+ } else {
+ DataPoint.builder(dataSource)
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ }
+
+ // Conversion is needed because glucose is stored as mmoll in Google Fit;
+ // while mgdl is used for glucose in this plugin.
+ val isGlucose = field == HealthFields.FIELD_BLOOD_GLUCOSE_LEVEL
+ val dataPoint =
+ if (!isIntField(dataSource, field)) {
+ builder.setField(
+ field,
+ (if (!isGlucose) value
+ else
+ (value /
+ MMOLL_2_MGDL)
+ .toFloat())
+ )
+ .build()
+ } else {
+ builder.setField(field, value.toInt()).build()
+ }
- val dataPoint = builder
- val dataSet = DataSet.builder(dataSource).add(dataPoint).build()
+ val dataSet = DataSet.builder(dataSource).add(dataPoint).build()
- val fitnessOptions = typesBuilder.build()
- try {
- val googleSignInAccount =
- GoogleSignIn.getAccountForExtension(
- context!!.applicationContext,
- fitnessOptions
- )
- Fitness.getHistoryClient(context!!.applicationContext, googleSignInAccount)
- .insertData(dataSet)
- .addOnSuccessListener {
- Log.i(
- "FLUTTER_HEALTH::SUCCESS",
- "Blood Pressure added successfully!"
- )
- result.success(true)
- }
- .addOnFailureListener(
- errHandler(
- result,
- "There was an error adding the blood pressure data!",
- ),
- )
- } catch (e3: Exception) {
- result.success(false)
- }
+ if (dataType == DataType.TYPE_SLEEP_SEGMENT) {
+ typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ)
}
-
- private fun writeMealHC(call: MethodCall, result: Result) {
- val startTime = Instant.ofEpochMilli(call.argument("startTime")!!)
- val endTime = Instant.ofEpochMilli(call.argument("endTime")!!)
- val calories = call.argument("caloriesConsumed")
- val carbs = call.argument("carbohydrates") as Double?
- val protein = call.argument("protein") as Double?
- val fat = call.argument("fatTotal") as Double?
- val caffeine = call.argument("caffeine") as Double?
- val name = call.argument("name")
- val mealType = call.argument("mealType")!!
-
- scope.launch {
- try {
- val list = mutableListOf()
- list.add(
- NutritionRecord(
- name = name,
- energy = calories?.kilocalories,
- totalCarbohydrate = carbs?.grams,
- protein = protein?.grams,
- totalFat = fat?.grams,
- caffeine = caffeine?.grams,
- startTime = startTime,
- startZoneOffset = null,
- endTime = endTime,
- endZoneOffset = null,
- mealType =
- MapMealTypeToTypeHC[
- mealType]
- ?: MEAL_TYPE_UNKNOWN,
- ),
- )
- healthConnectClient.insertRecords(
- list,
- )
- result.success(true)
- Log.i(
- "FLUTTER_HEALTH::SUCCESS",
- "[Health Connect] Meal was successfully added!"
- )
- } catch (e: Exception) {
- Log.w(
- "FLUTTER_HEALTH::ERROR",
- "[Health Connect] There was an error adding the meal",
- )
- Log.w("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error")
- Log.w("FLUTTER_HEALTH::ERROR", e.stackTrace.toString())
- result.success(false)
- }
+ val fitnessOptions = typesBuilder.build()
+ try {
+ val googleSignInAccount =
+ GoogleSignIn.getAccountForExtension(
+ context!!.applicationContext,
+ fitnessOptions
+ )
+ Fitness.getHistoryClient(context!!.applicationContext, googleSignInAccount)
+ .insertData(dataSet)
+ .addOnSuccessListener {
+ Log.i(
+ "FLUTTER_HEALTH::SUCCESS",
+ "Dataset added successfully!"
+ )
+ result.success(true)
}
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error adding the dataset"
+ )
+ )
+ } catch (e3: Exception) {
+ result.success(false)
+ }
+ }
+
+ /**
+ * Save the blood oxygen saturation, in Google Fit with the supplemental flow rate, in
+ * HealthConnect without
+ */
+ private fun writeBloodOxygen(call: MethodCall, result: Result) {
+ // Health Connect does not support supplemental flow rate, thus it is ignored
+ if (useHealthConnectIfAvailable && healthConnectAvailable) {
+ writeHCData(call, result)
+ return
}
- /** Save a Nutrition measurement with calories, carbs, protein, fat, name and mealType */
- private fun writeMeal(call: MethodCall, result: Result) {
- if (useHealthConnectIfAvailable && healthConnectAvailable) {
- writeMealHC(call, result)
- return
- }
+ if (context == null) {
+ result.success(false)
+ return
+ }
- if (context == null) {
- result.success(false)
- return
+ val dataType = HealthDataTypes.TYPE_OXYGEN_SATURATION
+ val startTime = call.argument("startTime")!!
+ val endTime = call.argument("endTime")!!
+ val saturation = call.argument("value")!!
+ val flowRate = call.argument("flowRate")!!
+
+ val typesBuilder = FitnessOptions.builder()
+ typesBuilder.addDataType(dataType, FitnessOptions.ACCESS_WRITE)
+
+ val dataSource =
+ DataSource.Builder()
+ .setDataType(dataType)
+ .setType(DataSource.TYPE_RAW)
+ .setDevice(
+ Device.getLocalDevice(
+ context!!.applicationContext
+ )
+ )
+ .setAppPackageName(context!!.applicationContext)
+ .build()
+
+ val builder =
+ if (startTime == endTime) {
+ DataPoint.builder(dataSource)
+ .setTimestamp(
+ startTime,
+ TimeUnit.MILLISECONDS
+ )
+ } else {
+ DataPoint.builder(dataSource)
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ }
+
+ builder.setField(HealthFields.FIELD_SUPPLEMENTAL_OXYGEN_FLOW_RATE, flowRate)
+ builder.setField(HealthFields.FIELD_OXYGEN_SATURATION, saturation)
+
+ val dataPoint = builder.build()
+ val dataSet = DataSet.builder(dataSource).add(dataPoint).build()
+
+ val fitnessOptions = typesBuilder.build()
+ try {
+ val googleSignInAccount =
+ GoogleSignIn.getAccountForExtension(
+ context!!.applicationContext,
+ fitnessOptions
+ )
+ Fitness.getHistoryClient(context!!.applicationContext, googleSignInAccount)
+ .insertData(dataSet)
+ .addOnSuccessListener {
+ Log.i(
+ "FLUTTER_HEALTH::SUCCESS",
+ "Blood Oxygen added successfully!"
+ )
+ result.success(true)
}
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error adding the blood oxygen data!",
+ ),
+ )
+ } catch (e3: Exception) {
+ result.success(false)
+ }
+ }
- val startTime = call.argument("startTime")!!
- val endTime = call.argument("endTime")!!
- val calories = call.argument("caloriesConsumed")
- val carbs = call.argument("carbohydrates") as Double?
- val protein = call.argument("protein") as Double?
- val fat = call.argument("fatTotal") as Double?
- val name = call.argument("name")
- val mealType = call.argument("mealType")!!
-
- val dataType = DataType.TYPE_NUTRITION
-
- val typesBuilder = FitnessOptions.builder()
- typesBuilder.addDataType(dataType, FitnessOptions.ACCESS_WRITE)
-
- val dataSource =
- DataSource.Builder()
- .setDataType(dataType)
- .setType(DataSource.TYPE_RAW)
- .setDevice(
- Device.getLocalDevice(
- context!!.applicationContext
- )
- )
- .setAppPackageName(context!!.applicationContext)
- .build()
+ /** Save a Workout session with options for distance and calories expended */
+ private fun writeWorkoutData(call: MethodCall, result: Result) {
+ if (useHealthConnectIfAvailable && healthConnectAvailable) {
+ writeWorkoutHCData(call, result)
+ return
+ }
+ if (context == null) {
+ result.success(false)
+ return
+ }
- val nutrients = mutableMapOf(Field.NUTRIENT_CALORIES to calories?.toFloat())
+ val type = call.argument("activityType")!!
+ val startTime = call.argument("startTime")!!
+ val endTime = call.argument("endTime")!!
+ val totalEnergyBurned = call.argument("totalEnergyBurned")
+ val totalDistance = call.argument("totalDistance")
+
+ val activityType = getActivityType(type)
+ // Create the Activity Segment DataSource
+ val activitySegmentDataSource =
+ DataSource.Builder()
+ .setAppPackageName(context!!.packageName)
+ .setDataType(DataType.TYPE_ACTIVITY_SEGMENT)
+ .setStreamName("FLUTTER_HEALTH - Activity")
+ .setType(DataSource.TYPE_RAW)
+ .build()
+ // Create the Activity Segment
+ val activityDataPoint =
+ DataPoint.builder(activitySegmentDataSource)
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .setActivityField(
+ Field.FIELD_ACTIVITY,
+ activityType
+ )
+ .build()
+ // Add DataPoint to DataSet
+ val activitySegments =
+ DataSet.builder(activitySegmentDataSource)
+ .add(activityDataPoint)
+ .build()
+
+ // If distance is provided
+ var distanceDataSet: DataSet? = null
+ if (totalDistance != null) {
+ // Create a data source
+ val distanceDataSource =
+ DataSource.Builder()
+ .setAppPackageName(context!!.packageName)
+ .setDataType(DataType.TYPE_DISTANCE_DELTA)
+ .setStreamName("FLUTTER_HEALTH - Distance")
+ .setType(DataSource.TYPE_RAW)
+ .build()
+
+ val distanceDataPoint =
+ DataPoint.builder(distanceDataSource)
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .setField(
+ Field.FIELD_DISTANCE,
+ totalDistance.toFloat()
+ )
+ .build()
+ // Create a data set
+ distanceDataSet =
+ DataSet.builder(distanceDataSource)
+ .add(distanceDataPoint)
+ .build()
+ }
+ // If energyBurned is provided
+ var energyDataSet: DataSet? = null
+ if (totalEnergyBurned != null) {
+ // Create a data source
+ val energyDataSource =
+ DataSource.Builder()
+ .setAppPackageName(context!!.packageName)
+ .setDataType(
+ DataType.TYPE_CALORIES_EXPENDED
+ )
+ .setStreamName("FLUTTER_HEALTH - Calories")
+ .setType(DataSource.TYPE_RAW)
+ .build()
+
+ val energyDataPoint =
+ DataPoint.builder(energyDataSource)
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .setField(
+ Field.FIELD_CALORIES,
+ totalEnergyBurned.toFloat()
+ )
+ .build()
+ // Create a data set
+ energyDataSet =
+ DataSet.builder(energyDataSource)
+ .add(energyDataPoint)
+ .build()
+ }
- if (carbs != null) {
- nutrients[Field.NUTRIENT_TOTAL_CARBS] = carbs.toFloat()
- }
+ // Finish session setup
+ val session =
+ Session.Builder()
+ .setName(
+ activityType
+ ) // TODO: Make a sensible name / allow user to set
+ // name
+ .setDescription("")
+ .setIdentifier(UUID.randomUUID().toString())
+ .setActivity(activityType)
+ .setStartTime(startTime, TimeUnit.MILLISECONDS)
+ .setEndTime(endTime, TimeUnit.MILLISECONDS)
+ .build()
+ // Build a session and add the values provided
+ val sessionInsertRequestBuilder =
+ SessionInsertRequest.Builder()
+ .setSession(session)
+ .addDataSet(activitySegments)
+ if (totalDistance != null) {
+ sessionInsertRequestBuilder.addDataSet(distanceDataSet!!)
+ }
+ if (totalEnergyBurned != null) {
+ sessionInsertRequestBuilder.addDataSet(energyDataSet!!)
+ }
+ val insertRequest = sessionInsertRequestBuilder.build()
- if (protein != null) {
- nutrients[Field.NUTRIENT_PROTEIN] = protein.toFloat()
- }
+ val fitnessOptionsBuilder =
+ FitnessOptions.builder()
+ .addDataType(
+ DataType.TYPE_ACTIVITY_SEGMENT,
+ FitnessOptions.ACCESS_WRITE
+ )
+ if (totalDistance != null) {
+ fitnessOptionsBuilder.addDataType(
+ DataType.TYPE_DISTANCE_DELTA,
+ FitnessOptions.ACCESS_WRITE,
+ )
+ }
+ if (totalEnergyBurned != null) {
+ fitnessOptionsBuilder.addDataType(
+ DataType.TYPE_CALORIES_EXPENDED,
+ FitnessOptions.ACCESS_WRITE,
+ )
+ }
+ val fitnessOptions = fitnessOptionsBuilder.build()
- if (fat != null) {
- nutrients[Field.NUTRIENT_TOTAL_FAT] = fat.toFloat()
+ try {
+ val googleSignInAccount =
+ GoogleSignIn.getAccountForExtension(
+ context!!.applicationContext,
+ fitnessOptions
+ )
+ Fitness.getSessionsClient(
+ context!!.applicationContext,
+ googleSignInAccount,
+ )
+ .insertSession(insertRequest)
+ .addOnSuccessListener {
+ Log.i(
+ "FLUTTER_HEALTH::SUCCESS",
+ "Workout was successfully added!"
+ )
+ result.success(true)
}
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error adding the workout"
+ )
+ )
+ } catch (e: Exception) {
+ result.success(false)
+ }
+ }
- val dataBuilder =
- DataPoint.builder(dataSource)
- .setTimeInterval(
- startTime,
- endTime,
- TimeUnit.MILLISECONDS
- )
- .setField(Field.FIELD_NUTRIENTS, nutrients)
-
- if (name != null) {
- dataBuilder.setField(Field.FIELD_FOOD_ITEM, name as String)
- }
+ /** Get all datapoints of the DataType within the given time range */
+ private fun getData(call: MethodCall, result: Result) {
- dataBuilder.setField(
- Field.FIELD_MEAL_TYPE,
- MapMealTypeToType[mealType] ?: Field.MEAL_TYPE_UNKNOWN
- )
+ if (StepCounterService.initiated()) {
+ getSensorData(call, result)
+ return
+ }
- val dataPoint = dataBuilder.build()
+ if (useHealthConnectIfAvailable && healthConnectAvailable) {
+ getHCData(call, result)
+ return
+ }
- val dataSet = DataSet.builder(dataSource).add(dataPoint).build()
+ if (context == null) {
+ result.success(null)
+ return
+ }
- val fitnessOptions = typesBuilder.build()
- try {
- val googleSignInAccount =
- GoogleSignIn.getAccountForExtension(
- context!!.applicationContext,
- fitnessOptions
- )
- Fitness.getHistoryClient(context!!.applicationContext, googleSignInAccount)
- .insertData(dataSet)
- .addOnSuccessListener {
- Log.i(
- "FLUTTER_HEALTH::SUCCESS",
- "Meal added successfully!"
- )
- result.success(true)
- }
- .addOnFailureListener(
- errHandler(
- result,
- "There was an error adding the meal data!"
- )
- )
- } catch (e3: Exception) {
- result.success(false)
- }
+ val type = call.argument("dataTypeKey")!!
+ val startTime = call.argument("startTime")!!
+ val endTime = call.argument("endTime")!!
+ val includeManualEntry = call.argument("includeManualEntry")!!
+ // Look up data type and unit for the type key
+ val dataType = keyToHealthDataType(type)
+ val field = getField(type)
+ val typesBuilder = FitnessOptions.builder()
+ typesBuilder.addDataType(dataType)
+
+ // Add special cases for accessing workouts or sleep data.
+ if (dataType == DataType.TYPE_SLEEP_SEGMENT) {
+ typesBuilder.accessSleepSessions(FitnessOptions.ACCESS_READ)
+ } else if (dataType == DataType.TYPE_ACTIVITY_SEGMENT) {
+ typesBuilder.accessActivitySessions(FitnessOptions.ACCESS_READ)
+ .addDataType(
+ DataType.TYPE_CALORIES_EXPENDED,
+ FitnessOptions.ACCESS_READ
+ )
+ .addDataType(
+ DataType.TYPE_DISTANCE_DELTA,
+ FitnessOptions.ACCESS_READ
+ )
}
+ val fitnessOptions = typesBuilder.build()
+ val googleSignInAccount =
+ GoogleSignIn.getAccountForExtension(
+ context!!.applicationContext,
+ fitnessOptions
+ )
+ // Handle data types
+ when (dataType) {
+ DataType.TYPE_SLEEP_SEGMENT -> {
+ // request to the sessions for sleep data
+ val request =
+ SessionReadRequest.Builder()
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .enableServerQueries()
+ .readSessionsFromAllApps()
+ .includeSleepSessions()
+ .build()
+ Fitness.getSessionsClient(
+ context!!.applicationContext,
+ googleSignInAccount
+ )
+ .readSession(request)
+ .addOnSuccessListener(
+ threadPoolExecutor!!,
+ sleepDataHandler(type, result)
+ )
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error getting the sleeping data!",
+ ),
+ )
+ }
+
+ DataType.TYPE_ACTIVITY_SEGMENT -> {
+ val readRequest: SessionReadRequest
+ val readRequestBuilder =
+ SessionReadRequest.Builder()
+ .setTimeInterval(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .enableServerQueries()
+ .readSessionsFromAllApps()
+ .includeActivitySessions()
+ .read(dataType)
+ .read(
+ DataType.TYPE_CALORIES_EXPENDED
+ )
- /** Save a data type in Google Fit */
- private fun writeData(call: MethodCall, result: Result) {
- if (useHealthConnectIfAvailable && healthConnectAvailable) {
- writeHCData(call, result)
- return
- }
- if (context == null) {
- result.success(false)
- return
+ // If fine location is enabled, read distance data
+ if (ContextCompat.checkSelfPermission(
+ context!!.applicationContext,
+ android.Manifest.permission
+ .ACCESS_FINE_LOCATION,
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ readRequestBuilder.read(DataType.TYPE_DISTANCE_DELTA)
}
+ readRequest = readRequestBuilder.build()
+ Fitness.getSessionsClient(
+ context!!.applicationContext,
+ googleSignInAccount
+ )
+ .readSession(readRequest)
+ .addOnSuccessListener(
+ threadPoolExecutor!!,
+ workoutDataHandler(type, result)
+ )
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error getting the workout data!",
+ ),
+ )
+ }
+
+ else -> {
+ Fitness.getHistoryClient(
+ context!!.applicationContext,
+ googleSignInAccount
+ )
+ .readData(
+ DataReadRequest.Builder()
+ .read(dataType)
+ .setTimeRange(
+ startTime,
+ endTime,
+ TimeUnit.MILLISECONDS
+ )
+ .build(),
+ )
+ .addOnSuccessListener(
+ threadPoolExecutor!!,
+ dataHandler(
+ dataType,
+ field,
+ includeManualEntry,
+ result
+ ),
+ )
+ .addOnFailureListener(
+ errHandler(
+ result,
+ "There was an error getting the data!",
+ ),
+ )
+ }
+ }
+ }
+
+ private fun getSensorData(call: MethodCall, result: Result) {
+ val dataType = call.argument("dataTypeKey")!!
+ val startTime = Instant.ofEpochMilli(call.argument("startTime")!!)
+ val endTime = Instant.ofEpochMilli(call.argument("endTime")!!)
+
+ val healthConnectData = mutableListOf