-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RMET- 3142 H&F Plugin - Hook for permissions #106
Merged
alexgerardojacinto
merged 46 commits into
development
from
feat/RMET-3142/hook-permissions
Feb 29, 2024
Merged
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
48d119c
feat: first implementation of androidCopyPreferencesPermissions hook
alexgerardojacinto ce8c7fb
fix: fix path to hook file
alexgerardojacinto 6d5adb7
fix: add dependency to xmldom
alexgerardojacinto 25f59c5
feat: implement first version of hook processing preferences
alexgerardojacinto 42f8e26
refactor: remove unnecessary code
alexgerardojacinto 66a0579
fix: fix comparator in condition
alexgerardojacinto 3a8c9a4
test: add log for troubleshooting
alexgerardojacinto 2a82512
test: add log for troubleshooting
alexgerardojacinto 0f5f490
test: add log for troubleshooting
alexgerardojacinto 66233bb
fix: use "" instead of null in comparison
alexgerardojacinto ce06243
refactor: remove logs
alexgerardojacinto 1b24274
refactor: remove logs and comments
alexgerardojacinto 93bb61f
feat: add permissions code for Android <= 13
alexgerardojacinto 06aea9e
fix: fix variable name
alexgerardojacinto 4d5fe19
feat: add background permissions to AndroidManifest.xml file
alexgerardojacinto 67f789a
fix: properly pass DOMParser to functions
alexgerardojacinto 6c76b26
fix: fix condition in if for background job permissions
alexgerardojacinto 3639f4c
feat: remove unnecessary permissions
alexgerardojacinto b73c364
test: add logs for troubleshooting
alexgerardojacinto 81005c4
fix: fix if condition
alexgerardojacinto 6c6eaa5
feat: copy notification content to strings.xml
alexgerardojacinto 27bdfc4
fix: fix query selector
alexgerardojacinto 326b296
fix: properly look for string tags
alexgerardojacinto 2b627fc
test: add logs for troubleshooting
alexgerardojacinto 57ef033
misc: add log for troubleshooting
alexgerardojacinto 1cd29ce
misc: add logs for troubleshooting
alexgerardojacinto d6c9209
feat: use different way of setting texts in strings.xml
alexgerardojacinto 2cfb12b
refactor: remove logs and comments
alexgerardojacinto b2645e0
fix: replace const with var
alexgerardojacinto dd48586
feat: add PermissionsRationaleActivity to AndroidManifest.xml
alexgerardojacinto 71d6e9b
feat: also use default value for notificationDescription
alexgerardojacinto a5e5350
fix: add missing permissions for background jobs
alexgerardojacinto e854541
Merge branch 'development' into feat/RMET-3142/hook-permissions
alexgerardojacinto 1f35540
chore: update changelog
alexgerardojacinto 806814e
refactor: use correct english term
alexgerardojacinto ec7554d
fix: include necessary dependencies
alexgerardojacinto 82b3cdb
fix: include dependencies for Jetpack Compose in build.gradle
alexgerardojacinto cdb5c6c
refactor: fix typo
alexgerardojacinto a583d54
feat: add helper method to avoid code replication
alexgerardojacinto 7c928dc
fix: pass XML Documents to helper function
alexgerardojacinto aa02e3f
fix: pass necessary parameter to helper function
alexgerardojacinto 251451e
refactor: use helper methods to avoid code repetition
alexgerardojacinto ea92c44
refactor: use helper functions and maps to avoid code repetition and …
alexgerardojacinto eedc73c
feat: populate objects with correct values
alexgerardojacinto 90ea19a
refactor: remove unused requires
alexgerardojacinto f75b62b
Merge branch 'development' into feat/RMET-3142/hook-permissions
alexgerardojacinto File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,339 @@ | ||
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('<?xml version="1.0" encoding="utf-8"?><resources><array name="health_permissions"></array></resources>', 'text/xml'); | ||
|
||
// Get the <array> 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, 'android.permission.FOREGROUND_SERVICE') | ||
addEntryToManifest(manifestXmlDoc, 'android.permission.FOREGROUND_SERVICE_HEALTH') | ||
addEntryToManifest(manifestXmlDoc, 'android.permission.HIGH_SAMPLING_RATE_SENSORS') | ||
|
||
// 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 <string> 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'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,5 +14,8 @@ | |
"ios" | ||
] | ||
}, | ||
"engines": [] | ||
"engines": [], | ||
"dependencies": { | ||
"xmldom": "^0.6.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this type of util function is going to be useful across multiple repos/plugins maybe we pull these functions out into a npm typed lib package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For sure, if we have to use this kind of function again I think we should do that!