diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4f40d65323..ff5347fce5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -126,9 +126,6 @@ - - - diff --git a/app/src/main/java/info/nightscout/androidaps/MainApp.kt b/app/src/main/java/info/nightscout/androidaps/MainApp.kt index b78b3084d7..bd01ce6abc 100644 --- a/app/src/main/java/info/nightscout/androidaps/MainApp.kt +++ b/app/src/main/java/info/nightscout/androidaps/MainApp.kt @@ -7,6 +7,10 @@ import android.net.ConnectivityManager import android.net.wifi.WifiManager import android.os.Build import androidx.lifecycle.ProcessLifecycleOwner +import androidx.work.Data +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkManager import com.uber.rxdogtag.RxDogTag import dagger.android.AndroidInjector import dagger.android.DaggerApplication @@ -27,14 +31,11 @@ import info.nightscout.androidaps.plugins.constraints.versionChecker.VersionChec import info.nightscout.androidaps.plugins.general.overview.notifications.Notification import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore import info.nightscout.androidaps.plugins.general.themes.ThemeSwitcherPlugin -import info.nightscout.androidaps.receivers.BTReceiver -import info.nightscout.androidaps.receivers.ChargingStateReceiver -import info.nightscout.androidaps.receivers.KeepAliveReceiver.KeepAliveManager -import info.nightscout.androidaps.receivers.NetworkChangeReceiver -import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver +import info.nightscout.androidaps.receivers.* import info.nightscout.androidaps.services.AlarmSoundServiceHelper import info.nightscout.androidaps.utils.ActivityMonitor import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.LocalAlertUtils import info.nightscout.androidaps.utils.ProcessLifecycleListener import info.nightscout.androidaps.utils.buildHelper.BuildHelper import info.nightscout.androidaps.utils.locale.LocaleHelper @@ -48,6 +49,7 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins import net.danlew.android.joda.JodaTimeAndroid import java.io.IOException import java.net.SocketException +import java.util.concurrent.TimeUnit import javax.inject.Inject class MainApp : DaggerApplication() { @@ -62,7 +64,6 @@ class MainApp : DaggerApplication() { @Inject lateinit var config: Config @Inject lateinit var buildHelper: BuildHelper @Inject lateinit var configBuilder: ConfigBuilder - @Inject lateinit var keepAliveManager: KeepAliveManager @Inject lateinit var plugins: List<@JvmSuppressWildcards PluginBase> @Inject lateinit var compatDBHelper: CompatDBHelper @Inject lateinit var repository: AppRepository @@ -73,6 +74,7 @@ class MainApp : DaggerApplication() { @Inject lateinit var notificationStore: NotificationStore @Inject lateinit var processLifecycleListener: ProcessLifecycleListener @Inject lateinit var profileSwitchPlugin: ThemeSwitcherPlugin + @Inject lateinit var localAlertUtils: LocalAlertUtils override fun onCreate() { super.onCreate() @@ -118,7 +120,17 @@ class MainApp : DaggerApplication() { // Register all tabs in app here pluginStore.plugins = plugins configBuilder.initialize() - keepAliveManager.setAlarm(this) + + WorkManager.getInstance(this).enqueueUniquePeriodicWork( + "KeepAlive", + ExistingPeriodicWorkPolicy.REPLACE, + PeriodicWorkRequest.Builder(KeepAliveWorker::class.java, 15, TimeUnit.MINUTES) + .setInputData(Data.Builder().putString("schedule", "KeepAlive").build()) + .setInitialDelay(5, TimeUnit.SECONDS) + .build() + ) + localAlertUtils.shortenSnoozeInterval() + localAlertUtils.preSnoozeAlarms() doMigrations() uel.log(UserEntry.Action.START_AAPS, UserEntry.Sources.Aaps) } @@ -190,7 +202,6 @@ class MainApp : DaggerApplication() { override fun onTerminate() { aapsLogger.debug(LTag.CORE, "onTerminate") unregisterActivityLifecycleCallbacks(activityMonitor) - keepAliveManager.cancelAlarm(this) alarmSoundServiceHelper.stopService(this) super.onTerminate() } diff --git a/app/src/main/java/info/nightscout/androidaps/di/ReceiversModule.kt b/app/src/main/java/info/nightscout/androidaps/di/ReceiversModule.kt index 9d6615a362..72edb82bc1 100644 --- a/app/src/main/java/info/nightscout/androidaps/di/ReceiversModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/di/ReceiversModule.kt @@ -2,12 +2,11 @@ package info.nightscout.androidaps.di import dagger.Module import dagger.android.ContributesAndroidInjector +import info.nightscout.androidaps.plugins.aps.loop.CarbSuggestionReceiver import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBluetoothStateReceiver import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkBroadcastReceiver -import info.nightscout.androidaps.plugins.aps.loop.CarbSuggestionReceiver import info.nightscout.androidaps.receivers.* - @Module @Suppress("unused") abstract class ReceiversModule { @@ -16,8 +15,7 @@ abstract class ReceiversModule { @ContributesAndroidInjector abstract fun contributesBTReceiver(): BTReceiver @ContributesAndroidInjector abstract fun contributesChargingStateReceiver(): ChargingStateReceiver @ContributesAndroidInjector abstract fun contributesDataReceiver(): DataReceiver - @ContributesAndroidInjector abstract fun contributesKeepAliveReceiver(): KeepAliveReceiver - @ContributesAndroidInjector abstract fun contributesKeepAliveWorker(): KeepAliveReceiver.KeepAliveWorker + @ContributesAndroidInjector abstract fun contributesKeepAliveWorker(): KeepAliveWorker @ContributesAndroidInjector abstract fun contributesRileyLinkBluetoothStateReceiver(): RileyLinkBluetoothStateReceiver @ContributesAndroidInjector abstract fun contributesSmsReceiver(): SmsReceiver @ContributesAndroidInjector abstract fun contributesTimeDateOrTZChangeReceiver(): TimeDateOrTZChangeReceiver diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt deleted file mode 100644 index e20ebcb414..0000000000 --- a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveReceiver.kt +++ /dev/null @@ -1,198 +0,0 @@ -package info.nightscout.androidaps.receivers - -import android.app.AlarmManager -import android.app.PendingIntent -import android.app.PendingIntent.CanceledException -import android.app.PendingIntent.FLAG_IMMUTABLE -import android.content.Context -import android.content.Intent -import android.os.SystemClock -import androidx.work.* -import com.google.common.util.concurrent.ListenableFuture -import dagger.android.DaggerBroadcastReceiver -import dagger.android.HasAndroidInjector -import info.nightscout.androidaps.BuildConfig -import info.nightscout.androidaps.R -import info.nightscout.androidaps.data.ProfileSealed -import info.nightscout.androidaps.database.AppRepository -import info.nightscout.androidaps.events.EventProfileSwitchChanged -import info.nightscout.androidaps.extensions.buildDeviceStatus -import info.nightscout.androidaps.interfaces.* -import info.nightscout.shared.logging.AAPSLogger -import info.nightscout.shared.logging.LTag -import info.nightscout.androidaps.plugins.bus.RxBus -import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration -import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin -import info.nightscout.androidaps.queue.commands.Command -import info.nightscout.androidaps.utils.DateUtil -import info.nightscout.androidaps.utils.FabricPrivacy -import info.nightscout.androidaps.utils.LocalAlertUtils -import info.nightscout.androidaps.utils.T -import info.nightscout.androidaps.utils.resources.ResourceHelper -import javax.inject.Inject -import kotlin.math.abs - -class KeepAliveReceiver : DaggerBroadcastReceiver() { - - @Inject lateinit var aapsLogger: AAPSLogger - - companion object { - - private val KEEP_ALIVE_MILLISECONDS = T.mins(5).msecs() - } - - override fun onReceive(context: Context, intent: Intent) { - super.onReceive(context, intent) - aapsLogger.debug(LTag.CORE, "KeepAlive received") - - WorkManager.getInstance(context) - .enqueue(OneTimeWorkRequest.Builder(KeepAliveWorker::class.java).build()) - } - - class KeepAliveWorker( - private val context: Context, - params: WorkerParameters - ) : Worker(context, params) { - - @Inject lateinit var aapsLogger: AAPSLogger - @Inject lateinit var localAlertUtils: LocalAlertUtils - @Inject lateinit var repository: AppRepository - @Inject lateinit var config: Config - @Inject lateinit var iobCobCalculator: IobCobCalculator - @Inject lateinit var loop: Loop - @Inject lateinit var dateUtil: DateUtil - @Inject lateinit var activePlugin: ActivePlugin - @Inject lateinit var profileFunction: ProfileFunction - @Inject lateinit var runningConfiguration: RunningConfiguration - @Inject lateinit var receiverStatusStore: ReceiverStatusStore - @Inject lateinit var rxBus: RxBus - @Inject lateinit var commandQueue: CommandQueue - @Inject lateinit var fabricPrivacy: FabricPrivacy - @Inject lateinit var maintenancePlugin: MaintenancePlugin - @Inject lateinit var rh: ResourceHelper - - init { - (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) - } - - companion object { - - private val STATUS_UPDATE_FREQUENCY = T.mins(15).msecs() - private const val IOB_UPDATE_FREQUENCY_IN_MINUTES = 5L - - private var lastReadStatus: Long = 0 - private var lastRun: Long = 0 - private var lastIobUpload: Long = 0 - - } - - override fun doWork(): Result { - localAlertUtils.shortenSnoozeInterval() - localAlertUtils.checkStaleBGAlert() - checkPump() - checkAPS() - maintenancePlugin.deleteLogs(30) - workerDbStatus() - - return Result.success() - } - - // When Worker DB grows too much, work operations become slow - // Library is cleaning DB every 7 days which may not be sufficient for NSClient full sync - private fun workerDbStatus() { - val workQuery = WorkQuery.Builder - .fromStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.SUCCEEDED)) - .build() - - val workInfo: ListenableFuture> = WorkManager.getInstance(context).getWorkInfos(workQuery) - aapsLogger.debug(LTag.CORE, "WorkManager size is ${workInfo.get().size}") - if (workInfo.get().size > 1000) { - WorkManager.getInstance(context).pruneWork() - aapsLogger.debug(LTag.CORE, "WorkManager pruning ....") - } - } - - // Usually deviceStatus is uploaded through LoopPlugin after every loop cycle. - // if there is no BG available, we have to upload anyway to have correct - // IOB displayed in NS - private fun checkAPS() { - var shouldUploadStatus = false - if (config.NSCLIENT) return - if (config.PUMPCONTROL) shouldUploadStatus = true - else if (!(loop as PluginBase).isEnabled() || iobCobCalculator.ads.actualBg() == null) - shouldUploadStatus = true - else if (dateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true - if (dateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY_IN_MINUTES) && shouldUploadStatus) { - lastIobUpload = dateUtil.now() - buildDeviceStatus(dateUtil, loop, iobCobCalculator, profileFunction, - activePlugin.activePump, receiverStatusStore, runningConfiguration, - BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION)?.also { - repository.insert(it) - } - } - } - - private fun checkPump() { - val pump = activePlugin.activePump - val ps = profileFunction.getRequestedProfile() ?: return - val requestedProfile = ProfileSealed.PS(ps) - val runningProfile = profileFunction.getProfile() - val lastConnection = pump.lastDataTime() - val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis() - val isBasalOutdated = abs(requestedProfile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep - aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection)) - // sometimes keep alive broadcast stops - // as as workaround test if readStatus was requested before an alarm is generated - if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) { - localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loop.isDisconnected) - } - if (loop.isDisconnected) { - // do nothing if pump is disconnected - } else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) { - rxBus.send(EventProfileSwitchChanged()) - } else if (isStatusOutdated && !pump.isBusy()) { - lastReadStatus = System.currentTimeMillis() - commandQueue.readStatus(rh.gs(R.string.keepalive_status_outdated), null) - } else if (isBasalOutdated && !pump.isBusy()) { - lastReadStatus = System.currentTimeMillis() - commandQueue.readStatus(rh.gs(R.string.keepalive_basal_outdated), null) - } - if (lastRun != 0L && System.currentTimeMillis() - lastRun > T.mins(10).msecs()) { - aapsLogger.error(LTag.CORE, "KeepAlive fail") - fabricPrivacy.logCustom("KeepAliveFail") - } - lastRun = System.currentTimeMillis() - } - } - - class KeepAliveManager @Inject constructor( - private val aapsLogger: AAPSLogger, - private val localAlertUtils: LocalAlertUtils - ) { - - //called by MainApp at first app start - fun setAlarm(context: Context) { - aapsLogger.debug(LTag.CORE, "KeepAlive scheduled") - SystemClock.sleep(5000) // wait for app initialization - localAlertUtils.shortenSnoozeInterval() - localAlertUtils.preSnoozeAlarms() - val am = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - val i = Intent(context, KeepAliveReceiver::class.java) - val pi = PendingIntent.getBroadcast(context, 0, i, FLAG_IMMUTABLE) - try { - pi.send() - } catch (e: CanceledException) { - } - am.cancel(pi) - am.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), KEEP_ALIVE_MILLISECONDS, pi) - } - - fun cancelAlarm(context: Context) { - aapsLogger.debug(LTag.CORE, "KeepAlive canceled") - val intent = Intent(context, KeepAliveReceiver::class.java) - val sender = PendingIntent.getBroadcast(context, 0, intent, FLAG_IMMUTABLE) - val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - alarmManager.cancel(sender) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt new file mode 100644 index 0000000000..dc705c4a84 --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/receivers/KeepAliveWorker.kt @@ -0,0 +1,167 @@ +package info.nightscout.androidaps.receivers + +import android.content.Context +import androidx.work.* +import com.google.common.util.concurrent.ListenableFuture +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.R +import info.nightscout.androidaps.data.ProfileSealed +import info.nightscout.androidaps.database.AppRepository +import info.nightscout.androidaps.events.EventProfileSwitchChanged +import info.nightscout.androidaps.extensions.buildDeviceStatus +import info.nightscout.androidaps.interfaces.* +import info.nightscout.androidaps.plugins.bus.RxBus +import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration +import info.nightscout.androidaps.plugins.general.maintenance.MaintenancePlugin +import info.nightscout.androidaps.queue.commands.Command +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.androidaps.utils.FabricPrivacy +import info.nightscout.androidaps.utils.LocalAlertUtils +import info.nightscout.androidaps.utils.T +import info.nightscout.androidaps.utils.resources.ResourceHelper +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import kotlin.math.abs + +class KeepAliveWorker( + private val context: Context, + params: WorkerParameters +) : Worker(context, params) { + + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var localAlertUtils: LocalAlertUtils + @Inject lateinit var repository: AppRepository + @Inject lateinit var config: Config + @Inject lateinit var iobCobCalculator: IobCobCalculator + @Inject lateinit var loop: Loop + @Inject lateinit var dateUtil: DateUtil + @Inject lateinit var activePlugin: ActivePlugin + @Inject lateinit var profileFunction: ProfileFunction + @Inject lateinit var runningConfiguration: RunningConfiguration + @Inject lateinit var receiverStatusStore: ReceiverStatusStore + @Inject lateinit var rxBus: RxBus + @Inject lateinit var commandQueue: CommandQueue + @Inject lateinit var fabricPrivacy: FabricPrivacy + @Inject lateinit var maintenancePlugin: MaintenancePlugin + @Inject lateinit var rh: ResourceHelper + + init { + (context.applicationContext as HasAndroidInjector).androidInjector().inject(this) + } + + companion object { + + private val STATUS_UPDATE_FREQUENCY = T.mins(15).msecs() + private const val IOB_UPDATE_FREQUENCY_IN_MINUTES = 5L + + private var lastReadStatus: Long = 0 + private var lastRun: Long = 0 + private var lastIobUpload: Long = 0 + + } + + override fun doWork(): Result { + aapsLogger.debug(LTag.CORE, "KeepAlive received from: " + inputData.getString("schedule")) + + // 15 min interval is WorkManager minimum so schedule another instances to have 5 min interval + if (inputData.getString("schedule") == "KeepAlive") { + WorkManager.getInstance(context).enqueueUniqueWork( + "KeepAlive_5", + ExistingWorkPolicy.REPLACE, + OneTimeWorkRequest.Builder(KeepAliveWorker::class.java) + .setInputData(Data.Builder().putString("schedule", "KeepAlive_5").build()) + .setInitialDelay(5, TimeUnit.MINUTES) + .build() + ) + WorkManager.getInstance(context).enqueueUniqueWork( + "KeepAlive_10", + ExistingWorkPolicy.REPLACE, + OneTimeWorkRequest.Builder(KeepAliveWorker::class.java) + .setInputData(Data.Builder().putString("schedule", "KeepAlive_10").build()) + .setInitialDelay(10, TimeUnit.MINUTES) + .build() + ) + } + + localAlertUtils.shortenSnoozeInterval() + localAlertUtils.checkStaleBGAlert() + checkPump() + checkAPS() + maintenancePlugin.deleteLogs(30) + workerDbStatus() + + return Result.success() + } + + // When Worker DB grows too much, work operations become slow + // Library is cleaning DB every 7 days which may not be sufficient for NSClient full sync + private fun workerDbStatus() { + val workQuery = WorkQuery.Builder + .fromStates(listOf(WorkInfo.State.FAILED, WorkInfo.State.SUCCEEDED)) + .build() + + val workInfo: ListenableFuture> = WorkManager.getInstance(context).getWorkInfos(workQuery) + aapsLogger.debug(LTag.CORE, "WorkManager size is ${workInfo.get().size}") + if (workInfo.get().size > 1000) { + WorkManager.getInstance(context).pruneWork() + aapsLogger.debug(LTag.CORE, "WorkManager pruning ....") + } + } + + // Usually deviceStatus is uploaded through LoopPlugin after every loop cycle. + // if there is no BG available, we have to upload anyway to have correct + // IOB displayed in NS + private fun checkAPS() { + var shouldUploadStatus = false + if (config.NSCLIENT) return + if (config.PUMPCONTROL) shouldUploadStatus = true + else if (!(loop as PluginBase).isEnabled() || iobCobCalculator.ads.actualBg() == null) + shouldUploadStatus = true + else if (dateUtil.isOlderThan(activePlugin.activeAPS.lastAPSRun, 5)) shouldUploadStatus = true + if (dateUtil.isOlderThan(lastIobUpload, IOB_UPDATE_FREQUENCY_IN_MINUTES) && shouldUploadStatus) { + lastIobUpload = dateUtil.now() + buildDeviceStatus( + dateUtil, loop, iobCobCalculator, profileFunction, + activePlugin.activePump, receiverStatusStore, runningConfiguration, + BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION + )?.also { + repository.insert(it) + } + } + } + + private fun checkPump() { + val pump = activePlugin.activePump + val ps = profileFunction.getRequestedProfile() ?: return + val requestedProfile = ProfileSealed.PS(ps) + val runningProfile = profileFunction.getProfile() + val lastConnection = pump.lastDataTime() + val isStatusOutdated = lastConnection + STATUS_UPDATE_FREQUENCY < System.currentTimeMillis() + val isBasalOutdated = abs(requestedProfile.getBasal() - pump.baseBasalRate) > pump.pumpDescription.basalStep + aapsLogger.debug(LTag.CORE, "Last connection: " + dateUtil.dateAndTimeString(lastConnection)) + // sometimes keep alive broadcast stops + // as as workaround test if readStatus was requested before an alarm is generated + if (lastReadStatus != 0L && lastReadStatus > System.currentTimeMillis() - T.mins(5).msecs()) { + localAlertUtils.checkPumpUnreachableAlarm(lastConnection, isStatusOutdated, loop.isDisconnected) + } + if (loop.isDisconnected) { + // do nothing if pump is disconnected + } else if (runningProfile == null || ((!pump.isThisProfileSet(requestedProfile) || !requestedProfile.isEqual(runningProfile)) && !commandQueue.isRunning(Command.CommandType.BASAL_PROFILE))) { + rxBus.send(EventProfileSwitchChanged()) + } else if (isStatusOutdated && !pump.isBusy()) { + lastReadStatus = System.currentTimeMillis() + commandQueue.readStatus(rh.gs(R.string.keepalive_status_outdated), null) + } else if (isBasalOutdated && !pump.isBusy()) { + lastReadStatus = System.currentTimeMillis() + commandQueue.readStatus(rh.gs(R.string.keepalive_basal_outdated), null) + } + if (lastRun != 0L && System.currentTimeMillis() - lastRun > T.mins(10).msecs()) { + aapsLogger.error(LTag.CORE, "KeepAlive fail") + fabricPrivacy.logCustom("KeepAliveFail") + } + lastRun = System.currentTimeMillis() + } +} \ No newline at end of file