Wear refactor

This commit is contained in:
Milos Kozak 2022-04-11 14:25:00 +02:00
parent 391a471b27
commit 2dba081176
65 changed files with 2305 additions and 1623 deletions

View file

@ -46,6 +46,10 @@
android:supportsRtl="true"
android:theme="@style/AppTheme.Launcher" >
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<activity
android:name=".widget.WidgetConfigureActivity"
android:theme="@android:style/Theme.Material.Dialog.NoActionBar"
@ -106,14 +110,6 @@
android:theme="@style/AppTheme" />
<activity android:name=".activities.StatsActivity"
android:theme="@style/AppTheme" />
<activity
android:name="com.google.firebase.auth.internal.FederatedSignInActivity"
android:excludeFromRecents="true"
android:exported="true"
android:launchMode="singleInstance"
android:permission="com.google.firebase.auth.api.gms.permission.LAUNCH_FEDERATED_SIGN_IN"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:replace="android:launchMode" />
<!-- Receive new BG readings from other local apps -->
<receiver
@ -174,62 +170,38 @@
android:name=".plugins.general.wear.wearintegration.WatchUpdaterService"
android:exported="true">
<intent-filter>
<!-- <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> -->
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<action android:name="com.google.android.gms.wearable.CHANNEL_EVENT" />
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<data
android:host="*"
android:scheme="wear" />
</intent-filter>
<intent-filter>
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_data"
android:pathPrefix="@string/path_pong"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_data_resend"
android:pathPrefix="@string/path_resend_data_request"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_cancel_bolus"
android:pathPrefix="/@path_cancel_bolus_on_watch"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_confirmactionstring"
android:pathPrefix="@string/path_confirm_action"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_initiateactionstring"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/openwearsettings"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/sendstatustowear"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/sendpreferencestowear"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_basal"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_bolusprogress"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_actionconfirmationrequest"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_changeconfirmationrequest"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_cancelnotificationrequest"
android:pathPrefix="@string/path_initiate_action_on_phone"
android:scheme="wear" />
</intent-filter>
</service>
@ -247,10 +219,6 @@
<service android:name=".plugins.general.persistentNotification.DummyService" />
<meta-data
android:name="io.fabric.ApiKey"
android:value="59d462666c664c57b29e1d79ea123e01f8057cfa" />
<activity
android:name=".setupwizard.SetupWizardActivity"
android:configChanges="orientation|keyboardHidden|screenSize"

View file

@ -33,7 +33,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.*
import info.nightscout.androidaps.receivers.BTReceiver
import info.nightscout.androidaps.receivers.ChargingStateReceiver
import info.nightscout.androidaps.receivers.KeepAliveWorker
import info.nightscout.androidaps.receivers.NetworkChangeReceiver
import info.nightscout.androidaps.receivers.TimeDateOrTZChangeReceiver
import info.nightscout.androidaps.services.AlarmSoundServiceHelper
import info.nightscout.androidaps.utils.ActivityMonitor
import info.nightscout.androidaps.utils.DateUtil

View file

