Skip to content

Commit

Permalink
RMET-3190 H&F Plugin - Use exact alarms for background jobs (#109)
Browse files Browse the repository at this point in the history
* feat: declare SCHEDULE_EXACT_ALARM permission in manifest

Context: We're using exact alarms to schedule background jobs to run, so we need to declare this permission in the AndroidManifest.xml file of the app.

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* feat: request SCHEDULE_EXACT_ALARM permission when setting background job

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* feat: handle SCHEDULE_EXACT_ALARM permission not being given when setting a background job

Context: As for the time being the only way we have to set background jobs to run is using exact alarms, if the permission is not given, then we should return an error.

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* feat: ask for SCHEDULE_EXACT_ALARM permission before other ones

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* feat: get foreground notification info from strings.xml file

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* feat: replace workManager with alarmManager

Context: We're now using exact alarms for background jobs, so we need to use the AlarmManager.

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* feat: pass context parameter to AdvancedQuery, as it is needed

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* chore: update dependency to H&F Android library

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* chore: update dependency to H&F Android lib

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* chore: add explanatory comment

References: https://outsystemsrd.atlassian.net/browse/RMET-3190

* chore: update changelog

References: https://outsystemsrd.atlassian.net/browse/RMET-3190
  • Loading branch information
alexgerardojacinto authored Mar 15, 2024
1 parent 02fc444 commit 858ef9e
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 12 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The changes documented here do not include those from the original repository.

## [Unreleased]

## 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).

Expand Down
1 change: 1 addition & 0 deletions hooks/androidCopyPreferencesPermissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ function addBackgroundJobPermissionsToManifest(configParser, projectRoot, parser
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();
Expand Down
4 changes: 2 additions & 2 deletions src/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ dependencies{
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.20@aar")
implementation("com.github.outsystems:oscordova-android:2.0.1@aar")
implementation("com.github.outsystems:oshealthfitness-android:1.2.0.22@aar")
implementation("com.github.outsystems:osnotificationpermissions-android:0.0.4@aar")

// activity
Expand Down
109 changes: 99 additions & 10 deletions src/android/com/outsystems/plugins/healthfitness/OSHealthFitness.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
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
Expand All @@ -25,12 +28,24 @@ class OSHealthFitness : CordovaImplementation() {

var healthStore: HealthStoreInterface? = null
val gson by lazy { Gson() }
lateinit var healthConnectViewModel: HealthConnectViewModel
lateinit var healthConnectRepository: HealthConnectRepository
lateinit var healthConnectDataManager: HealthConnectDataManager
lateinit var healthConnectHelper: HealthConnectHelper
lateinit var workManagerHelper: WorkManagerHelperInterface
lateinit var backgroundParameters: BackgroundJobParameters
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 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)
Expand All @@ -41,9 +56,27 @@ class OSHealthFitness : CordovaImplementation() {
healthConnectDataManager = HealthConnectDataManager(database)
healthConnectRepository = HealthConnectRepository(healthConnectDataManager)
healthConnectHelper = HealthConnectHelper()
workManagerHelper = WorkManagerHelper()
alarmManagerHelper = AlarmManagerHelper()
healthConnectViewModel =
HealthConnectViewModel(healthConnectRepository, healthConnectHelper, workManagerHelper)
HealthConnectViewModel(healthConnectRepository, healthConnectHelper, alarmManagerHelper)
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(
Expand Down Expand Up @@ -96,6 +129,14 @@ class OSHealthFitness : CordovaImplementation() {
return true
}

// 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 {
healthConnectViewModel.initAndRequestPermissions(
Expand Down Expand Up @@ -178,6 +219,7 @@ class OSHealthFitness : CordovaImplementation() {
val parameters = gson.fromJson(args.getString(0), HealthAdvancedQueryParameters::class.java)
healthConnectViewModel.advancedQuery(
parameters,
getContext(),
{ response ->
val pluginResponseJson = gson.toJson(response)
sendPluginResult(pluginResponseJson)
Expand Down Expand Up @@ -227,10 +269,31 @@ class OSHealthFitness : CordovaImplementation() {

}

/**
* 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 (SDK_INT >= 31 && !alarmManager.canScheduleExactAlarms()) {
requestingExactAlarmPermission = true
// we only need to request this permission if exact alarms need to be used
// when there's another way to schedule background jobs to run, we can avoid this for some variables (e.g. steps)
// we intended to use the Activity Recognition API, but it currently has a bug already reported to Google
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<String>().apply {
if (SDK_INT >= 33) {
add(Manifest.permission.POST_NOTIFICATIONS)
Expand All @@ -243,9 +306,34 @@ class OSHealthFitness : CordovaImplementation() {
PermissionHelper.requestPermissions(this, BACKGROUND_JOB_PERMISSIONS_REQUEST_CODE, permissions)
}

/**
* 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()
}

/**
* Sets a background job by calling the setBackgroundJob method of the ViewModel
*/
private fun setBackgroundJobWithParameters(parameters: BackgroundJobParameters) {
healthConnectViewModel.setBackgroundJob(
parameters,
foregroundNotificationTitle,
foregroundNotificationDescription,
getContext(),
{
sendPluginResult("success", null)
Expand All @@ -260,6 +348,7 @@ class OSHealthFitness : CordovaImplementation() {
val jobId = args.getString(0)
healthConnectViewModel.deleteBackgroundJob(
jobId,
getContext(),
{
sendPluginResult("success", null)
},
Expand Down Expand Up @@ -323,7 +412,7 @@ class OSHealthFitness : CordovaImplementation() {
}
)
}

private fun openHealthConnect() {
healthConnectViewModel.openHealthConnect(
getContext(),
Expand All @@ -336,7 +425,7 @@ class OSHealthFitness : CordovaImplementation() {
)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent) {
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
super.onActivityResult(requestCode, resultCode, intent)
healthConnectViewModel.handleActivityResult(requestCode, resultCode, intent,
{
Expand Down

0 comments on commit 858ef9e

Please sign in to comment.