Skip to content
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

VIT-6016: Improve how Auto Sync interacts with signed-out state and signOut() #99

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions VitalClient/src/main/java/io/tryvital/client/VitalClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
Expand Down Expand Up @@ -96,6 +97,9 @@ class VitalClient internal constructor(context: Context) {
/** Moments which can materially change VitalClient.Companion.status */
private val statusChanged = MutableSharedFlow<Unit>(extraBufferCapacity = Int.MAX_VALUE)

val childSDKShouldReset: SharedFlow<Unit> get() = _childSDKShouldReset
private val _childSDKShouldReset = MutableSharedFlow<Unit>(extraBufferCapacity = Int.MAX_VALUE)


@Deprecated("Renamed to `signOut()` and is now a suspend function.", ReplaceWith("signOut()"))
fun cleanUp() {
Expand All @@ -110,6 +114,7 @@ class VitalClient internal constructor(context: Context) {
encryptedSharedPreferences.edit().clear().apply()
jwtAuth.signOut()
statusChanged.emit(Unit)
_childSDKShouldReset.emit(Unit)
}

@Deprecated(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.tryvital.vitalhealthconnect

import android.annotation.SuppressLint
import android.app.AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_BOOT_COMPLETED
import android.os.Looper
import io.tryvital.client.utils.VitalLogger
import io.tryvital.vitalhealthconnect.model.HealthConnectAvailability

const val ACTION_SYNC_DATA = "io.tryvital.vitalhealthconnect.action.SYNC_DATA"
private val ACTION_QUICKBOOT_POWERON = "android.intent.action.QUICKBOOT_POWERON"
Expand All @@ -29,25 +31,32 @@ class SyncBroadcastReceiver: BroadcastReceiver() {
VitalLogger.getOrCreate().info { "BgSync: exactAlarmPermissionStateChanged because ${intent.action}" }

val manager = VitalHealthConnectManager.getOrCreate(context)
if (manager.isBackgroundSyncEnabled) {
manager.scheduleNextExactAlarm(force = true)

// Just in case we are on an earlier Android OS version, in which one can uninstall
// Health Connect.
if (VitalHealthConnectManager.isAvailable(manager.context) != HealthConnectAvailability.Installed) {
return VitalLogger.getOrCreate().info { "BgSync: HealthConnect gone" }
}

manager.scheduleNextExactAlarm(force = true)
}

private fun exactAlarmFired(context: Context, intent: Intent) {
VitalLogger.getOrCreate().info { "BgSync: exactAlarmFired" }

val manager = VitalHealthConnectManager.getOrCreate(context)
manager.scheduleNextExactAlarm(force = true)

if (!context.isConnectedToInternet) {
return VitalLogger.getOrCreate().info { "BgSync: skipped launch - no internet" }
// Just in case we are on an earlier Android OS version, in which one can uninstall
// Health Connect.
if (VitalHealthConnectManager.isAvailable(manager.context) != HealthConnectAvailability.Installed) {
return VitalLogger.getOrCreate().info { "BgSync: HealthConnect gone" }
}

if (manager.pauseSynchronization) {
return VitalLogger.getOrCreate().info { "BgSync: skipped launch - sync paused" }
if (!manager.isBackgroundSyncEnabled) {
return VitalLogger.getOrCreate().info { "BgSync: skipped launch - backgroundSync is disabled" }
}

manager.scheduleNextExactAlarm(force = true)
manager.launchAutoSyncWorker {
context.startForegroundService(intent)
VitalLogger.getOrCreate().info { "BgSync: triggered by exact alarm" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class VitalHealthConnectManager private constructor(
}

private fun resetAutoSync() {
disableBackgroundSync()
taskScope.cancel()
taskScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
}
Expand Down Expand Up @@ -311,12 +312,18 @@ class VitalHealthConnectManager private constructor(
VitalLogger.getOrCreate().info { "BgSync: skipped by pause" }
return
}

if (shouldSkipAutoSync) {
VitalLogger.getOrCreate().info {
"BgSync: skipped by throttle; last synced at ${Instant.ofEpochMilli(lastAutoSyncedAt)}"
}
return
}

if (!context.isConnectedToInternet) {
return VitalLogger.getOrCreate().info { "BgSync: skipped; no internet" }
}

if (!vitalClient.hasUserConnectedTo(ProviderSlug.HealthConnect)) {
VitalLogger.getOrCreate().info { "BgSync: skipped; no CS" }
return
Expand Down Expand Up @@ -524,14 +531,9 @@ class VitalHealthConnectManager private constructor(
*/
@OptIn(DelicateCoroutinesApi::class)
private fun bind(client: VitalHealthConnectManager, coreClient: VitalClient, context: Context) {
VitalClient.Companion.statuses(context)
.onEach { statuses ->
if (VitalClient.Status.SignedIn !in statuses) {
client.resetAutoSync()
}
}
.launchIn(GlobalScope)
coreClient.childSDKShouldReset
.onEach { client.resetAutoSync() }
.launchIn(GlobalScope + Dispatchers.Main)
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import android.os.Looper
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContract
import androidx.core.app.AlarmManagerCompat
import io.tryvital.client.VitalClient
import io.tryvital.client.utils.VitalLogger
import java.time.Instant
import kotlin.time.Duration.Companion.hours
Expand Down Expand Up @@ -155,6 +156,11 @@ internal fun VitalHealthConnectManager.scheduleNextExactAlarm(force: Boolean): B
return false
}

if (!isBackgroundSyncEnabled) {
VitalLogger.getOrCreate().info { "BgSync: scheduling skipped; backgroundSync is disabled" }
return false
}

val alarmManager = context.getSystemService(AlarmManager::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
VitalLogger.getOrCreate().info { "BgSync: scheduling aborted; no permission to use exact alarm" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ internal fun processLifecycleObserver(
if (event != Lifecycle.Event.ON_START) {
return
}
if (VitalClient.Status.SignedIn !in VitalClient.status) {
return
}
if (VitalHealthConnectManager.isAvailable(manager.context) != HealthConnectAvailability.Installed) {
return
}

manager.scheduleNextExactAlarm(force = false)

source.lifecycleScope.launch(start = CoroutineStart.UNDISPATCHED) {
manager.checkAndUpdatePermissions()

if (
VitalClient.Status.SignedIn in VitalClient.status
&& VitalHealthConnectManager.isAvailable(manager.context) == HealthConnectAvailability.Installed
) {
manager.launchAutoSyncWorker {
VitalLogger.getOrCreate().info { "BgSync: triggered by process ON_START" }
}
manager.launchAutoSyncWorker {
VitalLogger.getOrCreate().info { "BgSync: triggered by process ON_START" }
}
}
}
}
}
Loading