@ -37,7 +37,7 @@ class SurveyActivity : NoSplashAppCompatActivity() {
binding = ActivitySurveyBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.id.text = InstanceId.instanceId()
binding.id.text = InstanceId.instanceId
val profileStore = activePlugin.activeProfileSource.profile
val profileList = profileStore?.getProfileList() ?: return
@ -80,7 +80,7 @@ class SurveyActivity : NoSplashAppCompatActivity() {
binding.submit.setOnClickListener {
val r = FirebaseRecord()
r.id = InstanceId.instanceId()
r.id = InstanceId.instanceId
r.age = SafeParse.stringToInt(binding.age.text.toString())
r.weight = SafeParse.stringToInt(binding.weight.text.toString())
if (r.age < 1 || r.age > 120) {

View file

@ -2,4 +2,4 @@ package info.nightscout.androidaps.plugins.general.tidepool.events
import info.nightscout.androidaps.events.Event
class EventTidepoolResetData :Event()
class EventTidepoolResetData : Event()

View file

@ -2,7 +2,6 @@ package info.nightscout.androidaps.plugins.general.wear
import android.app.NotificationManager
import android.content.Context
import android.util.Log
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
@ -25,8 +24,6 @@ import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTe
import info.nightscout.androidaps.extensions.total
import info.nightscout.androidaps.extensions.valueToUnits
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
@ -38,10 +35,28 @@ import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.utils.*
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.androidaps.utils.wizard.BolusWizard
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.shared.SafeParse
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.ActionData
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_BOLUS
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_CANCEL_CHANGE_REQUEST
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_CHANGE_REQUEST
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_CPP_SET
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_DISMISS_OVERVIEW_NOTIF
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_E_CARBS
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_FILL
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_FILL_PRESET
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_OPEN_CPP
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_QUICK_WIZARD
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_STATUS
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_TDD_STATS
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_TEMPORARY_TARGET
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_WIZARD
import info.nightscout.shared.weardata.WearConstants.Companion.ACTION_WIZARD2
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.text.DateFormat
@ -96,7 +111,7 @@ class ActionStringHandler @Inject constructor(
disposable += rxBus
.toObservable(EventWearInitiateAction::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ handleInitiate(it.action) }, fabricPrivacy::logException)
.subscribe({ handleInitiateActionOnPhone(it.action) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventWearConfirmAction::class.java)
@ -109,328 +124,360 @@ class ActionStringHandler @Inject constructor(
}
@Synchronized
private fun handleInitiate(actionString: String) {
private fun handleInitiateActionOnPhone(actionString: String) {
//TODO: i18n
Log.i("ActionStringHandler", "handleInitiate actionString=$actionString")
aapsLogger.debug(LTag.WEAR, "handleInitiateActionOnPhone actionString=$actionString")
if (!sp.getBoolean(R.string.key_wear_control, false)) return
lastBolusWizard = null
var rTitle = rh.gs(R.string.confirm).uppercase()
var rMessage = ""
var rAction = ""
// do the parsing and check constraints
val act = actionString.split("\\s+".toRegex()).toTypedArray()
if ("fillpreset" == act[0]) { ///////////////////////////////////// PRIME/FILL
val amount: Double = when {
"1" == act[1] -> sp.getDouble("fill_button1", 0.3)
"2" == act[1] -> sp.getDouble("fill_button2", 0.0)
"3" == act[1] -> sp.getDouble("fill_button3", 0.0)
else -> return
}
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
rMessage += rh.gs(R.string.primefill) + ": " + insulinAfterConstraints + "U"
if (insulinAfterConstraints - amount != 0.0) rMessage += "\n" + rh.gs(R.string.constraintapllied)
rAction += "fill $insulinAfterConstraints"
} else if ("fill" == act[0]) { ////////////////////////////////////////////// PRIME/FILL
val amount = SafeParse.stringToDouble(act[1])
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
rMessage += rh.gs(R.string.primefill) + ": " + insulinAfterConstraints + "U"
if (insulinAfterConstraints - amount != 0.0) rMessage += "\n" + rh.gs(R.string.constraintapllied)
rAction += "fill $insulinAfterConstraints"
} else if ("bolus" == act[0]) { ////////////////////////////////////////////// BOLUS
val insulin = SafeParse.stringToDouble(act[1])
val carbs = SafeParse.stringToInt(act[2])
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(insulin)).value()
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value()
val pump = activePlugin.activePump
if (insulinAfterConstraints > 0 && (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected)) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
rMessage += rh.gs(R.string.bolus) + ": " + insulinAfterConstraints + "U\n"
rMessage += rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"
if (insulinAfterConstraints - insulin != 0.0 || carbsAfterConstraints - carbs != 0) {
rMessage += "\n" + rh.gs(R.string.constraintapllied)
}
rAction += "bolus $insulinAfterConstraints $carbsAfterConstraints"
} else if ("temptarget" == act[0]) { ///////////////////////////////////////////////////////// TEMPTARGET
aapsLogger.info(LTag.WEAR, "temptarget received: $act")
if ("cancel" == act[1]) {
rMessage += rh.gs(R.string.wear_action_tempt_cancel_message)
rAction = "temptarget true 0 0 0"
} else if ("preset" == act[1]) {
val presetIsMGDL = profileFunction.getUnits() == GlucoseUnit.MGDL
val preset = act[2]
when (preset) {
"activity" -> {
val activityTTDuration = defaultValueHelper.determineActivityTTDuration()
val activityTT = defaultValueHelper.determineActivityTT()
val reason = rh.gs(R.string.activity)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, activityTT, activityTTDuration)
rAction = "temptarget $presetIsMGDL $activityTTDuration $activityTT $activityTT"
}
"hypo" -> {
val hypoTTDuration = defaultValueHelper.determineHypoTTDuration()
val hypoTT = defaultValueHelper.determineHypoTT()
val reason = rh.gs(R.string.hypo)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, hypoTT, hypoTTDuration)
rAction = "temptarget $presetIsMGDL $hypoTTDuration $hypoTT $hypoTT"
if (actionString.startsWith("{")) {
when (val command = ActionData.deserialize(actionString)) {
is ActionData.Bolus -> {
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(command.insulin)).value()
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(command.carbs)).value()
val pump = activePlugin.activePump
if (insulinAfterConstraints > 0 && (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected)) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
"eating" -> {
val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration()
val eatingSoonTT = defaultValueHelper.determineEatingSoonTT()
val reason = rh.gs(R.string.eatingsoon)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, eatingSoonTT, eatingSoonTTDuration)
rAction = "temptarget $presetIsMGDL $eatingSoonTTDuration $eatingSoonTT $eatingSoonTT"
rMessage += rh.gs(R.string.bolus) + ": " + insulinAfterConstraints + "U\n"
rMessage += rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"
if (insulinAfterConstraints - command.insulin != 0.0 || carbsAfterConstraints - command.carbs != 0) {
rMessage += "\n" + rh.gs(R.string.constraintapllied)
}
else -> {
sendError(rh.gs(R.string.wear_action_tempt_preset_error, preset))
rAction += "bolus $insulinAfterConstraints $carbsAfterConstraints"
}
is ActionData.ProfileSwitch -> {
val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
if (activeProfileSwitch is ValueWrapper.Existing) {
rMessage = "Profile:" + "\n\n" +
"Timeshift: " + command.timeShift + "\n" +
"Percentage: " + command.percentage + "%"
rAction = actionString
} else { // read CPP values
sendError("No active profile switch!")
return
}
}
} else {
val isMGDL = java.lang.Boolean.parseBoolean(act[1])
if (profileFunction.getUnits() == GlucoseUnit.MGDL != isMGDL) {
sendError(rh.gs(R.string.wear_action_tempt_unit_error))
return
}
} else {
// do the parsing and check constraints
val act = actionString.split("\\s+".toRegex()).toTypedArray()
when (act[0]) {
ACTION_FILL_PRESET -> { ///////////////////////////////////// PRIME/FILL
val amount: Double = when {
"1" == act[1] -> sp.getDouble("fill_button1", 0.3)
"2" == act[1] -> sp.getDouble("fill_button2", 0.0)
"3" == act[1] -> sp.getDouble("fill_button3", 0.0)
else -> return
}
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
rMessage += rh.gs(R.string.primefill) + ": " + insulinAfterConstraints + "U"
if (insulinAfterConstraints - amount != 0.0) rMessage += "\n" + rh.gs(R.string.constraintapllied)
rAction += "fill $insulinAfterConstraints"
}
val duration = SafeParse.stringToInt(act[2])
if (duration == 0) {
rMessage += rh.gs(R.string.wear_action_tempt_zero_message)
rAction = "temptarget true 0 0 0"
} else {
var low = SafeParse.stringToDouble(act[3])
var high = SafeParse.stringToDouble(act[4])
if (!isMGDL) {
low *= Constants.MMOLL_TO_MGDL
high *= Constants.MMOLL_TO_MGDL
}
if (low < HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) {
sendError(rh.gs(R.string.wear_action_tempt_min_bg_error))
return
}
if (high < HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) {
sendError(rh.gs(R.string.wear_action_tempt_max_bg_error))
return
}
rMessage += if (act[3] === act[4]) rh.gs(R.string.wear_action_tempt_manual_message, act[3], act[2])
else rh.gs(R.string.wear_action_tempt_manual_range_message, act[3], act[4], act[2])
rAction = actionString
ACTION_FILL -> {
val amount = SafeParse.stringToDouble(act[1])
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
rMessage += rh.gs(R.string.primefill) + ": " + insulinAfterConstraints + "U"
if (insulinAfterConstraints - amount != 0.0) rMessage += "\n" + rh.gs(R.string.constraintapllied)
rAction += "fill $insulinAfterConstraints"
}
}
} else if ("status" == act[0]) { ////////////////////////////////////////////// STATUS
rTitle = "STATUS"
rAction = "statusmessage"
if ("pump" == act[1]) {
rTitle += " PUMP"
rMessage = pumpStatus
} else if ("loop" == act[1]) {
rTitle += " LOOP"
rMessage = "TARGETS:\n$targetsStatus"
rMessage += "\n\n" + loopStatus
rMessage += "\n\nOAPS RESULT:\n$oAPSResultStatus"
}
} else if ("wizard" == act[0]) {
sendError("Update APP on Watch!")
return
} else if ("wizard2" == act[0]) { ////////////////////////////////////////////// WIZARD
val pump = activePlugin.activePump
if (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
val carbsBeforeConstraints = SafeParse.stringToInt(act[1])
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbsBeforeConstraints)).value()
if (carbsAfterConstraints - carbsBeforeConstraints != 0) {
sendError(rh.gs(R.string.wizard_carbs_constraint))
return
}
val useBG = sp.getBoolean(R.string.key_wearwizard_bg, true)
val useTT = sp.getBoolean(R.string.key_wearwizard_tt, false)
val useBolusIOB = sp.getBoolean(R.string.key_wearwizard_bolusiob, true)
val useBasalIOB = sp.getBoolean(R.string.key_wearwizard_basaliob, true)
val useCOB = sp.getBoolean(R.string.key_wearwizard_cob, true)
val useTrend = sp.getBoolean(R.string.key_wearwizard_trend, false)
val percentage = act[2].toInt()
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
if (profile == null) {
sendError(rh.gs(R.string.wizard_no_active_profile))
return
}
val bgReading = iobCobCalculator.ads.actualBg()
if (bgReading == null) {
sendError(rh.gs(R.string.wizard_no_actual_bg))
return
}
val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard wear")
if (cobInfo.displayCob == null) {
sendError(rh.gs(R.string.wizard_no_cob))
return
}
val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
val tempTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null
val bolusWizard = BolusWizard(injector).doCalc(
profile, profileName, tempTarget,
carbsAfterConstraints, cobInfo.displayCob!!, bgReading.valueToUnits(profileFunction.getUnits()),
0.0, percentage, useBG, useCOB, useBolusIOB, useBasalIOB, false, useTT, useTrend, false
)
val insulinAfterConstraints = bolusWizard.insulinAfterConstraints
val minStep = pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)
if (abs(insulinAfterConstraints - bolusWizard.calculatedTotalInsulin) >= minStep) {
sendError(rh.gs(R.string.wizard_constraint_bolus_size, bolusWizard.calculatedTotalInsulin))
return
}
if (bolusWizard.calculatedTotalInsulin <= 0 && bolusWizard.carbs <= 0) {
rAction = "info"
rTitle = rh.gs(R.string.info)
} else {
rAction = actionString
}
rMessage += rh.gs(R.string.wizard_result, bolusWizard.calculatedTotalInsulin, bolusWizard.carbs)
rMessage += "\n_____________"
rMessage += "\n" + bolusWizard.explainShort()
lastBolusWizard = bolusWizard
} else if ("quick_wizard" == act[0]) {
val guid = act[1]
val actualBg = iobCobCalculator.ads.actualBg()
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
val quickWizardEntry = quickWizard.get(guid)
Log.i("QuickWizard", "handleInitiate: quick_wizard " + quickWizardEntry?.buttonText() + " c " + quickWizardEntry?.carbs())
if (quickWizardEntry == null) {
sendError(rh.gs(R.string.quick_wizard_not_available))
return
}
if (actualBg == null) {
sendError(rh.gs(R.string.wizard_no_actual_bg))
return
}
if (profile == null) {
sendError(rh.gs(R.string.wizard_no_active_profile))
return
}
val cobInfo = iobCobCalculator.getCobInfo(false, "QuickWizard wear")
if (cobInfo.displayCob == null) {
sendError(rh.gs(R.string.wizard_no_cob))
return
}
val pump = activePlugin.activePump
if (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
ACTION_TEMPORARY_TARGET -> {
aapsLogger.info(LTag.WEAR, "temptarget received: $act")
if ("cancel" == act[1]) {
rMessage += rh.gs(R.string.wear_action_tempt_cancel_message)
rAction = "temptarget true 0 0 0"
} else if ("preset" == act[1]) {
val presetIsMGDL = profileFunction.getUnits() == GlucoseUnit.MGDL
val preset = act[2]
when (preset) {
"activity" -> {
val activityTTDuration = defaultValueHelper.determineActivityTTDuration()
val activityTT = defaultValueHelper.determineActivityTT()
val reason = rh.gs(R.string.activity)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, activityTT, activityTTDuration)
rAction = "temptarget $presetIsMGDL $activityTTDuration $activityTT $activityTT"
}
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
"hypo" -> {
val hypoTTDuration = defaultValueHelper.determineHypoTTDuration()
val hypoTT = defaultValueHelper.determineHypoTT()
val reason = rh.gs(R.string.hypo)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, hypoTT, hypoTTDuration)
rAction = "temptarget $presetIsMGDL $hypoTTDuration $hypoTT $hypoTT"
}
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
if (carbsAfterConstraints != quickWizardEntry.carbs()) {
sendError(rh.gs(R.string.wizard_carbs_constraint))
return
}
val insulinAfterConstraints = wizard.insulinAfterConstraints
val minStep = pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)
if (abs(insulinAfterConstraints - wizard.calculatedTotalInsulin) >= minStep) {
sendError(rh.gs(R.string.wizard_constraint_bolus_size, wizard.calculatedTotalInsulin))
return
}
"eating" -> {
val eatingSoonTTDuration = defaultValueHelper.determineEatingSoonTTDuration()
val eatingSoonTT = defaultValueHelper.determineEatingSoonTT()
val reason = rh.gs(R.string.eatingsoon)
rMessage += rh.gs(R.string.wear_action_tempt_preset_message, reason, eatingSoonTT, eatingSoonTTDuration)
rAction = "temptarget $presetIsMGDL $eatingSoonTTDuration $eatingSoonTT $eatingSoonTT"
}
rMessage = rh.gs(R.string.quick_wizard_message, quickWizardEntry.buttonText(), wizard.calculatedTotalInsulin, quickWizardEntry.carbs())
rAction = "bolus $insulinAfterConstraints $carbsAfterConstraints"
Log.i("QuickWizard", "handleInitiate: quick_wizard action=$rAction")
rMessage += "\n_____________"
rMessage += "\n" + wizard.explainShort()
} else if ("opencpp" == act[0]) {
val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
if (activeProfileSwitch is ValueWrapper.Existing) { // read CPP values
rTitle = "opencpp"
rMessage = "opencpp"
rAction = "opencpp" + " " + activeProfileSwitch.value.originalPercentage + " " + activeProfileSwitch.value.originalTimeshift
} else {
sendError("No active profile switch!")
return
}
} else if ("cppset" == act[0]) {
val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
if (activeProfileSwitch is ValueWrapper.Existing) {
rMessage = "CPP:" + "\n\n" +
"Timeshift: " + act[1] + "\n" +
"Percentage: " + act[2] + "%"
rAction = actionString
} else { // read CPP values
sendError("No active profile switch!")
return
}
} else if ("tddstats" == act[0]) {
val activePump = activePlugin.activePump
// check if DB up to date
val dummies: MutableList<TotalDailyDose> = LinkedList()
val historyList = getTDDList(dummies)
if (isOldData(historyList)) {
rTitle = "TDD"
rAction = "statusmessage"
rMessage = "OLD DATA - "
//if pump is not busy: try to fetch data
if (activePump.isBusy()) {
rMessage += rh.gs(R.string.pumpbusy)
} else {
rMessage += "trying to fetch data from pump."
commandQueue.loadTDDs(object : Callback() {
override fun run() {
val dummies1: MutableList<TotalDailyDose> = LinkedList()
val historyList1 = getTDDList(dummies1)
if (isOldData(historyList1)) {
sendStatusMessage("TDD: Still old data! Cannot load from pump.\n" + generateTDDMessage(historyList1, dummies1))
} else {
sendStatusMessage(generateTDDMessage(historyList1, dummies1))
else -> {
sendError(rh.gs(R.string.wear_action_tempt_preset_error, preset))
return
}
}
})
} else {
val isMGDL = java.lang.Boolean.parseBoolean(act[1])
if (profileFunction.getUnits() == GlucoseUnit.MGDL != isMGDL) {
sendError(rh.gs(R.string.wear_action_tempt_unit_error))
return
}
val duration = SafeParse.stringToInt(act[2])
if (duration == 0) {
rMessage += rh.gs(R.string.wear_action_tempt_zero_message)
rAction = "temptarget true 0 0 0"
} else {
var low = SafeParse.stringToDouble(act[3])
var high = SafeParse.stringToDouble(act[4])
if (!isMGDL) {
low *= Constants.MMOLL_TO_MGDL
high *= Constants.MMOLL_TO_MGDL
}
if (low < HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[0] || low > HardLimits.VERY_HARD_LIMIT_TEMP_MIN_BG[1]) {
sendError(rh.gs(R.string.wear_action_tempt_min_bg_error))
return
}
if (high < HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[0] || high > HardLimits.VERY_HARD_LIMIT_TEMP_MAX_BG[1]) {
sendError(rh.gs(R.string.wear_action_tempt_max_bg_error))
return
}
rMessage += if (act[3] === act[4]) rh.gs(R.string.wear_action_tempt_manual_message, act[3], act[2])
else rh.gs(R.string.wear_action_tempt_manual_range_message, act[3], act[4], act[2])
rAction = actionString
}
}
}
ACTION_STATUS -> {
rTitle = "STATUS"
rAction = "statusmessage"
if ("pump" == act[1]) {
rTitle += " PUMP"
rMessage = pumpStatus
} else if ("loop" == act[1]) {
rTitle += " LOOP"
rMessage = "TARGETS:\n$targetsStatus"
rMessage += "\n\n" + loopStatus
rMessage += "\n\nOAPS RESULT:\n$oAPSResultStatus"
}
}
ACTION_WIZARD -> {
sendError("Update APP on Watch!")
return
}
ACTION_WIZARD2 -> {
val pump = activePlugin.activePump
if (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
val carbsBeforeConstraints = SafeParse.stringToInt(act[1])
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbsBeforeConstraints)).value()
if (carbsAfterConstraints - carbsBeforeConstraints != 0) {
sendError(rh.gs(R.string.wizard_carbs_constraint))
return
}
val useBG = sp.getBoolean(R.string.key_wearwizard_bg, true)
val useTT = sp.getBoolean(R.string.key_wearwizard_tt, false)
val useBolusIOB = sp.getBoolean(R.string.key_wearwizard_bolusiob, true)
val useBasalIOB = sp.getBoolean(R.string.key_wearwizard_basaliob, true)
val useCOB = sp.getBoolean(R.string.key_wearwizard_cob, true)
val useTrend = sp.getBoolean(R.string.key_wearwizard_trend, false)
val percentage = act[2].toInt()
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
if (profile == null) {
sendError(rh.gs(R.string.wizard_no_active_profile))
return
}
val bgReading = iobCobCalculator.ads.actualBg()
if (bgReading == null) {
sendError(rh.gs(R.string.wizard_no_actual_bg))
return
}
val cobInfo = iobCobCalculator.getCobInfo(false, "Wizard wear")
if (cobInfo.displayCob == null) {
sendError(rh.gs(R.string.wizard_no_cob))
return
}
val dbRecord = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
val tempTarget = if (dbRecord is ValueWrapper.Existing) dbRecord.value else null
val bolusWizard = BolusWizard(injector).doCalc(
profile, profileName, tempTarget,
carbsAfterConstraints, cobInfo.displayCob!!, bgReading.valueToUnits(profileFunction.getUnits()),
0.0, percentage, useBG, useCOB, useBolusIOB, useBasalIOB, false, useTT, useTrend, false
)
val insulinAfterConstraints = bolusWizard.insulinAfterConstraints
val minStep = pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)
if (abs(insulinAfterConstraints - bolusWizard.calculatedTotalInsulin) >= minStep) {
sendError(rh.gs(R.string.wizard_constraint_bolus_size, bolusWizard.calculatedTotalInsulin))
return
}
if (bolusWizard.calculatedTotalInsulin <= 0 && bolusWizard.carbs <= 0) {
rAction = "info"
rTitle = rh.gs(R.string.info)
} else {
rAction = actionString
}
rMessage += rh.gs(R.string.wizard_result, bolusWizard.calculatedTotalInsulin, bolusWizard.carbs)
rMessage += "\n_____________"
rMessage += "\n" + bolusWizard.explainShort()
lastBolusWizard = bolusWizard
}
ACTION_QUICK_WIZARD -> {
val guid = act[1]
val actualBg = iobCobCalculator.ads.actualBg()
val profile = profileFunction.getProfile()
val profileName = profileFunction.getProfileName()
val quickWizardEntry = quickWizard.get(guid)
//Log.i("QuickWizard", "handleInitiate: quick_wizard " + quickWizardEntry?.buttonText() + " c " + quickWizardEntry?.carbs())
if (quickWizardEntry == null) {
sendError(rh.gs(R.string.quick_wizard_not_available))
return
}
if (actualBg == null) {
sendError(rh.gs(R.string.wizard_no_actual_bg))
return
}
if (profile == null) {
sendError(rh.gs(R.string.wizard_no_active_profile))
return
}
val cobInfo = iobCobCalculator.getCobInfo(false, "QuickWizard wear")
if (cobInfo.displayCob == null) {
sendError(rh.gs(R.string.wizard_no_cob))
return
}
val pump = activePlugin.activePump
if (!pump.isInitialized() || pump.isSuspended() || loop.isDisconnected) {
sendError(rh.gs(R.string.wizard_pump_not_available))
return
}
val wizard = quickWizardEntry.doCalc(profile, profileName, actualBg, true)
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(quickWizardEntry.carbs())).value()
if (carbsAfterConstraints != quickWizardEntry.carbs()) {
sendError(rh.gs(R.string.wizard_carbs_constraint))
return
}
val insulinAfterConstraints = wizard.insulinAfterConstraints
val minStep = pump.pumpDescription.pumpType.determineCorrectBolusStepSize(insulinAfterConstraints)
if (abs(insulinAfterConstraints - wizard.calculatedTotalInsulin) >= minStep) {
sendError(rh.gs(R.string.wizard_constraint_bolus_size, wizard.calculatedTotalInsulin))
return
}
rMessage = rh.gs(R.string.quick_wizard_message, quickWizardEntry.buttonText(), wizard.calculatedTotalInsulin, quickWizardEntry.carbs())
rAction = "bolus $insulinAfterConstraints $carbsAfterConstraints"
//Log.i("QuickWizard", "handleInitiate: quick_wizard action=$rAction")
rMessage += "\n_____________"
rMessage += "\n" + wizard.explainShort()
}
ACTION_OPEN_CPP -> {
val activeProfileSwitch = repository.getEffectiveProfileSwitchActiveAt(dateUtil.now()).blockingGet()
if (activeProfileSwitch is ValueWrapper.Existing) { // read CPP values
rTitle = "opencpp"
rMessage = "opencpp"
rAction = "opencpp" + " " + activeProfileSwitch.value.originalPercentage + " " + activeProfileSwitch.value.originalTimeshift
} else {
sendError("No active profile switch!")
return
}
}
ACTION_TDD_STATS -> {
val activePump = activePlugin.activePump
// check if DB up to date
val dummies: MutableList<TotalDailyDose> = LinkedList()
val historyList = getTDDList(dummies)
if (isOldData(historyList)) {
rTitle = "TDD"
rAction = "statusmessage"
rMessage = "OLD DATA - "
//if pump is not busy: try to fetch data
if (activePump.isBusy()) {
rMessage += rh.gs(R.string.pumpbusy)
} else {
rMessage += "trying to fetch data from pump."
commandQueue.loadTDDs(object : Callback() {
override fun run() {
val dummies1: MutableList<TotalDailyDose> = LinkedList()
val historyList1 = getTDDList(dummies1)
if (isOldData(historyList1)) {
sendStatusMessage("TDD: Still old data! Cannot load from pump.\n" + generateTDDMessage(historyList1, dummies1))
} else {
sendStatusMessage(generateTDDMessage(historyList1, dummies1))
}
}
})
}
} else { // if up to date: prepare, send (check if CPP is activated -> add CPP stats)
rTitle = "TDD"
rAction = "statusmessage"
rMessage = generateTDDMessage(historyList, dummies)
}
}
ACTION_E_CARBS -> {
val carbs = SafeParse.stringToInt(act[1])
val starttime = SafeParse.stringToInt(act[2])
val duration = SafeParse.stringToInt(act[3])
val starttimestamp = System.currentTimeMillis() + starttime * 60 * 1000
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value()
rMessage += rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"
rMessage += "\n" + rh.gs(R.string.time) + ": " + dateUtil.timeString(starttimestamp)
rMessage += "\n" + rh.gs(R.string.duration) + ": " + duration + "h"
if (carbsAfterConstraints - carbs != 0) {
rMessage += "\n" + rh.gs(R.string.constraintapllied)
}
if (carbsAfterConstraints <= 0) {
sendError("Carbs = 0! No action taken!")
return
}
rAction += "ecarbs $carbsAfterConstraints $starttimestamp $duration"
}
ACTION_CHANGE_REQUEST -> {
rTitle = rh.gs(R.string.openloop_newsuggestion)
rAction = "changeRequest"
loop.lastRun?.let {
rMessage += it.constraintsProcessed
wearPlugin.requestChangeConfirmation(rTitle, rMessage, rAction)
lastSentTimestamp = System.currentTimeMillis()
lastConfirmActionString = rAction
}
return
}
ACTION_CANCEL_CHANGE_REQUEST -> {
rAction = "cancelChangeRequest"
wearPlugin.requestNotificationCancel(rAction)
return
}
else -> {
sendError(rh.gs(R.string.wear_unknown_action_string) + " " + act[0])
return
}
} else { // if up to date: prepare, send (check if CPP is activated -> add CPP stats)
rTitle = "TDD"
rAction = "statusmessage"
rMessage = generateTDDMessage(historyList, dummies)
}
} else if ("ecarbs" == act[0]) { ////////////////////////////////////////////// ECARBS
val carbs = SafeParse.stringToInt(act[1])
val starttime = SafeParse.stringToInt(act[2])
val duration = SafeParse.stringToInt(act[3])
val starttimestamp = System.currentTimeMillis() + starttime * 60 * 1000
val carbsAfterConstraints = constraintChecker.applyCarbsConstraints(Constraint(carbs)).value()
rMessage += rh.gs(R.string.carbs) + ": " + carbsAfterConstraints + "g"
rMessage += "\n" + rh.gs(R.string.time) + ": " + dateUtil.timeString(starttimestamp)
rMessage += "\n" + rh.gs(R.string.duration) + ": " + duration + "h"
if (carbsAfterConstraints - carbs != 0) {
rMessage += "\n" + rh.gs(R.string.constraintapllied)
}
if (carbsAfterConstraints <= 0) {
sendError("Carbs = 0! No action taken!")
return
}
rAction += "ecarbs $carbsAfterConstraints $starttimestamp $duration"
} else if ("changeRequest" == act[0]) { ////////////////////////////////////////////// CHANGE REQUEST
rTitle = rh.gs(R.string.openloop_newsuggestion)
rAction = "changeRequest"
loop.lastRun?.let {
rMessage += it.constraintsProcessed
wearPlugin.requestChangeConfirmation(rTitle, rMessage, rAction)
lastSentTimestamp = System.currentTimeMillis()
lastConfirmActionString = rAction
}
return
} else if ("cancelChangeRequest" == act[0]) { ////////////////////////////////////////////// CANCEL CHANGE REQUEST NOTIFICATION
rAction = "cancelChangeRequest"
wearPlugin.requestNotificationCancel(rAction)
return
} else {
sendError(rh.gs(R.string.wear_unknown_action_string) + act[0])
return
}
// send result
wearPlugin.requestActionConfirmation(rTitle, rMessage, rAction)
@ -594,44 +641,60 @@ class ActionStringHandler @Inject constructor(
lastConfirmActionString = null
// do the parsing, check constraints and enact!
val act = actionString.split("\\s+".toRegex()).toTypedArray()
if ("fill" == act[0]) {
val amount = SafeParse.stringToDouble(act[1])
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
if (amount - insulinAfterConstraints != 0.0) {
ToastUtils.showToastInUiThread(context, "aborting: previously applied constraint changed")
sendError("aborting: previously applied constraint changed")
return
when (act[0]) {
ACTION_FILL -> {
val amount = SafeParse.stringToDouble(act[1])
val insulinAfterConstraints = constraintChecker.applyBolusConstraints(Constraint(amount)).value()
if (amount - insulinAfterConstraints != 0.0) {
ToastUtils.showToastInUiThread(context, "aborting: previously applied constraint changed")
sendError("aborting: previously applied constraint changed")
return
}
doFillBolus(amount)
}
doFillBolus(amount)
} else if ("temptarget" == act[0]) {
val duration = SafeParse.stringToInt(act[2])
val low = SafeParse.stringToDouble(act[3])
val high = SafeParse.stringToDouble(act[4])
generateTempTarget(duration, low, high)
} else if ("wizard2" == act[0]) {
if (lastBolusWizard != null) { //use last calculation as confirmed string matches
doBolus(lastBolusWizard!!.calculatedTotalInsulin, lastBolusWizard!!.carbs, null, 0)
lastBolusWizard = null
ACTION_TEMPORARY_TARGET -> {
val duration = SafeParse.stringToInt(act[2])
val low = SafeParse.stringToDouble(act[3])
val high = SafeParse.stringToDouble(act[4])
generateTempTarget(duration, low, high)
}
ACTION_WIZARD2 -> {
if (lastBolusWizard != null) { //use last calculation as confirmed string matches
doBolus(lastBolusWizard!!.calculatedTotalInsulin, lastBolusWizard!!.carbs, null, 0)
lastBolusWizard = null
}
}
ACTION_BOLUS -> {
val insulin = SafeParse.stringToDouble(act[1])
val carbs = SafeParse.stringToInt(act[2])
doBolus(insulin, carbs, null, 0)
}
ACTION_CPP_SET -> {
val timeshift = SafeParse.stringToInt(act[1])
val percentage = SafeParse.stringToInt(act[2])
setCPP(timeshift, percentage)
}
ACTION_E_CARBS -> {
val carbs = SafeParse.stringToInt(act[1])
val starttime = SafeParse.stringToLong(act[2])
val duration = SafeParse.stringToInt(act[3])
doECarbs(carbs, starttime, duration)
}
ACTION_DISMISS_OVERVIEW_NOTIF -> {
rxBus.send(EventDismissNotification(SafeParse.stringToInt(act[1])))
}
ACTION_CHANGE_REQUEST -> {
loop.acceptChangeRequest()
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(Constants.notificationID)
}
} else if ("bolus" == act[0]) {
val insulin = SafeParse.stringToDouble(act[1])
val carbs = SafeParse.stringToInt(act[2])
doBolus(insulin, carbs, null, 0)
} else if ("cppset" == act[0]) {
val timeshift = SafeParse.stringToInt(act[1])
val percentage = SafeParse.stringToInt(act[2])
setCPP(timeshift, percentage)
} else if ("ecarbs" == act[0]) {
val carbs = SafeParse.stringToInt(act[1])
val starttime = SafeParse.stringToLong(act[2])
val duration = SafeParse.stringToInt(act[3])
doECarbs(carbs, starttime, duration)
} else if ("dismissoverviewnotification" == act[0]) {
rxBus.send(EventDismissNotification(SafeParse.stringToInt(act[1])))
} else if ("changeRequest" == act[0]) {
loop.acceptChangeRequest()
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(Constants.notificationID)
}
lastBolusWizard = null
}

View file

@ -6,14 +6,25 @@ import android.view.View
import android.view.ViewGroup
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.databinding.WearFragmentBinding
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
class WearFragment : DaggerFragment() {
@Inject lateinit var wearPlugin: WearPlugin
@Inject lateinit var rxBus: RxBus
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var fabricPrivacy: FabricPrivacy
private var _binding: WearFragmentBinding? = null
private val disposable = CompositeDisposable()
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
@ -27,7 +38,21 @@ class WearFragment : DaggerFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.resend.setOnClickListener { wearPlugin.resendDataToWatch() }
binding.opensettings.setOnClickListener { wearPlugin.openSettings() }
binding.openSettings.setOnClickListener { wearPlugin.openSettings() }
}
override fun onResume() {
super.onResume()
disposable += rxBus
.toObservable(EventNSClientUpdateGUI::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ updateGui() }, fabricPrivacy::logException)
updateGui()
}
override fun onPause() {
super.onPause()
disposable.clear()
}
@Synchronized
@ -35,4 +60,9 @@ class WearFragment : DaggerFragment() {
super.onDestroyView()
_binding = null
}
fun updateGui() {
_binding ?: return
binding.connectedDevice.text = wearPlugin.connectedDevice
}
}

View file

@ -6,12 +6,11 @@ import dagger.Lazy
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.interfaces.Loop
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.androidaps.plugins.aps.events.EventOpenAPSUpdateGui
import info.nightscout.androidaps.plugins.aps.loop.events.EventLoopUpdateGui
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning
import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress
@ -19,8 +18,10 @@ import info.nightscout.androidaps.plugins.general.wear.wearintegration.WatchUpda
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
@ -33,100 +34,103 @@ class WearPlugin @Inject constructor(
private val sp: SP,
private val ctx: Context,
private val fabricPrivacy: FabricPrivacy,
private val loop: Loop,
private val rxBus: RxBus,
private val actionStringHandler: Lazy<ActionStringHandler>
) : PluginBase(PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(WearFragment::class.java.name)
.pluginIcon(R.drawable.ic_watch)
.pluginName(R.string.wear)
.shortName(R.string.wear_shortname)
.preferencesId(R.xml.pref_wear)
.description(R.string.description_wear),
) : PluginBase(
PluginDescription()
.mainType(PluginType.GENERAL)
.fragmentClass(WearFragment::class.java.name)
.pluginIcon(R.drawable.ic_watch)
.pluginName(R.string.wear)
.shortName(R.string.wear_shortname)
.preferencesId(R.xml.pref_wear)
.description(R.string.description_wear),
aapsLogger, rh, injector
) {
private val disposable = CompositeDisposable()
var connectedDevice = "---"
override fun onStart() {
super.onStart()
disposable.add(rxBus
disposable += rxBus
.toObservable(EventOpenAPSUpdateGui::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException))
disposable.add(rxBus
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventExtendedBolusChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException))
disposable.add(rxBus
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTempBasalChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException))
disposable.add(rxBus
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventTreatmentChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException))
disposable.add(rxBus
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = false) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventEffectiveProfileSwitchChanged::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendDataToWatch(status = false, basals = true, bgValue = false) }, fabricPrivacy::logException))
disposable.add(rxBus
.subscribe({ sendDataToWatch(status = false, basals = true, bgValue = false) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = true) }, fabricPrivacy::logException))
disposable.add(rxBus
.subscribe({ sendDataToWatch(status = true, basals = true, bgValue = true) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
// possibly new high or low mark
resendDataToWatch()
// status may be formatted differently
sendDataToWatch(status = true, basals = false, bgValue = false)
}, fabricPrivacy::logException))
disposable.add(rxBus
.toObservable(EventRefreshOverview::class.java)
// possibly new high or low mark
resendDataToWatch()
// status may be formatted differently
sendDataToWatch(status = true, basals = false, bgValue = false)
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventLoopUpdateGui::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
if (WatchUpdaterService.shouldReportLoopStatus((loop as PluginBase).isEnabled()))
sendDataToWatch(status = true, basals = false, bgValue = false)
}, fabricPrivacy::logException))
disposable.add(rxBus
sendDataToWatch(status = true, basals = false, bgValue = false)
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventBolusRequested::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventBolusRequested ->
val status = rh.gs(R.string.bolusrequested, event.amount)
val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS)
intent.putExtra("progresspercent", 0)
intent.putExtra("progressstatus", status)
ctx.startService(intent)
}, fabricPrivacy::logException))
disposable.add(rxBus
val status = rh.gs(R.string.bolusrequested, event.amount)
val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUS_PROGRESS)
intent.putExtra("progresspercent", 0)
intent.putExtra("progressstatus", status)
ctx.startService(intent)
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventDismissBolusProgressIfRunning::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventDismissBolusProgressIfRunning ->
if (event.result == null) return@subscribe
val status: String = if (event.result!!.success) {
rh.gs(R.string.success)
} else {
rh.gs(R.string.nosuccess)
}
val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS)
intent.putExtra("progresspercent", 100)
intent.putExtra("progressstatus", status)
ctx.startService(intent)
}, fabricPrivacy::logException))
disposable.add(rxBus
if (event.result == null) return@subscribe
val status: String = if (event.result!!.success) {
rh.gs(R.string.success)
} else {
rh.gs(R.string.nosuccess)
}
val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUS_PROGRESS)
intent.putExtra("progresspercent", 100)
intent.putExtra("progressstatus", status)
ctx.startService(intent)
}, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventOverviewBolusProgress::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ event: EventOverviewBolusProgress ->
if (!event.isSMB() || sp.getBoolean("wear_notifySMB", true)) {
val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUSPROGRESS)
intent.putExtra("progresspercent", event.percent)
intent.putExtra("progressstatus", event.status)
ctx.startService(intent)
}
}, fabricPrivacy::logException))
if (!event.isSMB() || sp.getBoolean("wear_notifySMB", true)) {
val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BOLUS_PROGRESS)
intent.putExtra("progresspercent", event.percent)
intent.putExtra("progressstatus", event.status)
ctx.startService(intent)
}
}, fabricPrivacy::logException)
actionStringHandler.get().setup()
}
@ -137,51 +141,50 @@ class WearPlugin @Inject constructor(
}
private fun sendDataToWatch(status: Boolean, basals: Boolean, bgValue: Boolean) {
//Log.d(TAG, "WR: WearPlugin:sendDataToWatch (status=" + status + ",basals=" + basals + ",bgValue=" + bgValue + ")");
if (isEnabled(getType())) {
// only start service when this plugin is enabled
if (bgValue) {
ctx.startService(Intent(ctx, WatchUpdaterService::class.java))
}
if (basals) {
ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BASALS))
}
if (status) {
ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_STATUS))
}
// only start service when this plugin is enabled
if (isEnabled()) {
if (bgValue) ctx.startService(Intent(ctx, WatchUpdaterService::class.java))
if (basals) ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_BASALS))
if (status) ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_STATUS))
}
}
fun resendDataToWatch() {
//Log.d(TAG, "WR: WearPlugin:resendDataToWatch");
ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_RESEND))
}
fun openSettings() {
//Log.d(TAG, "WR: WearPlugin:openSettings");
ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_OPEN_SETTINGS))
}
fun requestNotificationCancel(actionString: String?) { //Log.d(TAG, "WR: WearPlugin:requestNotificationCancel");
val intent = Intent(ctx, WatchUpdaterService::class.java)
.setAction(WatchUpdaterService.ACTION_CANCEL_NOTIFICATION)
intent.putExtra("actionstring", actionString)
ctx.startService(intent)
fun requestNotificationCancel(actionString: String?) {
ctx.startService(
Intent(ctx, WatchUpdaterService::class.java)
.setAction(WatchUpdaterService.ACTION_CANCEL_NOTIFICATION)
.also {
it.putExtra("actionstring", actionString)
})
}
fun requestActionConfirmation(title: String, message: String, actionString: String) {
val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_ACTIONCONFIRMATIONREQUEST)
intent.putExtra("title", title)
intent.putExtra("message", message)
intent.putExtra("actionstring", actionString)
ctx.startService(intent)
ctx.startService(
Intent(ctx, WatchUpdaterService::class.java)
.setAction(WatchUpdaterService.ACTION_SEND_ACTION_CONFIRMATION_REQUEST)
.also {
it.putExtra("title", title)
it.putExtra("message", message)
it.putExtra("actionstring", actionString)
})
}
fun requestChangeConfirmation(title: String, message: String, actionString: String) {
val intent = Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_SEND_CHANGECONFIRMATIONREQUEST)
intent.putExtra("title", title)
intent.putExtra("message", message)
intent.putExtra("actionstring", actionString)
ctx.startService(intent)
ctx.startService(
Intent(ctx, WatchUpdaterService::class.java)
.setAction(WatchUpdaterService.ACTION_SEND_CHANGE_CONFIRMATION_REQUEST)
.also {
it.putExtra("title", title)
it.putExtra("message", message)
it.putExtra("actionstring", actionString)
})
}
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.general.wear.events
import info.nightscout.androidaps.events.Event
class EventWearUpdateGui : Event()

