huawei watch addded
parent
c00fe3e10c
commit
532c894e16
@ -0,0 +1,87 @@
|
||||
package com.cloudsolutions.HMGPatientApp.watch.huawei
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.cloudsolutions.HMGPatientApp.watch.samsung_watch.model.Vitals
|
||||
import com.huawei.hms.adapter.AvailableAdapter
|
||||
import com.huawei.hms.adapter.internal.AvailableCode
|
||||
|
||||
import com.samsung.android.sdk.health.data.HealthDataStore
|
||||
import com.samsung.android.sdk.health.data.permission.AccessType
|
||||
import com.samsung.android.sdk.health.data.permission.Permission
|
||||
import com.samsung.android.sdk.health.data.request.DataType
|
||||
import com.samsung.android.sdk.health.data.request.DataTypes
|
||||
import com.samsung.android.sdk.health.data.request.LocalTimeFilter
|
||||
import com.samsung.android.sdk.health.data.request.LocalTimeGroup
|
||||
import com.samsung.android.sdk.health.data.request.LocalTimeGroupUnit
|
||||
import com.samsung.android.sdk.health.data.request.Ordering
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import com.huawei.hms.hihealth.HuaweiHiHealth
|
||||
import com.cloudsolutions.HMGPatientApp.MainActivity
|
||||
|
||||
|
||||
class HuaweiWatch(
|
||||
private var flutterEngine: FlutterEngine,
|
||||
private var mainActivity: MainActivity
|
||||
) {
|
||||
|
||||
private lateinit var channel: MethodChannel
|
||||
private lateinit var dataStore: HealthDataStore
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private val TAG = "HuaweiWatch"
|
||||
|
||||
|
||||
private lateinit var vitals: MutableMap<String, List<Vitals>>
|
||||
companion object {
|
||||
private const val CHANNEL = "huawei_watch"
|
||||
|
||||
}
|
||||
init{
|
||||
create()
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun create() {
|
||||
Log.d(TAG, "create: is called")
|
||||
// openTok = OpenTok(mainActivity, flutterEngine)
|
||||
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||
channel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
|
||||
when (call.method) {
|
||||
"init" -> {
|
||||
init(result)
|
||||
}
|
||||
|
||||
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun init(result: MethodChannel.Result) {
|
||||
|
||||
Log.d(TAG, "onMethodCall: init called")
|
||||
val mSettingController = HuaweiHiHealth.getSettingController(mainActivity);
|
||||
val availableAdapter = AvailableAdapter(60400312)
|
||||
val results= availableAdapter.isHuaweiMobileServicesAvailable(mainActivity) // context indicates a context object.
|
||||
if (results != AvailableCode.SUCCESS) {
|
||||
availableAdapter.startResolution(mainActivity, object : AvailableAdapter.AvailableCallBack {
|
||||
override fun onComplete(result: Int) {
|
||||
Log.d(TAG, "onComplete result: " + result)
|
||||
}
|
||||
})
|
||||
}
|
||||
result.success("init success")
|
||||
}
|
||||
}
|
||||
@ -1,86 +1,403 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:async/src/result/result.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:hmg_patient_app_new/features/smartwatch_health_data/model/vitals_data_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/smartwatch_health_data/watch_connectors/watch_helper.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:huawei_health/huawei_health.dart';
|
||||
|
||||
|
||||
class HuaweiHealthDataConnector extends WatchHelper{
|
||||
final MethodChannel _channel = MethodChannel('huawei_watch');
|
||||
@override
|
||||
Future<Result<bool>> initDevice() async{
|
||||
try{
|
||||
await _channel.invokeMethod('init');
|
||||
|
||||
}catch(e){
|
||||
DataController? _dataController;
|
||||
final Map<String, List<Vitals>> mappedData = <String, List<Vitals>>{};
|
||||
|
||||
DateTime get _defaultEndDate => DateTime.now();
|
||||
DateTime get _defaultStartDate => _defaultEndDate.subtract(const Duration(days: 365));
|
||||
|
||||
@override
|
||||
Future<Result<bool>> initDevice() async {
|
||||
try {
|
||||
await _channel.invokeMethod('init');
|
||||
} catch (e) {
|
||||
debugPrint('Huawei channel init failed: $e');
|
||||
}
|
||||
// List of scopes to ask for authorization.
|
||||
// Note: These scopes should also be authorized on the Huawei Developer Console.
|
||||
List<Scope> scopes = [
|
||||
Scope.HEALTHKIT_STEP_READ, Scope.HEALTHKIT_OXYGEN_SATURATION_READ, // View and store height and weight data in Health Service Kit.
|
||||
Scope.HEALTHKIT_HEARTRATE_READ, Scope.HEALTHKIT_SLEEP_READ,
|
||||
Scope.HEALTHKIT_BODYTEMPERATURE_READ, Scope.HEALTHKIT_CALORIES_READ
|
||||
|
||||
final List<Scope> scopes = <Scope>[
|
||||
Scope.HEALTHKIT_ACTIVITY_READ,
|
||||
Scope.HEALTHKIT_ACTIVITY_WRITE,
|
||||
Scope.HEALTHKIT_BLOODGLUCOSE_READ,
|
||||
Scope.HEALTHKIT_BLOODGLUCOSE_WRITE,
|
||||
Scope.HEALTHKIT_CALORIES_READ,
|
||||
Scope.HEALTHKIT_CALORIES_WRITE,
|
||||
Scope.HEALTHKIT_DISTANCE_READ,
|
||||
Scope.HEALTHKIT_DISTANCE_WRITE,
|
||||
Scope.HEALTHKIT_HEARTRATE_READ,
|
||||
Scope.HEALTHKIT_HEARTRATE_WRITE,
|
||||
Scope.HEALTHKIT_HEIGHTWEIGHT_READ,
|
||||
Scope.HEALTHKIT_HEIGHTWEIGHT_WRITE,
|
||||
Scope.HEALTHKIT_LOCATION_READ,
|
||||
Scope.HEALTHKIT_LOCATION_WRITE,
|
||||
Scope.HEALTHKIT_PULMONARY_READ,
|
||||
Scope.HEALTHKIT_PULMONARY_WRITE,
|
||||
Scope.HEALTHKIT_SLEEP_READ,
|
||||
Scope.HEALTHKIT_SLEEP_WRITE,
|
||||
Scope.HEALTHKIT_SPEED_READ,
|
||||
Scope.HEALTHKIT_SPEED_WRITE,
|
||||
Scope.HEALTHKIT_STEP_READ,
|
||||
Scope.HEALTHKIT_STEP_WRITE,
|
||||
Scope.HEALTHKIT_STRENGTH_READ,
|
||||
Scope.HEALTHKIT_STRENGTH_WRITE,
|
||||
Scope.HEALTHKIT_BODYFAT_READ,
|
||||
Scope.HEALTHKIT_BODYFAT_WRITE,
|
||||
Scope.HEALTHKIT_NUTRITION_READ,
|
||||
Scope.HEALTHKIT_NUTRITION_WRITE,
|
||||
Scope.HEALTHKIT_BLOODPRESSURE_READ,
|
||||
Scope.HEALTHKIT_BLOODPRESSURE_WRITE,
|
||||
Scope.HEALTHKIT_BODYTEMPERATURE_READ,
|
||||
Scope.HEALTHKIT_BODYTEMPERATURE_WRITE,
|
||||
Scope.HEALTHKIT_OXYGENSTATURATION_READ,
|
||||
Scope.HEALTHKIT_OXYGENSTATURATION_WRITE,
|
||||
Scope.HEALTHKIT_REPRODUCTIVE_READ,
|
||||
Scope.HEALTHKIT_REPRODUCTIVE_WRITE,
|
||||
Scope.HEALTHKIT_ACTIVITY_RECORD_READ,
|
||||
Scope.HEALTHKIT_ACTIVITY_RECORD_WRITE,
|
||||
Scope.HEALTHKIT_HEARTRATE_REALTIME,
|
||||
Scope.HEALTHKIT_STEP_REALTIME,
|
||||
Scope.HEALTHKIT_HEARTHEALTH_WRITE,
|
||||
Scope.HEALTHKIT_HEARTHEALTH_READ,
|
||||
Scope.HEALTHKIT_STRESS_WRITE,
|
||||
Scope.HEALTHKIT_STRESS_READ,
|
||||
Scope.HEALTHKIT_OXYGEN_SATURATION_WRITE,
|
||||
Scope.HEALTHKIT_OXYGEN_SATURATION_READ,
|
||||
Scope.HEALTHKIT_HISTORYDATA_OPEN_WEEK,
|
||||
Scope.HEALTHKIT_HISTORYDATA_OPEN_MONTH,
|
||||
Scope.HEALTHKIT_HISTORYDATA_OPEN_YEAR,
|
||||
];
|
||||
|
||||
try {
|
||||
bool? result = await SettingController.getHealthAppAuthorization();
|
||||
debugPrint(
|
||||
'Granted Scopes for result == is $result}',
|
||||
);
|
||||
await HealthAuth.signIn(scopes);
|
||||
_dataController = await DataController.init();
|
||||
if (_dataController == null) {
|
||||
return Result.error(false);
|
||||
}
|
||||
return Result.value(true);
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('Error on Huawei authorization: $e');
|
||||
return Result.error(false);
|
||||
} catch (e) {
|
||||
debugPrint('Error on authorization, Error:${e.toString()}');
|
||||
debugPrint('Unexpected Huawei init error: $e');
|
||||
return Result.error(false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getActivity() async {
|
||||
DataType dataTypeResult = await SettingController.readDataType(
|
||||
DataType.DT_CONTINUOUS_STEPS_DELTA.name
|
||||
);
|
||||
|
||||
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_CONTINUOUS_CALORIES_BURNT,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('activity', data, preferredKeys: const <String>['calories', 'calories_total']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting activity data: $e');
|
||||
mappedData['activity'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future getBloodOxygen() {
|
||||
throw UnimplementedError();
|
||||
Future<void> getBloodOxygen() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
HealthDataTypes.DT_INSTANTANEOUS_SPO2,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap(
|
||||
'bloodOxygen',
|
||||
data,
|
||||
preferredKeys: const <String>['saturation', 'saturation_last', 'saturation_avg'],
|
||||
transform: _normalizeSpo2,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting blood oxygen data: $e');
|
||||
mappedData['bloodOxygen'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getBodyTemperature() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
HealthDataTypes.DT_INSTANTANEOUS_BODY_TEMPERATURE,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('bodyTemperature', data, preferredKeys: const <String>['temperature']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting body temperature data: $e');
|
||||
mappedData['bodyTemperature'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future getBodyTemperature() {
|
||||
FutureOr<void> getHeartRate() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_INSTANTANEOUS_HEART_RATE,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('heartRate', data, preferredKeys: const <String>['bpm']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting heart rate data: $e');
|
||||
mappedData['heartRate'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
throw UnimplementedError();
|
||||
@override
|
||||
FutureOr<void> getSleep() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_CONTINUOUS_SLEEP,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addSleepDataToMap(data);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting sleep data: $e');
|
||||
mappedData['sleep'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getHeartRate() {
|
||||
throw UnimplementedError();
|
||||
FutureOr<void> getSteps() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_CONTINUOUS_STEPS_DELTA,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('steps', data, preferredKeys: const <String>['steps_delta', 'steps', 'step_rate']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting steps data: $e');
|
||||
mappedData['steps'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getSleep() {
|
||||
throw UnimplementedError();
|
||||
Future<Result<String>> retrieveData() async {
|
||||
return Result.value(getMappedData());
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getSteps() {
|
||||
throw UnimplementedError();
|
||||
FutureOr<void> getDistance() async {
|
||||
try {
|
||||
final ReadReply? data = await readDataType(
|
||||
DataType.DT_CONTINUOUS_DISTANCE_DELTA,
|
||||
_defaultStartDate,
|
||||
_defaultEndDate,
|
||||
);
|
||||
addDataToMap('distance', data, preferredKeys: const <String>['distance_delta', 'distance']);
|
||||
} catch (e) {
|
||||
debugPrint('Error getting distance data: $e');
|
||||
mappedData['distance'] = <Vitals>[];
|
||||
}
|
||||
}
|
||||
|
||||
Future<ReadReply?> readDataType(
|
||||
DataType dataType,
|
||||
DateTime startTime,
|
||||
DateTime endTime,
|
||||
) async {
|
||||
final DataCollector dataCollector = DataCollector(
|
||||
dataType: dataType,
|
||||
dataGenerateType: DataGenerateType.DATA_TYPE_RAW,
|
||||
);
|
||||
|
||||
@override
|
||||
Future retrieveData() {
|
||||
throw UnimplementedError();
|
||||
final ReadOptions readOptions = ReadOptions(
|
||||
dataCollectors: <DataCollector>[dataCollector],
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
allowRemoteInquiry: true,
|
||||
)..groupByTime(1, timeUnit: TimeUnit.HOURS);
|
||||
|
||||
try {
|
||||
return await _dataController?.read(readOptions);
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('Huawei read error for ${dataType.name}: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> getDistance() {
|
||||
// TODO: implement getDistance
|
||||
throw UnimplementedError();
|
||||
void addDataToMap(
|
||||
String key,
|
||||
ReadReply? data, {
|
||||
List<String> preferredKeys = const <String>[],
|
||||
double Function(double value)? transform,
|
||||
}) {
|
||||
mappedData[key] = <Vitals>[];
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.groups.isNotEmpty) {
|
||||
for (final Group group in data.groups) {
|
||||
for (final SampleSet sampleSet in group.sampleSets ?? <SampleSet>[]) {
|
||||
for (final SamplePoint samplePoint in sampleSet.samplePoints) {
|
||||
_addNumericSampleToMappedData(
|
||||
key: key,
|
||||
samplePoint: samplePoint,
|
||||
preferredKeys: preferredKeys,
|
||||
groupStartTime: group.startTime,
|
||||
transform: transform,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (final SampleSet sampleSet in data.sampleSets) {
|
||||
for (final SamplePoint samplePoint in sampleSet.samplePoints) {
|
||||
_addNumericSampleToMappedData(
|
||||
key: key,
|
||||
samplePoint: samplePoint,
|
||||
preferredKeys: preferredKeys,
|
||||
transform: transform,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addSleepDataToMap(ReadReply? data) {
|
||||
mappedData['sleep'] = <Vitals>[];
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
void appendSleepPoint(SamplePoint samplePoint, {DateTime? groupStart, DateTime? groupEnd}) {
|
||||
final DateTime? start = samplePoint.startTime ?? groupStart;
|
||||
final DateTime? end = samplePoint.endTime ?? groupEnd;
|
||||
|
||||
double? value;
|
||||
if (start != null && end != null && end.isAfter(start)) {
|
||||
value = end.difference(start).inMinutes / 60;
|
||||
}
|
||||
|
||||
value ??= _extractNumericValue(
|
||||
samplePoint,
|
||||
const <String>['sleep_state', 'all_sleep_time', 'deep_sleep_time', 'light_sleep_time'],
|
||||
);
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String timestamp =
|
||||
(start ?? samplePoint.samplingTime ?? DateTime.now()).toIso8601String();
|
||||
|
||||
mappedData['sleep']?.add(
|
||||
Vitals(
|
||||
value: value.toStringAsFixed(2),
|
||||
timestamp: timestamp,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (data.groups.isNotEmpty) {
|
||||
for (final Group group in data.groups) {
|
||||
for (final SampleSet sampleSet in group.sampleSets ?? <SampleSet>[]) {
|
||||
for (final SamplePoint samplePoint in sampleSet.samplePoints) {
|
||||
appendSleepPoint(
|
||||
samplePoint,
|
||||
groupStart: group.startTime,
|
||||
groupEnd: group.endTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (final SampleSet sampleSet in data.sampleSets) {
|
||||
for (final SamplePoint samplePoint in sampleSet.samplePoints) {
|
||||
appendSleepPoint(samplePoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _addNumericSampleToMappedData({
|
||||
required String key,
|
||||
required SamplePoint samplePoint,
|
||||
required List<String> preferredKeys,
|
||||
DateTime? groupStartTime,
|
||||
double Function(double value)? transform,
|
||||
}) {
|
||||
final double? numericValue = _extractNumericValue(samplePoint, preferredKeys);
|
||||
if (numericValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final double transformedValue = transform?.call(numericValue) ?? numericValue;
|
||||
final String timestamp =
|
||||
(samplePoint.samplingTime ?? samplePoint.startTime ?? groupStartTime ?? DateTime.now())
|
||||
.toIso8601String();
|
||||
|
||||
mappedData[key]?.add(
|
||||
Vitals(
|
||||
value: transformedValue.toStringAsFixed(2),
|
||||
timestamp: timestamp,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double? _extractNumericValue(SamplePoint samplePoint, List<String> preferredKeys) {
|
||||
final Map<String, dynamic> values = <String, dynamic>{
|
||||
...samplePoint.pairs,
|
||||
...?samplePoint.fieldValues,
|
||||
};
|
||||
|
||||
for (final String key in preferredKeys) {
|
||||
final dynamic value = values[key];
|
||||
if (value is num) {
|
||||
return value.toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
for (final dynamic value in values.values) {
|
||||
if (value is num) {
|
||||
return value.toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
double _normalizeSpo2(double value) {
|
||||
if (value <= 1) {
|
||||
return value * 100;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
String getMappedData() {
|
||||
return jsonEncode(<String, dynamic>{
|
||||
'heartRate': (mappedData['heartRate'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'sleep': (mappedData['sleep'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'steps': (mappedData['steps'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'activity': (mappedData['activity'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'bloodOxygen':
|
||||
(mappedData['bloodOxygen'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'bodyTemperature':
|
||||
(mappedData['bodyTemperature'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
'distance': (mappedData['distance'] ?? <Vitals>[]).map((Vitals v) => v.toJson()).toList(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue