diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a16afd3..67a4974c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The changes documented here do not include those from the original repository. +## [2.0.0] + +### 2024-04-05 +- Fix privacy policy opening for Android <= 13 (https://outsystemsrd.atlassian.net/browse/RMET-3134). + +### 2024-04-04 +- Update the iOS framework. This adds the Privacy Manifest file (https://outsystemsrd.atlassian.net/browse/RMET-3280). +- Update GSON version to remove vulnerability (https://outsystemsrd.atlassian.net/browse/RMET-3134). + +### 2024-04-01 +- Remove old code and re-arrange file structure (https://outsystemsrd.atlassian.net/browse/RMET-3134). + +### 2024-03-22 +- Fixed hook for ODC (https://outsystemsrd.atlassian.net/browse/RMET-3191). + +### 2024-03-18 +- Implemented the usage of the Activity Transition Recognition API for background jobs (https://outsystemsrd.atlassian.net/browse/RMET-3246). + +### 2024-03-14 +- Implemented the usage of exact alarms for background jobs (https://outsystemsrd.atlassian.net/browse/RMET-3190). + +### 2024-02-28 +- Implemented `Open Health Connect App` (https://outsystemsrd.atlassian.net/browse/RMET-3158). + +### 2024-02-27 +- Implemented hook for permissions (https://outsystemsrd.atlassian.net/browse/RMET-3142). + +### 2024-02-26 +- Implemented `Show app's privacy policy dialog` (https://outsystemsrd.atlassian.net/browse/RMET-3145). + +### 2024-02-23 +- Re-implement `DeleteBackgroundJob` feature (https://outsystemsrd.atlassian.net/browse/RMET-3068). + +### 2024-02-20 +- Deprecated `DisableGoogleFit` feature and implemented `DisableHealthConnect` feature (https://outsystemsrd.atlassian.net/browse/RMET-3070). +- Re-implemented UpdateBackgroundJob feature (https://outsystemsrd.atlassian.net/browse/RMET-3067). +- Re-implemented SetBackgroundJob feature (https://outsystemsrd.atlassian.net/browse/RMET-3050). + +### 2024-02-09 +- Re-implemented AdvanceQuery feature (https://outsystemsrd.atlassian.net/browse/RMET-3047). + +### 2024-02-09 +- Re-implemented ListBackgroundJobs feature (https://outsystemsrd.atlassian.net/browse/RMET-3069). + +### 2024-02-08 +- Re-implement `GetLastRecord` feature: + - GetFitnessData (https://outsystemsrd.atlassian.net/browse/RMET-3048) + - GetHealthData (https://outsystemsrd.atlassian.net/browse/RMET-3065) + - GetProfileData (https://outsystemsrd.atlassian.net/browse/RMET-3066) + +### 2024-02-05 +- Re-implemented WriteProfieleData feature (https://outsystemsrd.atlassian.net/browse/RMET-3049). + +### 2024-02-01 +- Re-implemented RequestPermissions feature (https://outsystemsrd.atlassian.net/browse/RMET-3046). + ## [Version 1.4.0] ## 2023-08-25 @@ -18,6 +74,8 @@ The changes documented here do not include those from the original repository. ### 16-12-2022 - Replaced jcenter with more up to date mavenCentral [RMET-2036](https://outsystemsrd.atlassian.net/browse/RMET-2036) +## [Version 1.3.0] +- Feat: [iOS] Add a method to retrieve workouts raw data from HealthKit (https://outsystemsrd.atlassian.net/browse/RMET-2128). ## [Version 1.2.11] - Fix: [iOS] Replace the old `OSCore` framework for the new `OSCommonPluginLib` pod. diff --git a/hooks/androidCopyPreferencesPermissions.js b/hooks/androidCopyPreferencesPermissions.js new file mode 100644 index 00000000..c272ee9b --- /dev/null +++ b/hooks/androidCopyPreferencesPermissions.js @@ -0,0 +1,341 @@ +const fs = require('fs'); +const path = require('path'); +const { ConfigParser } = require('cordova-common'); +const { DOMParser, XMLSerializer } = require('xmldom'); + +const READ = "Read" +const WRITE = "Write" +const READWRITE = "ReadWrite" + +let permissions = { + HeartRate: { + variableName: "HeartRate", + readPermission: "android.permission.health.READ_HEART_RATE", + writePermission: "android.permission.health.WRITE_HEART_RATE", + configValue: undefined, + // we'll use these to know if we should write group permissions or not + wasSet: false + }, + Steps: { + variableName: "Steps", + readPermission: "android.permission.health.READ_STEPS", + writePermission: "android.permission.health.WRITE_STEPS", + configValue: undefined, + wasSet: false + }, + Weight: { + variableName: "Weight", + readPermission: "android.permission.health.READ_WEIGHT", + writePermission: "android.permission.health.WRITE_WEIGHT", + configValue: undefined, + wasSet: false + }, + Height: { + variableName: "Height", + readPermission: "android.permission.health.READ_HEIGHT", + writePermission: "android.permission.health.WRITE_HEIGHT", + configValue: undefined, + wasSet: false + }, + CaloriesBurned: { + variableName: "CaloriesBurned", + readPermission: "android.permission.health.READ_TOTAL_CALORIES_BURNED", + writePermission: "android.permission.health.WRITE_TOTAL_CALORIES_BURNED", + configValue: undefined, + wasSet: false + }, + Sleep: { + variableName: "Sleep", + readPermission: "android.permission.health.READ_SLEEP", + writePermission: "android.permission.health.WRITE_SLEEP", + configValue: undefined, + wasSet: false + }, + BloodPressure: { + variableName: "BloodPressure", + readPermission: "android.permission.health.READ_BLOOD_PRESSURE", + writePermission: "android.permission.health.WRITE_BLOOD_PRESSURE", + configValue: undefined, + wasSet: false + }, + BloodGlucose: { + variableName: "BloodGlucose", + readPermission: "android.permission.health.READ_BLOOD_GLUCOSE", + writePermission: "android.permission.health.WRITE_BLOOD_GLUCOSE", + configValue: undefined, + wasSet: false + }, + BodyFatPercentage: { + variableName: "BodyFatPercentage", + readPermission: "android.permission.health.READ_BODY_FAT", + writePermission: "android.permission.health.WRITE_BODY_FAT", + configValue: undefined, + wasSet: false + }, + BasalMetabolicRate: { + variableName: "BasalMetabolicRate", + readPermission: "android.permission.health.READ_BASAL_METABOLIC_RATE", + writePermission: "android.permission.health.WRITE_BASAL_METABOLIC_RATE", + configValue: undefined, + wasSet: false + }, + WalkingSpeed: { + variableName: "WalkingSpeed", + readPermission: "android.permission.health.READ_SPEED", + writePermission: "android.permission.health.WRITE_SPEED", + configValue: undefined, + wasSet: false + }, + Distance: { + variableName: "Distance", + readPermission: "android.permission.health.READ_DISTANCE", + writePermission: "android.permission.health.WRITE_DISTANCE", + configValue: undefined, + wasSet: false + } +} + +let groupPermissions = { + AllVariables: { + variableName: "AllVariables", + configValue: undefined, + wasSet: false, + groupVariables: [] + }, + FitnessVariables: { + variableName: "FitnessVariables", + configValue: undefined, + // we'll use these to know if we should set individual permissions or not + // e.g. when checking HeartRate, if all healthVariables were already set, we don't need to add it again + wasSet: false, + groupVariables: ["Steps", "CaloriesBurned", "WalkingSpeed", "Distance"] + }, + HealthVariables: { + variableName: "HealthVariables", + configValue: undefined, + wasSet: false, + groupVariables: ["HeartRate", "Sleep", "BloodPressure", "BloodGlucose"] + }, + ProfileVariables: { + variableName: "ProfileVariables", + configValue: undefined, + wasSet: false, + groupVariables: ["Weight", "Height", "BodyFatPercentage", "BasalMetabolicRate"] + } +} + +module.exports = async function (context) { + const projectRoot = context.opts.cordova.project ? context.opts.cordova.project.root : context.opts.projectRoot; + const configXML = path.join(projectRoot, 'config.xml'); + const configParser = new ConfigParser(configXML); + const parser = new DOMParser(); + + // add health connect permissions to AndroidManifest.xml and health_permissions.xml files + addHealthConnectPermissionsToXmlFiles(configParser, projectRoot, parser); + + // add background job permissions to AndroidManifest.xml + addBackgroundJobPermissionsToManifest(configParser, projectRoot, parser); + + // copy notification title and content for notificaiton for Foreground Service + copyNotificationContent(configParser, projectRoot, parser); +}; + +function addHealthConnectPermissionsToXmlFiles(configParser, projectRoot, parser) { + + for(const key in permissions){ + permissions[key].configValue = configParser.getPlatformPreference(permissions[key].variableName, 'android'); + } + + for(const key in groupPermissions){ + groupPermissions[key].configValue = configParser.getPlatformPreference(groupPermissions[key].variableName, 'android'); + } + + // Android >= 14 dependencies should be included directly in the AndroidManifest.xml file + // Read the AndroidManifest.xml file + const manifestFilePath = path.join(projectRoot, 'platforms/android/app/src/main/AndroidManifest.xml'); + const manifestXmlString = fs.readFileSync(manifestFilePath, 'utf-8'); + + // Parse the XML string + const manifestXmlDoc = parser.parseFromString(manifestXmlString, 'text/xml'); + + // Android <= 13 dependencies should be included in a separate XML file + // Create the health_permissions.xml file + const permissionsXmlDoc = parser.parseFromString('', 'text/xml'); + + // Get the element + const arrayElement = permissionsXmlDoc.getElementsByTagName('array')[0]; + + // process each individual variable + for(const key in permissions){ + let p = permissions[key] + if (p.configValue == READWRITE || p.configValue == READ) { + p.wasSet = true; + processPermission(manifestXmlDoc, permissionsXmlDoc, arrayElement, p.readPermission) + } + if (p.configValue == READWRITE || p.configValue == WRITE) { + p.wasSet = true; + processPermission(manifestXmlDoc, permissionsXmlDoc, arrayElement, p.writePermission) + } + } + + // process group variables + for(const key in groupPermissions){ + let p = groupPermissions[key] + if (p.configValue == READWRITE || p.configValue == READ) { + p.wasSet = true; + p.groupVariables.forEach( v => { + if (!permissions[v].wasSet) { + processPermission(manifestXmlDoc, permissionsXmlDoc, arrayElement, permissions[v].readPermission) + } + }) + } + if (p.configValue == READWRITE || p.configValue == WRITE) { + p.wasSet = true; + p.groupVariables.forEach( v => { + if (!permissions[v].wasSet) { + processPermission(manifestXmlDoc, permissionsXmlDoc, arrayElement, permissions[v].writePermission) + } + }) + } + } + + let permissionValues = Object.values(permissions) + let groupPermissionValues = Object.values(groupPermissions) + + // process AllVariables + if (groupPermissions.AllVariables.configValue == READWRITE || groupPermissions.AllVariables.configValue == READ) { + processAllVariables(manifestXmlDoc, permissionsXmlDoc, arrayElement, READ, groupPermissionValues) + + } + + if ((groupPermissions.AllVariables.configValue == READWRITE || groupPermissions.AllVariables.configValue == WRITE)) { + processAllVariables(manifestXmlDoc, permissionsXmlDoc, arrayElement, WRITE, groupPermissionValues) + } + + let numberOfPermissions = permissionValues.filter(p => p.configValue != "").length + groupPermissionValues.filter(p => p.configValue != "").length + + // if there is no AllVariables nor anything else, then by default we add all the permissions + if (numberOfPermissions == 0) { + permissionValues.forEach( p => { + processPermission(manifestXmlDoc, permissionsXmlDoc, arrayElement, p.readPermission) + processPermission(manifestXmlDoc, permissionsXmlDoc, arrayElement, p.writePermission) + }) + } + + // Serialize the updated XML document back to string + const serializer = new XMLSerializer(); + + // Android >= 14 + const updatedManifestXmlString = serializer.serializeToString(manifestXmlDoc); + + // Write the updated XML string back to the same file + fs.writeFileSync(manifestFilePath, updatedManifestXmlString, 'utf-8'); + + // Android <= 13 + const updatedPermissionsXmlString = serializer.serializeToString(permissionsXmlDoc); + const permissionsXmlFilePath = path.join(projectRoot, 'platforms/android/app/src/main/res/values/health_permissions.xml'); + + // Write the updated XML string back to the same file + fs.writeFileSync(permissionsXmlFilePath, updatedPermissionsXmlString, 'utf-8'); + +} + +function processAllVariables(manifestXmlDoc, permissionsXmlDoc, arrayElement, permissionOperation, groupPermissionsValues) { + groupPermissionsValues.forEach(p => { + p.groupVariables.forEach( v => { + if (!p.wasSet && !permissions[v].wasSet) { + processPermission(manifestXmlDoc, permissionsXmlDoc, arrayElement, permissionOperation == READ ? permissions[v].readPermission : permissions[v].writePermission) + } + }) + }) +} + +function processPermission(manifestXmlDoc, permissionsXmlDoc, arrayElement, permissionOperation) { + addEntryToManifest(manifestXmlDoc, permissionOperation) + addEntryToPermissionsXML(permissionsXmlDoc, arrayElement, permissionOperation) +} + +function addBackgroundJobPermissionsToManifest(configParser, projectRoot, parser) { + + const disableBackgroundJobs = configParser.getPlatformPreference('DisableBackgroundJobs', 'android'); + + // we want to include the permissions by default + // if disableBackgroundJobs == true then we don't want to include the permissions in the manfiest + if (disableBackgroundJobs !== "true") { + + const manifestFilePath = path.join(projectRoot, 'platforms/android/app/src/main/AndroidManifest.xml'); + const manifestXmlString = fs.readFileSync(manifestFilePath, 'utf-8'); + + // Parse the XML string + const manifestXmlDoc = parser.parseFromString(manifestXmlString, 'text/xml'); + + // add permissions to XML document + addEntryToManifest(manifestXmlDoc, 'android.permission.POST_NOTIFICATIONS') + addEntryToManifest(manifestXmlDoc, 'android.permission.ACTIVITY_RECOGNITION') + addEntryToManifest(manifestXmlDoc, 'com.google.android.gms.permission.ACTIVITY_RECOGNITION') // necessary for API 28 and below + addEntryToManifest(manifestXmlDoc, 'android.permission.FOREGROUND_SERVICE') + addEntryToManifest(manifestXmlDoc, 'android.permission.FOREGROUND_SERVICE_HEALTH') + addEntryToManifest(manifestXmlDoc, 'android.permission.HIGH_SAMPLING_RATE_SENSORS') + addEntryToManifest(manifestXmlDoc, 'android.permission.SCHEDULE_EXACT_ALARM') + + // serialize the updated XML document back to string + const serializer = new XMLSerializer(); + const updatedManifestXmlString = serializer.serializeToString(manifestXmlDoc); + + // write the updated XML string back to the same file + fs.writeFileSync(manifestFilePath, updatedManifestXmlString, 'utf-8'); + } + +} + +function addEntryToManifest(manifestXmlDoc, permission) { + const newPermission = manifestXmlDoc.createElement('uses-permission'); + newPermission.setAttribute('android:name', permission); + manifestXmlDoc.documentElement.appendChild(newPermission); +} + +function addEntryToPermissionsXML(permissionsXmlDoc, arrayElement, permission) { + const newItem = permissionsXmlDoc.createElement('item'); + const textNode = permissionsXmlDoc.createTextNode(permission); + newItem.appendChild(textNode); + arrayElement.appendChild(newItem); +} + +function copyNotificationContent(configParser, projectRoot, parser) { + + // get values from config.xml + var notificationTitle = configParser.getPlatformPreference('BackgroundNotificationTitle', 'android'); + var notificationDescription = configParser.getPlatformPreference('BackgroundNotificationDescription', 'android'); + + if (notificationTitle == "") { + notificationTitle = "Measuring your health and fitness data." + } + + if (notificationDescription == "") { + notificationDescription = "The app is running in the background." + } + + // insert values in strings.xml + const stringsXmlPath = path.join(projectRoot, 'platforms/android/app/src/main/res/values/strings.xml'); + const stringsXmlString = fs.readFileSync(stringsXmlPath, 'utf-8'); + const stringsXmlDoc = parser.parseFromString(stringsXmlString, 'text/xml') + const stringElements = stringsXmlDoc.getElementsByTagName('string'); + + // set text for each element + for (let i = 0; i < stringElements.length; i++) { + const name = stringElements[i].getAttribute('name'); + if (name == "background_notification_title") { + stringElements[i].textContent = notificationTitle; + } + else if (name == "background_notification_description") { + stringElements[i].textContent = notificationDescription; + } + } + + // serialize the updated XML document back to string + const serializer = new XMLSerializer(); + const updatedXmlString = serializer.serializeToString(stringsXmlDoc); + + // write the updated XML string back to the same file + fs.writeFileSync(stringsXmlPath, updatedXmlString, 'utf-8'); +} diff --git a/hooks/androidCopyPrivacyUrlEnv.js b/hooks/androidCopyPrivacyUrlEnv.js new file mode 100644 index 00000000..ec290c29 --- /dev/null +++ b/hooks/androidCopyPrivacyUrlEnv.js @@ -0,0 +1,60 @@ +"use strict"; + +const fs = require('fs'); +const path = require('path'); +const { ConfigParser } = require('cordova-common'); +const et = require('elementtree'); +const { fileExists } = require('./utils'); +let fileNamePrivacyPolicy = "HealthConnect_PrivacyPolicy.txt"; + +module.exports = async function (context) { + const projectRoot = context.opts.cordova.project ? context.opts.cordova.project.root : context.opts.projectRoot; + const platformPath = path.join(projectRoot, `platforms/android/app/src/main/assets/www/${fileNamePrivacyPolicy}`); + + if (fileExists(platformPath) || policyFileExists()) { + const configXML = path.join(projectRoot, 'config.xml'); + const configParser = new ConfigParser(configXML); + + setPrivacyPolicyUrl(configParser, projectRoot); + } else { + throw new Error("Privacy Policy file not found in the resources folder."); + } +}; + +function setPrivacyPolicyUrl(configParser, projectRoot) { + const hostname = configParser.getPreference('hostname', 'android'); + const applicationNameUrl = configParser.getPreference('DefaultApplicationURL', 'android'); + + if (hostname && applicationNameUrl) { + const url = `https://${hostname}/${applicationNameUrl}/${fileNamePrivacyPolicy}`; + const stringsPath = path.join(projectRoot, 'platforms/android/app/src/main/res/values/strings.xml'); + const stringsFile = fs.readFileSync(stringsPath).toString(); + const etreeStrings = et.parse(stringsFile); + + let privacyPolicyUrl = etreeStrings.find('./string[@name="privacy_policy_url"]'); + if (!privacyPolicyUrl) { + console.error('Privacy policy URL string not found in strings.xml'); + return; + } + privacyPolicyUrl.text = url; + const resultXmlStrings = etreeStrings.write({method: 'xml'}); + fs.writeFileSync(stringsPath, resultXmlStrings); + } else { + throw new Error("Error getting the environment variables."); + } +} + +function policyFileExists() { + const directoryPath = 'platforms/android/app/src/main/assets/www'; + const searchString = 'HealthConnect_PrivacyPolicy'; + try { + const files = fs.readdirSync(directoryPath); + const matchingFiles = files.filter(fileName => fileName.includes(searchString)); + + // return true if there are matching files, false otherwise + return matchingFiles.length > 0; + } catch (error) { + console.error('An error occurred:', error); + return false; + } +} diff --git a/hooks/utils.js b/hooks/utils.js new file mode 100644 index 00000000..1b92dea1 --- /dev/null +++ b/hooks/utils.js @@ -0,0 +1,11 @@ +"use strict" + +var fs = require("fs"); + +function fileExists(filePath) { + return fs.existsSync(filePath); +} + +module.exports = { + fileExists +}; diff --git a/package.json b/package.json index ee9bb79f..96f20b20 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.outsystems.plugins.healthfitness", - "version": "1.4.0", + "version": "2.0.0", "description": "Health & Fitness cordova plugin for OutSystems applications.", "keywords": [ "ecosystem:cordova", @@ -14,5 +14,8 @@ "ios" ] }, - "engines": [] + "engines": [], + "dependencies": { + "xmldom": "^0.6.0" + } } diff --git a/plugin.xml b/plugin.xml index cfc36e3e..465b88ef 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + HealthFitness Health & Fitness cordova plugin for OutSystems applications. OutSystems Inc @@ -8,6 +8,9 @@ + + + @@ -18,12 +21,11 @@ - - - - - - + + PRIVACY_POLICY_URL + + + diff --git a/src/android/build.gradle b/src/android/build.gradle index d3f5ff46..6b2f368a 100644 --- a/src/android/build.gradle +++ b/src/android/build.gradle @@ -17,16 +17,30 @@ allprojects { } dependencies{ - implementation("com.google.android.gms:play-services-fitness:20.0.0") implementation("com.google.android.gms:play-services-auth:19.2.0") - implementation("com.google.code.gson:gson:2.8.8") + implementation("com.google.android.gms:play-services-location:19.0.1") + implementation("com.google.code.gson:gson:2.8.9") implementation 'com.google.code.findbugs:jsr305:1.3.9' implementation("com.github.outsystems:oscore-android:1.2.0@aar") - implementation("com.github.outsystems:oscordova-android:1.2.0@aar") - implementation("com.github.outsystems:oshealthfitness-android:1.2.0@aar") + implementation("com.github.outsystems:oscordova-android:2.0.1@aar") + implementation("com.github.outsystems:oshealthfitness-android:2.0.0@aar") implementation("com.github.outsystems:osnotificationpermissions-android:0.0.4@aar") + + // activity + implementation "androidx.activity:activity-ktx:1.8.2" + + // appcompact + implementation "androidx.appcompat:appcompat:1.6.1" + + // health connect sdk + implementation "androidx.health.connect:connect-client:1.1.0-alpha07" + + // compose + implementation 'androidx.activity:activity-compose:1.8.2' + implementation 'androidx.compose.material3:material3:1.2.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0' def roomVersion = "2.4.2" implementation("androidx.room:room-runtime:$roomVersion") diff --git a/src/android/com/outsystems/plugins/healthfitness/OSHealthFitness.kt b/src/android/com/outsystems/plugins/healthfitness/OSHealthFitness.kt index e2b11d66..9d787f0a 100755 --- a/src/android/com/outsystems/plugins/healthfitness/OSHealthFitness.kt +++ b/src/android/com/outsystems/plugins/healthfitness/OSHealthFitness.kt @@ -1,10 +1,12 @@ package com.outsystems.plugins.healthfitness import android.Manifest +import android.app.AlarmManager +import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.os.Build import android.os.Build.VERSION.SDK_INT +import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM import androidx.core.content.ContextCompat import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability @@ -13,23 +15,77 @@ import com.outsystems.osnotificationpermissions.* import com.outsystems.plugins.healthfitness.background.BackgroundJobParameters import com.outsystems.plugins.healthfitness.background.DatabaseManager import com.outsystems.plugins.healthfitness.background.UpdateBackgroundJobParameters +import com.outsystems.plugins.healthfitness.data.Constants +import com.outsystems.plugins.healthfitness.data.HealthFitnessError +import com.outsystems.plugins.healthfitness.data.HealthRecord +import com.outsystems.plugins.healthfitness.data.types.HealthAdvancedQueryParameters +import com.outsystems.plugins.healthfitness.data.types.HealthFitnessGroupPermission +import com.outsystems.plugins.healthfitness.data.types.HealthFitnessPermission +import com.outsystems.plugins.healthfitness.helpers.ActivityTransitionHelper +import com.outsystems.plugins.healthfitness.helpers.AlarmManagerHelper +import com.outsystems.plugins.healthfitness.helpers.HealthConnectHelper +import com.outsystems.plugins.healthfitness.repository.HealthConnectRepository import com.outsystems.plugins.healthfitness.store.* +import com.outsystems.plugins.healthfitness.viewmodel.HealthConnectDataManager +import com.outsystems.plugins.healthfitness.viewmodel.HealthConnectViewModel import com.outsystems.plugins.oscordova.CordovaImplementation import org.apache.cordova.* import org.json.JSONArray +import org.json.JSONException class OSHealthFitness : CordovaImplementation() { override var callbackContext: CallbackContext? = null - var healthStore: HealthStoreInterface? = null val gson by lazy { Gson() } - var notificationPermissions = OSNotificationPermissions() + private lateinit var healthConnectViewModel: HealthConnectViewModel + private lateinit var healthConnectRepository: HealthConnectRepository + private lateinit var healthConnectDataManager: HealthConnectDataManager + private lateinit var healthConnectHelper: HealthConnectHelper + private lateinit var alarmManagerHelper: AlarmManagerHelper + private lateinit var activityTransitionHelper: ActivityTransitionHelper + private lateinit var backgroundParameters: BackgroundJobParameters + + private lateinit var alarmManager: AlarmManager + + // we need this variable because onResume is being called when + // returning from the SCHEDULE_EXACT_ALARM permission screen + private var requestingExactAlarmPermission = false + + // variables to hold foreground notification title and description + // these values are defined in build time so we only need to read + // them once on the initialize method + private lateinit var foregroundNotificationTitle: String + private lateinit var foregroundNotificationDescription: String override fun initialize(cordova: CordovaInterface, webView: CordovaWebView) { super.initialize(cordova, webView) - val manager = HealthFitnessManager(cordova.context, cordova.activity) val database = DatabaseManager(cordova.context) - healthStore = HealthStore(cordova.context.applicationContext.packageName, manager, database) + + healthConnectDataManager = HealthConnectDataManager(database) + healthConnectRepository = HealthConnectRepository(healthConnectDataManager) + healthConnectHelper = HealthConnectHelper() + alarmManagerHelper = AlarmManagerHelper() + activityTransitionHelper = ActivityTransitionHelper() + healthConnectViewModel = + HealthConnectViewModel(healthConnectRepository, healthConnectHelper, alarmManagerHelper, activityTransitionHelper) + alarmManager = getContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager + + // get foreground notification title and description from resources (strings.xml) + foregroundNotificationTitle = getContext().resources.getString( + getActivity().resources.getIdentifier( + "background_notification_title", + "string", + getActivity().packageName + ) + ) + foregroundNotificationDescription = getContext().resources.getString( + getActivity().resources.getIdentifier( + "background_notification_description", + "string", + getActivity().packageName + ) + ) + } override fun execute( @@ -37,10 +93,11 @@ class OSHealthFitness : CordovaImplementation() { args: JSONArray, callbackContext: CallbackContext ): Boolean { + this.callbackContext = callbackContext - if(!areGooglePlayServicesAvailable()) { - return false; + if (!areGooglePlayServicesAvailable()) { + return false } when (action) { @@ -68,77 +125,81 @@ class OSHealthFitness : CordovaImplementation() { "updateBackgroundJob" -> { updateBackgroundJob(args) } - "disconnectFromGoogleFit" -> { - disconnectFromGoogleFit() + "disconnectFromHealthConnect" -> { + disconnectFromHealthConnect() + } + "openHealthConnect" -> { + openHealthConnect() } } return true } - //create array of permission oauth - private fun initAndRequestPermissions(args : JSONArray) { - val customPermissions = args.getString(0) - val allVariables = args.getString(1) - val fitnessVariables = args.getString(2) - val healthVariables = args.getString(3) - val profileVariables = args.getString(4) - val summaryVariables = args.getString(5) + // onResume is called when returning from the SCHEDULE_EXACT_ALARM permission screen + override fun onResume(multitasking: Boolean) { + if (requestingExactAlarmPermission) { + requestingExactAlarmPermission = false + onScheduleExactAlarmPermissionResult() + } + } + private fun initAndRequestPermissions(args: JSONArray) { try { - healthStore?.initAndRequestPermissions( - customPermissions, - allVariables, - fitnessVariables, - healthVariables, - profileVariables, - summaryVariables) - checkAndGrantPermissions() - } - catch (hse : HealthStoreException) { + healthConnectViewModel.initAndRequestPermissions( + getActivity(), + gson.fromJson(args.getString(0), Array::class.java), + gson.fromJson(args.getString(1), HealthFitnessGroupPermission::class.java), + gson.fromJson(args.getString(2), HealthFitnessGroupPermission::class.java), + gson.fromJson(args.getString(3), HealthFitnessGroupPermission::class.java), + gson.fromJson(args.getString(4), HealthFitnessGroupPermission::class.java), + privacyPolicyUrl = getActivity().resources.getString(getActivity().resources.getIdentifier("privacy_policy_url", "string", getActivity().packageName)), + { + setAsActivityResultCallback() + }, + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) + } + ) + } catch (hse: HealthStoreException) { sendPluginResult(null, Pair(hse.error.code.toString(), hse.error.message)) + } catch (e: JSONException) { + sendPluginResult( + null, + Pair( + HealthFitnessError.PARSING_PARAMETERS_ERROR.code.toString(), + HealthFitnessError.PARSING_PARAMETERS_ERROR.message + ) + ) + } catch (e: Exception) { + sendPluginResult( + null, + Pair( + HealthFitnessError.REQUEST_PERMISSIONS_GENERAL_ERROR.code.toString(), + HealthFitnessError.REQUEST_PERMISSIONS_GENERAL_ERROR.message + ) + ) } } + private fun areAndroidPermissionsGranted(permissions: List): Boolean { permissions.forEach { - if (ContextCompat.checkSelfPermission(cordova.activity, it) != PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission( + getActivity(), + it + ) != PackageManager.PERMISSION_GRANTED + ) { return false } } return true } - private fun checkAndGrantPermissions(){ - val permissions = mutableListOf( - Manifest.permission.ACCESS_FINE_LOCATION, - Manifest.permission.BODY_SENSORS - ) - - if(SDK_INT >= Build.VERSION_CODES.Q) { - permissions.add(Manifest.permission.ACTIVITY_RECOGNITION) - } - - if (areAndroidPermissionsGranted(permissions)) { - if(!healthStore!!.areGoogleFitPermissionsGranted()){ - setAsActivityResultCallback() - } - if(healthStore?.requestGoogleFitPermissions() == true) { - sendPluginResult("success") - } - } - else { - PermissionHelper.requestPermissions( - this, - ACTIVITY_LOCATION_PERMISSIONS_REQUEST_CODE, - permissions.toTypedArray() - ) - } - } - - private fun advancedQuery(args : JSONArray) { - val parameters = gson.fromJson(args.getString(0), AdvancedQueryParameters::class.java) - healthStore?.advancedQueryAsync( + private fun advancedQuery(args: JSONArray) { + val parameters = gson.fromJson(args.getString(0), HealthAdvancedQueryParameters::class.java) + healthConnectViewModel.advancedQuery( parameters, + getContext(), { response -> val pluginResponseJson = gson.toJson(response) sendPluginResult(pluginResponseJson) @@ -150,94 +211,161 @@ class OSHealthFitness : CordovaImplementation() { } private fun writeData(args: JSONArray) { + try { + val variable = args.getString(0) + val healthRecord = HealthRecord.valueOf(variable) + val value = args.getDouble(1) - //process parameters - val variable = args.getString(0) - val value = args.getDouble(1).toFloat() + healthConnectViewModel.writeData( + healthRecord, + value, + getActivity().packageName, + { + sendPluginResult("success", null) + }, + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) + } + ) + } catch (e: Exception) { + sendPluginResult(null, Pair(HealthFitnessError.VARIABLE_NOT_AVAILABLE_ERROR.code.toString(), HealthFitnessError.VARIABLE_NOT_AVAILABLE_ERROR.message)) + } + } - healthStore?.updateDataAsync( - variable, - value, - { response -> - sendPluginResult(response) - }, - { error -> - sendPluginResult(null, Pair(error.code.toString(), error.message)) + private fun getLastRecord(args: JSONArray) { + try { + healthConnectViewModel.getLastRecord( + HealthRecord.valueOf(args.getString(0)), + { + sendPluginResult(it, null) + }, + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) + } + ) + } catch (e: Exception) { + sendPluginResult(null, Pair(HealthFitnessError.VARIABLE_NOT_AVAILABLE_ERROR.code.toString(), HealthFitnessError.VARIABLE_NOT_AVAILABLE_ERROR.message)) + } + + } + + /** + * Navigates to the permission screen for exact alarms or + * skips it and request the other necessary permissions. + * Also stores the background job parameters in a global variable to be used later. + */ + private fun setBackgroundJob(args: JSONArray) { + // save arguments for later use + backgroundParameters = gson.fromJson(args.getString(0), BackgroundJobParameters::class.java) + + //request permission for exact alarms if necessary + if (!Constants.ACTIVITY_VARIABLES.contains(backgroundParameters.variable) && SDK_INT >= 31 && !alarmManager.canScheduleExactAlarms()) { + requestingExactAlarmPermission = true + // we only need to request this permission if exact alarms need to be used + // when the variable is an activity variable (e.g. steps), + // we use the Activity Recognition Transition API instead of exact alarms. + getContext().startActivity(Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM)) + } else { // we can move on to other permissions if we don't need to request exact alarm permissions + requestBackgroundJobPermissions() + } + } + + /** + * Requests the POST_NOTIFICATIONS and ACTIVITY_RECOGNITION permissions. + */ + private fun requestBackgroundJobPermissions() { + val permissions = mutableListOf().apply { + if (SDK_INT >= 33) { + add(Manifest.permission.POST_NOTIFICATIONS) + } + if (SDK_INT >= 29) { + add(Manifest.permission.ACTIVITY_RECOGNITION) } + }.toTypedArray() - ) + PermissionHelper.requestPermissions(this, BACKGROUND_JOB_PERMISSIONS_REQUEST_CODE, permissions) } - private fun getLastRecord(args: JSONArray) { - //process parameters - val variable = args.getString(0) - healthStore?.getLastRecordAsync( - variable, - { response -> - val pluginResponseJson = gson.toJson(response) - sendPluginResult(pluginResponseJson) - }, - { error -> - sendPluginResult(null, Pair(error.code.toString(), error.message)) - }) + /** + * Handles user response to exact alarm permission request. + * + */ + private fun onScheduleExactAlarmPermissionResult() { + val permissionDenied = SDK_INT >= 31 && !alarmManager.canScheduleExactAlarms() + if (permissionDenied) { + // send plugin result with error + sendPluginResult( + null, + Pair( + HealthFitnessError.BACKGROUND_JOB_EXACT_ALARM_PERMISSION_DENIED_ERROR.code.toString(), + HealthFitnessError.BACKGROUND_JOB_EXACT_ALARM_PERMISSION_DENIED_ERROR.message + ) + ) + return + } + requestBackgroundJobPermissions() } - private fun setBackgroundJob(args: JSONArray) { - notificationPermissions.requestNotificationPermission(this, ACTIVITY_NOTIFICATION_PERMISSIONS_REQUEST_CODE) - - //process parameters - val parameters = gson.fromJson(args.getString(0), BackgroundJobParameters::class.java) - healthStore?.setBackgroundJob( + /** + * Sets a background job by calling the setBackgroundJob method of the ViewModel + */ + private fun setBackgroundJobWithParameters(parameters: BackgroundJobParameters) { + healthConnectViewModel.setBackgroundJob( parameters, - { response -> - sendPluginResult(response) + foregroundNotificationTitle, + foregroundNotificationDescription, + getContext(), + { + sendPluginResult("success", null) }, - { error -> - sendPluginResult(null, Pair(error.code.toString(), error.message)) + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) } ) } private fun deleteBackgroundJob(args: JSONArray) { - val parameters = args.getString(0) - healthStore?.deleteBackgroundJob( - parameters, - { response -> - sendPluginResult(response) + val jobId = args.getString(0) + healthConnectViewModel.deleteBackgroundJob( + jobId, + getContext(), + { + sendPluginResult("success", null) }, - { error -> - sendPluginResult(null, Pair(error.code.toString(), error.message)) + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) } ) } - + private fun listBackgroundJobs() { - healthStore?.listBackgroundJobs( - { response -> - val pluginResponseJson = gson.toJson(response) + healthConnectViewModel.listBackgroundJobs( + { + val pluginResponseJson = gson.toJson(it) sendPluginResult(pluginResponseJson) }, - { error -> - sendPluginResult(null, Pair(error.code.toString(), error.message)) + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) } ) } private fun updateBackgroundJob(args: JSONArray) { val parameters = gson.fromJson(args.getString(0), UpdateBackgroundJobParameters::class.java) - healthStore?.updateBackgroundJob( + healthConnectViewModel.updateBackgroundJob( parameters, - { response -> - sendPluginResult(response) + { + sendPluginResult("success", null) }, - { error -> - sendPluginResult(null, Pair(error.code.toString(), error.message)) + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) } ) } - - private fun disconnectFromGoogleFit() { - healthStore?.disconnectFromGoogleFit( + + private fun disconnectFromHealthConnect() { + healthConnectViewModel.disconnectFromHealthConnect( + getActivity(), { sendPluginResult("success", null) }, @@ -247,28 +375,47 @@ class OSHealthFitness : CordovaImplementation() { ) } - override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent) { - //super.onActivityResult(requestCode, resultCode, intent) - try { - healthStore?.handleActivityResult(requestCode, resultCode, intent) - } - catch(hse : HealthStoreException) { - val error = hse.error - sendPluginResult(null, Pair(error.code.toString(), error.message)) - } + private fun openHealthConnect() { + healthConnectViewModel.openHealthConnect( + getContext(), + { + sendPluginResult("success", null) + }, + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) + } + ) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { + super.onActivityResult(requestCode, resultCode, intent) + healthConnectViewModel.handleActivityResult(requestCode, resultCode, intent, + { + sendPluginResult("success", null) + }, + { + sendPluginResult(null, Pair(it.code.toString(), it.message)) + } + ) } override fun areGooglePlayServicesAvailable(): Boolean { val googleApiAvailability = GoogleApiAvailability.getInstance() - val status = googleApiAvailability.isGooglePlayServicesAvailable(cordova.activity) + val status = googleApiAvailability.isGooglePlayServicesAvailable(getActivity()) if (status != ConnectionResult.SUCCESS) { var result: Pair? = null result = if (googleApiAvailability.isUserResolvableError(status)) { - googleApiAvailability.getErrorDialog(cordova.activity, status, 1)?.show() - Pair(HealthFitnessError.GOOGLE_SERVICES_RESOLVABLE_ERROR.code.toString(), HealthFitnessError.GOOGLE_SERVICES_RESOLVABLE_ERROR.message) + googleApiAvailability.getErrorDialog(getActivity(), status, 1)?.show() + Pair( + HealthFitnessError.GOOGLE_SERVICES_RESOLVABLE_ERROR.code.toString(), + HealthFitnessError.GOOGLE_SERVICES_RESOLVABLE_ERROR.message + ) } else { - Pair(HealthFitnessError.GOOGLE_SERVICES_ERROR.code.toString(), HealthFitnessError.GOOGLE_SERVICES_ERROR.message) + Pair( + HealthFitnessError.GOOGLE_SERVICES_ERROR.code.toString(), + HealthFitnessError.GOOGLE_SERVICES_ERROR.message + ) } sendPluginResult(null, result) return false @@ -279,23 +426,28 @@ class OSHealthFitness : CordovaImplementation() { override fun onRequestPermissionResult( requestCode: Int, permissions: Array, - grantResults: IntArray) { + grantResults: IntArray + ) { when (requestCode) { - ACTIVITY_LOCATION_PERMISSIONS_REQUEST_CODE -> { - // If request is cancelled, the result arrays are empty. - if (grantResults.isNotEmpty() && - grantResults[0] == PackageManager.PERMISSION_GRANTED - ) { - checkAndGrantPermissions() - } else { + BACKGROUND_JOB_PERMISSIONS_REQUEST_CODE -> { + for (result in grantResults) { + if (result == PackageManager.PERMISSION_DENIED) { + sendPluginResult( + null, + Pair( + HealthFitnessError.BACKGROUND_JOB_PERMISSIONS_DENIED_ERROR.code.toString(), + HealthFitnessError.BACKGROUND_JOB_PERMISSIONS_DENIED_ERROR.message + ) + ) + return + } } - return + setBackgroundJobWithParameters(backgroundParameters) } } } companion object { - const val ACTIVITY_LOCATION_PERMISSIONS_REQUEST_CODE = 1 - const val ACTIVITY_NOTIFICATION_PERMISSIONS_REQUEST_CODE = 2 + const val BACKGROUND_JOB_PERMISSIONS_REQUEST_CODE = 2 } } \ No newline at end of file diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeDirectory b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeDirectory new file mode 100644 index 00000000..5bc08c34 Binary files /dev/null and b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeDirectory differ diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeRequirements b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeRequirements new file mode 100644 index 00000000..799807a4 Binary files /dev/null and b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeRequirements differ diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeRequirements-1 b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeRequirements-1 new file mode 100644 index 00000000..8ea874ed Binary files /dev/null and b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeRequirements-1 differ diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeResources b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeResources new file mode 100644 index 00000000..de7d57c7 --- /dev/null +++ b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeResources @@ -0,0 +1,563 @@ + + + + + files + + ios-arm64/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel 2.mom + + WmEy7tCqK7VdDvPp1L+Zcjcv15A= + + ios-arm64/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel 2.omo + + ajrZlaH1WIiSvtK4zjYmN1cfJGg= + + ios-arm64/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel.mom + + W6x/EHmIpxBSwBMDHQE5fB5k6os= + + ios-arm64/OSHealthFitnessLib.framework/BackgroundModel.momd/VersionInfo.plist + + XiC0ivyLo+mC+FqE+19bvMOLCgI= + + ios-arm64/OSHealthFitnessLib.framework/Headers/OSHealthFitnessLib-Swift.h + + tFh7jOwfvsXhkmxPjq/nCoGuxbo= + + ios-arm64/OSHealthFitnessLib.framework/Info.plist + + hymD3FrEV6d/JaIyz8WZ1EcbbUw= + + ios-arm64/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios.abi.json + + vhy6jg03VOYB5O2yjEoA5QhssDY= + + ios-arm64/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios.private.swiftinterface + + 5L5A07FVaUIJD1lME3KxbDKm6i8= + + ios-arm64/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios.swiftdoc + + JbJUJqorr7ZbNbYJqqYWMWq+V84= + + ios-arm64/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios.swiftinterface + + 5L5A07FVaUIJD1lME3KxbDKm6i8= + + ios-arm64/OSHealthFitnessLib.framework/Modules/module.modulemap + + sw87f2yJ3lU2kd25gEKxIZALetM= + + ios-arm64/OSHealthFitnessLib.framework/OSHealthFitnessLib + + uJrT8ZJyE2dTWzQ1gsydYL5ZHoo= + + ios-arm64/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy + + MiuKzpVIs0pYdHP2HzUd3nlWjzM= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel 2.mom + + WmEy7tCqK7VdDvPp1L+Zcjcv15A= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel 2.omo + + ajrZlaH1WIiSvtK4zjYmN1cfJGg= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel.mom + + W6x/EHmIpxBSwBMDHQE5fB5k6os= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/BackgroundModel.momd/VersionInfo.plist + + XiC0ivyLo+mC+FqE+19bvMOLCgI= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Headers/OSHealthFitnessLib-Swift.h + + Tzi/2WcpwfKL4Ps8ZU7n6sHzJks= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Info.plist + + OlBN7pQMeLN9ByFYKpm0E/JK1KI= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.abi.json + + vhy6jg03VOYB5O2yjEoA5QhssDY= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface + + lxY3837m7v+jDwXrqL5APkU3b2s= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + fZYiyx+/nzg0xqorND2Rfo7dd2k= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface + + lxY3837m7v+jDwXrqL5APkU3b2s= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/x86_64-apple-ios-simulator.abi.json + + vhy6jg03VOYB5O2yjEoA5QhssDY= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + dyXCAwDZuYrRMZSETrDm77QfhYQ= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + yze2Ljf7/YaHLG3a+vjW1sMIp6Q= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + dyXCAwDZuYrRMZSETrDm77QfhYQ= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/module.modulemap + + sw87f2yJ3lU2kd25gEKxIZALetM= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/OSHealthFitnessLib + + cKFP4c+JZDhH+KYadY4FSxqP22M= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy + + MiuKzpVIs0pYdHP2HzUd3nlWjzM= + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/_CodeSignature/CodeResources + + YtGbLkVkBjDjKDVuX3aS2NG1YRY= + + + files2 + + ios-arm64/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel 2.mom + + hash + + WmEy7tCqK7VdDvPp1L+Zcjcv15A= + + hash2 + + R4LfpjsuiJ+AuPKAvLjBqjrmeFDFxB7VVraRKh8eNBY= + + + ios-arm64/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel 2.omo + + hash + + ajrZlaH1WIiSvtK4zjYmN1cfJGg= + + hash2 + + vd3kQHM3ZF1tQrX39KE2AsvW8IBF8a+4a2+459bZvk0= + + + ios-arm64/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel.mom + + hash + + W6x/EHmIpxBSwBMDHQE5fB5k6os= + + hash2 + + qRJigHLv9rlYsVac/moYMl5ly9UbGTmlNInvZGu7yeU= + + + ios-arm64/OSHealthFitnessLib.framework/BackgroundModel.momd/VersionInfo.plist + + hash + + XiC0ivyLo+mC+FqE+19bvMOLCgI= + + hash2 + + 9Wei4k8LCmjUieFX4fzHhZSc+DoajysUC8LuTDd52IY= + + + ios-arm64/OSHealthFitnessLib.framework/Headers/OSHealthFitnessLib-Swift.h + + hash + + tFh7jOwfvsXhkmxPjq/nCoGuxbo= + + hash2 + + vNT8+lqW6PkwQRPttcnTQLHIjBIeD5ERyRM0qEC0dVs= + + + ios-arm64/OSHealthFitnessLib.framework/Info.plist + + hash + + hymD3FrEV6d/JaIyz8WZ1EcbbUw= + + hash2 + + TqDxrB0jxcaArOvfnWpbi9aaRZ4RN+NcmFPFZyZJhlI= + + + ios-arm64/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios.abi.json + + hash + + vhy6jg03VOYB5O2yjEoA5QhssDY= + + hash2 + + xUYeELV1++5cy/tsWv++R6V7LZ1FgDg+N4864w3qyPs= + + + ios-arm64/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios.private.swiftinterface + + hash + + 5L5A07FVaUIJD1lME3KxbDKm6i8= + + hash2 + + pKaiDC/O/A05qM8c3FXoMkNdetRzUn8lME2j8U/o9IE= + + + ios-arm64/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios.swiftdoc + + hash + + JbJUJqorr7ZbNbYJqqYWMWq+V84= + + hash2 + + irAYQkwaczEfAdWuH8B4+Z+foraBbKTbO8WpSVi/e0A= + + + ios-arm64/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios.swiftinterface + + hash + + 5L5A07FVaUIJD1lME3KxbDKm6i8= + + hash2 + + pKaiDC/O/A05qM8c3FXoMkNdetRzUn8lME2j8U/o9IE= + + + ios-arm64/OSHealthFitnessLib.framework/Modules/module.modulemap + + hash + + sw87f2yJ3lU2kd25gEKxIZALetM= + + hash2 + + P7SE820E52XFwuSc9IyT2YlJFy1uZqPxi8tOPOxwGfg= + + + ios-arm64/OSHealthFitnessLib.framework/OSHealthFitnessLib + + hash + + uJrT8ZJyE2dTWzQ1gsydYL5ZHoo= + + hash2 + + 2nCWtsV5vs+n14nQpvSsnNKKmnugQYzVhPql/eF0VpQ= + + + ios-arm64/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy + + hash + + MiuKzpVIs0pYdHP2HzUd3nlWjzM= + + hash2 + + tJsHvjtv5Yj/fAdXa5lrQhNxNvF1CYow/Bte4ARYb4I= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel 2.mom + + hash + + WmEy7tCqK7VdDvPp1L+Zcjcv15A= + + hash2 + + R4LfpjsuiJ+AuPKAvLjBqjrmeFDFxB7VVraRKh8eNBY= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel 2.omo + + hash + + ajrZlaH1WIiSvtK4zjYmN1cfJGg= + + hash2 + + vd3kQHM3ZF1tQrX39KE2AsvW8IBF8a+4a2+459bZvk0= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/BackgroundModel.momd/BackgroundModel.mom + + hash + + W6x/EHmIpxBSwBMDHQE5fB5k6os= + + hash2 + + qRJigHLv9rlYsVac/moYMl5ly9UbGTmlNInvZGu7yeU= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/BackgroundModel.momd/VersionInfo.plist + + hash + + XiC0ivyLo+mC+FqE+19bvMOLCgI= + + hash2 + + 9Wei4k8LCmjUieFX4fzHhZSc+DoajysUC8LuTDd52IY= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Headers/OSHealthFitnessLib-Swift.h + + hash + + Tzi/2WcpwfKL4Ps8ZU7n6sHzJks= + + hash2 + + 37W0F4g9c0cOUjZ4zv35kFosbGcfrrLN0Fi35ttffWY= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Info.plist + + hash + + OlBN7pQMeLN9ByFYKpm0E/JK1KI= + + hash2 + + GPl1xNhlKlNqJqhvPPPt/DsxBWbgaxe4ZTwTtMe6H5w= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.abi.json + + hash + + vhy6jg03VOYB5O2yjEoA5QhssDY= + + hash2 + + xUYeELV1++5cy/tsWv++R6V7LZ1FgDg+N4864w3qyPs= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface + + hash + + lxY3837m7v+jDwXrqL5APkU3b2s= + + hash2 + + WYiBdk+I7U8Pxk4rkg0ihFDHE8iWSNW4izebMztdSGA= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.swiftdoc + + hash + + fZYiyx+/nzg0xqorND2Rfo7dd2k= + + hash2 + + B4o2qQ3qSCDLeOKuTN97+QK/02u8/9jDksMfnRR9Pso= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.swiftinterface + + hash + + lxY3837m7v+jDwXrqL5APkU3b2s= + + hash2 + + WYiBdk+I7U8Pxk4rkg0ihFDHE8iWSNW4izebMztdSGA= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/x86_64-apple-ios-simulator.abi.json + + hash + + vhy6jg03VOYB5O2yjEoA5QhssDY= + + hash2 + + xUYeELV1++5cy/tsWv++R6V7LZ1FgDg+N4864w3qyPs= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface + + hash + + dyXCAwDZuYrRMZSETrDm77QfhYQ= + + hash2 + + Uh7kksBEhd2YGS1lLGC0CNDrJe9WXnGu9+8mlrJCOWs= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/x86_64-apple-ios-simulator.swiftdoc + + hash + + yze2Ljf7/YaHLG3a+vjW1sMIp6Q= + + hash2 + + MU/Yaw9Xd8dvRYhZ6+yRxp0lgReWigV+Q04ycLpfSbY= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/OSHealthFitnessLib.swiftmodule/x86_64-apple-ios-simulator.swiftinterface + + hash + + dyXCAwDZuYrRMZSETrDm77QfhYQ= + + hash2 + + Uh7kksBEhd2YGS1lLGC0CNDrJe9WXnGu9+8mlrJCOWs= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Modules/module.modulemap + + hash + + sw87f2yJ3lU2kd25gEKxIZALetM= + + hash2 + + P7SE820E52XFwuSc9IyT2YlJFy1uZqPxi8tOPOxwGfg= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/OSHealthFitnessLib + + hash + + cKFP4c+JZDhH+KYadY4FSxqP22M= + + hash2 + + yKPto2v6HemlmGb7M5LUjI8D7VPnk7Bs85i9DvbL+eI= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy + + hash + + MiuKzpVIs0pYdHP2HzUd3nlWjzM= + + hash2 + + tJsHvjtv5Yj/fAdXa5lrQhNxNvF1CYow/Bte4ARYb4I= + + + ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/_CodeSignature/CodeResources + + hash + + YtGbLkVkBjDjKDVuX3aS2NG1YRY= + + hash2 + + ITOUOiQb8+QEv9LQ2NNGNv3L6rLR9q95EE7lYbW+TNo= + + + + rules + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^.* + + ^.*\.lproj/ + + optional + + weight + 1000 + + ^.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Base\.lproj/ + + weight + 1010 + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeSignature b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeSignature new file mode 100644 index 00000000..0355c148 Binary files /dev/null and b/src/ios/frameworks/OSHealthFitnessLib.xcframework/_CodeSignature/CodeSignature differ diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/Info.plist b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/Info.plist index 67458372..028b1234 100644 Binary files a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/Info.plist and b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/Info.plist differ diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/OSHealthFitnessLib b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/OSHealthFitnessLib index d3a5fbb4..a32e2151 100755 Binary files a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/OSHealthFitnessLib and b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/OSHealthFitnessLib differ diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..48582894 --- /dev/null +++ b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,59 @@ + + + + + NSPrivacyTracking + + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeHealth + + NSPrivacyCollectedDataTypeLinked + + + NSPrivacyCollectedDataTypeTracking + + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeFitness + + NSPrivacyCollectedDataTypeLinked + + + NSPrivacyCollectedDataTypeTracking + + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Info.plist b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Info.plist index 3697806d..d3a8510f 100644 Binary files a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Info.plist and b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/Info.plist differ diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/OSHealthFitnessLib b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/OSHealthFitnessLib index 026571b2..725c7b4e 100755 Binary files a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/OSHealthFitnessLib and b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/OSHealthFitnessLib differ diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..48582894 --- /dev/null +++ b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/PrivacyInfo.xcprivacy @@ -0,0 +1,59 @@ + + + + + NSPrivacyTracking + + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeHealth + + NSPrivacyCollectedDataTypeLinked + + + NSPrivacyCollectedDataTypeTracking + + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeFitness + + NSPrivacyCollectedDataTypeLinked + + + NSPrivacyCollectedDataTypeTracking + + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + diff --git a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/_CodeSignature/CodeResources b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/_CodeSignature/CodeResources index 5f6f7bcb..03d83321 100644 --- a/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/_CodeSignature/CodeResources +++ b/src/ios/frameworks/OSHealthFitnessLib.xcframework/ios-arm64_x86_64-simulator/OSHealthFitnessLib.framework/_CodeSignature/CodeResources @@ -26,7 +26,7 @@ Info.plist - P953UGaWh0AJIzLmDo8gOMYd9W8= + OlBN7pQMeLN9ByFYKpm0E/JK1KI= Modules/OSHealthFitnessLib.swiftmodule/arm64-apple-ios-simulator.abi.json @@ -72,6 +72,10 @@ sw87f2yJ3lU2kd25gEKxIZALetM= + PrivacyInfo.xcprivacy + + MiuKzpVIs0pYdHP2HzUd3nlWjzM= + files2 @@ -187,6 +191,13 @@ P7SE820E52XFwuSc9IyT2YlJFy1uZqPxi8tOPOxwGfg= + PrivacyInfo.xcprivacy + + hash2 + + tJsHvjtv5Yj/fAdXa5lrQhNxNvF1CYow/Bte4ARYb4I= + + rules diff --git a/www/OSHealthFitness.js b/www/OSHealthFitness.js index a1b23040..4a9a78b4 100644 --- a/www/OSHealthFitness.js +++ b/www/OSHealthFitness.js @@ -8,11 +8,10 @@ exports.requestPermissions = function (success, error, params) { fitnessVariables, healthVariables, profileVariables, - summaryVariables, workoutVariables } = params; - var args = [customPermissions, allVariables, fitnessVariables, healthVariables, profileVariables, summaryVariables, workoutVariables]; + var args = [customPermissions, allVariables, fitnessVariables, healthVariables, profileVariables, workoutVariables]; exec(success, error, 'OSHealthFitness', 'requestPermissions', args); }; @@ -57,6 +56,10 @@ exports.updateBackgroundJob = function (success, error, params) { exec(success, error, 'OSHealthFitness', 'updateBackgroundJob', [params]); }; -exports.disconnectFromGoogleFit = function (success, error) { - exec(success, error, 'OSHealthFitness', 'disconnectFromGoogleFit'); +exports.disconnectFromHealthConnect = function (success, error) { + exec(success, error, 'OSHealthFitness', 'disconnectFromHealthConnect'); +}; + +exports.openHealthConnect = function (success, error) { + exec(success, error, 'OSHealthFitness', 'openHealthConnect'); };