View file

@ -1,139 +0,0 @@
package info.nightscout.androidaps.plugins.general.wear.wearintegration;
import android.os.AsyncTask;
import android.util.Log;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by emmablack on 12/26/14.
*/
class SendToDataLayerThread extends AsyncTask<DataMap,Void,Void> {
private final GoogleApiClient googleApiClient;
private static final String TAG = "SendToDataLayerThread";
private final String path;
private final String logPrefix = ""; // "WR: ";
private static int concurrency = 0;
private static int state = 0;
private static final ReentrantLock lock = new ReentrantLock();
private static long lastlock = 0;
private static final boolean testlockup = false; // always false in production
SendToDataLayerThread(String path, GoogleApiClient pGoogleApiClient) {
// Log.d(TAG, logPrefix + "SendToDataLayerThread: " + path);
this.path = path;
googleApiClient = pGoogleApiClient;
}
@Override
protected void onPreExecute() {
concurrency++;
if ((concurrency > 12) || ((concurrency > 3 && (lastlock != 0) && (tsl() - lastlock) > 300000))) {
// error if 9 concurrent threads or lock held for >5 minutes with concurrency of 4
final String err = "Wear Integration deadlock detected!! " + ((lastlock != 0) ? "locked" : "") + " state:"
+ state + " @" + hourMinuteString(tsl());
// Home.toaststaticnext(err);
Log.e(TAG, logPrefix + err);
}
if (concurrency < 0)
Log.d(TAG, logPrefix + "Wear Integration impossible concurrency!!");
Log.d(TAG, logPrefix + "SendDataToLayerThread pre-execute concurrency: " + concurrency);
}
@Override
protected Void doInBackground(DataMap... params) {
if (testlockup) {
try {
Log.e(TAG, logPrefix + "WARNING RUNNING TEST LOCK UP CODE - NEVER FOR PRODUCTION");
Thread.sleep(1000000); // DEEEBBUUGGGG
} catch (Exception e) {
}
}
sendToWear(params);
concurrency--;
Log.d(TAG, logPrefix + "SendDataToLayerThread post-execute concurrency: " + concurrency);
return null;
}
// Debug function to expose where it might be locking up
private synchronized void sendToWear(final DataMap... params) {
if (!lock.tryLock()) {
Log.d(TAG, logPrefix + "Concurrent access - waiting for thread unlock");
lock.lock(); // enforce single threading
Log.d(TAG, logPrefix + "Thread unlocked - proceeding");
}
lastlock = tsl();
try {
if (state != 0) {
Log.e(TAG, logPrefix + "WEAR STATE ERROR: state=" + state);
}
state = 1;
final NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await(15,
TimeUnit.SECONDS);
Log.d(TAG, logPrefix + "Nodes: " + nodes);
state = 2;
for (Node node : nodes.getNodes()) {
state = 3;
for (DataMap dataMap : params) {
state = 4;
PutDataMapRequest putDMR = PutDataMapRequest.create(path);
state = 5;
putDMR.getDataMap().putAll(dataMap);
putDMR.setUrgent();
state = 6;
PutDataRequest request = putDMR.asPutDataRequest();
state = 7;
DataApi.DataItemResult result = Wearable.DataApi.putDataItem(googleApiClient, request).await(15,
TimeUnit.SECONDS);
state = 8;
if (result.getStatus().isSuccess()) {
Log.d(TAG, logPrefix + "DataMap: " + dataMap + " sent to: " + node.getDisplayName());
} else {
Log.e(TAG, logPrefix + "ERROR: failed to send DataMap");
result = Wearable.DataApi.putDataItem(googleApiClient, request).await(30, TimeUnit.SECONDS);
if (result.getStatus().isSuccess()) {
Log.d(TAG, logPrefix + "DataMap retry: " + dataMap + " sent to: " + node.getDisplayName());
} else {
Log.e(TAG, logPrefix + "ERROR on retry: failed to send DataMap: "
+ result.getStatus().toString());
}
}
state = 9;
}
}
state = 0;
} catch (Exception e) {
Log.e(TAG, logPrefix + "Got exception in sendToWear: " + e);
} finally {
lastlock = 0;
lock.unlock();
}
}
private static long tsl() {
return System.currentTimeMillis();
}
private static String hourMinuteString(long timestamp) {
return android.text.format.DateFormat.format("kk:mm", timestamp).toString();
}
}

View file

@ -0,0 +1,633 @@
package info.nightscout.androidaps.plugins.general.wear.wearintegration
import android.content.Intent
import android.os.Handler
import android.os.HandlerThread
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.*
import dagger.android.AndroidInjection
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.entities.Bolus
import info.nightscout.androidaps.database.entities.GlucoseValue
import info.nightscout.androidaps.database.entities.TemporaryBasal
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.toStringShort
import info.nightscout.androidaps.extensions.valueToUnitsString
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.interfaces.Profile.Companion.fromMgdlToUnits
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAction
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.general.wear.events.EventWearUpdateGui
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.utils.DecimalFormatter.to0Decimal
import info.nightscout.androidaps.utils.DecimalFormatter.to1Decimal
import info.nightscout.androidaps.utils.DecimalFormatter.to2Decimal
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.TrendCalculator
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.WearConstants
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.coroutines.*
import kotlinx.coroutines.tasks.await
import java.util.function.Consumer
import java.util.stream.Collectors
import javax.inject.Inject
import kotlin.math.abs
class WatchUpdaterService : WearableListenerService() {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var profileFunction: ProfileFunction
@Inject lateinit var iobCobCalculator: IobCobCalculator
@Inject lateinit var rh: ResourceHelper
@Inject lateinit var loop: Loop
@Inject lateinit var wearPlugin: WearPlugin
@Inject lateinit var sp: SP
@Inject lateinit var quickWizard: QuickWizard
@Inject lateinit var config: Config
@Inject lateinit var nsDeviceStatus: NSDeviceStatus
@Inject lateinit var receiverStatusStore: ReceiverStatusStore
@Inject lateinit var repository: AppRepository
@Inject lateinit var defaultValueHelper: DefaultValueHelper
@Inject lateinit var glucoseStatusProvider: GlucoseStatusProvider
@Inject lateinit var trendCalculator: TrendCalculator
@Inject lateinit var activePlugin: ActivePlugin
@Inject lateinit var rxBus: RxBus
@Inject lateinit var wearConstants: WearConstants
private val dataClient by lazy { Wearable.getDataClient(this) }
private val messageClient by lazy { Wearable.getMessageClient(this) }
private val capabilityClient by lazy { Wearable.getCapabilityClient(this) }
//private val nodeClient by lazy { Wearable.getNodeClient(this) }
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
private val disposable = CompositeDisposable()
override fun onCreate() {
AndroidInjection.inject(this)
super.onCreate()
aapsLogger.debug(LTag.WEAR, "onCreate")
handler.post { updateTranscriptionCapability() }
}
override fun onCapabilityChanged(p0: CapabilityInfo) {
super.onCapabilityChanged(p0)
handler.post { updateTranscriptionCapability() }
aapsLogger.debug(LTag.WEAR, "onCapabilityChanged: ${p0.name} ${p0.nodes.joinToString(", ") { it.displayName + "(" + it.id + ")" }}")
}
override fun onDestroy() {
super.onDestroy()
disposable.clear()
scope.cancel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
aapsLogger.debug(LTag.WEAR, "onStartCommand ${intent?.action}")
if (wearPlugin.isEnabled()) {
handler.post {
when (intent?.action) {
ACTION_RESEND -> resendData()
ACTION_OPEN_SETTINGS -> sendMessage(wearConstants.M_W_OPEN_SETTINGS, byteArrayOf())
ACTION_SEND_STATUS -> sendStatus()
ACTION_SEND_BASALS -> sendBasals()
ACTION_SEND_BOLUS_PROGRESS -> sendBolusProgress(
intent.getIntExtra("progresspercent", 0),
intent.getStringExtra("progressstatus")
)
ACTION_SEND_ACTION_CONFIRMATION_REQUEST -> sendActionConfirmationRequest(
intent.getStringExtra("title"),
intent.getStringExtra("message"),
intent.getStringExtra("actionstring")
)
ACTION_SEND_CHANGE_CONFIRMATION_REQUEST -> sendChangeConfirmationRequest(
intent.getStringExtra("title"),
intent.getStringExtra("message"),
intent.getStringExtra("actionstring")
)
ACTION_CANCEL_NOTIFICATION -> sendCancelNotificationRequest(intent.getStringExtra("actionstring"))
null -> {}
else -> sendData()
}
}
}
return START_STICKY
}
@Suppress("ControlFlowWithEmptyBody", "UNUSED_EXPRESSION")
override fun onDataChanged(dataEvents: DataEventBuffer) {
//aapsLogger.debug(LTag.WEAR, "onDataChanged")
if (wearPlugin.isEnabled()) {
dataEvents.forEach { event ->
if (event.type == DataEvent.TYPE_CHANGED) {
val path = event.dataItem.uri.path
aapsLogger.debug(LTag.WEAR, "onDataChanged: Path: $path, EventDataItem=${event.dataItem}")
try {
when (path) {
}
} catch (exception: Exception) {
aapsLogger.error(LTag.WEAR, "Message failed", exception)
}
}
}
}
super.onDataChanged(dataEvents)
}
override fun onMessageReceived(messageEvent: MessageEvent) {
super.onMessageReceived(messageEvent)
aapsLogger.debug(LTag.WEAR, "onMessageReceived: $messageEvent")
if (wearPlugin.isEnabled()) {
when (messageEvent.path) {
wearConstants.W_M_RESEND_DATA -> resendData()
wearConstants.W_M_CANCEL_BOLUS -> activePlugin.activePump.stopBolusDelivering()
wearConstants.W_M_INITIATE_ACTION ->
String(messageEvent.data).also { actionstring ->
aapsLogger.debug(LTag.WEAR, "Initiate action: $actionstring")
rxBus.send(EventWearInitiateAction(actionstring))
}
wearConstants.W_M_CONFIRM_ACTION ->
String(messageEvent.data).also { actionstring ->
aapsLogger.debug(LTag.WEAR, "Wear confirm action: $actionstring")
rxBus.send(EventWearConfirmAction(actionstring))
}
wearConstants.W_M_PONG -> aapsLogger.debug(LTag.WEAR, "Pong response from ${messageEvent.sourceNodeId}")
}
}
}
private var transcriptionNodeId: String? = null
private fun updateTranscriptionCapability() {
val capabilityInfo: CapabilityInfo = Tasks.await(
capabilityClient.getCapability(WEAR_CAPABILITY, CapabilityClient.FILTER_REACHABLE)
)
aapsLogger.debug(LTag.WEAR, "Nodes: ${capabilityInfo.nodes.joinToString(", ") { it.displayName + "(" + it.id + ")" }}")
val bestNode = pickBestNodeId(capabilityInfo.nodes)
transcriptionNodeId = bestNode?.id
wearPlugin.connectedDevice = bestNode?.displayName ?: "---"
rxBus.send(EventWearUpdateGui())
aapsLogger.debug(LTag.WEAR, "Selected node: ${bestNode?.displayName} $transcriptionNodeId")
sendMessage(wearConstants.M_W_PING, byteArrayOf())
}
// Find a nearby node or pick one arbitrarily
private fun pickBestNodeId(nodes: Set<Node>): Node? =
nodes.firstOrNull { it.isNearby } ?: nodes.firstOrNull()
private fun sendData(path: String, vararg params: DataMap) {
if (wearPlugin.isEnabled()) {
scope.launch {
try {
for (dm in params) {
val request = PutDataMapRequest.create(path).apply {
dataMap.putAll(dm)
}
.asPutDataRequest()
.setUrgent()
val result = dataClient.putDataItem(request).await()
aapsLogger.debug(LTag.WEAR, "sendData: ${result.uri} ${params.joinToString()}")
}
} catch (cancellationException: CancellationException) {
throw cancellationException
} catch (exception: Exception) {
aapsLogger.error(LTag.WEAR, "DataItem failed: $exception")
}
}
}
}
private fun sendMessage(path: String, data: ByteArray) {
if (wearPlugin.isEnabled()) {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path")
transcriptionNodeId?.also { nodeId ->
messageClient
.sendMessage(nodeId, path, data).apply {
addOnSuccessListener { }
addOnFailureListener {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path failure")
}
}
}
}
}
private fun sendData() {
val lastBG = iobCobCalculator.ads.lastBg() ?: return
val glucoseStatus = glucoseStatusProvider.glucoseStatusData
aapsLogger.debug(LTag.WEAR, "Sending bg data to wear")
sendData(
wearConstants.M_W_DATA,
dataMapSingleBG(lastBG, glucoseStatus)
)
}
private fun resendData() {
sendPreferences()
sendQuickWizard()
val startTime = System.currentTimeMillis() - (60000 * 60 * 5.5).toLong()
val lastBg = iobCobCalculator.ads.lastBg() ?: return
val graphBgs = repository.compatGetBgReadingsDataFromTime(startTime, true).blockingGet()
val glucoseStatus = glucoseStatusProvider.getGlucoseStatusData(true)
if (graphBgs.isNotEmpty()) {
val entries = dataMapSingleBG(lastBg, glucoseStatus)
val dataMaps = ArrayList<DataMap>(graphBgs.size)
for (bg in graphBgs) {
val dataMap: DataMap = dataMapSingleBG(bg, glucoseStatus)
dataMaps.add(dataMap)
}
entries.putDataMapArrayList("entries", dataMaps)
aapsLogger.debug(LTag.WEAR, "Sending graph bg data to wear")
sendData(
wearConstants.M_W_DATA,
entries
)
}
sendBasals()
sendStatus()
}
private fun sendBasals() {
val now = System.currentTimeMillis()
val startTimeWindow = now - (60000 * 60 * 5.5).toLong()
val basals = java.util.ArrayList<DataMap>()
val temps = java.util.ArrayList<DataMap>()
val boluses = java.util.ArrayList<DataMap>()
val predictions = java.util.ArrayList<DataMap>()
val profile = profileFunction.getProfile() ?: return
var beginBasalSegmentTime = startTimeWindow
var runningTime = startTimeWindow
var beginBasalValue = profile.getBasal(beginBasalSegmentTime)
var endBasalValue = beginBasalValue
var tb1 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime)
var tb2: TemporaryBasal?
var tbBefore = beginBasalValue
var tbAmount = beginBasalValue
var tbStart = runningTime
if (tb1 != null) {
val profileTB = profileFunction.getProfile(runningTime)
if (profileTB != null) {
tbAmount = tb1.convertedToAbsolute(runningTime, profileTB)
tbStart = runningTime
}
}
while (runningTime < now) {
val profileTB = profileFunction.getProfile(runningTime) ?: return
//basal rate
endBasalValue = profile.getBasal(runningTime)
if (endBasalValue != beginBasalValue) {
//push the segment we recently left
basals.add(basalMap(beginBasalSegmentTime, runningTime, beginBasalValue))
//begin new Basal segment
beginBasalSegmentTime = runningTime
beginBasalValue = endBasalValue
}
//temps
tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime)
if (tb1 == null && tb2 == null) {
//no temp stays no temp
} else if (tb1 != null && tb2 == null) {
//temp is over -> push it
temps.add(tempMap(tbStart, tbBefore, runningTime, endBasalValue, tbAmount))
tb1 = null
} else if (tb1 == null && tb2 != null) {
//temp begins
tb1 = tb2
tbStart = runningTime
tbBefore = endBasalValue
tbAmount = tb1.convertedToAbsolute(runningTime, profileTB)
} else if (tb1 != null && tb2 != null) {
val currentAmount = tb2.convertedToAbsolute(runningTime, profileTB)
if (currentAmount != tbAmount) {
temps.add(tempMap(tbStart, tbBefore, runningTime, currentAmount, tbAmount))
tbStart = runningTime
tbBefore = tbAmount
tbAmount = currentAmount
tb1 = tb2
}
}
runningTime += (5 * 60 * 1000).toLong()
}
if (beginBasalSegmentTime != runningTime) {
//push the remaining segment
basals.add(basalMap(beginBasalSegmentTime, runningTime, beginBasalValue))
}
if (tb1 != null) {
tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(now) //use "now" to express current situation
if (tb2 == null) {
//express the cancelled temp by painting it down one minute early
temps.add(tempMap(tbStart, tbBefore, now - 60 * 1000, endBasalValue, tbAmount))
} else {
//express currently running temp by painting it a bit into the future
val profileNow = profileFunction.getProfile(now)
val currentAmount = tb2.convertedToAbsolute(now, profileNow!!)
if (currentAmount != tbAmount) {
temps.add(tempMap(tbStart, tbBefore, now, tbAmount, tbAmount))
temps.add(tempMap(now, tbAmount, runningTime + 5 * 60 * 1000, currentAmount, currentAmount))
} else {
temps.add(tempMap(tbStart, tbBefore, runningTime + 5 * 60 * 1000, tbAmount, tbAmount))
}
}
} else {
tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(now) //use "now" to express current situation
if (tb2 != null) {
//onset at the end
val profileTB = profileFunction.getProfile(runningTime)
val currentAmount = tb2.convertedToAbsolute(runningTime, profileTB!!)
temps.add(tempMap(now - 60 * 1000, endBasalValue, runningTime + 5 * 60 * 1000, currentAmount, currentAmount))
}
}
repository.getBolusesIncludingInvalidFromTime(startTimeWindow, true).blockingGet()
.stream()
.filter { (_, _, _, _, _, _, _, _, _, type) -> type !== Bolus.Type.PRIMING }
.forEach { (_, _, _, isValid, _, _, timestamp, _, amount, type) -> boluses.add(treatmentMap(timestamp, amount, 0.0, type === Bolus.Type.SMB, isValid)) }
repository.getCarbsDataFromTimeExpanded(startTimeWindow, true).blockingGet()
.forEach(Consumer { (_, _, _, isValid, _, _, timestamp, _, _, amount) -> boluses.add(treatmentMap(timestamp, 0.0, amount, false, isValid)) })
val finalLastRun = loop.lastRun
if (sp.getBoolean("wear_predictions", true) && finalLastRun?.request?.hasPredictions == true && finalLastRun.constraintsProcessed != null) {
val predArray = finalLastRun.constraintsProcessed!!.predictions
.stream().map { bg: GlucoseValue -> GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh) }
.collect(Collectors.toList())
if (predArray.isNotEmpty())
for (bg in predArray) if (bg.data.value > 39) predictions.add(predictionMap(bg.data.timestamp, bg.data.value, bg.color(null)))
}
aapsLogger.debug(LTag.WEAR, "Sending basal data to wear")
sendData(
wearConstants.M_W_BASAL,
DataMap().apply {
putDataMapArrayList("basals", basals)
putDataMapArrayList("temps", temps)
putDataMapArrayList("boluses", boluses)
putDataMapArrayList("predictions", predictions)
})
}
private fun deltaString(deltaMGDL: Double, deltaMMOL: Double, units: GlucoseUnit): String {
val detailed = sp.getBoolean(R.string.key_wear_detailed_delta, false)
var deltaString = if (deltaMGDL >= 0) "+" else "-"
deltaString += if (units == GlucoseUnit.MGDL) {
if (detailed) to1Decimal(abs(deltaMGDL)) else to0Decimal(abs(deltaMGDL))
} else {
if (detailed) to2Decimal(abs(deltaMMOL)) else to1Decimal(abs(deltaMMOL))
}
return deltaString
}
private fun dataMapSingleBG(lastBG: GlucoseValue, glucoseStatus: GlucoseStatus?): DataMap {
val units = profileFunction.getUnits()
val lowLine = Profile.toMgdl(defaultValueHelper.determineLowLine(), units)
val highLine = Profile.toMgdl(defaultValueHelper.determineHighLine(), units)
val sgvLevel = if (lastBG.value > highLine) 1L else if (lastBG.value < lowLine) -1L else 0L
val dataMap = DataMap()
dataMap.putString("sgvString", lastBG.valueToUnitsString(units))
dataMap.putString("glucoseUnits", units.asText)
dataMap.putLong("timestamp", lastBG.timestamp)
if (glucoseStatus == null) {
dataMap.putString("slopeArrow", "")
dataMap.putString("delta", "--")
dataMap.putString("avgDelta", "--")
} else {
dataMap.putString("slopeArrow", trendCalculator.getTrendArrow(lastBG).symbol)
dataMap.putString("delta", deltaString(glucoseStatus.delta, glucoseStatus.delta * Constants.MGDL_TO_MMOLL, units))
dataMap.putString("avgDelta", deltaString(glucoseStatus.shortAvgDelta, glucoseStatus.shortAvgDelta * Constants.MGDL_TO_MMOLL, units))
}
dataMap.putLong("sgvLevel", sgvLevel)
dataMap.putDouble("sgvDouble", lastBG.value)
dataMap.putDouble("high", highLine)
dataMap.putDouble("low", lowLine)
return dataMap
}
private fun tempMap(startTime: Long, startBasal: Double, to: Long, toBasal: Double, amount: Double) =
DataMap().apply {
putLong("starttime", startTime)
putDouble("startBasal", startBasal)
putLong("endtime", to)
putDouble("endbasal", toBasal)
putDouble("amount", amount)
}
private fun basalMap(startTime: Long, endTime: Long, amount: Double) =
DataMap().apply {
putLong("starttime", startTime)
putLong("endtime", endTime)
putDouble("amount", amount)
}
private fun treatmentMap(date: Long, bolus: Double, carbs: Double, isSMB: Boolean, isValid: Boolean) =
DataMap().apply {
putLong("date", date)
putDouble("bolus", bolus)
putDouble("carbs", carbs)
putBoolean("isSMB", isSMB)
putBoolean("isValid", isValid)
}
private fun predictionMap(timestamp: Long, sgv: Double, color: Int) =
DataMap().apply {
putLong("timestamp", timestamp)
putDouble("sgv", sgv)
putInt("color", color)
}
private fun quickMap(q: QuickWizardEntry) =
DataMap().apply {
putString("guid", q.guid())
putString("button_text", q.buttonText())
putInt("carbs", q.carbs())
putInt("from", q.validFrom())
putInt("to", q.validTo())
}
private fun sendBolusProgress(progressPercent: Int?, status: String?) {
progressPercent ?: return
aapsLogger.debug(LTag.WEAR, "Sending bolus progress: $progressPercent $status")
sendData(
wearConstants.M_W_BOLUS_PROGRESS,
DataMap().apply {
putLong("timestamp", System.currentTimeMillis())
putString("bolusProgress", "bolusProgress")
putString("progressstatus", status ?: "")
putInt("progresspercent", progressPercent)
})
}
private fun sendActionConfirmationRequest(title: String?, message: String?, actionstring: String?) {
title ?: message ?: actionstring ?: return
aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: $actionstring")
sendData(
wearConstants.M_W_ACTION_CONFIRMATION_REQUEST,
DataMap().apply {
putLong("timestamp", System.currentTimeMillis())
putString("actionConfirmationRequest", "actionConfirmationRequest")
putString("title", title)
putString("message", message)
putString("actionstring", actionstring)
})
}
private fun sendChangeConfirmationRequest(title: String?, message: String?, actionstring: String?) {
title ?: message ?: actionstring ?: return
aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: $actionstring")
sendData(
wearConstants.M_W_ACTION_CHANGE_CONFIRMATION_REQUEST,
DataMap().apply {
putLong("timestamp", System.currentTimeMillis())
putString("changeConfirmationRequest", "changeConfirmationRequest")
putString("title", title)
putString("message", message)
putString("actionstring", actionstring)
})
}
private fun sendCancelNotificationRequest(actionstring: String?) {
actionstring ?: return
aapsLogger.debug(LTag.WEAR, "Canceling notification on wear: $actionstring")
sendData(
wearConstants.M_W_ACTION_CANCEL_NOTIFICATION_REQUEST,
DataMap().apply {
putLong("timestamp", System.currentTimeMillis())
putString("cancelNotificationRequest", "cancelNotificationRequest")
putString("actionstring", actionstring)
})
}
private fun sendStatus() {
aapsLogger.debug(LTag.WEAR, "Updating status on wear")
val profile = profileFunction.getProfile()
var status = rh.gs(R.string.noprofile)
var iobSum = ""
var iobDetail = ""
var cobString = ""
var currentBasal = ""
var bgiString = ""
if (profile != null) {
val bolusIob = iobCobCalculator.calculateIobFromBolus().round()
val basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round()
iobSum = to2Decimal(bolusIob.iob + basalIob.basaliob)
iobDetail = "(" + to2Decimal(bolusIob.iob) + "|" + to2Decimal(basalIob.basaliob) + ")"
cobString = iobCobCalculator.getCobInfo(false, "WatcherUpdaterService").generateCOBString()
currentBasal = generateBasalString()
//bgi
val bgi = -(bolusIob.activity + basalIob.activity) * 5 * fromMgdlToUnits(profile.getIsfMgdl(), profileFunction.getUnits())
bgiString = "" + (if (bgi >= 0) "+" else "") + to1Decimal(bgi)
status = generateStatusString(profile, currentBasal, iobSum, iobDetail, bgiString)
}
//batteries
val phoneBattery = receiverStatusStore.batteryLevel
val rigBattery = nsDeviceStatus.uploaderStatus.trim { it <= ' ' }
//OpenAPS status
val openApsStatus =
if (config.APS) loop.lastRun?.let { if (it.lastTBREnact != 0L) it.lastTBREnact else -1 } ?: -1
else nsDeviceStatus.openApsTimestamp
sendData(
wearConstants.M_W_STATUS,
DataMap().apply {
//unique content
putString("externalStatusString", status)
putString("iobSum", iobSum)
putString("iobDetail", iobDetail)
putBoolean("detailedIob", sp.getBoolean(R.string.key_wear_detailediob, false))
putString("cob", cobString)
putString("currentBasal", currentBasal)
putString("battery", "" + phoneBattery)
putString("rigBattery", rigBattery)
putLong("openApsStatus", openApsStatus)
putString("bgi", bgiString)
putBoolean("showBgi", sp.getBoolean(R.string.key_wear_showbgi, false))
putInt("batteryLevel", if (phoneBattery >= 30) 1 else 0)
})
}
private fun sendPreferences() {
sendData(
wearConstants.M_W_PREFERENCES,
DataMap().apply {
putLong("timestamp", System.currentTimeMillis())
putBoolean(rh.gs(R.string.key_wear_control), sp.getBoolean(R.string.key_wear_control, false))
putBoolean(rh.gs(R.string.key_units_mgdl), profileFunction.getUnits() == GlucoseUnit.MGDL)
putInt(rh.gs(R.string.key_boluswizard_percentage), sp.getInt(R.string.key_boluswizard_percentage, 100))
putInt(rh.gs(R.string.key_treatmentssafety_maxcarbs), sp.getInt(R.string.key_treatmentssafety_maxcarbs, 48))
putDouble(rh.gs(R.string.key_treatmentssafety_maxbolus), sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0))
})
}
private fun sendQuickWizard() {
val entities = ArrayList<DataMap>()
for (i in 0 until quickWizard.size()) {
val q = quickWizard[i]
if (q.forDevice(QuickWizardEntry.DEVICE_WATCH)) entities.add(quickMap(q))
}
sendData(
wearConstants.M_W_QUICK_WIZARD,
DataMap().apply {
putLong("timestamp", System.currentTimeMillis())
putDataMapArrayList("quick_wizard", entities)
})
}
private fun generateStatusString(profile: Profile?, currentBasal: String, iobSum: String, iobDetail: String, bgiString: String): String {
var status = ""
if (profile == null) return rh.gs(R.string.noprofile)
if (!(loop as PluginBase).isEnabled()) status += rh.gs(R.string.disabledloop) + "\n"
val iobString =
if (sp.getBoolean(R.string.key_wear_detailediob, false)) "$iobSum $iobDetail"
else iobSum + "U"
status += "$currentBasal $iobString"
//add BGI if shown, otherwise return
if (sp.getBoolean(R.string.key_wear_showbgi, false)) status += " $bgiString"
return status
}
private fun generateBasalString(): String {
val profile: Profile = profileFunction.getProfile() ?: return ""
return iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis())?.toStringShort() ?: to2Decimal(profile.getBasal()) + "U/h"
}
companion object {
const val WEAR_CAPABILITY = "androidaps_wear"
val ACTION_RESEND = WatchUpdaterService::class.java.name + ".Resend"
val ACTION_OPEN_SETTINGS = WatchUpdaterService::class.java.name + ".OpenSettings"
val ACTION_SEND_STATUS = WatchUpdaterService::class.java.name + ".SendStatus"
val ACTION_SEND_BASALS = WatchUpdaterService::class.java.name + ".SendBasals"
val ACTION_SEND_BOLUS_PROGRESS = WatchUpdaterService::class.java.name + ".BolusProgress"
val ACTION_SEND_ACTION_CONFIRMATION_REQUEST = WatchUpdaterService::class.java.name + ".ActionConfirmationRequest"
val ACTION_SEND_CHANGE_CONFIRMATION_REQUEST = WatchUpdaterService::class.java.name + ".ChangeConfirmationRequest"
val ACTION_CANCEL_NOTIFICATION = WatchUpdaterService::class.java.name + ".CancelNotification"
}
}

View file

@ -1,73 +1,7 @@
package info.nightscout.androidaps.plugins.general.wear.wearintegration;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.CapabilityApi;
import com.google.android.gms.wearable.CapabilityInfo;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import dagger.android.AndroidInjection;
import info.nightscout.androidaps.Constants;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.IobTotal;
import info.nightscout.androidaps.database.AppRepository;
import info.nightscout.androidaps.database.entities.Bolus;
import info.nightscout.androidaps.database.entities.GlucoseValue;
import info.nightscout.androidaps.database.entities.TemporaryBasal;
import info.nightscout.androidaps.extensions.GlucoseValueExtensionKt;
import info.nightscout.androidaps.extensions.TemporaryBasalExtensionKt;
import info.nightscout.androidaps.interfaces.ActivePlugin;
import info.nightscout.androidaps.interfaces.Config;
import info.nightscout.androidaps.interfaces.GlucoseUnit;
import info.nightscout.androidaps.interfaces.IobCobCalculator;
import info.nightscout.androidaps.interfaces.Loop;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.Profile;
import info.nightscout.androidaps.interfaces.ProfileFunction;
import info.nightscout.androidaps.plugins.aps.loop.LoopPlugin;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus;
import info.nightscout.androidaps.plugins.general.overview.graphExtensions.GlucoseValueDataPoint;
import info.nightscout.androidaps.plugins.general.wear.WearPlugin;
import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAction;
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatus;
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider;
import info.nightscout.androidaps.receivers.ReceiverStatusStore;
import info.nightscout.androidaps.utils.DecimalFormatter;
import info.nightscout.androidaps.utils.DefaultValueHelper;
import info.nightscout.androidaps.utils.TrendCalculator;
import info.nightscout.androidaps.utils.resources.ResourceHelper;
import info.nightscout.androidaps.utils.wizard.QuickWizard;
import info.nightscout.androidaps.utils.wizard.QuickWizardEntry;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import info.nightscout.shared.sharedPreferences.SP;
import info.nightscout.shared.weardata.WearUris;
public class WatchUpdaterService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
/*
public class WatchUpdaterService1 extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
@Inject public GlucoseStatusProvider glucoseStatusProvider;
@Inject public AAPSLogger aapsLogger;
@Inject public WearPlugin wearPlugin;
@ -841,3 +775,4 @@ public class WatchUpdaterService extends WearableListenerService implements Goog
return (lastLoopStatus != enabled);
}
}
*/

View file

@ -29,6 +29,7 @@ import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.workflow.CalculationWorkflow
import info.nightscout.androidaps.events.Event
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP

View file

@ -5,13 +5,13 @@ import info.nightscout.androidaps.R
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.common.ManufacturerType
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.InstanceId.instanceId
import info.nightscout.androidaps.utils.InstanceId
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import org.json.JSONException
import org.json.JSONObject
import javax.inject.Inject
@ -152,7 +152,7 @@ class MDIPlugin @Inject constructor(
override fun manufacturer(): ManufacturerType = ManufacturerType.AndroidAPS
override fun model(): PumpType = PumpType.MDI
override fun serialNumber(): String = instanceId()
override fun serialNumber(): String = InstanceId.instanceId
override fun shortStatus(veryShort: Boolean): String = model().model
override fun canHandleDST(): Boolean = true
}

View file

@ -11,8 +11,6 @@ import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.extensions.convertedToAbsolute
import info.nightscout.androidaps.extensions.plannedRemainingMinutes
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.common.ManufacturerType
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
@ -22,11 +20,13 @@ import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.pump.virtual.events.EventVirtualPumpUpdateGui
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.InstanceId.instanceId
import info.nightscout.androidaps.utils.InstanceId
import info.nightscout.androidaps.utils.T
import info.nightscout.androidaps.utils.TimeChangeType
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
@ -365,7 +365,7 @@ open class VirtualPumpPlugin @Inject constructor(
override fun model(): PumpType = pumpDescription.pumpType
override fun serialNumber(): String = instanceId()
override fun serialNumber(): String = InstanceId.instanceId
override fun shortStatus(veryShort: Boolean): String = "Virtual Pump"

View file

@ -10,31 +10,38 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
style="@style/GrayButton"
android:id="@+id/resend"
android:layout_width="fill_parent"
<TextView
android:id="@+id/connected_device"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="3dp"
android:layout_weight="0.5"
android:text="@string/resend_all_data"
android:textColor="?attr/treatmentButton" />
android:gravity="center_vertical|center_horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
tools:text="---" />
<com.google.android.material.button.MaterialButton
style="@style/GrayButton"
android:id="@+id/opensettings"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="3dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="3dp"
android:layout_weight="0.5"
android:text="@string/open_settings_on_wear"
android:textColor="?attr/treatmentButton" />
android:id="@+id/resend"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_refresh"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/resend_all_data" />
<com.google.android.material.button.MaterialButton
android:id="@+id/open_settings"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_settings"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/open_settings_on_wear" />
</LinearLayout>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<string-array
name="android_wear_capabilities"
translatable="false"
tools:ignore="UnusedResources">
<!-- declaring the provided capabilities -->
<item>androidaps_mobile</item>
</string-array>
</resources>

View file

@ -10,7 +10,7 @@ buildscript {
room_version = '2.4.2'
lifecycle_version = '2.4.1'
dagger_version = '2.41'
coroutines_version = '1.4.1'
coroutines_version = '1.6.1'
activity_version = '1.3.1'
fragmentktx_version = '1.3.6'
ormLite_version = '4.46'
@ -51,6 +51,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.hiya:jacoco-android:0.2'
}
}

View file

@ -1343,7 +1343,7 @@ public class ComboPlugin extends PumpPluginBase implements Pump, Constraints {
}
private String fakeSerialNumber() {
return InstanceId.INSTANCE.instanceId();
return InstanceId.INSTANCE.getInstanceId();
}
@NonNull @Override

View file

@ -5,6 +5,8 @@ dependencies {
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutines_version"
api "androidx.core:core-ktx:$core_version"
api 'androidx.legacy:legacy-support-v13:1.0.0'
api 'androidx.legacy:legacy-support-v4:1.0.0'
@ -25,7 +27,7 @@ dependencies {
api "com.google.dagger:dagger-android-support:$dagger_version"
//Firebase
api platform('com.google.firebase:firebase-bom:25.12.0')
api platform('com.google.firebase:firebase-bom:29.3.0')
api "com.google.firebase:firebase-analytics-ktx"
api "com.google.firebase:firebase-crashlytics-ktx"
api "com.google.firebase:firebase-messaging-ktx"

View file

@ -1,7 +1,5 @@
package info.nightscout.androidaps.events
import info.nightscout.androidaps.utils.StringUtils
class EventNetworkChange : Event() {
var mobileConnected = false

View file

@ -1,5 +1,3 @@
package info.nightscout.androidaps.events
import info.nightscout.androidaps.events.Event
class EventNtpStatus(val status: String, val percent: Int) : Event()

View file

@ -1,10 +1,13 @@
package info.nightscout.androidaps.utils
import com.google.firebase.iid.FirebaseInstanceId
import com.google.firebase.installations.FirebaseInstallations
object InstanceId {
fun instanceId(): String {
var id = FirebaseInstanceId.getInstance().id
return id
var instanceId : String = ""
init {
FirebaseInstallations.getInstance().id.addOnCompleteListener {
instanceId = it.result
}
}
}

View file

@ -18,6 +18,9 @@ android {
}
dependencies {
// shared needed for OpenForTesting
implementation project(':shared')
api "androidx.core:core-ktx:$core_version"
api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View file

@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-allopen'
apply plugin: 'com.hiya.jacoco-android'
apply plugin: 'kotlinx-serialization'
apply from: "${project.rootDir}/gradle/android_dependencies.gradle"
apply from: "${project.rootDir}/gradle/android_module_dependencies.gradle"
@ -29,6 +30,9 @@ dependencies {
exclude group: "com.google.android", module: "android"
}
api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
api "org.apache.commons:commons-lang3:$commonslang3_version"
//RxBus
api "io.reactivex.rxjava3:rxjava:$rxjava_version"
api "io.reactivex.rxjava3:rxkotlin:$rxkotlin_version"

View file

@ -15,4 +15,4 @@ abstract class Event {
ReflectionToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE)
}
}
}
}

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.events
import info.nightscout.shared.weardata.ActionData
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
class EventWearToMobileAction(val actionData: ActionData) : Event()

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.events
import info.nightscout.shared.weardata.ActionData
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
class EventWearToMobileChange(val actionData: ActionData) : Event()

View file

@ -0,0 +1,7 @@
package info.nightscout.androidaps.events
import info.nightscout.shared.weardata.ActionData
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
class EventWearToMobileConfirm(val actionData: ActionData) : Event()

View file

@ -0,0 +1,33 @@
package info.nightscout.shared.weardata
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
sealed class ActionData {
fun serialize() = Json.encodeToString(serializer(), this)
companion object {
fun deserialize(json: String) = Json.decodeFromString(serializer(), json)
}
// Wear -> Mobile
@Serializable
data class Pong(val timeStamp: Long) : ActionData()
@Serializable
data class Bolus(val insulin: Double, val carbs: Int) : ActionData()
@Serializable
data class ProfileSwitch(val timeShift: Int, val percentage: Int) : ActionData()
@Serializable
data class OpenProfileSwitch(val timeShift: Int, val percentage: Int) : ActionData()
// Mobile -> Wear
@Serializable
data class Ping(val timeStamp: Long) : ActionData()
@Serializable
data class ConfirmAction(val title: String, val message: String, val originalCommand: ActionData) : ActionData()
@Serializable
data class ChangeAction(val title: String, val message: String, val originalCommand: ActionData) : ActionData()
}

View file

@ -0,0 +1,61 @@
package info.nightscout.shared.weardata
import android.content.Context
import info.nightscout.shared.R
import javax.inject.Inject
import javax.inject.Singleton
@Suppress("PropertyName")
@Singleton
class WearConstants @Inject constructor(private val context: Context) {
// Paths must be defined in manifest
// mobile -> wear (data)
val M_W_DATA get() = context.getString(R.string.path_watch_data)
val M_W_STATUS get() = context.getString(R.string.path_status)
val M_W_PREFERENCES get() = context.getString(R.string.path_preferences)
val M_W_QUICK_WIZARD get() = context.getString(R.string.path_quick_wizard)
val M_W_BASAL get() = context.getString(R.string.path_basal)
val M_W_BOLUS_PROGRESS get() = context.getString(R.string.path_bolus_progress)
val M_W_ACTION_CONFIRMATION_REQUEST get() = context.getString(R.string.path_action_confirmation)
val M_W_ACTION_CHANGE_CONFIRMATION_REQUEST get() = context.getString(R.string.path_change_confirmation_request)
val M_W_ACTION_CANCEL_NOTIFICATION_REQUEST get() = context.getString(R.string.path_cancel_notification_request)
// mobile -> wear (message)
val M_W_OPEN_SETTINGS get() = context.getString(R.string.path_open_wear_setting)
val M_W_PING get() = context.getString(R.string.path_ping)
// wear -> mobile (message)
val W_M_RESEND_DATA get() = context.getString(R.string.path_resend_data_request)
val W_M_CANCEL_BOLUS get() = context.getString(R.string.path_cancel_bolus_on_phone)
val W_M_CONFIRM_ACTION get() = context.getString(R.string.path_confirm_action)
val W_M_INITIATE_ACTION get() = context.getString(R.string.path_initiate_action_on_phone)
val W_M_PONG get() = context.getString(R.string.path_pong)
companion object {
// actions for WEAR_INITIATE_ACTION_ON_PHONE
// used by
// DataLayerListenerService::initiateAction
// ActionStringHandler::handleInitiateActionOnPhone
// EventWearInitiateAction
const val ACTION_FILL_PRESET = "fillpreset"
const val ACTION_FILL = "fill"
const val ACTION_BOLUS = "bolus"
const val ACTION_TEMPORARY_TARGET = "temptarget"
const val ACTION_STATUS = "status"
const val ACTION_WIZARD = "wizard"
const val ACTION_WIZARD2 = "wizard2"
const val ACTION_QUICK_WIZARD = "quick_wizard"
const val ACTION_OPEN_CPP = "opencpp"
const val ACTION_CPP_SET = "cppset"
const val ACTION_TDD_STATS = "tddstats"
const val ACTION_E_CARBS = "ecarbs"
const val ACTION_CHANGE_REQUEST = "changeRequest"
const val ACTION_CANCEL_CHANGE_REQUEST = "cancelChangeRequest"
const val ACTION_DISMISS_OVERVIEW_NOTIF = "dismissoverviewnotification"
//data keys
const val KEY_ACTION_DATA = "actionData"
}
}

View file

@ -1,21 +0,0 @@
package info.nightscout.shared.weardata
object WearUris {
const val WEARABLE_DATA_PATH = "/nightscout_watch_data"
const val WEARABLE_RESEND_PATH = "/nightscout_watch_data_resend"
const val WEARABLE_CANCELBOLUS_PATH = "/nightscout_watch_cancel_bolus"
const val WEARABLE_CONFIRM_ACTIONSTRING_PATH = "/nightscout_watch_confirmactionstring"
const val WEARABLE_INITIATE_ACTIONSTRING_PATH = "/nightscout_watch_initiateactionstring"
const val OPEN_SETTINGS_PATH = "/openwearsettings"
const val NEW_STATUS_PATH = "/sendstatustowear"
const val NEW_PREFERENCES_PATH = "/sendpreferencestowear"
const val QUICK_WIZARD_PATH = "/send_quick_wizard"
const val BASAL_DATA_PATH = "/nightscout_watch_basal"
const val BOLUS_PROGRESS_PATH = "/nightscout_watch_bolusprogress"
const val ACTION_CONFIRMATION_REQUEST_PATH = "/nightscout_watch_actionconfirmationrequest"
const val ACTION_CHANGECONFIRMATION_REQUEST_PATH = "/nightscout_watch_changeconfirmationrequest"
const val ACTION_CANCELNOTIFICATION_REQUEST_PATH = "/nightscout_watch_cancelnotificationrequest"
}

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="path_ping" translatable="false">/ping</string>
<string name="path_pong" translatable="false">/pong</string>
<string name="path_open_wear_setting" translatable="false">/openwearsettings</string>
<string name="path_resend_data_request" translatable="false">/nightscout_watch_data_resend</string>
<string name="path_initiate_action_on_phone" translatable="false">/nightscout_watch_initiateactionstring</string>
<string name="path_cancel_bolus_on_phone" translatable="false">/nightscout_watch_cancel_bolus</string>
<string name="path_confirm_action" translatable="false">/nightscout_watch_confirmactionstring</string>
<string name="path_watch_data" translatable="false">/nightscout_watch_data</string>
<string name="path_status" translatable="false">/sendstatustowear</string>
<string name="path_preferences" translatable="false">/sendpreferencestowear</string>
<string name="path_quick_wizard" translatable="false">/send_quick_wizard</string>
<string name="path_basal" translatable="false">/nightscout_watch_basal</string>
<string name="path_bolus_progress" translatable="false">/nightscout_watch_bolusprogress</string>
<string name="path_action_confirmation" translatable="false">/nightscout_watch_actionconfirmationrequest</string>
<string name="path_change_confirmation_request" translatable="false">/nightscout_watch_changeconfirmationrequest</string>
<string name="path_cancel_notification_request" translatable="false">/nightscout_watch_cancelnotificationrequest</string>
</resources>

View file

@ -23,8 +23,7 @@ apply from: "${project.rootDir}/gradle/jacoco_global.gradle"
ext {
wearableVersion = "2.9.0"
// playServicesWearable 17.1.0 breaks test
playServicesWearable = "17.0.0"
playServicesWearable = "17.1.0"
}
def generateGitBuild = { ->
@ -52,7 +51,6 @@ android {
compileSdkVersion 31
defaultConfig {
applicationId "info.nightscout.androidaps"
minSdkVersion 23
targetSdkVersion 29
versionCode 2
@ -112,8 +110,10 @@ dependencies {
implementation(files('libs/wearpreferenceactivity-0.5.0.aar'))
implementation('com.github.lecho:hellocharts-library:1.5.8@aar')
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutines_version"
implementation "androidx.core:core-ktx:$core_version"
implementation "androidx.wear.tiles:tiles:1.0.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"

View file

@ -17,6 +17,11 @@
android:icon="@drawable/ic_icon"
android:label="@string/app_name"
android:theme="@android:style/Theme.DeviceDefault">
<meta-data
android:name="com.google.android.wearable.standalone"
android:value="false" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
@ -25,7 +30,8 @@
android:name=".watchfaces.BIGChart"
android:allowEmbedded="true"
android:label="@string/label_xdrip_big_chart"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -46,7 +52,8 @@
android:name=".watchfaces.NOChart"
android:allowEmbedded="true"
android:label="@string/label_xdrip_no_chart"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -67,7 +74,8 @@
android:name=".watchfaces.Home"
android:allowEmbedded="true"
android:label="@string/label_xdrip"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -91,7 +99,8 @@
android:name=".watchfaces.Home2"
android:allowEmbedded="true"
android:label="@string/label_xdrip_v2"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -115,7 +124,8 @@
android:name=".watchfaces.Cockpit"
android:allowEmbedded="true"
android:label="@string/label_xdrip_cockpit"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -139,7 +149,8 @@
android:name=".watchfaces.Steampunk"
android:allowEmbedded="true"
android:label="@string/label_xdrip_steampunk"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -163,7 +174,8 @@
android:name=".watchfaces.LargeHome"
android:allowEmbedded="true"
android:label="@string/label_xdrip_large"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -186,7 +198,8 @@
android:name=".watchfaces.CircleWatchface"
android:allowEmbedded="true"
android:label="@string/label_xdrip_circle"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -207,7 +220,8 @@
android:name=".watchfaces.DigitalStyle"
android:allowEmbedded="true"
android:label="@string/label_digitalstyle"
android:permission="android.permission.BIND_WALLPAPER">
android:permission="android.permission.BIND_WALLPAPER"
android:exported="false">
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
@ -227,68 +241,73 @@
</intent-filter>
</service>
<service android:name=".data.ListenerService">
<service android:name=".data.DataLayerListenerService"
android:exported="true">
<intent-filter>
<!-- <action android:name="com.google.android.gms.wearable.BIND_LISTENER" /> -->
<!-- listeners receive events that match the action and data filters -->
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<data
android:host="*"
android:scheme="wear" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<data
android:host="*"
android:pathPrefix="@string/path_watch_data"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_status"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_preferences"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_quick_wizard"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_basal"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_bolus_progress"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_action_confirmation"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_change_confirmation_request"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_cancel_notification_request"
android:scheme="wear" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.wearable.CHANNEL_EVENT" />
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_data"
android:pathPrefix="@string/path_ping"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_data_resend"
android:pathPrefix="@string/path_open_wear_setting"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_cancel_bolus"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_confirmactionstring"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_initiateactionstring"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/openwearsettings"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/send_quick_wizard"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/sendstatustowear"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/sendpreferencestowear"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_basal"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_bolusprogress"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_actionconfirmationrequest"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_changeconfirmationrequest"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/nightscout_watch_cancelnotificationrequest"
android:pathPrefix="@string/path_action_confirmation"
android:scheme="wear" />
</intent-filter>
</service>
@ -466,7 +485,7 @@
<service
android:name=".complications.WallpaperLightComplication"
android:icon="@drawable/ic_aaps_light"
android:label="Light Walpaper"
android:label="Light Wallpaper"
android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
<intent-filter>
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
@ -483,7 +502,7 @@
<service
android:name=".complications.WallpaperDarkComplication"
android:icon="@drawable/ic_aaps_dark"
android:label="Dark Walpaper"
android:label="Dark Wallpaper"
android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
<intent-filter>
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
@ -500,7 +519,7 @@
<service
android:name=".complications.WallpaperGrayComplication"
android:icon="@drawable/ic_aaps_gray"
android:label="Gray Walpaper"
android:label="Gray Wallpaper"
android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
<intent-filter>
<action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
@ -623,7 +642,7 @@
<activity android:name=".interaction.ConfigurationActivity">
<intent-filter>
<!-- action-name must be equal with name of xml-ressource where the configuration is describe -->
<!-- action-name must be equal with name of xml-resource where the configuration is describe -->
<action android:name="watch_face_configuration_bigchart" />
<action android:name="watch_face_configuration_circle" />
<action android:name="watch_face_configuration_cockpit" />

View file

@ -0,0 +1,39 @@
<configuration>
<!-- Create a file appender for a log in the application's data directory -->
<property name="EXT_FILES_DIR" scope="context"
value="${EXT_DIR:-/sdcard}/AAPS/logs/${PACKAGE_NAME}" />
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${EXT_FILES_DIR}/AndroidAPS.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- daily rollover. Make sure the path matches the one in the file element or else
the rollover logs are placed in the working directory. -->
<fileNamePattern>${EXT_FILES_DIR}/AndroidAPS._%d{yyyy-MM-dd}_%d{HH-mm-ss, aux}_.%i.zip
</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>5MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- keep 30 days' worth of history -->
<maxHistory>120</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %.-1level/%logger: %msg%n</pattern>
</encoder>
</appender>
<appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
<tagEncoder>
<pattern>%logger{0}</pattern>
</tagEncoder>
<encoder>
<pattern>[%thread]: %msg%n</pattern>
</encoder>
</appender>
<!-- Write INFO (and higher-level) messages to the log file -->
<root level="DEBUG">
<appender-ref ref="file" />
<appender-ref ref="logcat" />
</root>
</configuration>

View file

@ -8,11 +8,17 @@ import androidx.preference.PreferenceManager
import dagger.android.AndroidInjector
import dagger.android.DaggerApplication
import info.nightscout.androidaps.di.DaggerWearComponent
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import javax.inject.Inject
class Aaps : DaggerApplication(), OnSharedPreferenceChangeListener {
@Inject lateinit var aapsLogger: AAPSLogger
override fun onCreate() {
super.onCreate()
aapsLogger.debug(LTag.WEAR, "onCreate")
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
}

View file

@ -22,7 +22,7 @@ import javax.inject.Inject;
import dagger.android.AndroidInjection;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.androidaps.interaction.utils.Constants;
import info.nightscout.androidaps.interaction.utils.DisplayFormat;
@ -205,7 +205,7 @@ public abstract class BaseComplicationProviderService extends ComplicationProvid
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(messageReceiver, messageFilter);
ListenerService.requestData(this);
DataLayerListenerService.Companion.requestData(this);
checkIfUpdateNeeded();
}

View file

@ -0,0 +1,552 @@
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.nightscout.androidaps.data
import android.annotation.TargetApi
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.os.SystemClock
import android.util.Base64
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.wear.tiles.TileService
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.*
import dagger.android.AndroidInjection
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventWearToMobileAction
import info.nightscout.androidaps.events.EventWearToMobileChange
import info.nightscout.androidaps.events.EventWearToMobileConfirm
import info.nightscout.androidaps.interaction.AAPSPreferences
import info.nightscout.androidaps.interaction.actions.AcceptActivity
import info.nightscout.androidaps.interaction.actions.CPPActivity
import info.nightscout.androidaps.interaction.utils.Persistence
import info.nightscout.androidaps.interaction.utils.WearUtil
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.tile.ActionsTileService
import info.nightscout.androidaps.tile.QuickWizardTileService
import info.nightscout.androidaps.tile.TempTargetTileService
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.SafeParse.stringToInt
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.ActionData
import info.nightscout.shared.weardata.WearConstants
import info.nightscout.shared.weardata.WearConstants.Companion.KEY_ACTION_DATA
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import kotlinx.coroutines.*
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
class DataLayerListenerService : WearableListenerService() {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var wearUtil: WearUtil
@Inject lateinit var persistence: Persistence
@Inject lateinit var sp: SP
@Inject lateinit var rxBus: RxBus
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var wearConstants: WearConstants
private val dataClient by lazy { Wearable.getDataClient(this) }
private val messageClient by lazy { Wearable.getMessageClient(this) }
private val capabilityClient by lazy { Wearable.getCapabilityClient(this) }
//private val nodeClient by lazy { Wearable.getNodeClient(this) }
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private var handler = Handler(HandlerThread(this::class.simpleName + "Handler").also { it.start() }.looper)
private val disposable = CompositeDisposable()
override fun onCreate() {
AndroidInjection.inject(this)
super.onCreate()
handler.post { updateTranscriptionCapability() }
disposable += rxBus
.toObservable(EventWearToMobileAction::class.java)
.observeOn(aapsSchedulers.io)
.subscribe { sendMessage(wearConstants.W_M_INITIATE_ACTION, it.actionData.serialize().toByteArray()) }
disposable += rxBus
.toObservable(EventWearToMobileConfirm::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
NotificationManagerCompat.from(this).cancel(CONFIRM_NOTIF_ID)
sendMessage(wearConstants.W_M_CONFIRM_ACTION, it.actionData.serialize().toByteArray())
}
disposable += rxBus
.toObservable(EventWearToMobileChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
NotificationManagerCompat.from(this).cancel(CHANGE_NOTIF_ID)
sendMessage(wearConstants.W_M_CONFIRM_ACTION, it.actionData.serialize().toByteArray())
}
}
override fun onPeerConnected(p0: Node) {
super.onPeerConnected(p0)
}
override fun onCapabilityChanged(p0: CapabilityInfo) {
super.onCapabilityChanged(p0)
handler.post { updateTranscriptionCapability() }
aapsLogger.debug(LTag.WEAR, "onCapabilityChanged: ${p0.name} ${p0.nodes.joinToString(", ") { it.displayName + "(" + it.id + ")" }}")
}
override fun onDestroy() {
super.onDestroy()
scope.cancel()
disposable.clear()
}
override fun onDataChanged(dataEvents: DataEventBuffer) {
//aapsLogger.debug(LTag.WEAR, "onDataChanged")
dataEvents.forEach { event ->
if (event.type == DataEvent.TYPE_CHANGED) {
val path = event.dataItem.uri.path
aapsLogger.debug(LTag.WEAR, "onDataChanged: Path: $path, EventDataItem=${event.dataItem}")
try {
when (path) {
wearConstants.M_W_BOLUS_PROGRESS -> {
val progress = DataMapItem.fromDataItem(event.dataItem).dataMap.getInt("progresspercent", 0)
val status = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("progressstatus", "")
showBolusProgress(progress, status)
}
// remove when finished -> converted to message
wearConstants.M_W_ACTION_CONFIRMATION_REQUEST -> {
val title = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("title") ?: return@forEach
val message = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("message") ?: return@forEach
val actionstring = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("actionstring") ?: return@forEach
if ("opencpp" == title && actionstring.startsWith("opencpp")) {
val act = actionstring.split("\\s+").toTypedArray()
startActivity(Intent(this@DataLayerListenerService, CPPActivity::class.java).also { intent ->
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtras(Bundle().also {
it.putInt("percentage", stringToInt(act[1]))
it.putInt("timeshift", stringToInt(act[2]))
})
})
} else {
showConfirmationDialog(title, message, actionstring)
}
}
wearConstants.M_W_STATUS -> {
val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap
val messageIntent = Intent()
messageIntent.action = Intent.ACTION_SEND
messageIntent.putExtra("status", dataMap.toBundle())
persistence.storeDataMap(RawDisplayData.STATUS_PERSISTENCE_KEY, dataMap)
LocalBroadcastManager.getInstance(this@DataLayerListenerService).sendBroadcast(messageIntent)
}
wearConstants.M_W_BASAL -> {
val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap
val messageIntent = Intent()
messageIntent.action = Intent.ACTION_SEND
messageIntent.putExtra("basals", dataMap.toBundle())
persistence.storeDataMap(RawDisplayData.BASALS_PERSISTENCE_KEY, dataMap)
LocalBroadcastManager.getInstance(this@DataLayerListenerService).sendBroadcast(messageIntent)
}
wearConstants.M_W_PREFERENCES -> {
val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap
val keyControl = getString(R.string.key_wear_control)
if (dataMap.containsKey(keyControl)) {
val previousWearControl = sp.getBoolean(keyControl, false)
val wearControl: Boolean = dataMap.getBoolean(keyControl, false)
sp.putBoolean(keyControl, wearControl)
if (wearControl != previousWearControl) {
updateTiles()
}
}
val keyPercentage = getString(R.string.key_boluswizard_percentage)
if (dataMap.containsKey(keyPercentage)) {
val wpercentage: Int = dataMap.getInt(keyPercentage, 100)
sp.putInt(keyPercentage, wpercentage)
}
val keyUnits = getString(R.string.key_units_mgdl)
if (dataMap.containsKey(keyUnits)) {
val mgdl: Boolean = dataMap.getBoolean(keyUnits, true)
sp.putBoolean(keyUnits, mgdl)
}
val keyMaxCarbs = getString(R.string.key_treatmentssafety_maxcarbs)
if (dataMap.containsKey(keyMaxCarbs)) {
val maxCarbs: Int = dataMap.getInt(keyMaxCarbs, 48)
sp.putInt(keyMaxCarbs, maxCarbs)
}
val keyMaxBolus = getString(R.string.key_treatmentssafety_maxbolus)
if (dataMap.containsKey(keyMaxBolus)) {
sp.putDouble(keyMaxBolus, dataMap.getDouble(keyMaxBolus, 3.0))
}
}
wearConstants.M_W_QUICK_WIZARD -> {
val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap
aapsLogger.info(LTag.WEAR, "onDataChanged: QUICK_WIZARD_PATH$dataMap")
dataMap.remove("timestamp")
val key = getString(R.string.key_quick_wizard_data_map)
val dataString = Base64.encodeToString(dataMap.toByteArray(), Base64.DEFAULT)
if (dataString != sp.getString(key, "")) {
sp.putString(key, dataString)
// Todo maybe add debounce function, due to 20 seconds update limit?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
TileService.getUpdater(this@DataLayerListenerService).requestUpdate(QuickWizardTileService::class.java)
}
aapsLogger.info(LTag.WEAR, "onDataChanged: updated QUICK_WIZARD")
} else {
aapsLogger.info(LTag.WEAR, "onDataChanged: ignore update")
}
}
wearConstants.M_W_ACTION_CHANGE_CONFIRMATION_REQUEST -> {
val title = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("title") ?: return@forEach
val message = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("message") ?: return@forEach
val actionstring = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("actionstring") ?: return@forEach
notifyChangeRequest(title, message, actionstring)
}
wearConstants.M_W_ACTION_CANCEL_NOTIFICATION_REQUEST -> {
//val actionstring = DataMapItem.fromDataItem(event.getDataItem()).dataMap.getString("actionstring") ?: return@forEach
cancelNotificationRequest()
}
wearConstants.M_W_DATA -> {
val dataMap = DataMapItem.fromDataItem(event.dataItem).dataMap
val messageIntent = Intent()
messageIntent.action = Intent.ACTION_SEND
messageIntent.putExtra("data", dataMap.toBundle())
persistence.storeDataMap(RawDisplayData.DATA_PERSISTENCE_KEY, dataMap)
LocalBroadcastManager.getInstance(this@DataLayerListenerService).sendBroadcast(messageIntent)
}
}
} catch (exception: Exception) {
aapsLogger.error(LTag.WEAR, "onDataChanged failed", exception)
}
}
}
super.onDataChanged(dataEvents)
}
override fun onMessageReceived(messageEvent: MessageEvent) {
super.onMessageReceived(messageEvent)
aapsLogger.debug(LTag.WEAR, "onMessageReceived: $messageEvent")
when (messageEvent.path) {
wearConstants.M_W_PING -> sendMessage(wearConstants.W_M_PONG, byteArrayOf())
wearConstants.M_W_OPEN_SETTINGS -> startActivity(Intent(this@DataLayerListenerService, AAPSPreferences::class.java).also { it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
wearConstants.M_W_ACTION_CONFIRMATION_REQUEST -> {
val command = ActionData.deserialize(String(messageEvent.data)) as ActionData.ConfirmAction
if (command.originalCommand is ActionData.OpenProfileSwitch) {
val originalCommand = command.originalCommand as ActionData.OpenProfileSwitch
startActivity(Intent(this, CPPActivity::class.java).also { intent ->
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtras(Bundle().also {
it.putInt("percentage", originalCommand.percentage)
it.putInt("timeshift", originalCommand.timeShift)
})
})
} else {
startActivity(
Intent(this, AcceptActivity::class.java).also { intent ->
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtras(
Bundle().also { bundle ->
bundle.putString("title", command.title)
bundle.putString("message", command.message)
bundle.putString(KEY_ACTION_DATA, command.originalCommand.serialize())
}
)
})
}
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
ACTION_RESEND -> sendMessage(wearConstants.W_M_RESEND_DATA, byteArrayOf())
ACTION_CANCEL_BOLUS -> {
//dismiss notification
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.cancel(BOLUS_PROGRESS_NOTIF_ID)
//send cancel-request to phone.
sendMessage(wearConstants.W_M_CANCEL_BOLUS, byteArrayOf())
}
ACTION_CONFIRMATION -> {
//dismiss notification
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.cancel(CONFIRM_NOTIF_ID)
intent.getStringExtra("actionstring")?.let { actionString ->
sendMessage(wearConstants.W_M_CONFIRM_ACTION, actionString.toByteArray())
}
}
ACTION_CONFIRM_CHANGE -> {
//dismiss notification
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.cancel(CHANGE_NOTIF_ID)
intent.getStringExtra("actionstring")?.let { actionString ->
sendMessage(wearConstants.W_M_CONFIRM_ACTION, actionString.toByteArray())
}
}
ACTION_INITIATE_ACTION ->
if (intent.hasExtra("actionstring"))
intent.getStringExtra("actionstring")?.let { actionString ->
sendMessage(wearConstants.W_M_INITIATE_ACTION, actionString.toByteArray())
}
else if (intent.hasExtra(KEY_ACTION_DATA))
intent.getStringExtra(KEY_ACTION_DATA)?.let { actionData ->
sendMessage(wearConstants.W_M_INITIATE_ACTION, actionData.toByteArray())
}
}
return START_STICKY
}
private var transcriptionNodeId: String? = null
private fun updateTranscriptionCapability() {
val capabilityInfo: CapabilityInfo = Tasks.await(
capabilityClient.getCapability(PHONE_CAPABILITY, CapabilityClient.FILTER_REACHABLE)
)
aapsLogger.debug(LTag.WEAR, "Nodes: ${capabilityInfo.nodes.joinToString(", ") { it.displayName + "(" + it.id + ")" }}")
transcriptionNodeId = pickBestNodeId(capabilityInfo.nodes)
aapsLogger.debug(LTag.WEAR, "Selected node: $transcriptionNodeId")
}
// Find a nearby node or pick one arbitrarily
private fun pickBestNodeId(nodes: Set<Node>): String? =
nodes.firstOrNull { it.isNearby }?.id ?: nodes.firstOrNull()?.id
private fun sendData(path: String, vararg params: DataMap) {
scope.launch {
try {
for (dm in params) {
val request = PutDataMapRequest.create(path).apply {
dataMap.putAll(dm)
}
.asPutDataRequest()
.setUrgent()
val result = dataClient.putDataItem(request).await()
aapsLogger.debug(LTag.WEAR, "sendData: ${result.uri} ${params.joinToString()}")
}
} catch (cancellationException: CancellationException) {
throw cancellationException
} catch (exception: Exception) {
aapsLogger.error(LTag.WEAR, "DataItem failed: $exception")
}
}
}
private fun sendMessage(path: String, data: ByteArray) {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path")
transcriptionNodeId?.also { nodeId ->
messageClient
.sendMessage(nodeId, path, data).apply {
addOnSuccessListener { }
addOnFailureListener {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path failure")
}
}
}
}
private fun updateTiles() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
TileService.getUpdater(this)
.requestUpdate(ActionsTileService::class.java)
TileService.getUpdater(this)
.requestUpdate(TempTargetTileService::class.java)
TileService.getUpdater(this)
.requestUpdate(QuickWizardTileService::class.java)
}
}
private fun notifyChangeRequest(title: String, message: String, actionstring: String) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name: CharSequence = "AAPS Open Loop"
val description = "Open Loop request notification"
val channel = NotificationChannel(AAPS_NOTIFY_CHANNEL_ID_OPENLOOP, name, NotificationManager.IMPORTANCE_HIGH)
channel.description = description
channel.enableVibration(true)
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
var builder = NotificationCompat.Builder(this, AAPS_NOTIFY_CHANNEL_ID_OPENLOOP)
builder = builder.setSmallIcon(R.drawable.notif_icon)
.setContentTitle(title)
.setContentText(message)
.setPriority(Notification.PRIORITY_HIGH)
.setVibrate(longArrayOf(1000, 1000, 1000, 1000, 1000))
// Creates an explicit intent for an Activity in your app
val intent = Intent(this, AcceptActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val params = Bundle()
params.putString("title", title)
params.putString("message", message)
params.putString("actionstring", actionstring)
intent.putExtras(params)
val resultPendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
builder = builder.setContentIntent(resultPendingIntent)
val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
// mId allows you to update the notification later on.
mNotificationManager.notify(CHANGE_NOTIF_ID, builder.build())
}
private fun cancelNotificationRequest() {
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).cancel(CHANGE_NOTIF_ID)
}
private fun showBolusProgress(progressPercent: Int, progresStatus: String) {
val vibratePattern: LongArray
val vibrate = sp.getBoolean("vibrateOnBolus", true)
vibratePattern = if (vibrate) longArrayOf(0, 50, 1000) else longArrayOf(0, 1, 1000)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createBolusProgressChannels()
}
val cancelIntent = Intent(this, DataLayerListenerService::class.java)
cancelIntent.action = ACTION_CANCEL_BOLUS
val cancelPendingIntent = PendingIntent.getService(this, 0, cancelIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val notificationBuilder: NotificationCompat.Builder =
NotificationCompat.Builder(this, if (vibrate) AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS else AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT)
.setSmallIcon(R.drawable.ic_icon)
.setContentTitle(getString(R.string.bolus_progress))
.setContentText("$progressPercent% - $progresStatus")
.setSubText(getString(R.string.press_to_cancel))
.setContentIntent(cancelPendingIntent)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVibrate(vibratePattern)
.addAction(R.drawable.ic_cancel, getString(R.string.cancel_bolus), cancelPendingIntent)
val notificationManager = NotificationManagerCompat.from(this)
notificationManager.notify(BOLUS_PROGRESS_NOTIF_ID, notificationBuilder.build())
notificationManager.cancel(CONFIRM_NOTIF_ID) // multiple watch setup
if (progressPercent == 100) {
scheduleDismissBolusProgress(5)
}
}
@TargetApi(value = 26) private fun createBolusProgressChannels() {
createNotificationChannel(
longArrayOf(0, 50, 1000),
AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS,
getString(R.string.bolus_progress_channel_name),
getString(R.string.bolus_progress_channel_description)
)
createNotificationChannel(
longArrayOf(0, 1, 1000),
AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT,
getString(R.string.bolus_progress_silent_channel_name),
getString(R.string.bolus_progress_silent_channel_description)
)
}
@TargetApi(value = 26) private fun createNotificationChannel(vibratePattern: LongArray, channelID: String, name: CharSequence, description: String) {
val channel = NotificationChannel(channelID, name, NotificationManager.IMPORTANCE_HIGH)
channel.description = description
channel.enableVibration(true)
channel.vibrationPattern = vibratePattern
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
private fun showConfirmationDialog(title: String, message: String, actionstring: String) {
val intent = Intent(this, AcceptActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val params = Bundle()
params.putString("title", title)
params.putString("message", message)
params.putString("actionstring", actionstring)
intent.putExtras(params)
startActivity(intent)
}
@Suppress("SameParameterValue")
private fun scheduleDismissBolusProgress(seconds: Int) {
Thread {
SystemClock.sleep(seconds * 1000L)
NotificationManagerCompat.from(this@DataLayerListenerService)
.cancel(BOLUS_PROGRESS_NOTIF_ID)
}.start()
}
companion object {
const val PHONE_CAPABILITY = "androidaps_mobile"
const val ACTION_RESEND = "com.dexdrip.stephenblack.nightwatch.RESEND_DATA"
const val ACTION_CANCEL_BOLUS = "com.dexdrip.stephenblack.nightwatch.CANCELBOLUS"
const val ACTION_CONFIRMATION = "com.dexdrip.stephenblack.nightwatch.CONFIRMACTION"
const val ACTION_CONFIRM_CHANGE = "com.dexdrip.stephenblack.nightwatch.CONFIRMCHANGE"
val ACTION_INITIATE_ACTION = DataLayerListenerService::class.java.name + ".INITIATE_ACTION"
const val BOLUS_PROGRESS_NOTIF_ID = 1
const val CONFIRM_NOTIF_ID = 2
const val CHANGE_NOTIF_ID = 556677
const val AAPS_NOTIFY_CHANNEL_ID_OPENLOOP = "AndroidAPS-OpenLoop"
const val AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS = "bolus progress vibration"
const val AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT = "bolus progress silent"
fun initiateAction(context: Context, actionstring: String) {
context.startService(
Intent(context, DataLayerListenerService::class.java).also {
it.putExtra("actionstring", actionstring)
it.action = ACTION_INITIATE_ACTION
})
}
fun requestData(context: Context) {
context.startService(
Intent(context, DataLayerListenerService::class.java).also { it.action = ACTION_RESEND })
}
fun confirmAction(context: Context, actionstring: String) {
context.startService(
Intent(context, DataLayerListenerService::class.java).also {
it.putExtra("actionstring", actionstring)
if (actionstring == "changeRequest") it.action = ACTION_CONFIRM_CHANGE
else it.action = ACTION_CONFIRMATION
})
}
}
}

View file

@ -1,628 +0,0 @@
package info.nightscout.androidaps.data;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.wear.tiles.TileService;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.ChannelApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import dagger.android.AndroidInjection;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interaction.AAPSPreferences;
import info.nightscout.androidaps.interaction.actions.AcceptActivity;
import info.nightscout.androidaps.interaction.actions.CPPActivity;
import info.nightscout.androidaps.interaction.utils.Persistence;
import info.nightscout.androidaps.interaction.utils.WearUtil;
import info.nightscout.androidaps.tile.ActionsTileService;
import info.nightscout.androidaps.tile.QuickWizardTileService;
import info.nightscout.androidaps.tile.TempTargetTileService;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.WearUris;
/**
* Created by emmablack on 12/26/14.
*/
public class ListenerService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener, ChannelApi.ChannelListener {
@Inject WearUtil wearUtil;
@Inject Persistence persistence;
public static final int BOLUS_PROGRESS_NOTIF_ID = 1;
public static final int CONFIRM_NOTIF_ID = 2;
public static final int CHANGE_NOTIF_ID = 556677;
private static final String ACTION_RESEND = "com.dexdrip.stephenblack.nightwatch.RESEND_DATA";
private static final String ACTION_CANCELBOLUS = "com.dexdrip.stephenblack.nightwatch.CANCELBOLUS";
private static final String ACTION_CONFIRMATION = "com.dexdrip.stephenblack.nightwatch.CONFIRMACTION";
private static final String ACTION_CONFIRMCHANGE = "com.dexdrip.stephenblack.nightwatch.CONFIRMCHANGE";
private static final String ACTION_INITIATE_ACTION = "com.dexdrip.stephenblack.nightwatch.INITIATE_ACTION";
private static final String AAPS_NOTIFY_CHANNEL_ID_OPENLOOP = "AndroidAPS-OpenLoop";
private static final String AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS = "bolus progress vibration";
private static final String AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT = "bolus progress silent";
GoogleApiClient googleApiClient;
private DismissThread bolusprogressThread;
private static final String TAG = "ListenerService";
private final String logPrefix = ""; // "WR: "
// Not derived from DaggerService, do injection here
@Override
public void onCreate() {
AndroidInjection.inject(this);
super.onCreate();
}
public class BolusCancelTask extends AsyncTask<Void, Void, Void> {
Context mContext;
BolusCancelTask(Context context) {
mContext = context;
}
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, logPrefix + "BolusCancelTask.doInBackground: " + params);
if (!googleApiClient.isConnected()) {
Log.i(TAG, "BolusCancelTask.doInBackground: not connected");
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(),
WearUris.WEARABLE_CANCELBOLUS_PATH, null);
}
}
return null;
}
}
public class MessageActionTask extends AsyncTask<Void, Void, Void> {
Context mContext;
String mActionstring;
String mMessagePath;
MessageActionTask(Context context, String messagePath, String actionstring) {
mContext = context;
mActionstring = actionstring;
mMessagePath = messagePath;
}
@Override
protected Void doInBackground(Void... params) {
Log.i(TAG, "MessageActionTask.doInBackground: ");
if (!googleApiClient.isConnected()) {
Log.i(TAG, "MessageActionTask.doInBackground: not connected");
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
if (googleApiClient.isConnected()) {
NodeApi.GetConnectedNodesResult nodes =
Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), mMessagePath, mActionstring.getBytes());
}
}
return null;
}
}
public class ResendDataTask extends AsyncTask<Void, Void, Void> {
Context mContext;
ResendDataTask(Context context) {
mContext = context;
}
@Override
protected Void doInBackground(Void... params) {
Log.d(TAG, logPrefix + "ResendDataTask.doInBackground: " + params);
if (!googleApiClient.isConnected()) {
Log.i(TAG, "ResendDataTask.doInBackground: not connected");
googleApiClient.blockingConnect(15, TimeUnit.SECONDS);
}
if (googleApiClient.isConnected()) {
Log.i(TAG, "ResendDataTask.doInBackground: connected");
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleApiClient).await();
for (Node node : nodes.getNodes()) {
Wearable.MessageApi.sendMessage(googleApiClient, node.getId(), WearUris.WEARABLE_RESEND_PATH, null);
}
} else {
Log.i(TAG, "ResendDataTask.doInBackground: could not connect");
}
return null;
}
}
public void requestData() {
new ResendDataTask(this).execute();
}
public void cancelBolus() {
new BolusCancelTask(this).execute();
}
private void sendConfirmActionstring(String actionstring) {
new MessageActionTask(this, WearUris.WEARABLE_CONFIRM_ACTIONSTRING_PATH, actionstring).execute();
}
private void sendInitiateActionstring(String actionstring) {
new MessageActionTask(this, WearUris.WEARABLE_INITIATE_ACTIONSTRING_PATH, actionstring).execute();
}
private void googleApiConnect() {
if (googleApiClient != null) {
// Remove old listener(s)
try {
Wearable.ChannelApi.removeListener(googleApiClient, this);
} catch (Exception e) {
//
}
try {
Wearable.MessageApi.removeListener(googleApiClient, this);
} catch (Exception e) {
//
}
}
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Wearable.API)
.build();
Wearable.MessageApi.addListener(googleApiClient, this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Log.d(TAG, logPrefix + "onStartCommand: Intent: " + intent);
if (intent != null && ACTION_RESEND.equals(intent.getAction())) {
googleApiConnect();
requestData();
} else if (intent != null && ACTION_CANCELBOLUS.equals(intent.getAction())) {
googleApiConnect();
//dismiss notification
NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(ListenerService.this);
notificationManager.cancel(BOLUS_PROGRESS_NOTIF_ID);
//send cancel-request to phone.
cancelBolus();
} else if (intent != null && ACTION_CONFIRMATION.equals(intent.getAction())) {
googleApiConnect();
//dismiss notification
NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(ListenerService.this);
notificationManager.cancel(CONFIRM_NOTIF_ID);
String actionstring = intent.getStringExtra("actionstring");
sendConfirmActionstring(actionstring);
} else if (intent != null && ACTION_CONFIRMCHANGE.equals(intent.getAction())) {
googleApiConnect();
//dismiss notification
NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(ListenerService.this);
notificationManager.cancel(CHANGE_NOTIF_ID);
String actionstring = intent.getStringExtra("actionstring");
sendConfirmActionstring(actionstring);
} else if (intent != null && ACTION_INITIATE_ACTION.equals(intent.getAction())) {
googleApiConnect();
String actionstring = intent.getStringExtra("actionstring");
sendInitiateActionstring(actionstring);
}
return START_STICKY;
}
@Override
public void onDataChanged(DataEventBuffer dataEvents) {
DataMap dataMap;
// Log.d(TAG, logPrefix + "onDataChanged: DataEvents=" + dataEvents);
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_CHANGED) {
String path = event.getDataItem().getUri().getPath();
//Log.d(TAG, "WR: onDataChanged: Path: " + path + ", EventDataItem=" + event.getDataItem());
if (path.equals(WearUris.OPEN_SETTINGS_PATH)) {
Intent intent = new Intent(this, AAPSPreferences.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} else if (path.equals(WearUris.BOLUS_PROGRESS_PATH)) {
int progress = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getInt("progresspercent", 0);
String status = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("progressstatus", "");
showBolusProgress(progress, status);
} else if (path.equals(WearUris.ACTION_CONFIRMATION_REQUEST_PATH)) {
String title = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("title");
String message = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("message");
String actionstring = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("actionstring");
if ("opencpp".equals(title) && actionstring.startsWith("opencpp")) {
String[] act = actionstring.split("\\s+");
Intent intent = new Intent(this, CPPActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//TODO adrian: parse actionstring and add parameters
Bundle params = new Bundle();
params.putInt("percentage", SafeParse.stringToInt(act[1]));
params.putInt("timeshift", SafeParse.stringToInt(act[2]));
intent.putExtras(params);
startActivity(intent);
} else {
showConfirmationDialog(title, message, actionstring);
}
} else if (path.equals(WearUris.NEW_STATUS_PATH)) {
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
Intent messageIntent = new Intent();
messageIntent.setAction(Intent.ACTION_SEND);
messageIntent.putExtra("status", dataMap.toBundle());
persistence.storeDataMap(RawDisplayData.STATUS_PERSISTENCE_KEY, dataMap);
LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
} else if (path.equals(WearUris.BASAL_DATA_PATH)) {
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
Intent messageIntent = new Intent();
messageIntent.setAction(Intent.ACTION_SEND);
messageIntent.putExtra("basals", dataMap.toBundle());
persistence.storeDataMap(RawDisplayData.BASALS_PERSISTENCE_KEY, dataMap);
LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
} else if (path.equals(WearUris.NEW_PREFERENCES_PATH)) {
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sharedPreferences.edit();
String keyControl = getString(R.string.key_wear_control);
if (dataMap.containsKey(keyControl)) {
boolean previousWearControl = sharedPreferences.getBoolean(keyControl, false);
boolean wearControl = dataMap.getBoolean(keyControl, false);
editor.putBoolean(keyControl, wearControl);
editor.apply();
if (wearControl != previousWearControl) {
updateTiles();
}
}
String keyPercentage = getString(R.string.key_boluswizard_percentage);
if (dataMap.containsKey(keyPercentage)) {
int wpercentage = dataMap.getInt(keyPercentage, 100);
editor.putInt(keyPercentage, wpercentage);
editor.apply();
}
String keyUnits = getString(R.string.key_units_mgdl);
if (dataMap.containsKey(keyUnits)) {
boolean mgdl = dataMap.getBoolean(keyUnits, true);
editor.putBoolean(keyUnits, mgdl);
editor.apply();
}
String keyMaxCarbs = getString(R.string.key_treatmentssafety_maxcarbs);
if (dataMap.containsKey(keyMaxCarbs)) {
int maxCarbs = dataMap.getInt(keyMaxCarbs, 48);
editor.putInt(keyMaxCarbs, maxCarbs);
editor.apply();
}
String keyMaxBolus = getString(R.string.key_treatmentssafety_maxbolus);
if (dataMap.containsKey(keyMaxBolus)) {
float maxBolus = (float) dataMap.getDouble(keyMaxBolus, 3.0f);
editor.putFloat(keyMaxBolus, maxBolus);
editor.apply();
}
} else if (path.equals(WearUris.QUICK_WIZARD_PATH)) {
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
Log.i(TAG, "onDataChanged: QUICK_WIZARD_PATH" + dataMap);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
dataMap.remove("timestamp");
String key = getString(R.string.key_quick_wizard_data_map);
String dataString = Base64.encodeToString(dataMap.toByteArray(), Base64.DEFAULT);
if (!dataString.equals(sharedPreferences.getString(key, ""))) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(key, dataString);
editor.apply();
// Todo maybe add debounce function, due to 20 seconds update limit?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
TileService.getUpdater(this)
.requestUpdate(QuickWizardTileService.class);
}
Log.i(TAG, "onDataChanged: updated QUICK_WIZARD");
} else {
Log.i(TAG, "onDataChanged: ignore update");
}
} else if (path.equals(WearUris.ACTION_CHANGECONFIRMATION_REQUEST_PATH)) {
String title = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("title");
String message = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("message");
String actionstring = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("actionstring");
notifyChangeRequest(title, message, actionstring);
} else if (path.equals(WearUris.ACTION_CANCELNOTIFICATION_REQUEST_PATH)) {
String actionstring = DataMapItem.fromDataItem(event.getDataItem()).getDataMap().getString("actionstring");
cancelNotificationRequest(actionstring);
} else {
dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
Intent messageIntent = new Intent();
messageIntent.setAction(Intent.ACTION_SEND);
messageIntent.putExtra("data", dataMap.toBundle());
persistence.storeDataMap(RawDisplayData.DATA_PERSISTENCE_KEY, dataMap);
LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
}
}
}
}
private void updateTiles() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
TileService.getUpdater(this)
.requestUpdate(ActionsTileService.class);
TileService.getUpdater(this)
.requestUpdate(TempTargetTileService.class);
TileService.getUpdater(this)
.requestUpdate(QuickWizardTileService.class);
}
}
private void notifyChangeRequest(String title, String message, String actionstring) {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "AAPS Open Loop";
String description = "Open Loop request notiffication";//getString(R.string.channel_description);
NotificationChannel channel = new NotificationChannel(AAPS_NOTIFY_CHANNEL_ID_OPENLOOP, name, NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(description);
channel.enableVibration(true);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this, AAPS_NOTIFY_CHANNEL_ID_OPENLOOP);
builder = builder.setSmallIcon(R.drawable.notif_icon)
.setContentTitle(title)
.setContentText(message)
.setPriority(Notification.PRIORITY_HIGH)
.setVibrate(new long[]{1000, 1000, 1000, 1000, 1000});
// Creates an explicit intent for an Activity in your app
Intent intent = new Intent(this, AcceptActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Bundle params = new Bundle();
params.putString("title", title);
params.putString("message", message);
params.putString("actionstring", actionstring);
intent.putExtras(params);
PendingIntent resultPendingIntent =
PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
builder = builder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
mNotificationManager.notify(CHANGE_NOTIF_ID, builder.build());
}
private void cancelNotificationRequest(String actionstring) {
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotificationManager.cancel(CHANGE_NOTIF_ID);
}
private void showBolusProgress(int progresspercent, String progresstatus) {
long[] vibratePattern;
boolean vibrate = PreferenceManager
.getDefaultSharedPreferences(this).getBoolean("vibrateOnBolus", true);
if (vibrate) {
vibratePattern = new long[]{0, 50, 1000};
} else {
vibratePattern = new long[]{0, 1, 1000};
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createBolusProgressChannels();
}
Intent cancelIntent = new Intent(this, ListenerService.class);
cancelIntent.setAction(ACTION_CANCELBOLUS);
PendingIntent cancelPendingIntent = PendingIntent.getService(this, 0, cancelIntent, 0);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, vibrate ? AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS : AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT)
.setSmallIcon(R.drawable.ic_icon)
.setContentTitle(getString(R.string.bolus_progress))
.setContentText(progresspercent + "% - " + progresstatus)
.setSubText(getString(R.string.press_to_cancel))
.setContentIntent(cancelPendingIntent)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVibrate(vibratePattern)
.addAction(R.drawable.ic_cancel, getString(R.string.cancel_bolus), cancelPendingIntent);
NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(this);
notificationManager.notify(BOLUS_PROGRESS_NOTIF_ID, notificationBuilder.build());
notificationManager.cancel(CONFIRM_NOTIF_ID); // multiple watch setup
if (progresspercent == 100) {
scheduleDismissBolusprogress(5);
}
}
@TargetApi(value = 26)
private void createBolusProgressChannels() {
createNotificationChannel(new long[]{0, 50, 1000}, AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS, getString(R.string.bolus_progress_channel_name), getString(R.string.bolus_progress_channel_description));
createNotificationChannel(new long[]{0, 1, 1000}, AAPS_NOTIFY_CHANNEL_ID_BOLUSPROGRESS_SILENT, getString(R.string.bolus_progress_silent_channel_name), getString(R.string.bolus_progress_silent_channel_description));
}
@TargetApi(value = 26)
private void createNotificationChannel(long[] vibratePattern, String channelID, CharSequence name, String description) {
NotificationChannel channel = new NotificationChannel(channelID, name, NotificationManager.IMPORTANCE_HIGH);
channel.setDescription(description);
channel.enableVibration(true);
channel.setVibrationPattern(vibratePattern);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
private void showConfirmationDialog(String title, String message, String actionstring) {
Intent intent = new Intent(this, AcceptActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Bundle params = new Bundle();
params.putString("title", title);
params.putString("message", message);
params.putString("actionstring", actionstring);
intent.putExtras(params);
startActivity(intent);
}
private void scheduleDismissBolusprogress(final int seconds) {
bolusprogressThread = new DismissThread(BOLUS_PROGRESS_NOTIF_ID, seconds);
bolusprogressThread.start();
}
private class DismissThread extends Thread {
private final int notificationID;
private final int seconds;
private boolean valid = true;
DismissThread(int notificationID, int seconds) {
this.notificationID = notificationID;
this.seconds = seconds;
}
public synchronized void invalidate() {
valid = false;
}
@Override
public void run() {
SystemClock.sleep(seconds * 1000);
synchronized (this) {
if (valid) {
NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(ListenerService.this);
notificationManager.cancel(notificationID);
}
}
}
}
public static void requestData(Context context) {
Intent intent = new Intent(context, ListenerService.class);
intent.setAction(ACTION_RESEND);
context.startService(intent);
}
public static void initiateAction(Context context, @NotNull String actionstring) {
Intent intent = new Intent(context, ListenerService.class);
intent.putExtra("actionstring", actionstring);
intent.setAction(ACTION_INITIATE_ACTION);
context.startService(intent);
}
public static void confirmAction(Context context, String actionstring) {
Intent intent = new Intent(context, ListenerService.class);
intent.putExtra("actionstring", actionstring);
if (actionstring.equals("changeRequest")) {
intent.setAction(ACTION_CONFIRMCHANGE);
} else {
intent.setAction(ACTION_CONFIRMATION);
}
context.startService(intent);
}
@Override
public void onConnected(Bundle bundle) {
// Log.d(TAG, logPrefix + "onConnected call requestData");
Wearable.ChannelApi.addListener(googleApiClient, this);
// requestData();
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
@Override
public void onDestroy() {
super.onDestroy();
if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect();
}
if (googleApiClient != null) {
Wearable.MessageApi.removeListener(googleApiClient, this);
Wearable.ChannelApi.removeListener(googleApiClient, this);
}
}
}

View file

@ -174,7 +174,7 @@ public class RawDisplayData {
wearUtil.releaseWakeLock(wl);
}
public DataMap updateBasalsFromMessage(Intent intent, PowerManager.WakeLock wakeLock) {
public DataMap updateBasalsFromMessage(Intent intent) {
Bundle bundle = intent.getBundleExtra("basals");
if (bundle != null) {
DataMap dataMap = wearUtil.bundleToDataMap(bundle);

View file

@ -0,0 +1,23 @@
package info.nightscout.androidaps.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.interaction.actions.*
@Module
@Suppress("unused")
abstract class WearActivitiesModule {
@ContributesAndroidInjector abstract fun contributesBackgroundActionActivity(): BackgroundActionActivity
@ContributesAndroidInjector abstract fun contributesViewSelectorActivity(): ViewSelectorActivity
@ContributesAndroidInjector abstract fun contributesAcceptActivity(): AcceptActivity
@ContributesAndroidInjector abstract fun contributesBolusActivity(): BolusActivity
@ContributesAndroidInjector abstract fun contributesCarbActivity(): CarbActivity
@ContributesAndroidInjector abstract fun contributesCPPActivity(): CPPActivity
@ContributesAndroidInjector abstract fun contributesECarbActivity(): ECarbActivity
@ContributesAndroidInjector abstract fun contributesFillActivity(): FillActivity
@ContributesAndroidInjector abstract fun contributesTempTargetActivity(): TempTargetActivity
@ContributesAndroidInjector abstract fun contributesTreatmentActivity(): TreatmentActivity
@ContributesAndroidInjector abstract fun contributesWizardActivity(): WizardActivity
}

View file

@ -7,6 +7,8 @@ import dagger.Module
import dagger.Provides
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Aaps
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.rx.DefaultAapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.AAPSLoggerProduction
import info.nightscout.shared.logging.L
@ -16,7 +18,8 @@ import javax.inject.Singleton
@Suppress("unused")
@Module(includes = [
WearModule.AppBindings::class
WearModule.AppBindings::class,
WearActivitiesModule::class
])
open class WearModule {
@ -28,6 +31,10 @@ open class WearModule {
@Singleton
fun provideAAPSLogger(l: L): AAPSLogger = AAPSLoggerProduction(l)
@Provides
@Singleton
internal fun provideSchedulers(): AapsSchedulers = DefaultAapsSchedulers()
@Module
interface AppBindings {

View file

@ -3,14 +3,15 @@ package info.nightscout.androidaps.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.complications.*
import info.nightscout.androidaps.data.ListenerService
import info.nightscout.androidaps.data.DataLayerListenerService
import info.nightscout.androidaps.interaction.actions.BackgroundActionActivity
import info.nightscout.androidaps.watchfaces.*
@Module
@Suppress("unused")
abstract class WearServicesModule {
@ContributesAndroidInjector abstract fun contributesListenerService(): ListenerService
@ContributesAndroidInjector abstract fun contributesDataLayerListenerService(): DataLayerListenerService
@ContributesAndroidInjector abstract fun contributesBaseComplicationProviderService(): BaseComplicationProviderService
@ContributesAndroidInjector abstract fun contributesBrCobIobComplication(): BrCobIobComplication

View file

@ -1,5 +1,7 @@
package info.nightscout.androidaps.interaction.actions;
import static info.nightscout.shared.weardata.WearConstants.KEY_ACTION_DATA;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@ -19,7 +21,10 @@ import androidx.core.view.MotionEventCompat;
import androidx.core.view.ViewConfigurationCompat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobileChange;
import info.nightscout.androidaps.events.EventWearToMobileConfirm;
import info.nightscout.shared.weardata.ActionData;
/**
* Created by adrian on 09/02/17.
@ -29,6 +34,7 @@ public class AcceptActivity extends ViewSelectorActivity {
String message = "";
String actionstring = "";
String actionKey = "";
private DismissThread dismissThread;
@Override
@ -41,8 +47,9 @@ public class AcceptActivity extends ViewSelectorActivity {
Bundle extras = getIntent().getExtras();
message = extras.getString("message", "");
actionstring = extras.getString("actionstring", "");
actionKey = extras.getString(KEY_ACTION_DATA, "");
if ("".equals(message) || "".equals(actionstring)) {
if (message.isEmpty() || (actionstring.isEmpty() && actionKey.isEmpty())) {
finish();
return;
}
@ -60,6 +67,7 @@ public class AcceptActivity extends ViewSelectorActivity {
finish();
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -74,48 +82,53 @@ public class AcceptActivity extends ViewSelectorActivity {
@Override
public Object instantiateItem(ViewGroup container, int row, int col) {
final View view;
if (col == 0) {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_confirm_text, container, false);
view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_confirm_text, container, false);
final TextView textView = view.findViewById(R.id.message);
final View scrollView = view.findViewById(R.id.message_scroll);
textView.setText(message);
container.addView(view);
scrollView.setOnGenericMotionListener(new View.OnGenericMotionListener() {
@Override
public boolean onGenericMotion(View v, MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_SCROLL &&
ev.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER)
) {
float delta = -ev.getAxisValue(MotionEventCompat.AXIS_SCROLL) *
ViewConfigurationCompat.getScaledVerticalScrollFactor(
ViewConfiguration.get(container.getContext()),
container.getContext());
v.scrollBy(0, Math.round(delta));
scrollView.setOnGenericMotionListener((v, ev) -> {
if (ev.getAction() == MotionEvent.ACTION_SCROLL &&
ev.isFromSource(InputDeviceCompat.SOURCE_ROTARY_ENCODER)
) {
float delta = -ev.getAxisValue(MotionEventCompat.AXIS_SCROLL) *
ViewConfigurationCompat.getScaledVerticalScrollFactor(
ViewConfiguration.get(container.getContext()),
container.getContext());
v.scrollBy(0, Math.round(delta));
return true;
}
return false;
return true;
}
return false;
});
scrollView.requestFocus();
return view;
} else {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener((View v) -> {
ListenerService.confirmAction(AcceptActivity.this, actionstring);
view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmButton = view.findViewById(R.id.confirmbutton);
confirmButton.setOnClickListener((View v) -> {
if (!actionstring.isEmpty())
DataLayerListenerService.Companion.confirmAction(AcceptActivity.this, actionstring);
else {
ActionData actionData = ActionData.Companion.deserialize(actionKey);
if (actionData instanceof ActionData.ConfirmAction)
rxBus.send(new EventWearToMobileConfirm(actionData));
if (actionData instanceof ActionData.ChangeAction)
rxBus.send(new EventWearToMobileChange(actionData));
}
finishAffinity();
});
container.addView(view);
return view;
}
return view;
}
@Override
public void destroyItem(ViewGroup container, int row, int col, Object view) {
// Handle this to get the data before the view is destroyed?
// Object should still be kept by this, just setup for reinit?
// Object should still be kept by this, just setup for re-init?
container.removeView((View) view);
}

View file

@ -1,28 +1,26 @@
package info.nightscout.androidaps.interaction.actions
import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import info.nightscout.androidaps.data.ListenerService
import dagger.android.DaggerActivity
import info.nightscout.androidaps.data.DataLayerListenerService
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import javax.inject.Inject
const val TAG = "QuickWizard"
class BackgroundActionActivity : DaggerActivity() {
class BackgroundActionActivity : Activity() {
@Inject lateinit var aapsLogger: AAPSLogger
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val actionString = intent.extras?.getString("actionString")
Log.i(TAG, "QuickWizardActivity.onCreate: actionString=$actionString")
if (actionString != null) {
ListenerService.initiateAction(this, actionString)
val message = intent.extras?.getString("message")
if (message != null) {
intent.extras?.getString("actionString")?.let { actionString ->
aapsLogger.info(LTag.WEAR, "QuickWizardActivity.onCreate: actionString=$actionString")
DataLayerListenerService.initiateAction(this, actionString)
intent.extras?.getString("message")?.let { message ->
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
} else {
Log.e(TAG, "BackgroundActionActivity.onCreate extras 'actionString' required")
}
} ?: aapsLogger.error(LTag.WEAR, "BackgroundActionActivity.onCreate extras 'actionString' required")
finishAffinity()
}

View file

@ -1,8 +1,6 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -12,21 +10,21 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.events.EventWearToMobileAction;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.ActionData;
public class BolusActivity extends ViewSelectorActivity {
PlusMinusEditText editInsulin;
float maxBolus;
double maxBolus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
maxBolus = sp.getFloat(getString(R.string.key_treatmentssafety_maxbolus), 3f);
maxBolus = sp.getDouble(getString(R.string.key_treatmentssafety_maxbolus), 3.0);
}
@Override
@ -35,6 +33,7 @@ public class BolusActivity extends ViewSelectorActivity {
finish();
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -49,30 +48,29 @@ public class BolusActivity extends ViewSelectorActivity {
@Override
public Object instantiateItem(ViewGroup container, int row, int col) {
final View view;
if (col == 0) {
final View view = getInflatedPlusMinusView(container);
view = getInflatedPlusMinusView(container);
double def = 0;
if (editInsulin != null) {
def = SafeParse.stringToDouble(editInsulin.editText.getText().toString());
}
editInsulin = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double)maxBolus, 0.1d, new DecimalFormat("#0.0"),false);
editInsulin = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, maxBolus, 0.1d, new DecimalFormat("#0.0"), false);
setLabelToPlusMinusView(view, getString(R.string.action_insulin));
container.addView(view);
view.requestFocus();
return view;
} else {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener((View v) -> {
String actionstring = "bolus " + SafeParse.stringToDouble(editInsulin.editText.getText().toString())
+ " 0"; // Zero carbs
ListenerService.initiateAction(BolusActivity.this, actionstring);
confirmAction(BolusActivity.this, R.string.action_bolus_confirmation);
view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmButton = view.findViewById(R.id.confirmbutton);
confirmButton.setOnClickListener((View v) -> {
ActionData.Bolus bolus = new ActionData.Bolus(SafeParse.stringToDouble(editInsulin.editText.getText().toString()), 0);
rxBus.send(new EventWearToMobileAction(bolus));
showToast(BolusActivity.this, R.string.action_bolus_confirmation);
finishAffinity();
});
container.addView(view);
return view;
}
return view;
}
@Override

View file

@ -10,9 +10,11 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobileAction;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.ActionData;
/**
* Created by adrian on 09/02/17.
@ -88,15 +90,14 @@ public class CPPActivity extends ViewSelectorActivity {
} else {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
final ImageView confirmButton = view.findViewById(R.id.confirmbutton);
confirmButton.setOnClickListener((View v) -> {
//check if it can happen that the fragment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "cppset " + SafeParse.stringToInt(editTimeshift.editText.getText().toString())
+ " " + SafeParse.stringToInt(editPercentage.editText.getText().toString());
ListenerService.initiateAction(CPPActivity.this, actionstring);
confirmAction(CPPActivity.this, R.string.action_cpp_confirmation);
ActionData.ProfileSwitch ps =
new ActionData.ProfileSwitch(SafeParse.stringToInt(editTimeshift.editText.getText().toString()), SafeParse.stringToInt(editPercentage.editText.getText().toString()));
rxBus.send(new EventWearToMobileAction(ps));
showToast(CPPActivity.this, R.string.action_cpp_confirmation);
finishAffinity();
});
container.addView(view);

View file

@ -1,8 +1,6 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -12,7 +10,7 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
@ -25,7 +23,6 @@ public class CarbActivity extends ViewSelectorActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
maxCarbs = sp.getInt(getString(R.string.key_treatmentssafety_maxcarbs), 48);
}
@ -66,8 +63,8 @@ public class CarbActivity extends ViewSelectorActivity {
confirmbutton.setOnClickListener((View v) -> {
// With start time 0 and duration 0
String actionstring = "ecarbs " + SafeParse.stringToInt(editCarbs.editText.getText().toString()) + " 0 0";
ListenerService.initiateAction(CarbActivity.this, actionstring);
confirmAction(CarbActivity.this, R.string.action_ecarb_confirmation);
DataLayerListenerService.Companion.initiateAction(CarbActivity.this, actionstring);
showToast(CarbActivity.this, R.string.action_ecarb_confirmation);
finishAffinity();
});

View file

@ -1,8 +1,6 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -12,7 +10,7 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
@ -31,7 +29,6 @@ public class ECarbActivity extends ViewSelectorActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
maxCarbs = sp.getInt(getString(R.string.key_treatmentssafety_maxcarbs), 48);
}
@ -92,14 +89,14 @@ public class ECarbActivity extends ViewSelectorActivity {
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
//check if it can happen that the fragment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "ecarbs " + SafeParse.stringToInt(editCarbs.editText.getText().toString())
+ " " + SafeParse.stringToInt(editStartTime.editText.getText().toString())
+ " " + SafeParse.stringToInt(editDuration.editText.getText().toString());
ListenerService.initiateAction(ECarbActivity.this, actionstring);
confirmAction(ECarbActivity.this, R.string.action_ecarb_confirmation);
DataLayerListenerService.Companion.initiateAction(ECarbActivity.this, actionstring);
showToast(ECarbActivity.this, R.string.action_ecarb_confirmation);
finishAffinity();
});

View file

@ -10,7 +10,7 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
@ -64,13 +64,13 @@ public class FillActivity extends ViewSelectorActivity {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "fill " + SafeParse.stringToDouble(editInsulin.editText.getText().toString());
ListenerService.initiateAction(FillActivity.this, actionstring);
confirmAction(FillActivity.this, R.string.action_fill_confirmation);
finishAffinity();
String actionstring = "fill " + SafeParse.stringToDouble(editInsulin.editText.getText().toString());
DataLayerListenerService.Companion.initiateAction(FillActivity.this, actionstring);
showToast(FillActivity.this, R.string.action_fill_confirmation);
finishAffinity();
});
container.addView(view);
return view;

View file

@ -1,8 +1,6 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -12,7 +10,7 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
@ -34,7 +32,6 @@ public class TempTargetActivity extends ViewSelectorActivity {
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
isMGDL = sp.getBoolean("units_mgdl", true);
isSingleTarget = sp.getBoolean("singletarget", true);
}
@ -126,8 +123,8 @@ public class TempTargetActivity extends ViewSelectorActivity {
+ " " + SafeParse.stringToDouble(lowRange.editText.getText().toString())
+ " " + (isSingleTarget ? SafeParse.stringToDouble(lowRange.editText.getText().toString()) : SafeParse.stringToDouble(highRange.editText.getText().toString()));
ListenerService.initiateAction(TempTargetActivity.this, actionstring);
confirmAction(TempTargetActivity.this, R.string.action_tempt_confirmation);
DataLayerListenerService.Companion.initiateAction(TempTargetActivity.this, actionstring);
showToast(TempTargetActivity.this, R.string.action_tempt_confirmation);
finishAffinity();
});
container.addView(view);

View file

@ -1,8 +1,6 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -12,9 +10,11 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobileAction;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.ActionData;
/**
* Created by adrian on 09/02/17.
@ -25,15 +25,14 @@ public class TreatmentActivity extends ViewSelectorActivity {
PlusMinusEditText editCarbs;
PlusMinusEditText editInsulin;
int maxCarbs;
float maxBolus;
double maxBolus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
maxCarbs = sp.getInt(getString(R.string.key_treatmentssafety_maxcarbs), 48);
maxBolus = sp.getFloat(getString(R.string.key_treatmentssafety_maxbolus), 3f);
maxBolus = sp.getDouble(getString(R.string.key_treatmentssafety_maxbolus), 3.0);
}
@Override
@ -63,7 +62,7 @@ public class TreatmentActivity extends ViewSelectorActivity {
if (editInsulin != null) {
def = SafeParse.stringToDouble(editInsulin.editText.getText().toString());
}
editInsulin = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double) maxBolus, 0.1d, new DecimalFormat("#0.0"),false);
editInsulin = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double) maxBolus, 0.1d, new DecimalFormat("#0.0"), false);
setLabelToPlusMinusView(view, getString(R.string.action_insulin));
container.addView(view);
view.requestFocus();
@ -74,7 +73,7 @@ public class TreatmentActivity extends ViewSelectorActivity {
if (editCarbs != null) {
def = SafeParse.stringToDouble(editCarbs.editText.getText().toString());
}
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double)maxCarbs, 1d, new DecimalFormat("0"),false);
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), false);
setLabelToPlusMinusView(view, getString(R.string.action_carbs));
container.addView(view);
return view;
@ -83,13 +82,12 @@ public class TreatmentActivity extends ViewSelectorActivity {
final View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmbutton = view.findViewById(R.id.confirmbutton);
confirmbutton.setOnClickListener((View v) -> {
//check if it can happen that the fagment is never created that hold data?
// (you have to swipe past them anyways - but still)
String actionstring = "bolus " + SafeParse.stringToDouble(editInsulin.editText.getText().toString())
+ " " + SafeParse.stringToInt(editCarbs.editText.getText().toString());
ListenerService.initiateAction(TreatmentActivity.this, actionstring);
confirmAction(TreatmentActivity.this, R.string.action_treatment_confirmation);
finishAffinity();
//check if it can happen that the fragment is never created that hold data?
// (you have to swipe past them anyways - but still)
ActionData.Bolus bolus = new ActionData.Bolus(SafeParse.stringToDouble(editInsulin.editText.getText().toString()), SafeParse.stringToInt(editCarbs.editText.getText().toString()));
rxBus.send(new EventWearToMobileAction(bolus));
showToast(TreatmentActivity.this, R.string.action_treatment_confirmation);
finishAffinity();
});
container.addView(view);
return view;

View file

@ -1,10 +1,7 @@
package info.nightscout.androidaps.interaction.actions;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.DotsPageIndicator;
import android.support.wearable.view.GridPagerAdapter;
import android.support.wearable.view.GridViewPager;
@ -16,13 +13,21 @@ import android.widget.Toast;
import androidx.wear.widget.CurvedTextView;
import javax.inject.Inject;
import dagger.android.DaggerActivity;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.shared.sharedPreferences.SP;
/**
* Created by adrian on 13/02/17.
*/
public class ViewSelectorActivity extends Activity {
public class ViewSelectorActivity extends DaggerActivity {
@Inject SP sp;
@Inject RxBus rxBus;
private GridViewPager pager;
@ -81,9 +86,7 @@ public class ViewSelectorActivity extends Activity {
}
View getInflatedPlusMinusView(ViewGroup container) {
SharedPreferences sharedPrefs = PreferenceManager
.getDefaultSharedPreferences(this);
int design = Integer.parseInt(sharedPrefs.getString("input_design", "1"));
int design = sp.getInt("input_design", 1);
if (design == 2) {
return LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_editplusminus_item_quickrighty, container, false);
@ -102,7 +105,7 @@ public class ViewSelectorActivity extends Activity {
textView.setText(labelText);
}
void confirmAction(Context context, int text) {
void showToast(Context context, int text) {
Toast.makeText(context, getString(text), Toast.LENGTH_LONG).show();
}

View file

@ -1,8 +1,6 @@
package info.nightscout.androidaps.interaction.actions;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.wearable.view.GridPagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
@ -12,7 +10,7 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
@ -33,7 +31,6 @@ public class WizardActivity extends ViewSelectorActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setAdapter(new MyGridViewPagerAdapter());
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
hasPercentage = sp.getBoolean("wizardpercentage", false);
percentage = sp.getInt(getString(R.string.key_boluswizard_percentage), 100);
maxCarbs = sp.getInt(getString(R.string.key_treatmentssafety_maxcarbs), 48);
@ -93,8 +90,8 @@ public class WizardActivity extends ViewSelectorActivity {
String actionstring = "wizard2 " + SafeParse.stringToInt(editCarbs.editText.getText().toString())
+ " " + percentage;
ListenerService.initiateAction(WizardActivity.this, actionstring);
confirmAction(WizardActivity.this, R.string.action_wizard_confirmation);
DataLayerListenerService.Companion.initiateAction(WizardActivity.this, actionstring);
showToast(WizardActivity.this, R.string.action_wizard_confirmation);
finishAffinity();
});
container.addView(view);

View file

@ -7,7 +7,7 @@ import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.actions.FillActivity;
import info.nightscout.androidaps.interaction.utils.MenuListActivity;
@ -37,11 +37,11 @@ public class FillMenuActivity extends MenuListActivity {
@Override
protected void doAction(String action) {
if (getString(R.string.action_preset_1).equals(action)) {
ListenerService.initiateAction(this, "fillpreset 1");
DataLayerListenerService.Companion.initiateAction(this, "fillpreset 1");
} else if (getString(R.string.action_preset_2).equals(action)) {
ListenerService.initiateAction(this, "fillpreset 2");
DataLayerListenerService.Companion.initiateAction(this, "fillpreset 2");
} else if (getString(R.string.action_preset_3).equals(action)) {
ListenerService.initiateAction(this, "fillpreset 3");
DataLayerListenerService.Companion.initiateAction(this, "fillpreset 3");
} else if (getString(R.string.action_free_amount).equals(action)) {
Intent intent = new Intent(this, FillActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

View file

@ -9,11 +9,11 @@ import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.AAPSPreferences;
import info.nightscout.androidaps.interaction.actions.TreatmentActivity;
import info.nightscout.androidaps.interaction.actions.ECarbActivity;
import info.nightscout.androidaps.interaction.actions.TempTargetActivity;
import info.nightscout.androidaps.interaction.actions.TreatmentActivity;
import info.nightscout.androidaps.interaction.actions.WizardActivity;
import info.nightscout.androidaps.interaction.utils.MenuListActivity;
@ -30,7 +30,7 @@ public class MainMenuActivity extends MenuListActivity {
sp = PreferenceManager.getDefaultSharedPreferences(this);
setTitle(R.string.label_actions_activity);
super.onCreate(savedInstanceState);
ListenerService.requestData(this);
DataLayerListenerService.Companion.requestData(this);
}
@Override
@ -68,7 +68,7 @@ public class MainMenuActivity extends MenuListActivity {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
} else if (getString(R.string.menu_resync).equals(action)) {
ListenerService.requestData(this);
DataLayerListenerService.Companion.requestData(this);
} else if (getString(R.string.menu_tempt).equals(action)) {
intent = new Intent(this, TempTargetActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

View file

@ -6,7 +6,7 @@ import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.utils.MenuListActivity;
/**
@ -35,13 +35,13 @@ public class StatusMenuActivity extends MenuListActivity {
@Override
protected void doAction(String action) {
if (getString(R.string.status_pump).equals(action)) {
ListenerService.initiateAction(this, "status pump");
DataLayerListenerService.Companion.initiateAction(this, "status pump");
} else if (getString(R.string.status_loop).equals(action)) {
ListenerService.initiateAction(this, "status loop");
DataLayerListenerService.Companion.initiateAction(this, "status loop");
} else if (getString(R.string.status_cpp).equals(action)) {
ListenerService.initiateAction(this, "opencpp");
DataLayerListenerService.Companion.initiateAction(this, "opencpp");
} else if (getString(R.string.status_tdd).equals(action)) {
ListenerService.initiateAction(this, "tddstats");
DataLayerListenerService.Companion.initiateAction(this, "tddstats");
}
}
}

View file

@ -44,7 +44,7 @@ import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.BasalWatchData;
import info.nightscout.androidaps.data.BgWatchData;
import info.nightscout.androidaps.data.BolusWatchData;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.data.TempWatchData;
import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
import lecho.lib.hellocharts.view.LineChartView;
@ -158,7 +158,7 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
mRelativeLayout.getMeasuredHeight());
}
});
ListenerService.requestData(this);
DataLayerListenerService.Companion.requestData(this);
wakeLock.acquire(50);
}
@ -634,9 +634,9 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
}
public void missedReadingAlert() {
int minutes_since = (int) Math.floor(timeSince()/(1000*60));
if(minutes_since >= 16 && ((minutes_since - 16) % 5) == 0) {
ListenerService.requestData(this); // attempt endTime recover missing data
int minutes_since = (int) Math.floor(timeSince() / (1000 * 60));
if (minutes_since >= 16 && ((minutes_since - 16) % 5) == 0) {
DataLayerListenerService.Companion.requestData(this); // attempt endTime recover missing data
}
}
@ -690,7 +690,7 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
chart.setViewportCalculationEnabled(true);
chart.setMaximumViewport(chart.getMaximumViewport());
} else {
ListenerService.requestData(this);
DataLayerListenerService.Companion.requestData(this);
}
}
}

View file

@ -44,7 +44,7 @@ import javax.inject.Inject;
import dagger.android.AndroidInjection;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.androidaps.interaction.utils.Persistence;
import info.nightscout.androidaps.interaction.utils.WearUtil;
@ -678,7 +678,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
setupBatteryReceiver();
if ("delta_granularity".equals(key)) {
ListenerService.requestData(this);
DataLayerListenerService.Companion.requestData(this);
}
if (layoutSet) {
setDataFields();
@ -695,7 +695,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
public void missedReadingAlert() {
int minutes_since = (int) Math.floor(timeSince() / (1000 * 60));
if (rawData.datetime == 0 || minutes_since >= 16 && ((minutes_since - 16) % 5) == 0) {
ListenerService.requestData(this); // Attempt endTime recover missing data
DataLayerListenerService.Companion.requestData(this); // Attempt endTime recover missing data
}
}
@ -728,7 +728,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
setupCharts();
}
rawData.updateStatusFromMessage(intent, wakeLock);
rawData.updateBasalsFromMessage(intent, wakeLock);
rawData.updateBasalsFromMessage(intent);
if (isSimpleUi()) {
if (needUpdate()) {

View file

@ -43,7 +43,7 @@ import java.util.ArrayList;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.BasalWatchData;
import info.nightscout.androidaps.data.BgWatchData;
import info.nightscout.androidaps.data.ListenerService;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.data.TempWatchData;
import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
@ -137,7 +137,7 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
mRelativeLayout.getMeasuredHeight());
}
});
ListenerService.requestData(this);
DataLayerListenerService.Companion.requestData(this);
wakeLock.acquire(50);
}
@ -518,7 +518,7 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
public void missedReadingAlert() {
int minutes_since = (int) Math.floor(timeSince()/(1000*60));
if(minutes_since >= 16 && ((minutes_since - 16) % 5) == 0) {
ListenerService.requestData(this); // attempt endTime recover missing data
DataLayerListenerService.Companion.requestData(this); // attempt endTime recover missing data
}
}
}

View file

@ -1,18 +1,25 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
<resources>
<string-array name="android_wear_capabilities">
<item>phone_app_sync_bgs</item>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<string-array
name="android_wear_capabilities"
translatable="false"
tools:ignore="UnusedResources">
<!-- declaring the provided capabilities -->
<item>androidaps_wear</item>
</string-array>
</resources>
</resources>

View file

@ -220,7 +220,7 @@ public class RawDisplayDataBasalsTest extends TestBase {
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateBasalsFromMessage(intent, null);
newRaw.updateBasalsFromMessage(intent);
// THEN
assertBasalsOk(newRaw);
@ -233,7 +233,7 @@ public class RawDisplayDataBasalsTest extends TestBase {
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateBasalsFromMessage(intent, null);
newRaw.updateBasalsFromMessage(intent);
// THEN
assertBasalsEmpty(newRaw);