Wear: migrate to RxBus comm and data classes

This commit is contained in:
Milos Kozak 2022-04-22 22:19:10 +02:00
parent 2dba081176
commit 2ebaffa4ad
99 changed files with 3716 additions and 6509 deletions

View file

@ -167,7 +167,7 @@
</provider>
<service
android:name=".plugins.general.wear.wearintegration.WatchUpdaterService"
android:name=".plugins.general.wear.wearintegration.DataLayerListenerServiceMobile"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.wearable.CHANNEL_EVENT" />
@ -185,23 +185,7 @@
<data
android:host="*"
android:pathPrefix="@string/path_pong"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_resend_data_request"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="/@path_cancel_bolus_on_watch"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_confirm_action"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_initiate_action_on_phone"
android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" />
</intent-filter>
</service>

View file

@ -5,7 +5,7 @@ import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.plugins.general.nsclient.services.NSClientService
import info.nightscout.androidaps.plugins.general.overview.notifications.DismissNotificationService
import info.nightscout.androidaps.plugins.general.persistentNotification.DummyService
import info.nightscout.androidaps.plugins.general.wear.wearintegration.WatchUpdaterService
import info.nightscout.androidaps.plugins.general.wear.wearintegration.DataLayerListenerServiceMobile
import info.nightscout.androidaps.services.AlarmSoundService
import info.nightscout.androidaps.services.LocationService
@ -18,5 +18,5 @@ abstract class ServicesModule {
@ContributesAndroidInjector abstract fun contributesDummyService(): DummyService
@ContributesAndroidInjector abstract fun contributesLocationService(): LocationService
@ContributesAndroidInjector abstract fun contributesNSClientService(): NSClientService
@ContributesAndroidInjector abstract fun contributesWatchUpdaterService(): WatchUpdaterService
@ContributesAndroidInjector abstract fun contributesWatchUpdaterService(): DataLayerListenerServiceMobile
}

View file

@ -1,3 +0,0 @@
package info.nightscout.androidaps.events
class EventBolusRequested(var amount: Double) : Event()

View file

@ -27,6 +27,7 @@ import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentOfflineEventTransaction
import info.nightscout.androidaps.database.transactions.InsertTherapyEventAnnouncementTransaction
import info.nightscout.androidaps.events.EventAcceptOpenLoopChange
import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.events.EventTempTargetChange
import info.nightscout.androidaps.extensions.buildDeviceStatus
import info.nightscout.androidaps.extensions.convertedToAbsolute
@ -44,8 +45,6 @@ import info.nightscout.androidaps.plugins.configBuilder.RunningConfiguration
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification
import info.nightscout.androidaps.plugins.general.overview.notifications.Notification
import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAction
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin
import info.nightscout.androidaps.queue.Callback
import info.nightscout.androidaps.receivers.ReceiverStatusStore
@ -58,6 +57,7 @@ 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 info.nightscout.shared.weardata.EventData
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
@ -349,7 +349,7 @@ class LoopPlugin @Inject constructor(
//only send to wear if Native notifications are turned off
if (!sp.getBoolean(R.string.key_raise_notifications_as_android_notifications, true)) {
// Send to Wear
rxBus.send(EventWearInitiateAction("changeRequest"))
sendToWear()
}
}
} else {
@ -454,14 +454,28 @@ class LoopPlugin @Inject constructor(
rxBus.send(EventNewOpenLoopNotification())
// Send to Wear
rxBus.send(EventWearInitiateAction("changeRequest"))
sendToWear()
}
private fun dismissSuggestion() {
// dismiss notifications
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(Constants.notificationID)
rxBus.send(EventWearConfirmAction("cancelChangeRequest"))
rxBus.send(EventMobileToWear(EventData.CancelNotification(dateUtil.now())))
}
private fun sendToWear() {
lastRun?.let {
rxBus.send(
EventMobileToWear(
EventData.OpenLoopRequest(
rh.gs(R.string.openloop_newsuggestion),
it.constraintsProcessed.toString(),
EventData.OpenLoopRequestConfirmed(dateUtil.now())
)
)
)
}
}
override fun acceptChangeRequest() {

View file

@ -54,7 +54,6 @@ import info.nightscout.androidaps.plugins.general.overview.activities.QuickWizar
import info.nightscout.androidaps.plugins.general.overview.events.*
import info.nightscout.androidaps.plugins.general.overview.graphData.GraphData
import info.nightscout.androidaps.plugins.general.overview.notifications.NotificationStore
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.iob.iobCobCalculator.GlucoseStatusProvider
import info.nightscout.androidaps.plugins.pump.common.defs.PumpType
import info.nightscout.androidaps.plugins.pump.omnipod.eros.OmnipodErosPumpPlugin
@ -76,6 +75,7 @@ import info.nightscout.androidaps.utils.ui.UIRunnable
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.EventData
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
@ -408,7 +408,7 @@ class OverviewFragment : DaggerFragment(), View.OnClickListener, OnLongClickList
?: "".toSpanned(), {
uel.log(Action.ACCEPTS_TEMP_BASAL, Sources.Overview)
(context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?)?.cancel(Constants.notificationID)
rxBus.send(EventWearInitiateAction("cancelChangeRequest"))
rxBus.send(EventMobileToWear(EventData.CancelNotification(dateUtil.now())))
Thread { loop.acceptChangeRequest() }.run()
binding.buttonsLayout.acceptTempButton.visibility = View.GONE
})

View file

@ -1,841 +0,0 @@
package info.nightscout.androidaps.plugins.general.wear
import android.app.NotificationManager
import android.content.Context
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.Constants
import info.nightscout.androidaps.R
import info.nightscout.androidaps.dana.DanaPump
import info.nightscout.androidaps.danaRKorean.DanaRKoreanPlugin
import info.nightscout.androidaps.danaRv2.DanaRv2Plugin
import info.nightscout.androidaps.danar.DanaRPlugin
import info.nightscout.androidaps.danars.DanaRSPlugin
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.database.ValueWrapper
import info.nightscout.androidaps.database.entities.TemporaryTarget
import info.nightscout.androidaps.database.entities.TotalDailyDose
import info.nightscout.androidaps.database.entities.UserEntry.Action
import info.nightscout.androidaps.database.entities.UserEntry.Sources
import info.nightscout.androidaps.database.entities.ValueWithUnit
import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.database.transactions.CancelCurrentTemporaryTargetIfAnyTransaction
import info.nightscout.androidaps.database.transactions.InsertAndCancelCurrentTemporaryTargetTransaction
import info.nightscout.androidaps.extensions.total
import info.nightscout.androidaps.extensions.valueToUnits
import info.nightscout.androidaps.interfaces.*
import info.nightscout.androidaps.logging.UserEntryLogger
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification
import info.nightscout.androidaps.plugins.general.wear.events.EventWearConfirmAction
import info.nightscout.androidaps.plugins.general.wear.events.EventWearInitiateAction
import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin
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.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
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.abs
import kotlin.math.min
@Suppress("SpellCheckingInspection")
@Singleton
class ActionStringHandler @Inject constructor(
private val sp: SP,
private val rxBus: RxBus,
private val aapsLogger: AAPSLogger,
private val aapsSchedulers: AapsSchedulers,
private val rh: ResourceHelper,
private val injector: HasAndroidInjector,
private val context: Context,
private val constraintChecker: ConstraintChecker,
private val profileFunction: ProfileFunction,
private val loop: Loop,
private val wearPlugin: WearPlugin,
private val fabricPrivacy: FabricPrivacy,
private val commandQueue: CommandQueue,
private val activePlugin: ActivePlugin,
private val iobCobCalculator: IobCobCalculator,
private val localInsightPlugin: LocalInsightPlugin,
private val quickWizard: QuickWizard,
private val danaRPlugin: DanaRPlugin,
private val danaRKoreanPlugin: DanaRKoreanPlugin,
private val danaRv2Plugin: DanaRv2Plugin,
private val danaRSPlugin: DanaRSPlugin,
private val danaPump: DanaPump,
private val dateUtil: DateUtil,
private val config: Config,
private val repository: AppRepository,
private val uel: UserEntryLogger,
private val defaultValueHelper: DefaultValueHelper
) {
private val timeout = 65 * 1000
private var lastSentTimestamp: Long = 0
private var lastConfirmActionString: String? = null
private var lastBolusWizard: BolusWizard? = null
private val disposable = CompositeDisposable()
fun setup() {
disposable += rxBus
.toObservable(EventWearInitiateAction::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ handleInitiateActionOnPhone(it.action) }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventWearConfirmAction::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({ handleConfirmation(it.action) }, fabricPrivacy::logException)
}
fun tearDown() {
disposable.clear()
}
@Synchronized
private fun handleInitiateActionOnPhone(actionString: String) {
//TODO: i18n
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 = ""
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
}
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)
}
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 {
// 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"
}
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"
}
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"
}
"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"
}
"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"
}
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
}
}
}
// send result
wearPlugin.requestActionConfirmation(rTitle, rMessage, rAction)
lastSentTimestamp = System.currentTimeMillis()
lastConfirmActionString = rAction
}
private fun generateTDDMessage(historyList: MutableList<TotalDailyDose>, dummies: MutableList<TotalDailyDose>): String {
val profile = profileFunction.getProfile() ?: return "No profile loaded :("
if (historyList.isEmpty()) {
return "No history data!"
}
val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
var message = ""
val refTDD = profile.baseBasalSum() * 2
val pump = activePlugin.activePump
if (df.format(Date(historyList[0].timestamp)) == df.format(Date())) {
val tdd = historyList[0].total
historyList.removeAt(0)
message += "Today: " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + "\n"
message += "\n"
} else if (pump is DanaRPlugin) {
val tdd = danaPump.dailyTotalUnits
message += "Today: " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + "\n"
message += "\n"
}
var weighted03 = 0.0
var weighted05 = 0.0
var weighted07 = 0.0
historyList.reverse()
for ((i, record) in historyList.withIndex()) {
val tdd = record.total
if (i == 0) {
weighted03 = tdd
weighted05 = tdd
weighted07 = tdd
} else {
weighted07 = weighted07 * 0.3 + tdd * 0.7
weighted05 = weighted05 * 0.5 + tdd * 0.5
weighted03 = weighted03 * 0.7 + tdd * 0.3
}
}
message += "weighted:\n"
message += "0.3: " + DecimalFormatter.to2Decimal(weighted03) + "U " + (DecimalFormatter.to0Decimal(100 * weighted03 / refTDD) + "%") + "\n"
message += "0.5: " + DecimalFormatter.to2Decimal(weighted05) + "U " + (DecimalFormatter.to0Decimal(100 * weighted05 / refTDD) + "%") + "\n"
message += "0.7: " + DecimalFormatter.to2Decimal(weighted07) + "U " + (DecimalFormatter.to0Decimal(100 * weighted07 / refTDD) + "%") + "\n"
message += "\n"
historyList.reverse()
//add TDDs:
for (record in historyList) {
val tdd = record.total
message += df.format(Date(record.timestamp)) + " " + DecimalFormatter.to2Decimal(tdd) + "U " + (DecimalFormatter.to0Decimal(100 * tdd / refTDD) + "%") + (if (dummies.contains(record)) "x" else "") + "\n"
}
return message
}
private fun isOldData(historyList: List<TotalDailyDose>): Boolean {
val activePump = activePlugin.activePump
val startsYesterday = activePump === danaRPlugin || activePump === danaRSPlugin || activePump === danaRv2Plugin || activePump === danaRKoreanPlugin || activePump === localInsightPlugin
val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
return historyList.size < 3 || df.format(Date(historyList[0].timestamp)) != df.format(Date(System.currentTimeMillis() - if (startsYesterday) 1000 * 60 * 60 * 24 else 0))
}
private fun getTDDList(returnDummies: MutableList<TotalDailyDose>): MutableList<TotalDailyDose> {
var historyList = repository.getLastTotalDailyDoses(10, false).blockingGet().toMutableList()
//var historyList = databaseHelper.getTDDs().toMutableList()
historyList = historyList.subList(0, min(10, historyList.size))
//fill single gaps - only needed for Dana*R data
val dummies: MutableList<TotalDailyDose> = returnDummies
val df: DateFormat = SimpleDateFormat("dd.MM.", Locale.getDefault())
for (i in 0 until historyList.size - 1) {
val elem1 = historyList[i]
val elem2 = historyList[i + 1]
if (df.format(Date(elem1.timestamp)) != df.format(Date(elem2.timestamp + 25 * 60 * 60 * 1000))) {
val dummy = TotalDailyDose(timestamp = elem1.timestamp - T.hours(24).msecs(), bolusAmount = elem1.bolusAmount / 2, basalAmount = elem1.basalAmount / 2)
dummies.add(dummy)
elem1.basalAmount /= 2.0
elem1.bolusAmount /= 2.0
}
}
historyList.addAll(dummies)
historyList.sortWith { lhs, rhs -> (rhs.timestamp - lhs.timestamp).toInt() }
return historyList
}
private val pumpStatus: String
get() = activePlugin.activePump.shortStatus(false)
// decide if enabled/disabled closed/open; what Plugin as APS?
private val loopStatus: String
get() {
var ret = ""
// decide if enabled/disabled closed/open; what Plugin as APS?
if ((loop as PluginBase).isEnabled()) {
ret += if (constraintChecker.isClosedLoopAllowed().value()) {
"CLOSED LOOP\n"
} else {
"OPEN LOOP\n"
}
val aps = activePlugin.activeAPS
ret += "APS: " + (aps as PluginBase).name
val lastRun = loop.lastRun
if (lastRun != null) {
ret += "\nLast Run: " + dateUtil.timeString(lastRun.lastAPSRun)
if (lastRun.lastTBREnact != 0L) ret += "\nLast Enact: " + dateUtil.timeString(lastRun.lastTBREnact)
}
} else {
ret += "LOOP DISABLED\n"
}
return ret
}
//Check for Temp-Target:
private val targetsStatus: String
get() {
var ret = ""
if (!config.APS) {
return "Targets only apply in APS mode!"
}
val profile = profileFunction.getProfile() ?: return "No profile set :("
//Check for Temp-Target:
val tempTarget = repository.getTemporaryTargetActiveAt(dateUtil.now()).blockingGet()
if (tempTarget is ValueWrapper.Existing) {
ret += "Temp Target: " + Profile.toTargetRangeString(tempTarget.value.lowTarget, tempTarget.value.lowTarget, GlucoseUnit.MGDL, profileFunction.getUnits())
ret += "\nuntil: " + dateUtil.timeString(tempTarget.value.end)
ret += "\n\n"
}
ret += "DEFAULT RANGE: "
ret += Profile.fromMgdlToUnits(profile.getTargetLowMgdl(), profileFunction.getUnits()).toString() + " - " + Profile.fromMgdlToUnits(profile.getTargetHighMgdl(), profileFunction.getUnits())
ret += " target: " + Profile.fromMgdlToUnits(profile.getTargetMgdl(), profileFunction.getUnits())
return ret
}
private val oAPSResultStatus: String
get() {
var ret = ""
if (!config.APS)
return "Only apply in APS mode!"
val usedAPS = activePlugin.activeAPS
val result = usedAPS.lastAPSResult ?: return "Last result not available!"
ret += if (!result.isChangeRequested) {
rh.gs(R.string.nochangerequested) + "\n"
} else if (result.rate == 0.0 && result.duration == 0) {
rh.gs(R.string.canceltemp) + "\n"
} else {
rh.gs(R.string.rate) + ": " + DecimalFormatter.to2Decimal(result.rate) + " U/h " +
"(" + DecimalFormatter.to2Decimal(result.rate / activePlugin.activePump.baseBasalRate * 100) + "%)\n" +
rh.gs(R.string.duration) + ": " + DecimalFormatter.to0Decimal(result.duration.toDouble()) + " min\n"
}
ret += "\n" + rh.gs(R.string.reason) + ": " + result.reason
return ret
}
@Synchronized
fun handleConfirmation(actionString: String) {
if (!sp.getBoolean(R.string.key_wear_control, false)) return
//Guard from old or duplicate confirmations
if (lastConfirmActionString == null) return
if (lastConfirmActionString != actionString) return
if (System.currentTimeMillis() - lastSentTimestamp > timeout) return
lastConfirmActionString = null
// do the parsing, check constraints and enact!
val act = actionString.split("\\s+".toRegex()).toTypedArray()
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)
}
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)
}
}
lastBolusWizard = null
}
private fun setCPP(timeshift: Int, percentage: Int) {
var msg = ""
//check for validity
if (percentage < Constants.CPP_MIN_PERCENTAGE || percentage > Constants.CPP_MAX_PERCENTAGE) {
msg += rh.gs(R.string.valueoutofrange, "Profile-Percentage") + "\n"
}
if (timeshift < 0 || timeshift > 23) {
msg += rh.gs(R.string.valueoutofrange, "Profile-Timeshift") + "\n"
}
val profile = profileFunction.getProfile()
if (profile == null) {
msg += rh.gs(R.string.notloadedplugins) + "\n"
}
if ("" != msg) {
msg += rh.gs(R.string.valuesnotstored)
val rTitle = "STATUS"
val rAction = "statusmessage"
wearPlugin.requestActionConfirmation(rTitle, msg, rAction)
lastSentTimestamp = System.currentTimeMillis()
lastConfirmActionString = rAction
return
}
//send profile to pump
uel.log(Action.PROFILE_SWITCH, Sources.Wear,
ValueWithUnit.Percent(percentage),
ValueWithUnit.Hour(timeshift).takeIf { timeshift != 0 })
profileFunction.createProfileSwitch(0, percentage, timeshift)
}
private fun generateTempTarget(duration: Int, low: Double, high: Double) {
if (duration != 0) {
disposable += repository.runTransactionForResult(
InsertAndCancelCurrentTemporaryTargetTransaction(
timestamp = System.currentTimeMillis(),
duration = TimeUnit.MINUTES.toMillis(duration.toLong()),
reason = TemporaryTarget.Reason.WEAR,
lowTarget = Profile.toMgdl(low, profileFunction.getUnits()),
highTarget = Profile.toMgdl(high, profileFunction.getUnits())
)
).subscribe({ result ->
result.inserted.forEach { aapsLogger.debug(LTag.DATABASE, "Inserted temp target $it") }
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
uel.log(
Action.TT, Sources.Wear,
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR),
ValueWithUnit.fromGlucoseUnit(low, profileFunction.getUnits().asText),
ValueWithUnit.fromGlucoseUnit(high, profileFunction.getUnits().asText).takeIf { low != high },
ValueWithUnit.Minute(duration)
)
} else {
disposable += repository.runTransactionForResult(CancelCurrentTemporaryTargetIfAnyTransaction(System.currentTimeMillis()))
.subscribe({ result ->
result.updated.forEach { aapsLogger.debug(LTag.DATABASE, "Updated temp target $it") }
}, {
aapsLogger.error(LTag.DATABASE, "Error while saving temporary target", it)
})
uel.log(
Action.CANCEL_TT, Sources.Wear,
ValueWithUnit.TherapyEventTTReason(TemporaryTarget.Reason.WEAR)
)
}
}
private fun doFillBolus(amount: Double) {
val detailedBolusInfo = DetailedBolusInfo()
detailedBolusInfo.insulin = amount
detailedBolusInfo.bolusType = DetailedBolusInfo.BolusType.PRIMING
uel.log(Action.PRIME_BOLUS, Sources.Wear,
ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 })
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
sendError(
rh.gs(R.string.treatmentdeliveryerror) +
"\n" +
result.comment
)
}
}
})
}
private fun doECarbs(carbs: Int, time: Long, duration: Int) {
uel.log(if (duration == 0) Action.CARBS else Action.EXTENDED_CARBS, Sources.Wear,
ValueWithUnit.Timestamp(time),
ValueWithUnit.Gram(carbs),
ValueWithUnit.Hour(duration).takeIf { duration != 0 })
doBolus(0.0, carbs, time, duration)
}
private fun doBolus(amount: Double, carbs: Int, carbsTime: Long?, carbsDuration: Int) {
val detailedBolusInfo = DetailedBolusInfo()
detailedBolusInfo.insulin = amount
detailedBolusInfo.carbs = carbs.toDouble()
detailedBolusInfo.bolusType = DetailedBolusInfo.BolusType.NORMAL
detailedBolusInfo.carbsTimestamp = carbsTime
detailedBolusInfo.carbsDuration = T.hours(carbsDuration.toLong()).msecs()
if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) {
val action = when {
amount.equals(0.0) -> Action.CARBS
carbs.equals(0) -> Action.BOLUS
carbsDuration > 0 -> Action.EXTENDED_CARBS
else -> Action.TREATMENT
}
uel.log(action, Sources.Wear,
ValueWithUnit.Insulin(amount).takeIf { amount != 0.0 },
ValueWithUnit.Gram(carbs).takeIf { carbs != 0 },
ValueWithUnit.Hour(carbsDuration).takeIf { carbsDuration != 0 })
commandQueue.bolus(detailedBolusInfo, object : Callback() {
override fun run() {
if (!result.success) {
sendError(
rh.gs(R.string.treatmentdeliveryerror) +
"\n" +
result.comment
)
}
}
})
}
}
@Synchronized private fun sendError(errorMessage: String) {
wearPlugin.requestActionConfirmation("ERROR", errorMessage, "error")
lastSentTimestamp = System.currentTimeMillis()
lastConfirmActionString = null
lastBolusWizard = null
}
@Synchronized
private fun sendStatusMessage(message: String) {
wearPlugin.requestActionConfirmation("TDD", message, "statusmessage")
lastSentTimestamp = System.currentTimeMillis()
lastConfirmActionString = null
lastBolusWizard = null
}
}

View file

@ -6,10 +6,13 @@ import android.view.View
import android.view.ViewGroup
import dagger.android.support.DaggerFragment
import info.nightscout.androidaps.databinding.WearFragmentBinding
import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.events.EventNSClientUpdateGUI
import info.nightscout.androidaps.utils.DateUtil
import info.nightscout.androidaps.utils.FabricPrivacy
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.shared.weardata.EventData
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
@ -20,6 +23,7 @@ class WearFragment : DaggerFragment() {
@Inject lateinit var rxBus: RxBus
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var fabricPrivacy: FabricPrivacy
@Inject lateinit var dateUtil: DateUtil
private var _binding: WearFragmentBinding? = null
@ -37,8 +41,8 @@ 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.resend.setOnClickListener { rxBus.send(EventData.ActionResendData("WearFragment")) }
binding.openSettings.setOnClickListener { rxBus.send(EventMobileToWear(EventData.OpenSettings(dateUtil.now()))) }
}
override fun onResume() {

View file

@ -2,24 +2,26 @@ package info.nightscout.androidaps.plugins.general.wear
import android.content.Context
import android.content.Intent
import dagger.Lazy
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.*
import info.nightscout.androidaps.events.EventAutosensCalculationFinished
import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.events.EventPreferenceChange
import info.nightscout.androidaps.interfaces.PluginBase
import info.nightscout.androidaps.interfaces.PluginDescription
import info.nightscout.androidaps.interfaces.PluginType
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
import info.nightscout.androidaps.plugins.general.wear.wearintegration.WatchUpdaterService
import info.nightscout.androidaps.plugins.general.wear.wearintegration.DataHandlerMobile
import info.nightscout.androidaps.plugins.general.wear.wearintegration.DataLayerListenerServiceMobile
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 info.nightscout.shared.weardata.EventData
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
@ -32,10 +34,10 @@ class WearPlugin @Inject constructor(
rh: ResourceHelper,
private val aapsSchedulers: AapsSchedulers,
private val sp: SP,
private val ctx: Context,
private val fabricPrivacy: FabricPrivacy,
private val rxBus: RxBus,
private val actionStringHandler: Lazy<ActionStringHandler>
private val context: Context,
private val dataHandlerMobile: DataHandlerMobile
) : PluginBase(
PluginDescription()
@ -55,136 +57,43 @@ class WearPlugin @Inject constructor(
override fun onStart() {
super.onStart()
disposable += rxBus
.toObservable(EventOpenAPSUpdateGui::class.java)
.observeOn(aapsSchedulers.io)
.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 += rxBus
.toObservable(EventTempBasalChange::class.java)
.observeOn(aapsSchedulers.io)
.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 += rxBus
.toObservable(EventEffectiveProfileSwitchChanged::class.java)
.observeOn(aapsSchedulers.io)
.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 += 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 += rxBus
.toObservable(EventLoopUpdateGui::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({
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_BOLUS_PROGRESS)
intent.putExtra("progresspercent", 0)
intent.putExtra("progressstatus", status)
ctx.startService(intent)
}, fabricPrivacy::logException)
context.startService(Intent(context, DataLayerListenerServiceMobile::class.java))
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)
event.result?.let {
val status =
if (it.success) rh.gs(R.string.success)
else rh.gs(R.string.nosuccess)
if (isEnabled()) rxBus.send(EventMobileToWear(EventData.BolusProgress(percent = 100, status = status)))
}
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_BOLUS_PROGRESS)
intent.putExtra("progresspercent", event.percent)
intent.putExtra("progressstatus", event.status)
ctx.startService(intent)
if (isEnabled()) rxBus.send(EventMobileToWear(EventData.BolusProgress(percent = event.percent, status = event.status)))
}
}, fabricPrivacy::logException)
actionStringHandler.get().setup()
disposable += rxBus
.toObservable(EventPreferenceChange::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ dataHandlerMobile.resendData("EventPreferenceChange") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventAutosensCalculationFinished::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ dataHandlerMobile.resendData("EventAutosensCalculationFinished") }, fabricPrivacy::logException)
disposable += rxBus
.toObservable(EventLoopUpdateGui::class.java)
.observeOn(aapsSchedulers.io)
.subscribe({ dataHandlerMobile.resendData("EventLoopUpdateGui") }, fabricPrivacy::logException)
}
override fun onStop() {
disposable.clear()
super.onStop()
actionStringHandler.get().tearDown()
}
private fun sendDataToWatch(status: Boolean, basals: Boolean, bgValue: Boolean) {
// 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() {
ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_RESEND))
}
fun openSettings() {
ctx.startService(Intent(ctx, WatchUpdaterService::class.java).setAction(WatchUpdaterService.ACTION_OPEN_SETTINGS))
}
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) {
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) {
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)
})
context.stopService(Intent(context, DataLayerListenerServiceMobile::class.java))
}
}

View file

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

View file

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

View file

@ -0,0 +1,200 @@
package info.nightscout.androidaps.plugins.general.wear.wearintegration
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.R
import info.nightscout.androidaps.database.AppRepository
import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.interfaces.ActivePlugin
import info.nightscout.androidaps.interfaces.Config
import info.nightscout.androidaps.interfaces.IobCobCalculator
import info.nightscout.androidaps.interfaces.Loop
import info.nightscout.androidaps.interfaces.ProfileFunction
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.general.nsclient.data.NSDeviceStatus
import info.nightscout.androidaps.plugins.general.wear.WearPlugin
import info.nightscout.androidaps.plugins.general.wear.events.EventWearUpdateGui
import info.nightscout.androidaps.receivers.ReceiverStatusStore
import info.nightscout.androidaps.utils.DefaultValueHelper
import info.nightscout.androidaps.utils.resources.ResourceHelper
import info.nightscout.androidaps.utils.rx.AapsSchedulers
import info.nightscout.androidaps.utils.wizard.QuickWizard
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.EventData
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 DataLayerListenerServiceMobile : 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 activePlugin: ActivePlugin
@Inject lateinit var rxBus: RxBus
@Inject lateinit var aapsSchedulers: AapsSchedulers
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()
private val rxPath get() = getString(R.string.path_rx_bridge)
override fun onCreate() {
AndroidInjection.inject(this)
super.onCreate()
aapsLogger.debug(LTag.WEAR, "onCreate")
handler.post { updateTranscriptionCapability() }
disposable += rxBus
.toObservable(EventMobileToWear::class.java)
.observeOn(aapsSchedulers.io)
.subscribe { sendMessage(rxPath, it.payload.serialize()) }
}
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()
}
@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)
if (wearPlugin.isEnabled()) {
when (messageEvent.path) {
rxPath -> {
aapsLogger.debug(LTag.WEAR, "onMessageReceived: ${String(messageEvent.data)}")
val command = EventData.deserialize(String(messageEvent.data))
rxBus.send(command.also { it.sourceNodeId = 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")
rxBus.send(EventMobileToWear(EventData.ActionPing(System.currentTimeMillis())))
rxBus.send(EventData.ActionResendData("WatchUpdaterService"))
}
// Find a nearby node or pick one arbitrarily
private fun pickBestNodeId(nodes: Set<Node>): Node? =
nodes.firstOrNull { it.isNearby } ?: nodes.firstOrNull()
@Suppress("unused")
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: String?) {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path $data")
transcriptionNodeId?.also { nodeId ->
messageClient
.sendMessage(nodeId, path, data?.toByteArray() ?: byteArrayOf()).apply {
addOnSuccessListener { }
addOnFailureListener {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path failure")
}
}
}
}
@Suppress("unused")
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")
}
}
}
}
companion object {
const val WEAR_CAPABILITY = "androidaps_wear"
}
}

View file

@ -1,633 +0,0 @@
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,778 +0,0 @@
package info.nightscout.androidaps.plugins.general.wear.wearintegration;
/*
public class WatchUpdaterService1 extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
@Inject public GlucoseStatusProvider glucoseStatusProvider;
@Inject public AAPSLogger aapsLogger;
@Inject public WearPlugin wearPlugin;
@Inject public ResourceHelper rh;
@Inject public SP sp;
@Inject public RxBus rxBus;
@Inject public ProfileFunction profileFunction;
@Inject public DefaultValueHelper defaultValueHelper;
@Inject public NSDeviceStatus nsDeviceStatus;
@Inject public ActivePlugin activePlugin;
@Inject public Loop loop;
@Inject public IobCobCalculator iobCobCalculator;
@Inject public AppRepository repository;
@Inject ReceiverStatusStore receiverStatusStore;
@Inject Config config;
@Inject public TrendCalculator trendCalculator;
@Inject public QuickWizard quickWizard;
public static final String ACTION_RESEND = WatchUpdaterService.class.getName().concat(".Resend");
public static final String ACTION_OPEN_SETTINGS = WatchUpdaterService.class.getName().concat(".OpenSettings");
public static final String ACTION_SEND_STATUS = WatchUpdaterService.class.getName().concat(".SendStatus");
public static final String ACTION_SEND_BASALS = WatchUpdaterService.class.getName().concat(".SendBasals");
public static final String ACTION_SEND_BOLUSPROGRESS = WatchUpdaterService.class.getName().concat(".BolusProgress");
public static final String ACTION_SEND_ACTIONCONFIRMATIONREQUEST = WatchUpdaterService.class.getName().concat(".ActionConfirmationRequest");
public static final String ACTION_SEND_CHANGECONFIRMATIONREQUEST = WatchUpdaterService.class.getName().concat(".ChangeConfirmationRequest");
public static final String ACTION_CANCEL_NOTIFICATION = WatchUpdaterService.class.getName().concat(".CancelNotification");
private GoogleApiClient googleApiClient;
String TAG = "WatchUpdateService";
private static boolean lastLoopStatus;
private Handler handler;
// Phone
private static final String CAPABILITY_PHONE_APP = "phone_app_sync_bgs";
private static final String MESSAGE_PATH_PHONE = "/phone_message_path";
// Wear
private static final String CAPABILITY_WEAR_APP = "wear_app_sync_bgs";
private static final String MESSAGE_PATH_WEAR = "/wear_message_path";
@Override
public void onCreate() {
AndroidInjection.inject(this);
if (wearIntegration()) {
googleApiConnect();
}
if (handler == null) {
HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName() + "Handler");
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
}
}
private boolean wearIntegration() {
return wearPlugin.isEnabled();
}
private void googleApiConnect() {
if (googleApiClient != null && (googleApiClient.isConnected() || googleApiClient.isConnecting())) {
googleApiClient.disconnect();
}
googleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).addApi(Wearable.API).build();
Wearable.MessageApi.addListener(googleApiClient, this);
if (googleApiClient.isConnected()) {
aapsLogger.debug(LTag.WEAR, "API client is connected");
} else {
// Log.d("WatchUpdater", logPrefix + "API client is not connected and is trying to connect");
googleApiClient.connect();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String action = intent != null ? intent.getAction() : null;
// Log.d(TAG, "onStartCommand: " + action);
if (wearIntegration()) {
handler.post(() -> {
if (googleApiClient != null && googleApiClient.isConnected()) {
if (ACTION_RESEND.equals(action)) {
resendData();
} else if (ACTION_OPEN_SETTINGS.equals(action)) {
sendNotification();
} else if (ACTION_SEND_STATUS.equals(action)) {
sendStatus();
} else if (ACTION_SEND_BASALS.equals(action)) {
sendBasals();
} else if (ACTION_SEND_BOLUSPROGRESS.equals(action)) {
sendBolusProgress(intent.getIntExtra("progresspercent", 0), intent.hasExtra("progressstatus") ? intent.getStringExtra("progressstatus") : "");
} else if (ACTION_SEND_ACTIONCONFIRMATIONREQUEST.equals(action)) {
String title = intent.getStringExtra("title");
String message = intent.getStringExtra("message");
String actionstring = intent.getStringExtra("actionstring");
sendActionConfirmationRequest(title, message, actionstring);
} else if (ACTION_SEND_CHANGECONFIRMATIONREQUEST.equals(action)) {
String title = intent.getStringExtra("title");
String message = intent.getStringExtra("message");
String actionstring = intent.getStringExtra("actionstring");
sendChangeConfirmationRequest(title, message, actionstring);
} else if (ACTION_CANCEL_NOTIFICATION.equals(action)) {
String actionstring = intent.getStringExtra("actionstring");
sendCancelNotificationRequest(actionstring);
} else {
sendData();
}
} else {
if (googleApiClient != null) googleApiClient.connect();
}
});
}
return START_STICKY;
}
private void updateWearSyncBgsCapability(CapabilityInfo capabilityInfo) {
Log.d("WatchUpdaterService", "CabilityInfo: " + capabilityInfo);
Set<Node> connectedNodes = capabilityInfo.getNodes();
String mWearNodeId = pickBestNodeId(connectedNodes);
}
private String pickBestNodeId(Set<Node> nodes) {
String bestNodeId = null;
// Find a nearby node or pick one arbitrarily
for (Node node : nodes) {
if (node.isNearby()) {
return node.getId();
}
bestNodeId = node.getId();
}
return bestNodeId;
}
@Override
public void onConnected(Bundle connectionHint) {
CapabilityApi.CapabilityListener capabilityListener = capabilityInfo -> {
updateWearSyncBgsCapability(capabilityInfo);
// Log.d(TAG, logPrefix + "onConnected onCapabilityChanged mWearNodeID:" + mWearNodeId);
// new CheckWearableConnected().execute();
};
Wearable.CapabilityApi.addCapabilityListener(googleApiClient, capabilityListener, CAPABILITY_WEAR_APP);
sendData();
}
@Override
public void onPeerConnected(com.google.android.gms.wearable.Node peer) {// KS
super.onPeerConnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
Log.d(TAG, "onPeerConnected peer name & ID: " + name + "|" + id);
}
@Override
public void onPeerDisconnected(com.google.android.gms.wearable.Node peer) {// KS
super.onPeerDisconnected(peer);
String id = peer.getId();
String name = peer.getDisplayName();
Log.d(TAG, "onPeerDisconnected peer name & ID: " + name + "|" + id);
}
@Override
public void onMessageReceived(MessageEvent event) {
// Log.d(TAG, "onMessageRecieved: " + event);
if (wearIntegration()) {
if (event != null && event.getPath().equals(WearUris.WEARABLE_RESEND_PATH)) {
resendData();
}
if (event != null && event.getPath().equals(WearUris.WEARABLE_CANCELBOLUS_PATH)) {
cancelBolus();
}
if (event != null && event.getPath().equals(WearUris.WEARABLE_INITIATE_ACTIONSTRING_PATH)) {
String actionstring = new String(event.getData());
aapsLogger.debug(LTag.WEAR, "Wear: " + actionstring);
rxBus.send(new EventWearInitiateAction(actionstring));
}
if (event != null && event.getPath().equals(WearUris.WEARABLE_CONFIRM_ACTIONSTRING_PATH)) {
String actionstring = new String(event.getData());
aapsLogger.debug(LTag.WEAR, "Wear Confirm: " + actionstring);
rxBus.send(new EventWearConfirmAction(actionstring));
}
}
}
private void cancelBolus() {
activePlugin.getActivePump().stopBolusDelivering();
}
private void sendData() {
GlucoseValue lastBG = iobCobCalculator.getAds().lastBg();
// Log.d(TAG, "LastBg=" + lastBG);
if (lastBG != null) {
GlucoseStatus glucoseStatus = glucoseStatusProvider.getGlucoseStatusData();
if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
googleApiConnect();
}
if (wearIntegration()) {
final DataMap dataMap = dataMapSingleBG(lastBG, glucoseStatus);
(new SendToDataLayerThread(WearUris.WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataMap);
}
}
}
private DataMap dataMapSingleBG(GlucoseValue lastBG, GlucoseStatus glucoseStatus) {
GlucoseUnit units = profileFunction.getUnits();
double convert2MGDL = 1.0;
if (units.equals(GlucoseUnit.MMOL))
convert2MGDL = Constants.MMOLL_TO_MGDL;
double lowLine = defaultValueHelper.determineLowLine() * convert2MGDL;
double highLine = defaultValueHelper.determineHighLine() * convert2MGDL;
long sgvLevel = 0L;
if (lastBG.getValue() > highLine) {
sgvLevel = 1;
} else if (lastBG.getValue() < lowLine) {
sgvLevel = -1;
}
DataMap dataMap = new DataMap();
dataMap.putString("sgvString", GlucoseValueExtensionKt.valueToUnitsString(lastBG, units));
dataMap.putString("glucoseUnits", units.getAsText());
dataMap.putLong("timestamp", lastBG.getTimestamp());
if (glucoseStatus == null) {
dataMap.putString("slopeArrow", "");
dataMap.putString("delta", "--");
dataMap.putString("avgDelta", "--");
} else {
dataMap.putString("slopeArrow", trendCalculator.getTrendArrow(lastBG).getSymbol());
dataMap.putString("delta", deltastring(glucoseStatus.getDelta(), glucoseStatus.getDelta() * Constants.MGDL_TO_MMOLL, units));
dataMap.putString("avgDelta", deltastring(glucoseStatus.getShortAvgDelta(), glucoseStatus.getShortAvgDelta() * Constants.MGDL_TO_MMOLL, units));
}
dataMap.putLong("sgvLevel", sgvLevel);
dataMap.putDouble("sgvDouble", lastBG.getValue());
dataMap.putDouble("high", highLine);
dataMap.putDouble("low", lowLine);
return dataMap;
}
private String deltastring(double deltaMGDL, double deltaMMOL, GlucoseUnit units) {
String deltastring = "";
if (deltaMGDL >= 0) {
deltastring += "+";
} else {
deltastring += "-";
}
boolean detailed = sp.getBoolean(R.string.key_wear_detailed_delta, false);
if (units.equals(GlucoseUnit.MGDL)) {
if (detailed) {
deltastring += DecimalFormatter.INSTANCE.to1Decimal(Math.abs(deltaMGDL));
} else {
deltastring += DecimalFormatter.INSTANCE.to0Decimal(Math.abs(deltaMGDL));
}
} else {
if (detailed) {
deltastring += DecimalFormatter.INSTANCE.to2Decimal(Math.abs(deltaMMOL));
} else {
deltastring += DecimalFormatter.INSTANCE.to1Decimal(Math.abs(deltaMMOL));
}
}
return deltastring;
}
private void resendData() {
if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
googleApiConnect();
}
sendPreferences();
sendQuickWizard();
long startTime = System.currentTimeMillis() - (long) (60000 * 60 * 5.5);
GlucoseValue last_bg = iobCobCalculator.getAds().lastBg();
if (last_bg == null) return;
List<GlucoseValue> graph_bgs = repository.compatGetBgReadingsDataFromTime(startTime, true).blockingGet();
GlucoseStatus glucoseStatus = glucoseStatusProvider.getGlucoseStatusData(true);
if (!graph_bgs.isEmpty()) {
DataMap entries = dataMapSingleBG(last_bg, glucoseStatus);
final ArrayList<DataMap> dataMaps = new ArrayList<>(graph_bgs.size());
for (GlucoseValue bg : graph_bgs) {
DataMap dataMap = dataMapSingleBG(bg, glucoseStatus);
dataMaps.add(dataMap);
}
entries.putDataMapArrayList("entries", dataMaps);
(new SendToDataLayerThread(WearUris.WEARABLE_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, entries);
}
sendBasals();
sendStatus();
}
private void sendBasals() {
if (googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting()) {
googleApiConnect();
}
long now = System.currentTimeMillis();
final long startTimeWindow = now - (long) (60000 * 60 * 5.5);
ArrayList<DataMap> basals = new ArrayList<>();
ArrayList<DataMap> temps = new ArrayList<>();
ArrayList<DataMap> boluses = new ArrayList<>();
ArrayList<DataMap> predictions = new ArrayList<>();
Profile profile = profileFunction.getProfile();
if (profile == null) {
return;
}
long beginBasalSegmentTime = startTimeWindow;
long runningTime = startTimeWindow;
double beginBasalValue = profile.getBasal(beginBasalSegmentTime);
double endBasalValue = beginBasalValue;
TemporaryBasal tb1 = iobCobCalculator.getTempBasalIncludingConvertedExtended(runningTime);
TemporaryBasal tb2;
double tb_before = beginBasalValue;
double tb_amount = beginBasalValue;
long tb_start = runningTime;
if (tb1 != null) {
Profile profileTB = profileFunction.getProfile(runningTime);
if (profileTB != null) {
tb_amount = TemporaryBasalExtensionKt.convertedToAbsolute(tb1, runningTime, profileTB);
tb_start = runningTime;
}
}
for (; runningTime < now; runningTime += 5 * 60 * 1000) {
Profile profileTB = profileFunction.getProfile(runningTime);
if (profileTB == null)
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(tempDatamap(tb_start, tb_before, runningTime, endBasalValue, tb_amount));
tb1 = null;
} else if (tb1 == null && tb2 != null) {
//temp begins
tb1 = tb2;
tb_start = runningTime;
tb_before = endBasalValue;
tb_amount = TemporaryBasalExtensionKt.convertedToAbsolute(tb1, runningTime, profileTB);
} else if (tb1 != null && tb2 != null) {
double currentAmount = TemporaryBasalExtensionKt.convertedToAbsolute(tb2, runningTime, profileTB);
if (currentAmount != tb_amount) {
temps.add(tempDatamap(tb_start, tb_before, runningTime, currentAmount, tb_amount));
tb_start = runningTime;
tb_before = tb_amount;
tb_amount = currentAmount;
tb1 = tb2;
}
}
}
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(tempDatamap(tb_start, tb_before, now - 60 * 1000, endBasalValue, tb_amount));
} else {
//express currently running temp by painting it a bit into the future
Profile profileNow = profileFunction.getProfile(now);
double currentAmount = TemporaryBasalExtensionKt.convertedToAbsolute(tb2, now, profileNow);
if (currentAmount != tb_amount) {
temps.add(tempDatamap(tb_start, tb_before, now, tb_amount, tb_amount));
temps.add(tempDatamap(now, tb_amount, runningTime + 5 * 60 * 1000, currentAmount, currentAmount));
} else {
temps.add(tempDatamap(tb_start, tb_before, runningTime + 5 * 60 * 1000, tb_amount, tb_amount));
}
}
} else {
tb2 = iobCobCalculator.getTempBasalIncludingConvertedExtended(now); //use "now" to express current situation
if (tb2 != null) {
//onset at the end
Profile profileTB = profileFunction.getProfile(runningTime);
double currentAmount = TemporaryBasalExtensionKt.convertedToAbsolute(tb2, runningTime, profileTB);
temps.add(tempDatamap(now - 60 * 1000, endBasalValue, runningTime + 5 * 60 * 1000, currentAmount, currentAmount));
}
}
repository.getBolusesIncludingInvalidFromTime(startTimeWindow, true).blockingGet()
.stream()
.filter(bolus -> bolus.getType() != Bolus.Type.PRIMING)
.forEach(bolus -> boluses.add(treatmentMap(bolus.getTimestamp(), bolus.getAmount(), 0, bolus.getType() == Bolus.Type.SMB, bolus.isValid())));
repository.getCarbsDataFromTimeExpanded(startTimeWindow, true).blockingGet()
.forEach(carb -> boluses.add(treatmentMap(carb.getTimestamp(), 0, carb.getAmount(), false, carb.isValid())));
final LoopPlugin.LastRun finalLastRun = loop.getLastRun();
if (sp.getBoolean("wear_predictions", true) && finalLastRun != null && finalLastRun.getRequest().getHasPredictions() && finalLastRun.getConstraintsProcessed() != null) {
List<GlucoseValueDataPoint> predArray =
finalLastRun.getConstraintsProcessed().getPredictions()
.stream().map(bg -> new GlucoseValueDataPoint(bg, defaultValueHelper, profileFunction, rh))
.collect(Collectors.toList());
if (!predArray.isEmpty()) {
for (GlucoseValueDataPoint bg : predArray) {
if (bg.getData().getValue() < 40) continue;
predictions.add(predictionMap(bg.getData().getTimestamp(),
bg.getData().getValue(), bg.color(null)));
}
}
}
DataMap dm = new DataMap();
dm.putDataMapArrayList("basals", basals);
dm.putDataMapArrayList("temps", temps);
dm.putDataMapArrayList("boluses", boluses);
dm.putDataMapArrayList("predictions", predictions);
(new SendToDataLayerThread(WearUris.BASAL_DATA_PATH, googleApiClient)).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dm);
}
private DataMap tempDatamap(long startTime, double startBasal, long to, double toBasal, double amount) {
DataMap dm = new DataMap();
dm.putLong("starttime", startTime);
dm.putDouble("startBasal", startBasal);
dm.putLong("endtime", to);
dm.putDouble("endbasal", toBasal);
dm.putDouble("amount", amount);
return dm;
}
private DataMap basalMap(long startTime, long endTime, double amount) {
DataMap dm = new DataMap();
dm.putLong("starttime", startTime);
dm.putLong("endtime", endTime);
dm.putDouble("amount", amount);
return dm;
}
private DataMap treatmentMap(long date, double bolus, double carbs, boolean isSMB, boolean isValid) {
DataMap dm = new DataMap();
dm.putLong("date", date);
dm.putDouble("bolus", bolus);
dm.putDouble("carbs", carbs);
dm.putBoolean("isSMB", isSMB);
dm.putBoolean("isValid", isValid);
return dm;
}
private DataMap predictionMap(long timestamp, double sgv, int color) {
DataMap dm = new DataMap();
dm.putLong("timestamp", timestamp);
dm.putDouble("sgv", sgv);
dm.putInt("color", color);
return dm;
}
private void sendNotification() {
if (googleApiClient != null && googleApiClient.isConnected()) {
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.OPEN_SETTINGS_PATH);
//unique content
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putString("openSettings", "openSettings");
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("OpenSettings", "No connection to wearable available!");
}
}
private void sendBolusProgress(int progresspercent, String status) {
if (googleApiClient != null && googleApiClient.isConnected()) {
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.BOLUS_PROGRESS_PATH);
//unique content
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putString("bolusProgress", "bolusProgress");
dataMapRequest.getDataMap().putString("progressstatus", status);
dataMapRequest.getDataMap().putInt("progresspercent", progresspercent);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("BolusProgress", "No connection to wearable available!");
}
}
private void sendActionConfirmationRequest(String title, String message, String actionstring) {
if (googleApiClient != null && googleApiClient.isConnected()) {
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.ACTION_CONFIRMATION_REQUEST_PATH);
//unique content
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putString("actionConfirmationRequest", "actionConfirmationRequest");
dataMapRequest.getDataMap().putString("title", title);
dataMapRequest.getDataMap().putString("message", message);
dataMapRequest.getDataMap().putString("actionstring", actionstring);
aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("confirmationRequest", "No connection to wearable available!");
}
}
private void sendChangeConfirmationRequest(String title, String message, String actionstring) {
if (googleApiClient != null && googleApiClient.isConnected()) {
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.ACTION_CHANGECONFIRMATION_REQUEST_PATH);
//unique content
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putString("changeConfirmationRequest", "changeConfirmationRequest");
dataMapRequest.getDataMap().putString("title", title);
dataMapRequest.getDataMap().putString("message", message);
dataMapRequest.getDataMap().putString("actionstring", actionstring);
aapsLogger.debug(LTag.WEAR, "Requesting confirmation from wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("changeConfirmRequest", "No connection to wearable available!");
}
}
private void sendCancelNotificationRequest(String actionstring) {
if (googleApiClient != null && googleApiClient.isConnected()) {
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.ACTION_CANCELNOTIFICATION_REQUEST_PATH);
//unique content
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putString("cancelNotificationRequest", "cancelNotificationRequest");
dataMapRequest.getDataMap().putString("actionstring", actionstring);
aapsLogger.debug(LTag.WEAR, "Canceling notification on wear: " + actionstring);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("cancelNotificationReq", "No connection to wearable available!");
}
}
private void sendStatus() {
if (googleApiClient != null && googleApiClient.isConnected()) {
Profile profile = profileFunction.getProfile();
String status = rh.gs(R.string.noprofile);
String iobSum, iobDetail, cobString, currentBasal, bgiString;
iobSum = iobDetail = cobString = currentBasal = bgiString = "";
if (profile != null) {
IobTotal bolusIob = iobCobCalculator.calculateIobFromBolus().round();
IobTotal basalIob = iobCobCalculator.calculateIobFromTempBasalsIncludingConvertedExtended().round();
iobSum = DecimalFormatter.INSTANCE.to2Decimal(bolusIob.getIob() + basalIob.getBasaliob());
iobDetail =
"(" + DecimalFormatter.INSTANCE.to2Decimal(bolusIob.getIob()) + "|" + DecimalFormatter.INSTANCE.to2Decimal(basalIob.getBasaliob()) + ")";
cobString = iobCobCalculator.getCobInfo(false, "WatcherUpdaterService").generateCOBString();
currentBasal = generateBasalString();
//bgi
double bgi =
-(bolusIob.getActivity() + basalIob.getActivity()) * 5 * Profile.Companion.fromMgdlToUnits(profile.getIsfMgdl(), profileFunction.getUnits());
bgiString = "" + ((bgi >= 0) ? "+" : "") + DecimalFormatter.INSTANCE.to1Decimal(bgi);
status = generateStatusString(profile, currentBasal, iobSum, iobDetail, bgiString);
}
//batteries
int phoneBattery = receiverStatusStore.getBatteryLevel();
String rigBattery = nsDeviceStatus.getUploaderStatus().trim();
long openApsStatus;
//OpenAPS status
if (config.getAPS()) {
//we are AndroidAPS
openApsStatus = loop.getLastRun() != null && loop.getLastRun().getLastTBREnact() != 0 ? loop.getLastRun().getLastTBREnact() : -1;
} else {
//NSClient or remote
openApsStatus = nsDeviceStatus.getOpenApsTimestamp();
}
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.NEW_STATUS_PATH);
//unique content
dataMapRequest.getDataMap().putString("externalStatusString", status);
dataMapRequest.getDataMap().putString("iobSum", iobSum);
dataMapRequest.getDataMap().putString("iobDetail", iobDetail);
dataMapRequest.getDataMap().putBoolean("detailedIob", sp.getBoolean(R.string.key_wear_detailediob, false));
dataMapRequest.getDataMap().putString("cob", cobString);
dataMapRequest.getDataMap().putString("currentBasal", currentBasal);
dataMapRequest.getDataMap().putString("battery", "" + phoneBattery);
dataMapRequest.getDataMap().putString("rigBattery", rigBattery);
dataMapRequest.getDataMap().putLong("openApsStatus", openApsStatus);
dataMapRequest.getDataMap().putString("bgi", bgiString);
dataMapRequest.getDataMap().putBoolean("showBgi", sp.getBoolean(R.string.key_wear_showbgi, false));
dataMapRequest.getDataMap().putInt("batteryLevel", (phoneBattery >= 30) ? 1 : 0);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("SendStatus", "No connection to wearable available!");
}
}
private void sendPreferences() {
if (googleApiClient != null && googleApiClient.isConnected()) {
GlucoseUnit units = profileFunction.getUnits();
boolean wearcontrol = sp.getBoolean(R.string.key_wear_control, false);
boolean mgdl = units.equals(GlucoseUnit.MGDL);
int percentage = sp.getInt(R.string.key_boluswizard_percentage, 100);
int maxCarbs = sp.getInt(R.string.key_treatmentssafety_maxcarbs, 48);
double maxBolus = sp.getDouble(R.string.key_treatmentssafety_maxbolus, 3.0);
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.NEW_PREFERENCES_PATH);
//unique content
dataMapRequest.getDataMap().putLong("timestamp", System.currentTimeMillis());
dataMapRequest.getDataMap().putBoolean(rh.gs(R.string.key_wear_control), wearcontrol);
dataMapRequest.getDataMap().putBoolean(rh.gs(R.string.key_units_mgdl), mgdl);
dataMapRequest.getDataMap().putInt(rh.gs(R.string.key_boluswizard_percentage), percentage);
dataMapRequest.getDataMap().putInt(rh.gs(R.string.key_treatmentssafety_maxcarbs), maxCarbs);
dataMapRequest.getDataMap().putDouble(rh.gs(R.string.key_treatmentssafety_maxbolus), maxBolus);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("SendPreferences", "No connection to wearable available!");
}
}
private void sendQuickWizard() {
if (googleApiClient != null && googleApiClient.isConnected()) {
int size = quickWizard.size();
ArrayList<DataMap> entities = new ArrayList<>();
for (int i = 0; i < size; i++) {
QuickWizardEntry q = quickWizard.get(i);
if (q.forDevice(QuickWizardEntry.DEVICE_WATCH)) {
entities.add(quickMap(q));
}
}
PutDataMapRequest dataMapRequest = PutDataMapRequest.create(WearUris.QUICK_WIZARD_PATH);
DataMap dm = dataMapRequest.getDataMap();
dm.putLong("timestamp", System.currentTimeMillis());
dm.putDataMapArrayList("quick_wizard", entities);
PutDataRequest putDataRequest = dataMapRequest.asPutDataRequest();
Log.i(TAG, "sendQuickWizard: " + putDataRequest);
Wearable.DataApi.putDataItem(googleApiClient, putDataRequest);
} else {
Log.e("sendQuickWizard", "No connection to wearable available!");
}
}
private DataMap quickMap(QuickWizardEntry q) {
DataMap dm = new DataMap();
dm.putString("guid", q.guid());
dm.putString("button_text", q.buttonText());
dm.putInt("carbs", q.carbs());
dm.putInt("from", q.validFrom());
dm.putInt("to", q.validTo());
return dm;
}
@NonNull
private String generateStatusString(Profile profile, String currentBasal, String iobSum, String iobDetail, String bgiString) {
String status = "";
if (profile == null) {
status = rh.gs(R.string.noprofile);
return status;
}
if (!((PluginBase) loop).isEnabled()) {
status += rh.gs(R.string.disabledloop) + "\n";
lastLoopStatus = false;
} else {
lastLoopStatus = true;
}
String iobString;
if (sp.getBoolean(R.string.key_wear_detailediob, false)) {
iobString = iobSum + " " + iobDetail;
} else {
iobString = iobSum + "U";
}
status += currentBasal + " " + iobString;
//add BGI if shown, otherwise return
if (sp.getBoolean(R.string.key_wear_showbgi, false)) {
status += " " + bgiString;
}
return status;
}
@NonNull
private String generateBasalString() {
String basalStringResult;
Profile profile = profileFunction.getProfile();
if (profile == null)
return "";
TemporaryBasal activeTemp = iobCobCalculator.getTempBasalIncludingConvertedExtended(System.currentTimeMillis());
if (activeTemp != null) {
basalStringResult = TemporaryBasalExtensionKt.toStringShort(activeTemp);
} else {
basalStringResult = DecimalFormatter.INSTANCE.to2Decimal(profile.getBasal()) + "U/h";
}
return basalStringResult;
}
@Override
public void onDestroy() {
if (googleApiClient != null && googleApiClient.isConnected()) {
googleApiClient.disconnect();
}
}
@Override
public void onConnectionSuspended(int cause) {
}
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
}
public static boolean shouldReportLoopStatus(boolean enabled) {
return (lastLoopStatus != enabled);
}
}
*/

View file

@ -19,12 +19,10 @@ import info.nightscout.androidaps.database.entities.EffectiveProfileSwitch
import info.nightscout.androidaps.database.entities.ProfileSwitch
import info.nightscout.androidaps.database.interfaces.end
import info.nightscout.androidaps.dialogs.BolusProgressDialog
import info.nightscout.androidaps.events.EventBolusRequested
import info.nightscout.androidaps.events.EventMobileToWear
import info.nightscout.androidaps.events.EventProfileSwitchChanged
import info.nightscout.androidaps.extensions.getCustomizedName
import info.nightscout.androidaps.interfaces.*
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker
import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning
@ -40,7 +38,10 @@ import info.nightscout.androidaps.utils.HtmlHelper
import info.nightscout.androidaps.utils.buildHelper.BuildHelper
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 info.nightscout.shared.weardata.EventData
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.kotlin.subscribeBy
@ -297,7 +298,7 @@ class CommandQueueImplementation @Inject constructor(
// not when the Bolus command is starting. The command closes the dialog upon completion).
showBolusProgressDialog(detailedBolusInfo)
// Notify Wear about upcoming bolus
rxBus.send(EventBolusRequested(detailedBolusInfo.insulin))
rxBus.send(EventMobileToWear(EventData.BolusProgress(percent = 0, status = rh.gs(R.string.bolusrequested, detailedBolusInfo.insulin))))
}
}
notifyAboutNewCommand()

View file

@ -65,10 +65,14 @@ class BolusWizard @Inject constructor(
private val disposable = CompositeDisposable()
var timeStamp : Long
init {
injector.androidInjector().inject(this)
timeStamp = dateUtil.now()
}
// Intermediate
var sens = 0.0
private set
@ -236,7 +240,7 @@ class BolusWizard @Inject constructor(
// Total
calculatedTotalInsulin = insulinFromBG + insulinFromTrend + insulinFromCarbs + insulinFromBolusIOB + insulinFromBasalIOB + insulinFromCorrection + insulinFromSuperBolus + insulinFromCOB
var percentage = if (usePercentage) totalPercentage else percentageCorrection.toDouble()
val percentage = if (usePercentage) totalPercentage else percentageCorrection.toDouble()
// Percentage adjustment
totalBeforePercentageAdjustment = calculatedTotalInsulin

View file

@ -9,6 +9,7 @@ import org.json.JSONObject
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.collections.ArrayList
@Singleton
class QuickWizard @Inject constructor(
@ -55,6 +56,11 @@ class QuickWizard @Inject constructor(
operator fun get(position: Int): QuickWizardEntry =
QuickWizardEntry(injector).from(storage.get(position) as JSONObject, position)
fun list(): ArrayList<QuickWizardEntry> =
ArrayList<QuickWizardEntry>().also {
for (i in 0 until size()) it.add(get(i))
}
fun get(guid: String): QuickWizardEntry? {
for (i in 0 until storage.length()) {
val entry = QuickWizardEntry(injector).from(storage.get(i) as JSONObject, i)

View file

@ -1212,4 +1212,5 @@
<string name="show_hide_records">Hide loop records</string>
<string name="widget_description">AndroidAPS widget</string>
<string name="configure">Configure opacity</string>
<string name="loop_status">Loop status</string>
</resources>

View file

@ -4,9 +4,9 @@ import android.content.Context
import android.os.PowerManager
import dagger.android.AndroidInjector
import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.R
import info.nightscout.androidaps.TestBaseWithProfile
import info.nightscout.androidaps.TestPumpPlugin
import info.nightscout.androidaps.core.R
import info.nightscout.androidaps.data.DetailedBolusInfo
import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.database.AppRepository
@ -138,6 +138,7 @@ class CommandQueueImplementationTest : TestBaseWithProfile() {
`when`(constraintChecker.applyBasalPercentConstraints(anyObject(), anyObject())).thenReturn(percentageConstraint)
`when`(rh.gs(R.string.connectiontimedout)).thenReturn("Connection timed out")
`when`(rh.gs(R.string.formatinsulinunits)).thenReturn("%1\$.2f U")
`when`(rh.gs(R.string.bolusrequested)).thenReturn("Going to deliver %1\$.2f U")
}
@Test

View file

@ -48,6 +48,7 @@ class BolusWizardTest : TestBase() {
it.activePlugin = activePlugin
it.commandQueue = commandQueue
it.loop = loop
it.dateUtil = dateUtil
it.iobCobCalculator = iobCobCalculator
it.glucoseStatusProvider = GlucoseStatusProvider(aapsLogger = aapsLogger, iobCobCalculator = iobCobCalculator, dateUtil = dateUtil)
}

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.events
import info.nightscout.shared.weardata.EventData
class EventMobileToWear(val payload: EventData) : Event()

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.events
import info.nightscout.shared.weardata.EventData
class EventWearToMobile(val payload: EventData) : Event()

View file

@ -1,7 +0,0 @@
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

@ -1,7 +0,0 @@
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

@ -1,7 +0,0 @@
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

@ -1,33 +0,0 @@
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,237 @@
package info.nightscout.shared.weardata
import info.nightscout.androidaps.events.Event
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.util.*
@Serializable
sealed class EventData : Event() {
var sourceNodeId = ""
fun serialize() = Json.encodeToString(serializer(), this)
companion object {
fun deserialize(json: String) = Json.decodeFromString(serializer(), json)
}
// Mobile <- Wear
@Serializable
data class ActionPong(val timeStamp: Long) : EventData()
@Serializable
data class Error(val timeStamp: Long) : EventData() // ignored
@Serializable
data class CancelBolus(val timeStamp: Long) : EventData()
@Serializable
data class ActionResendData(val from: String) : EventData()
@Serializable
data class ActionPumpStatus(val timeStamp: Long) : EventData()
@Serializable
data class ActionLoopStatus(val timeStamp: Long) : EventData()
@Serializable
data class ActionTddStatus(val timeStamp: Long) : EventData()
@Serializable
data class ActionECarbsPreCheck(val carbs: Int, val carbsTimeShift: Int, val duration: Int) : EventData()
@Serializable
data class ActionBolusPreCheck(val insulin: Double, val carbs: Int) : EventData()
@Serializable
data class ActionFillPreCheck(val insulin: Double) : EventData()
@Serializable
data class ActionFillPresetPreCheck(val button: Int) : EventData()
@Serializable
data class ActionProfileSwitchSendInitialData(val timeStamp: Long) : EventData()
@Serializable
data class ActionProfileSwitchPreCheck(val timeShift: Int, val percentage: Int) : EventData()
@Serializable
data class ActionWizardPreCheck(val carbs: Int, val percentage: Int) : EventData()
@Serializable
data class ActionQuickWizardPreCheck(val guid: String) : EventData()
@Serializable
data class ActionTempTargetPreCheck(
val command: TempTargetCommand,
val isMgdl: Boolean = true, val duration: Int = 0, val low: Double = 0.0, val high: Double = 0.0 // manual
) : EventData() {
@Serializable
enum class TempTargetCommand {
PRESET_ACTIVITY, PRESET_HYPO, PRESET_EATING, CANCEL, MANUAL
}
}
// Mobile <- Wear return
@Serializable
data class ActionWizardConfirmed(val timeStamp: Long) : EventData()
@Serializable
data class ActionTempTargetConfirmed(val isMgdl: Boolean = true, val duration: Int = 0, val low: Double = 0.0, val high: Double = 0.0) : EventData()
@Serializable
data class ActionBolusConfirmed(val insulin: Double, val carbs: Int) : EventData()
@Serializable
data class ActionECarbsConfirmed(val carbs: Int, val carbsTime: Long, val duration: Int) : EventData()
@Serializable
data class ActionFillConfirmed(val insulin: Double) : EventData()
@Serializable
data class ActionProfileSwitchConfirmed(val timeShift: Int, val percentage: Int) : EventData()
@Serializable
data class OpenLoopRequestConfirmed(val timeStamp: Long) : EventData()
// Mobile -> Wear
@Serializable
data class CancelNotification(val timeStamp: Long) : EventData()
@Serializable
data class ActionPing(val timeStamp: Long) : EventData()
@Serializable
data class OpenSettings(val timeStamp: Long) : EventData()
@Serializable
data class BolusProgress(val percent: Int, val status: String) : EventData()
@Serializable
data class SingleBg @JvmOverloads constructor(
var timeStamp: Long,
val sgvString: String = "---",
val glucoseUnits: String = "-",
val slopeArrow: String = "--",
val delta: String = "--",
val avgDelta: String = "--",
val sgvLevel: Long = 0,
val sgv: Double,
val high: Double, // highLine
val low: Double, // lowLine
val color: Int = 0
) : EventData(), Comparable<SingleBg> {
override fun equals(other: Any?): Boolean =
when {
other !is SingleBg -> false
color != other.color -> false
else -> timeStamp == other.timeStamp
}
override fun hashCode(): Int {
return Objects.hash(timeStamp, color)
}
override fun compareTo(other: SingleBg): Int {
// reverse order endTime get latest first
if (this.timeStamp < other.timeStamp) return 1
return if (this.timeStamp > other.timeStamp) -1 else 0
}
}
@Serializable
data class GraphData(
val entries: ArrayList<SingleBg>
) : EventData()
@Serializable
data class TreatmentData(
val temps: ArrayList<TempBasal>,
val basals: ArrayList<Basal>,
val boluses: ArrayList<Treatment>,
val predictions: ArrayList<SingleBg>
) : EventData() {
@Serializable
data class TempBasal(
val startTime: Long,
val startBasal: Double,
val endTime: Long,
val endBasal: Double,
val amount: Double
)
@Serializable
data class Basal(
val startTime: Long,
val endTime: Long,
val amount: Double
)
@Serializable
data class Treatment(
val date: Long,
val bolus: Double,
val carbs: Double,
val isSMB: Boolean,
val isValid: Boolean,
)
}
@Serializable
data class Status(
val externalStatus: String,
val iobSum: String,
val iobDetail: String,
val detailedIob: Boolean,
val cob: String,
val currentBasal: String,
val battery: String,
val rigBattery: String,
val openApsStatus: Long,
val bgi: String,
val showBgi: Boolean,
val batteryLevel: Int
) : EventData()
@Serializable
data class Preferences(
val timeStamp: Long,
val wearControl: Boolean,
val unitsMgdl: Boolean,
val bolusPercentage: Int,
val maxCarbs: Int,
val maxBolus: Double
) : EventData()
@Serializable
data class QuickWizard(
val entries: ArrayList<QuickWizardEntry>
) : EventData() {
@Serializable
data class QuickWizardEntry(
val guid: String,
val buttonText: String,
val carbs: Int,
val validFrom: Int,
val validTo: Int
) : EventData()
}
@Serializable
data class ActionProfileSwitchOpenActivity(val timeShift: Int, val percentage: Int) : EventData()
@Serializable
data class OpenLoopRequest(val title: String, val message: String, val returnCommand: EventData?) : EventData()
@Serializable // returnCommand is sent back to Mobile after confirmation
data class ConfirmAction(val title: String, val message: String, val returnCommand: EventData?) : EventData()
}

View file

@ -1,61 +0,0 @@
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,19 +1,4 @@
<?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>
<string name="path_rx_bridge" translatable="false">/rx_bridge</string>
</resources>

View file

@ -241,7 +241,7 @@
</intent-filter>
</service>
<service android:name=".data.DataLayerListenerService"
<service android:name=".comm.DataLayerListenerServiceWear"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
@ -252,42 +252,6 @@
<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>
@ -299,15 +263,7 @@
<data
android:host="*"
android:pathPrefix="@string/path_ping"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_open_wear_setting"
android:scheme="wear" />
<data
android:host="*"
android:pathPrefix="@string/path_action_confirmation"
android:pathPrefix="@string/path_rx_bridge"
android:scheme="wear" />
</intent-filter>
</service>
@ -466,7 +422,7 @@
</service>
<service
android:name=".complications.UploaderBattery"
android:name=".complications.UploaderBatteryComplication"
android:icon="@drawable/ic_battery_charging_wireless_50"
android:label="Uploader/Phone Battery"
android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">

View file

@ -7,7 +7,11 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import dagger.android.AndroidInjector
import dagger.android.DaggerApplication
import info.nightscout.androidaps.comm.DataHandlerWear
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear
import info.nightscout.androidaps.di.DaggerWearComponent
import info.nightscout.androidaps.events.EventWearPreferenceChange
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import javax.inject.Inject
@ -15,11 +19,15 @@ import javax.inject.Inject
class Aaps : DaggerApplication(), OnSharedPreferenceChangeListener {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rxBus: RxBus
@Inject lateinit var dataHandlerWear: DataHandlerWear // instantiate only
override fun onCreate() {
super.onCreate()
aapsLogger.debug(LTag.WEAR, "onCreate")
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
startService(Intent(this, DataLayerListenerServiceWear::class.java))
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
@ -30,6 +38,7 @@ class Aaps : DaggerApplication(), OnSharedPreferenceChangeListener {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
// we trigger update on Complications
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(Intent.ACTION_SEND))
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(DataLayerListenerServiceWear.INTENT_NEW_DATA))
rxBus.send(EventWearPreferenceChange(key))
}
}

View file

@ -0,0 +1,301 @@
package info.nightscout.androidaps.comm
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.SystemClock
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.wearable.WearableListenerService
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventWearToMobile
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.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.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.EventData
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class DataHandlerWear @Inject constructor(
private val context: Context,
private val rxBus: RxBus,
private val aapsSchedulers: AapsSchedulers,
private val sp: SP,
private val aapsLogger: AAPSLogger,
private val persistence: Persistence
) {
private val disposable = CompositeDisposable()
init {
setupBus()
}
private fun setupBus() {
disposable += rxBus
.toObservable(EventData.ActionPing::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "Ping received from ${it.sourceNodeId}")
rxBus.send(EventWearToMobile(EventData.ActionPong(System.currentTimeMillis())))
}
disposable += rxBus
.toObservable(EventData.ConfirmAction::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "ConfirmAction received from ${it.sourceNodeId}")
context.startActivity(Intent(context, AcceptActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtras(
Bundle().also { bundle ->
bundle.putString("title", it.title)
bundle.putString("message", it.message)
bundle.putString(DataLayerListenerServiceWear.KEY_ACTION_DATA, it.returnCommand?.serialize())
}
)
})
}
disposable += rxBus
.toObservable(EventData.CancelNotification::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "ActionCancelNotification received from ${it.sourceNodeId}")
(context.getSystemService(WearableListenerService.NOTIFICATION_SERVICE) as NotificationManager).cancel(DataLayerListenerServiceWear.CHANGE_NOTIF_ID)
}
disposable += rxBus
.toObservable(EventData.OpenLoopRequest::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "OpenLoopRequest received from ${it.sourceNodeId}")
handleOpenLoopRequest(it)
}
disposable += rxBus
.toObservable(EventData.OpenSettings::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "ActionOpenSettings received from ${it.sourceNodeId}")
context.startActivity(Intent(context, AAPSPreferences::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
}
disposable += rxBus
.toObservable(EventData.ActionProfileSwitchOpenActivity::class.java)
.observeOn(aapsSchedulers.io)
.subscribe { event ->
aapsLogger.debug(LTag.WEAR, "ActionProfileSwitchOpenActivity received from ${event.sourceNodeId}")
context.startActivity(Intent(context, CPPActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtras(Bundle().also { bundle ->
bundle.putInt("percentage", event.percentage)
bundle.putInt("timeshift", event.timeShift)
})
})
}
disposable += rxBus
.toObservable(EventData.BolusProgress::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "Bolus progress received from ${it.sourceNodeId}")
handleBolusProgress(it)
rxBus.send(EventWearToMobile(EventData.ActionPong(System.currentTimeMillis())))
}
disposable += rxBus
.toObservable(EventData.Status::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "Status received from ${it.sourceNodeId}")
persistence.store(it)
LocalBroadcastManager.getInstance(context).sendBroadcast(
Intent(DataLayerListenerServiceWear.INTENT_NEW_DATA).apply {
putExtra(DataLayerListenerServiceWear.KEY_STATUS_DATA, it.serialize())
}
)
}
disposable += rxBus
.toObservable(EventData.SingleBg::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "SingleBg received from ${it.sourceNodeId}")
persistence.store(it)
LocalBroadcastManager.getInstance(context).sendBroadcast(
Intent(DataLayerListenerServiceWear.INTENT_NEW_DATA).apply {
putExtra(DataLayerListenerServiceWear.KEY_SINGLE_BG_DATA, it.serialize())
}
)
}
disposable += rxBus
.toObservable(EventData.GraphData::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "GraphData received from ${it.sourceNodeId}")
persistence.store(it)
LocalBroadcastManager.getInstance(context).sendBroadcast(
Intent(DataLayerListenerServiceWear.INTENT_NEW_DATA).apply {
putExtra(DataLayerListenerServiceWear.KEY_GRAPH_DATA, it.serialize())
}
)
}
disposable += rxBus
.toObservable(EventData.TreatmentData::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "TreatmentData received from ${it.sourceNodeId}")
persistence.store(it)
LocalBroadcastManager.getInstance(context).sendBroadcast(
Intent(DataLayerListenerServiceWear.INTENT_NEW_DATA).apply {
putExtra(DataLayerListenerServiceWear.KEY_TREATMENTS_DATA, it.serialize())
}
)
}
disposable += rxBus
.toObservable(EventData.Preferences::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "Preferences received from ${it.sourceNodeId}")
if (it.wearControl != sp.getBoolean(R.string.key_wear_control, false)) {
sp.putBoolean(R.string.key_wear_control, it.wearControl)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
TileService.getUpdater(context).requestUpdate(ActionsTileService::class.java)
TileService.getUpdater(context).requestUpdate(TempTargetTileService::class.java)
TileService.getUpdater(context).requestUpdate(QuickWizardTileService::class.java)
}
}
sp.putBoolean(R.string.key_units_mgdl, it.unitsMgdl)
sp.putInt(R.string.key_boluswizard_percentage, it.bolusPercentage)
sp.putInt(R.string.key_treatmentssafety_maxcarbs, it.maxCarbs)
sp.putDouble(R.string.key_treatmentssafety_maxbolus, it.maxBolus)
}
disposable += rxBus
.toObservable(EventData.QuickWizard::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
aapsLogger.debug(LTag.WEAR, "QuickWizard received from ${it.sourceNodeId}")
val serialized = it.serialize()
if (serialized != sp.getString(R.string.key_quick_wizard_data, "")) {
sp.putString(R.string.key_quick_wizard_data, serialized)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
TileService.getUpdater(context).requestUpdate(QuickWizardTileService::class.java)
}
}
}
private fun handleBolusProgress(bolusProgress: EventData.BolusProgress) {
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(context, DataLayerListenerServiceWear::class.java)
cancelIntent.action = DataLayerListenerServiceWear.INTENT_CANCEL_BOLUS
val cancelPendingIntent = PendingIntent.getService(context, 0, cancelIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
val notificationBuilder: NotificationCompat.Builder =
NotificationCompat.Builder(context, if (vibrate) DataLayerListenerServiceWear.AAPS_NOTIFY_CHANNEL_ID_BOLUS_PROGRESS else DataLayerListenerServiceWear.AAPS_NOTIFY_CHANNEL_ID_BOLUS_PROGRESS_SILENT)
.setSmallIcon(R.drawable.ic_icon)
.setContentTitle(context.getString(R.string.bolus_progress))
.setContentText("${bolusProgress.percent}% - ${bolusProgress.status}")
.setSubText(context.getString(R.string.press_to_cancel))
.setContentIntent(cancelPendingIntent)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setVibrate(vibratePattern)
.addAction(R.drawable.ic_cancel, context.getString(R.string.cancel_bolus), cancelPendingIntent)
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(DataLayerListenerServiceWear.BOLUS_PROGRESS_NOTIF_ID, notificationBuilder.build())
notificationManager.cancel(DataLayerListenerServiceWear.CONFIRM_NOTIF_ID) // multiple watch setup
if (bolusProgress.percent == 100) {
scheduleDismissBolusProgress(5)
}
}
@TargetApi(value = 26) private fun createBolusProgressChannels() {
createNotificationChannel(
longArrayOf(0, 50, 1000),
DataLayerListenerServiceWear.AAPS_NOTIFY_CHANNEL_ID_BOLUS_PROGRESS,
context.getString(R.string.bolus_progress_channel_name),
context.getString(R.string.bolus_progress_channel_description)
)
createNotificationChannel(
longArrayOf(0, 1, 1000),
DataLayerListenerServiceWear.AAPS_NOTIFY_CHANNEL_ID_BOLUS_PROGRESS_SILENT,
context.getString(R.string.bolus_progress_silent_channel_name),
context.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
context.getSystemService(NotificationManager::class.java)
.createNotificationChannel(channel)
}
private fun handleOpenLoopRequest(command: EventData.OpenLoopRequest) {
// 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(DataLayerListenerServiceWear.AAPS_NOTIFY_CHANNEL_ID_OPEN_LOOP, 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 = context.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
var builder = NotificationCompat.Builder(context, DataLayerListenerServiceWear.AAPS_NOTIFY_CHANNEL_ID_OPEN_LOOP)
builder = builder.setSmallIcon(R.drawable.notif_icon)
.setContentTitle(command.title)
.setContentText(command.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(context, AcceptActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtras(Bundle().also { bundle ->
bundle.putString("title", command.title)
bundle.putString("message", command.message)
bundle.putString(DataLayerListenerServiceWear.KEY_ACTION_DATA, command.returnCommand?.serialize())
})
}
val resultPendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
builder = builder.setContentIntent(resultPendingIntent)
val mNotificationManager = context.getSystemService(WearableListenerService.NOTIFICATION_SERVICE) as NotificationManager
// mId allows you to update the notification later on.
mNotificationManager.notify(DataLayerListenerServiceWear.CHANGE_NOTIF_ID, builder.build())
}
@Suppress("SameParameterValue")
private fun scheduleDismissBolusProgress(seconds: Int) {
Thread {
SystemClock.sleep(seconds * 1000L)
NotificationManagerCompat.from(context).cancel(DataLayerListenerServiceWear.BOLUS_PROGRESS_NOTIF_ID)
}.start()
}
}

View file

@ -0,0 +1,210 @@
package info.nightscout.androidaps.comm
import android.content.Intent
import android.os.Handler
import android.os.HandlerThread
import androidx.core.app.NotificationManagerCompat
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.EventWearToMobile
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.utils.rx.AapsSchedulers
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.EventData
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 DataLayerListenerServiceWear : 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
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()
private val rxPath get() = getString(R.string.path_rx_bridge)
override fun onCreate() {
AndroidInjection.inject(this)
super.onCreate()
handler.post { updateTranscriptionCapability() }
disposable += rxBus
.toObservable(EventWearToMobile::class.java)
.observeOn(aapsSchedulers.io)
.subscribe {
sendMessage(rxPath, it.payload.serialize())
}
}
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 {
@Suppress("ControlFlowWithEmptyBody", "UNUSED_EXPRESSION")
when (path) {
}
} catch (exception: Exception) {
aapsLogger.error(LTag.WEAR, "onDataChanged failed", exception)
}
}
}
super.onDataChanged(dataEvents)
}
override fun onMessageReceived(messageEvent: MessageEvent) {
super.onMessageReceived(messageEvent)
when (messageEvent.path) {
rxPath -> {
aapsLogger.debug(LTag.WEAR, "onMessageReceived: ${String(messageEvent.data)}")
val command = EventData.deserialize(String(messageEvent.data))
rxBus.send(command.also { it.sourceNodeId = messageEvent.sourceNodeId })
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
INTENT_CANCEL_BOLUS -> {
//dismiss notification
NotificationManagerCompat.from(this).cancel(BOLUS_PROGRESS_NOTIF_ID)
//send cancel-request to phone.
rxBus.send(EventWearToMobile(EventData.CancelBolus(System.currentTimeMillis())))
}
}
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
@Suppress("unused")
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: String?) {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path $data")
transcriptionNodeId?.also { nodeId ->
messageClient
.sendMessage(nodeId, path, data?.toByteArray() ?: byteArrayOf()).apply {
addOnSuccessListener { }
addOnFailureListener {
aapsLogger.debug(LTag.WEAR, "sendMessage: $path failure")
}
}
}
}
@Suppress("unused")
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")
}
}
}
}
companion object {
const val PHONE_CAPABILITY = "androidaps_mobile"
// Accepted intents
val INTENT_CANCEL_BOLUS = DataLayerListenerServiceWear::class.java.name + ".CancelBolus"
val INTENT_NEW_DATA = DataLayerListenerServiceWear::class.java.name + ".NewData"
//data keys
const val KEY_ACTION_DATA = "actionData"
const val KEY_ACTION = "action"
const val KEY_MESSAGE = "message"
const val KEY_SINGLE_BG_DATA = "single_bg_data"
const val KEY_TREATMENTS_DATA = "treatments_data"
const val KEY_GRAPH_DATA = "graph_data"
const val KEY_STATUS_DATA = "status_data"
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_OPEN_LOOP = "AndroidAPS-OpenLoop"
const val AAPS_NOTIFY_CHANNEL_ID_BOLUS_PROGRESS = "bolus progress vibration"
const val AAPS_NOTIFY_CHANNEL_ID_BOLUS_PROGRESS_SILENT = "bolus progress silent"
}
}

View file

@ -22,15 +22,18 @@ import javax.inject.Inject;
import dagger.android.AndroidInjection;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear;
import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.Constants;
import info.nightscout.androidaps.interaction.utils.DisplayFormat;
import info.nightscout.androidaps.interaction.utils.Inevitable;
import info.nightscout.androidaps.interaction.utils.Persistence;
import info.nightscout.androidaps.interaction.utils.WearUtil;
import info.nightscout.androidaps.plugins.bus.RxBus;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import info.nightscout.shared.weardata.EventData;
/**
* Base class for all complications
@ -44,6 +47,7 @@ public abstract class BaseComplicationProviderService extends ComplicationProvid
@Inject DisplayFormat displayFormat;
@Inject Persistence persistence;
@Inject AAPSLogger aapsLogger;
@Inject RxBus rxBus;
// Not derived from DaggerService, do injection here
@Override
@ -199,13 +203,13 @@ public abstract class BaseComplicationProviderService extends ComplicationProvid
persistence.putBoolean("complication_" + complicationId + "_since", usesSinceField());
persistence.addToSet(KEY_COMPLICATIONS, "complication_" + complicationId);
IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND);
IntentFilter messageFilter = new IntentFilter(DataLayerListenerServiceWear.Companion.getINTENT_NEW_DATA());
messageReceiver = new MessageReceiver();
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(messageReceiver, messageFilter);
DataLayerListenerService.Companion.requestData(this);
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("BaseComplicationProviderService")));
checkIfUpdateNeeded();
}
@ -232,12 +236,13 @@ public abstract class BaseComplicationProviderService extends ComplicationProvid
ComplicationTapBroadcastReceiver.getTapActionIntent(
getApplicationContext(), thisProvider, complicationId, getComplicationAction());
final RawDisplayData raw = new RawDisplayData(wearUtil);
final RawDisplayData raw = new RawDisplayData();
raw.updateForComplicationsFromPersistence(persistence);
aapsLogger.warn(LTag.WEAR, "Complication data: " + raw.toDebugString());
// store what is currently rendered in 'SGV since' field, to detect if it was changed and need update
persistence.putString(KEY_LAST_SHOWN_SINCE_VALUE, displayFormat.shortTimeSince(raw.datetime));
persistence.putString(KEY_LAST_SHOWN_SINCE_VALUE,
displayFormat.shortTimeSince(raw.getSingleBg().getTimeStamp()));
// by each render we clear stale flag to ensure it is re-rendered at next refresh detection round
persistence.putBoolean(KEY_STALE_REPORTED, false);
@ -249,11 +254,11 @@ public abstract class BaseComplicationProviderService extends ComplicationProvid
final PendingIntent infoToast = ComplicationTapBroadcastReceiver.getTapWarningSinceIntent(
getApplicationContext(), thisProvider, complicationId, ComplicationAction.WARNING_SYNC, persistence.whenDataUpdated());
complicationData = buildNoSyncComplicationData(dataType, raw, complicationPendingIntent, infoToast, persistence.whenDataUpdated());
} else if (wearUtil.msSince(raw.datetime) > Constants.STALE_MS) {
} else if (wearUtil.msSince(raw.getSingleBg().getTimeStamp()) > Constants.STALE_MS) {
// data arriving from phone AAPS, but it is outdated (uploader/NS/xDrip/Sensor error)
final PendingIntent infoToast = ComplicationTapBroadcastReceiver.getTapWarningSinceIntent(
getApplicationContext(), thisProvider, complicationId, ComplicationAction.WARNING_OLD, raw.datetime);
complicationData = buildOutdatedComplicationData(dataType, raw, complicationPendingIntent, infoToast, raw.datetime);
getApplicationContext(), thisProvider, complicationId, ComplicationAction.WARNING_OLD, raw.getSingleBg().getTimeStamp());
complicationData = buildOutdatedComplicationData(dataType, raw, complicationPendingIntent, infoToast, raw.getSingleBg().getTimeStamp());
} else {
// data is up-to-date, we can render standard complication
complicationData = buildComplicationData(dataType, raw, complicationPendingIntent);
@ -310,13 +315,13 @@ public abstract class BaseComplicationProviderService extends ComplicationProvid
* is up-to-date or need to be changed (a minute or more elapsed)
*/
private void requestUpdateIfSinceChanged() {
final RawDisplayData raw = new RawDisplayData(wearUtil);
final RawDisplayData raw = new RawDisplayData();
raw.updateForComplicationsFromPersistence(persistence);
final String lastSince = persistence.getString(KEY_LAST_SHOWN_SINCE_VALUE, "-");
final String calcSince = displayFormat.shortTimeSince(raw.datetime);
final String calcSince = displayFormat.shortTimeSince(raw.getSingleBg().getTimeStamp());
final boolean isStale = (wearUtil.msSince(persistence.whenDataUpdated()) > Constants.STALE_MS)
|| (wearUtil.msSince(raw.datetime) > Constants.STALE_MS);
|| (wearUtil.msSince(raw.getSingleBg().getTimeStamp()) > Constants.STALE_MS);
final boolean staleWasRefreshed = persistence.getBoolean(KEY_STALE_REPORTED, false);
final boolean sinceWasChanged = !lastSince.equals(calcSince);

View file

@ -3,7 +3,6 @@ package info.nightscout.androidaps.complications;
import android.app.PendingIntent;
import android.support.wearable.complications.ComplicationData;
import android.support.wearable.complications.ComplicationText;
import android.util.Log;
import javax.inject.Inject;
@ -32,11 +31,11 @@ public class BrCobIobComplication extends BaseComplicationProviderService {
ComplicationData complicationData = null;
if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
final String cob = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE).minimise(displayFormat.MIN_FIELD_LEN_COB);
final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(Math.max(displayFormat.MIN_FIELD_LEN_IOB, (displayFormat.MAX_FIELD_LEN_SHORT - 1) - cob.length()));
final String cob = new SmallestDoubleString(raw.getStatus().getCob(), SmallestDoubleString.Units.USE).minimise(displayFormat.MIN_FIELD_LEN_COB);
final String iob = new SmallestDoubleString(raw.getStatus().getIobSum(), SmallestDoubleString.Units.USE).minimise(Math.max(displayFormat.MIN_FIELD_LEN_IOB, (displayFormat.MAX_FIELD_LEN_SHORT - 1) - cob.length()));
final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(displayFormat.basalRateSymbol() + raw.sBasalRate))
.setShortText(ComplicationText.plainText(displayFormat.basalRateSymbol() + raw.getStatus().getCurrentBasal()))
.setShortTitle(ComplicationText.plainText(cob + " " + iob))
.setTapAction(complicationPendingIntent);

View file

@ -20,7 +20,7 @@ public class CobIconComplication extends BaseComplicationProviderService {
if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(raw.sCOB2))
.setShortText(ComplicationText.plainText(raw.getStatus().getCob()))
.setIcon(
Icon.createWithResource(
this, R.drawable.ic_carbs))

View file

@ -18,8 +18,8 @@ public class CobIobComplication extends BaseComplicationProviderService {
ComplicationData complicationData = null;
if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
final String cob = raw.sCOB2;
final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(displayFormat.MAX_FIELD_LEN_SHORT);
final String cob = raw.getStatus().getCob();
final String iob = new SmallestDoubleString(raw.getStatus().getIobSum(), SmallestDoubleString.Units.USE).minimise(displayFormat.MAX_FIELD_LEN_SHORT);
final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(cob))

View file

@ -20,7 +20,7 @@ public class IobIconComplication extends BaseComplicationProviderService {
ComplicationData complicationData = null;
if (dataType == ComplicationData.TYPE_SHORT_TEXT) {
final String iob = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(displayFormat.MAX_FIELD_LEN_SHORT);
final String iob = new SmallestDoubleString(raw.getStatus().getIobSum(), SmallestDoubleString.Units.USE).minimise(displayFormat.MAX_FIELD_LEN_SHORT);
final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(iob))

View file

@ -32,7 +32,8 @@ public class SgvComplication extends BaseComplicationProviderService {
switch (dataType) {
case ComplicationData.TYPE_SHORT_TEXT:
final ComplicationData.Builder builder = new ComplicationData.Builder(ComplicationData.TYPE_SHORT_TEXT)
.setShortText(ComplicationText.plainText(raw.sSgv + raw.sDirection + "\uFE0E"))
.setShortText(ComplicationText.plainText(raw.getSingleBg().getSgvString() + raw.getSingleBg().getSlopeArrow() +
"\uFE0E"))
.setShortTitle(ComplicationText.plainText(displayFormat.shortTrend(raw)))
.setTapAction(complicationPendingIntent);

View file

@ -14,7 +14,7 @@ import info.nightscout.shared.logging.LTag;
/*
* Created by dlvoy on 2019-11-12
*/
public class UploaderBattery extends BaseComplicationProviderService {
public class UploaderBatteryComplication extends BaseComplicationProviderService {
public ComplicationData buildComplicationData(int dataType, RawDisplayData raw, PendingIntent complicationPendingIntent) {
@ -25,9 +25,9 @@ public class UploaderBattery extends BaseComplicationProviderService {
int level = 0;
String levelStr = "???";
if (raw.sUploaderBattery.matches("^[0-9]+$")) {
if (raw.getStatus().getBattery().matches("^[0-9]+$")) {
try {
level = Integer.parseInt(raw.sUploaderBattery);
level = Integer.parseInt(raw.getStatus().getBattery());
level = Math.max(Math.min(level, 100), 0);
levelStr = level + "%";
int iconNo = (int) Math.floor(level / 10.0);
@ -112,7 +112,7 @@ public class UploaderBattery extends BaseComplicationProviderService {
} catch (NumberFormatException ex) {
aapsLogger.error(LTag.WEAR, "Cannot parse battery level of: " + raw.sUploaderBattery);
aapsLogger.error(LTag.WEAR, "Cannot parse battery level of: " + raw.getStatus().getBattery());
}
}
@ -147,7 +147,7 @@ public class UploaderBattery extends BaseComplicationProviderService {
@Override
public String getProviderCanonicalName() {
return UploaderBattery.class.getCanonicalName();
return UploaderBatteryComplication.class.getCanonicalName();
}
@Override

View file

@ -1,11 +0,0 @@
package info.nightscout.androidaps.data;
/**
* Created by adrian on 18/11/16.
*/
public class BasalWatchData {
public long startTime;
public long endTime;
public double amount;
}

View file

@ -1,49 +0,0 @@
package info.nightscout.androidaps.data;
import java.util.Objects;
/**
* Created by emmablack on 1/7/15.
*/
public class BgWatchData implements Comparable<BgWatchData>{
public double sgv;
public double high;
public double low;
public long timestamp;
public int color;
public BgWatchData(double aSgv, double aHigh, double aLow, long aTimestamp, int aColor) {
this.sgv = aSgv;
this.high = aHigh;
this.low = aLow;
this.timestamp = aTimestamp;
this.color = aColor;
}
public BgWatchData(){
}
@Override
public boolean equals(Object that){
if(! (that instanceof BgWatchData)){
return false;
}
if (this.color != ((BgWatchData) that).color)
return false;
return this.timestamp == ((BgWatchData) that).timestamp;
}
@Override
public int hashCode() {
return Objects.hash(timestamp, color);
}
@Override
public int compareTo(BgWatchData that) {
// reverse order endTime get latest first
if(this.timestamp < that.timestamp) return 1;
if(this.timestamp > that.timestamp) return -1;
return 0;
}
}

View file

@ -1,13 +0,0 @@
package info.nightscout.androidaps.data;
/**
* Created by adrian on 17/11/16.
*/
public class BolusWatchData {
public long date;
public double bolus;
public double carbs;
public boolean isSMB;
public boolean isValid;
}

View file

@ -1,552 +0,0 @@
/*
* 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,281 +0,0 @@
package info.nightscout.androidaps.data;
import android.content.Intent;
import android.os.Bundle;
import android.os.PowerManager;
import com.google.android.gms.wearable.DataMap;
import java.util.ArrayList;
import java.util.Iterator;
import info.nightscout.androidaps.interaction.utils.Constants;
import info.nightscout.androidaps.interaction.utils.Persistence;
import info.nightscout.androidaps.interaction.utils.WearUtil;
/**
* Holds bunch of data model variables and lists that arrive from phone app and are due to be
* displayed on watchface and complications. Keeping them together makes code cleaner and allows
* passing it to complications via persistence layer.
*
* Created by dlvoy on 2019-11-12
*/
public class RawDisplayData {
private final WearUtil wearUtil;
public RawDisplayData(WearUtil wearUtil) {
this.wearUtil = wearUtil;
}
static final String DATA_PERSISTENCE_KEY = "raw_data";
static final String BASALS_PERSISTENCE_KEY = "raw_basals";
static final String STATUS_PERSISTENCE_KEY = "raw_status";
// data bundle
public long sgvLevel = 0;
public long datetime;
public String sSgv = "---";
public String sDirection = "--";
public String sDelta = "--";
public String sAvgDelta = "--";
public String sUnits = "-";
// status bundle
public String sBasalRate = "-.--U/h";
public String sUploaderBattery = "--";
public String sRigBattery = "--";
public boolean detailedIOB = false;
public String sIOB1 = "IOB";
public String sIOB2 = "-.--";
public String sCOB1 = "Carb";
public String sCOB2= "--g";
public String sBgi = "--";
public boolean showBGI = false;
public String externalStatusString = "no status";
public int batteryLevel = 1;
public long openApsStatus = -1;
// basals bundle
public ArrayList<BgWatchData> bgDataList = new ArrayList<>();
public ArrayList<TempWatchData> tempWatchDataList = new ArrayList<>();
public ArrayList<BasalWatchData> basalWatchDataList = new ArrayList<>();
public ArrayList<BolusWatchData> bolusWatchDataList = new ArrayList<>();
public ArrayList<BgWatchData> predictionList = new ArrayList<>();
public String toDebugString() {
return "DisplayRawData{" +
"sgvLevel=" + sgvLevel +
", datetime=" + datetime +
", sSgv='" + sSgv + '\'' +
", sDirection='" + sDirection + '\'' +
", sDelta='" + sDelta + '\'' +
", sAvgDelta='" + sAvgDelta + '\'' +
", sUnits='" + sUnits + '\'' +
", sBasalRate='" + sBasalRate + '\'' +
", sUploaderBattery='" + sUploaderBattery + '\'' +
", sRigBattery='" + sRigBattery + '\'' +
", detailedIOB=" + detailedIOB +
", sIOB1='" + sIOB1 + '\'' +
", sIOB2='" + sIOB2 + '\'' +
", sCOB1='" + sCOB1 + '\'' +
", sCOB2='" + sCOB2 + '\'' +
", sBgi='" + sBgi + '\'' +
", showBGI=" + showBGI +
", externalStatusString='" + externalStatusString + '\'' +
", batteryLevel=" + batteryLevel +
", openApsStatus=" + openApsStatus +
", bgDataList size=" + bgDataList.size() +
", tempWatchDataList size=" + tempWatchDataList.size() +
", basalWatchDataList size=" + basalWatchDataList.size() +
", bolusWatchDataLis size=" + bolusWatchDataList.size() +
", predictionList size=" + predictionList.size() +
'}';
}
public void updateFromPersistence(Persistence persistence) {
DataMap dataMapData = persistence.getDataMap(DATA_PERSISTENCE_KEY);
if (dataMapData != null) {
updateData(dataMapData);
}
DataMap dataMapStatus = persistence.getDataMap(STATUS_PERSISTENCE_KEY);
if (dataMapStatus != null) {
updateStatus(dataMapStatus);
}
DataMap dataMapBasals = persistence.getDataMap(BASALS_PERSISTENCE_KEY);
if (dataMapBasals != null) {
updateBasals(dataMapBasals);
}
}
/*
* Since complications do not need Basals, we skip them for performance
*/
public void updateForComplicationsFromPersistence(Persistence persistence) {
DataMap dataMapData = persistence.getDataMap(DATA_PERSISTENCE_KEY);
if (dataMapData != null) {
updateData(dataMapData);
}
DataMap dataMapStatus = persistence.getDataMap(STATUS_PERSISTENCE_KEY);
if (dataMapStatus != null) {
updateStatus(dataMapStatus);
}
}
public DataMap updateDataFromMessage(Intent intent, PowerManager.WakeLock wakeLock) {
Bundle bundle = intent.getBundleExtra("data");
if (bundle != null) {
DataMap dataMap = wearUtil.bundleToDataMap(bundle);
updateData(dataMap);
return dataMap;
}
return null;
}
private void updateData(DataMap dataMap) {
PowerManager.WakeLock wl = wearUtil.getWakeLock("readingPrefs", 50);
sgvLevel = dataMap.getLong("sgvLevel");
datetime = dataMap.getLong("timestamp");
sSgv = dataMap.getString("sgvString");
sDirection = dataMap.getString("slopeArrow");
sDelta = dataMap.getString("delta");
sAvgDelta = dataMap.getString("avgDelta");
sUnits = dataMap.getString("glucoseUnits");
wearUtil.releaseWakeLock(wl);
}
public DataMap updateStatusFromMessage(Intent intent, PowerManager.WakeLock wakeLock) {
Bundle bundle = intent.getBundleExtra("status");
if (bundle != null) {
DataMap dataMap = wearUtil.bundleToDataMap(bundle);
updateStatus(dataMap);
return dataMap;
}
return null;
}
private void updateStatus(DataMap dataMap) {
PowerManager.WakeLock wl = wearUtil.getWakeLock("readingPrefs", 50);
sBasalRate = dataMap.getString("currentBasal");
sUploaderBattery = dataMap.getString("battery");
sRigBattery = dataMap.getString("rigBattery");
detailedIOB = dataMap.getBoolean("detailedIob");
sIOB1 = dataMap.getString("iobSum") + "U";
sIOB2 = dataMap.getString("iobDetail");
sCOB1 = "Carb";
sCOB2 = dataMap.getString("cob");
sBgi = dataMap.getString("bgi");
showBGI = dataMap.getBoolean("showBgi");
externalStatusString = dataMap.getString("externalStatusString");
batteryLevel = dataMap.getInt("batteryLevel");
openApsStatus = dataMap.getLong("openApsStatus");
wearUtil.releaseWakeLock(wl);
}
public DataMap updateBasalsFromMessage(Intent intent) {
Bundle bundle = intent.getBundleExtra("basals");
if (bundle != null) {
DataMap dataMap = wearUtil.bundleToDataMap(bundle);
updateBasals(dataMap);
return dataMap;
}
return null;
}
private void updateBasals(DataMap dataMap) {
PowerManager.WakeLock wl = wearUtil.getWakeLock("readingPrefs", 500);
loadBasalsAndTemps(dataMap);
wearUtil.releaseWakeLock(wl);
}
private void loadBasalsAndTemps(DataMap dataMap) {
ArrayList<DataMap> temps = dataMap.getDataMapArrayList("temps");
if (temps != null) {
tempWatchDataList = new ArrayList<>();
for (DataMap temp : temps) {
TempWatchData twd = new TempWatchData();
twd.startTime = temp.getLong("starttime");
twd.startBasal = temp.getDouble("startBasal");
twd.endTime = temp.getLong("endtime");
twd.endBasal = temp.getDouble("endbasal");
twd.amount = temp.getDouble("amount");
tempWatchDataList.add(twd);
}
}
ArrayList<DataMap> basals = dataMap.getDataMapArrayList("basals");
if (basals != null) {
basalWatchDataList = new ArrayList<>();
for (DataMap basal : basals) {
BasalWatchData bwd = new BasalWatchData();
bwd.startTime = basal.getLong("starttime");
bwd.endTime = basal.getLong("endtime");
bwd.amount = basal.getDouble("amount");
basalWatchDataList.add(bwd);
}
}
ArrayList<DataMap> boluses = dataMap.getDataMapArrayList("boluses");
if (boluses != null) {
bolusWatchDataList = new ArrayList<>();
for (DataMap bolus : boluses) {
BolusWatchData bwd = new BolusWatchData();
bwd.date = bolus.getLong("date");
bwd.bolus = bolus.getDouble("bolus");
bwd.carbs = bolus.getDouble("carbs");
bwd.isSMB = bolus.getBoolean("isSMB");
bwd.isValid = bolus.getBoolean("isValid");
bolusWatchDataList.add(bwd);
}
}
ArrayList<DataMap> predictions = dataMap.getDataMapArrayList("predictions");
if (boluses != null) {
predictionList = new ArrayList<>();
for (DataMap prediction : predictions) {
BgWatchData bwd = new BgWatchData();
bwd.timestamp = prediction.getLong("timestamp");
bwd.sgv = prediction.getDouble("sgv");
bwd.color = prediction.getInt("color");
predictionList.add(bwd);
}
}
}
public void addToWatchSet(DataMap dataMap) {
ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
if (entries != null) {
bgDataList = new ArrayList<>();
for (DataMap entry : entries) {
double sgv = entry.getDouble("sgvDouble");
double high = entry.getDouble("high");
double low = entry.getDouble("low");
long timestamp = entry.getLong("timestamp");
int color = entry.getInt("color", 0);
bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
}
} else {
double sgv = dataMap.getDouble("sgvDouble");
double high = dataMap.getDouble("high");
double low = dataMap.getDouble("low");
long timestamp = dataMap.getLong("timestamp");
int color = dataMap.getInt("color", 0);
final int size = bgDataList.size();
if (size > 0) {
if (bgDataList.get(size - 1).timestamp == timestamp)
return; // Ignore duplicates.
}
bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
}
// We use iterator instead for-loop because we iterate and remove on the go
Iterator itr = bgDataList.iterator();
while (itr.hasNext()) {
BgWatchData entry = (BgWatchData)itr.next();
if (entry.timestamp < (wearUtil.timestamp() - (Constants.HOUR_IN_MS * 5))) {
itr.remove(); //Get rid of anything more than 5 hours old
}
}
}
}

View file

@ -0,0 +1,95 @@
package info.nightscout.androidaps.data
import android.content.Intent
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear
import info.nightscout.androidaps.interaction.utils.Persistence
import info.nightscout.shared.weardata.EventData
/**
* Holds bunch of data model variables and lists that arrive from phone app and are due to be
* displayed on watchface and complications. Keeping them together makes code cleaner and allows
* passing it to complications via persistence layer.
*
* Created by dlvoy on 2019-11-12
*/
class RawDisplayData {
// bg data bundle
var singleBg = EventData.SingleBg(
timeStamp = 0,
sgv = 0.0,
high = 0.0,
low = 0.0,
color = 0
)
// status bundle
var status = EventData.Status(
externalStatus = "no status",
iobSum = "IOB",
iobDetail = "-.--",
detailedIob = false,
cob = "--g",
currentBasal = "-.--U/h",
battery = "--",
rigBattery = "--",
openApsStatus = -1,
bgi = "--",
showBgi = false,
batteryLevel = 1
)
// basals bundle
var graphData = EventData.GraphData(
entries = ArrayList<EventData.SingleBg>()
)
var treatmentData = EventData.TreatmentData(
temps = ArrayList<EventData.TreatmentData.TempBasal>(),
basals = ArrayList<EventData.TreatmentData.Basal>(),
boluses = ArrayList<EventData.TreatmentData.Treatment>(),
predictions = ArrayList<EventData.SingleBg>()
)
fun toDebugString(): String =
"DisplayRawData{singleBg=$singleBg, status=$status, graphData=$graphData, treatmentData=$treatmentData}"
fun updateFromPersistence(persistence: Persistence) {
persistence.readSingleBg()?.let { singleBg = it }
persistence.readGraphData()?.let { graphData = it }
persistence.readStatus()?.let { status = it }
persistence.readTreatments()?.let { treatmentData = it }
}
/*
* Since complications do not need Basals, we skip them for performance
*/
fun updateForComplicationsFromPersistence(persistence: Persistence) {
persistence.readSingleBg()?.let { singleBg = it }
persistence.readGraphData()?.let { graphData = it }
persistence.readStatus()?.let { status = it }
}
fun updateFromMessage(intent: Intent) {
intent.getStringExtra(DataLayerListenerServiceWear.KEY_SINGLE_BG_DATA)?.let{
singleBg = EventData.deserialize(it) as EventData.SingleBg
}
intent.getStringExtra(DataLayerListenerServiceWear.KEY_STATUS_DATA)?.let{
status = EventData.deserialize(it) as EventData.Status
}
intent.getStringExtra(DataLayerListenerServiceWear.KEY_TREATMENTS_DATA)?.let{
treatmentData = EventData.deserialize(it) as EventData.TreatmentData
}
intent.getStringExtra(DataLayerListenerServiceWear.KEY_GRAPH_DATA)?.let{
graphData = EventData.deserialize(it) as EventData.GraphData
}
}
companion object {
const val BG_DATA_PERSISTENCE_KEY = "raw_data"
const val GRAPH_DATA_PERSISTENCE_KEY = "raw_data"
const val BASALS_PERSISTENCE_KEY = "raw_basals"
const val STATUS_PERSISTENCE_KEY = "raw_status"
}
}

View file

@ -1,13 +0,0 @@
package info.nightscout.androidaps.data;
/**
* Created by adrian on 17/11/16.
*/
public class TempWatchData {
public long startTime;
public double startBasal;
public long endTime;
public double endBasal;
public double amount;
}

View file

@ -3,6 +3,10 @@ package info.nightscout.androidaps.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.interaction.actions.*
import info.nightscout.androidaps.interaction.menus.FillMenuActivity
import info.nightscout.androidaps.interaction.menus.MainMenuActivity
import info.nightscout.androidaps.interaction.menus.StatusMenuActivity
import info.nightscout.androidaps.interaction.utils.MenuListActivity
@Module
@Suppress("unused")
@ -20,4 +24,9 @@ abstract class WearActivitiesModule {
@ContributesAndroidInjector abstract fun contributesTempTargetActivity(): TempTargetActivity
@ContributesAndroidInjector abstract fun contributesTreatmentActivity(): TreatmentActivity
@ContributesAndroidInjector abstract fun contributesWizardActivity(): WizardActivity
@ContributesAndroidInjector abstract fun contributesMenuListActivity(): MenuListActivity
@ContributesAndroidInjector abstract fun contributesFillMenuActivity(): FillMenuActivity
@ContributesAndroidInjector abstract fun contributesMainMenuActivity(): MainMenuActivity
@ContributesAndroidInjector abstract fun contributesStatusMenuActivity(): StatusMenuActivity
}

View file

@ -2,16 +2,18 @@ package info.nightscout.androidaps.di
import dagger.Module
import dagger.android.ContributesAndroidInjector
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear
import info.nightscout.androidaps.complications.*
import info.nightscout.androidaps.data.DataLayerListenerService
import info.nightscout.androidaps.interaction.actions.BackgroundActionActivity
import info.nightscout.androidaps.tile.QuickWizardTileService
import info.nightscout.androidaps.tile.TempTargetTileService
import info.nightscout.androidaps.tile.TileBase
import info.nightscout.androidaps.watchfaces.*
@Module
@Suppress("unused")
abstract class WearServicesModule {
@ContributesAndroidInjector abstract fun contributesDataLayerListenerService(): DataLayerListenerService
@ContributesAndroidInjector abstract fun contributesDataLayerListenerService(): DataLayerListenerServiceWear
@ContributesAndroidInjector abstract fun contributesBaseComplicationProviderService(): BaseComplicationProviderService
@ContributesAndroidInjector abstract fun contributesBrCobIobComplication(): BrCobIobComplication
@ -24,7 +26,7 @@ abstract class WearServicesModule {
@ContributesAndroidInjector abstract fun contributesLongStatusComplication(): LongStatusComplication
@ContributesAndroidInjector abstract fun contributesLongStatusFlippedComplication(): LongStatusFlippedComplication
@ContributesAndroidInjector abstract fun contributesSgvComplication(): SgvComplication
@ContributesAndroidInjector abstract fun contributesUploaderBattery(): UploaderBattery
@ContributesAndroidInjector abstract fun contributesUploaderBatteryComplication(): UploaderBatteryComplication
@ContributesAndroidInjector abstract fun contributesWallpaperComplication(): WallpaperComplication
@ContributesAndroidInjector abstract fun contributesBaseWatchFace(): BaseWatchFace
@ -34,4 +36,12 @@ abstract class WearServicesModule {
@ContributesAndroidInjector abstract fun contributesSteampunk(): Steampunk
@ContributesAndroidInjector abstract fun contributesDigitalStyle(): DigitalStyle
@ContributesAndroidInjector abstract fun contributesCockpit(): Cockpit
@ContributesAndroidInjector abstract fun contributesBIGChart(): BIGChart
@ContributesAndroidInjector abstract fun contributesNOChart(): NOChart
@ContributesAndroidInjector abstract fun contributesCircleWatchface(): CircleWatchface
@ContributesAndroidInjector abstract fun contributesTileBase(): TileBase
@ContributesAndroidInjector abstract fun contributesQuickWizardTileService(): QuickWizardTileService
@ContributesAndroidInjector abstract fun contributesTempTargetTileService(): TempTargetTileService
}

View file

@ -0,0 +1,22 @@
package info.nightscout.androidaps.events
import android.content.Context
@Suppress("unused")
class EventWearPreferenceChange : Event {
var changedKey: String? = null
private set
constructor(key: String) {
changedKey = key
}
constructor(context: Context, resourceID: Int) {
changedKey = context.getString(resourceID)
}
fun isChanged(context: Context, id: Int): Boolean {
return changedKey == context.getString(id)
}
}

View file

@ -1,6 +1,6 @@
package info.nightscout.androidaps.interaction.actions;
import static info.nightscout.shared.weardata.WearConstants.KEY_ACTION_DATA;
import static info.nightscout.androidaps.comm.DataLayerListenerServiceWear.KEY_ACTION_DATA;
import android.content.Context;
import android.content.Intent;
@ -21,10 +21,8 @@ import androidx.core.view.MotionEventCompat;
import androidx.core.view.ViewConfigurationCompat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobileChange;
import info.nightscout.androidaps.events.EventWearToMobileConfirm;
import info.nightscout.shared.weardata.ActionData;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.shared.weardata.EventData;
/**
* Created by adrian on 09/02/17.
@ -33,7 +31,6 @@ import info.nightscout.shared.weardata.ActionData;
public class AcceptActivity extends ViewSelectorActivity {
String message = "";
String actionstring = "";
String actionKey = "";
private DismissThread dismissThread;
@ -46,10 +43,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 (message.isEmpty() || (actionstring.isEmpty() && actionKey.isEmpty())) {
if (message.isEmpty() || actionKey.isEmpty()) {
finish();
return;
}
@ -109,15 +105,9 @@ public class AcceptActivity extends ViewSelectorActivity {
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));
}
EventData returnCommand = EventData.Companion.deserialize(actionKey);
rxBus.send(new EventWearToMobile(returnCommand));
rxBus.send(new EventData.CancelNotification(System.currentTimeMillis()));
finishAffinity();
});
container.addView(view);

View file

@ -3,21 +3,25 @@ package info.nightscout.androidaps.interaction.actions
import android.os.Bundle
import android.widget.Toast
import dagger.android.DaggerActivity
import info.nightscout.androidaps.data.DataLayerListenerService
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear
import info.nightscout.androidaps.events.EventWearToMobile
import info.nightscout.androidaps.plugins.bus.RxBus
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.weardata.EventData
import javax.inject.Inject
class BackgroundActionActivity : DaggerActivity() {
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var rxBus: RxBus
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
intent.extras?.getString("actionString")?.let { actionString ->
aapsLogger.info(LTag.WEAR, "QuickWizardActivity.onCreate: actionString=$actionString")
DataLayerListenerService.initiateAction(this, actionString)
intent.extras?.getString("message")?.let { message ->
intent.extras?.getString(DataLayerListenerServiceWear.KEY_ACTION)?.let { action ->
aapsLogger.info(LTag.WEAR, "QuickWizardActivity.onCreate: action=$action")
rxBus.send(EventWearToMobile(EventData.deserialize(action)))
intent.extras?.getString(DataLayerListenerServiceWear.KEY_MESSAGE)?.let { message ->
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
} ?: aapsLogger.error(LTag.WEAR, "BackgroundActionActivity.onCreate extras 'actionString' required")

View file

@ -10,10 +10,10 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.events.EventWearToMobileAction;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.ActionData;
import info.nightscout.shared.weardata.EventData;
public class BolusActivity extends ViewSelectorActivity {
@ -63,8 +63,7 @@ public class BolusActivity extends ViewSelectorActivity {
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));
rxBus.send(new EventWearToMobile(new EventData.ActionBolusPreCheck(SafeParse.stringToDouble(editInsulin.editText.getText().toString()), 0)));
showToast(BolusActivity.this, R.string.action_bolus_confirmation);
finishAffinity();
});

View file

@ -10,11 +10,10 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobileAction;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.ActionData;
import info.nightscout.shared.weardata.EventData;
/**
* Created by adrian on 09/02/17.
@ -52,6 +51,7 @@ public class CPPActivity extends ViewSelectorActivity {
finish();
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -94,9 +94,9 @@ public class CPPActivity extends ViewSelectorActivity {
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)
ActionData.ProfileSwitch ps =
new ActionData.ProfileSwitch(SafeParse.stringToInt(editTimeshift.editText.getText().toString()), SafeParse.stringToInt(editPercentage.editText.getText().toString()));
rxBus.send(new EventWearToMobileAction(ps));
EventData.ActionProfileSwitchPreCheck ps =
new EventData.ActionProfileSwitchPreCheck(SafeParse.stringToInt(editTimeshift.editText.getText().toString()), SafeParse.stringToInt(editPercentage.editText.getText().toString()));
rxBus.send(new EventWearToMobile(ps));
showToast(CPPActivity.this, R.string.action_cpp_confirmation);
finishAffinity();
});
@ -108,7 +108,7 @@ public class CPPActivity extends ViewSelectorActivity {
@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

@ -10,9 +10,10 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.EventData;
public class CarbActivity extends ViewSelectorActivity {
@ -32,6 +33,7 @@ public class CarbActivity extends ViewSelectorActivity {
finish();
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -46,37 +48,42 @@ public class CarbActivity 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 (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"), true);
editCarbs = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, (double) maxCarbs, 1d, new DecimalFormat("0"), true);
setLabelToPlusMinusView(view, getString(R.string.action_carbs));
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) -> {
view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.action_send_item, container, false);
final ImageView confirmButton = view.findViewById(R.id.confirmbutton);
confirmButton.setOnClickListener((View v) -> {
// With start time 0 and duration 0
String actionstring = "ecarbs " + SafeParse.stringToInt(editCarbs.editText.getText().toString()) + " 0 0";
DataLayerListenerService.Companion.initiateAction(CarbActivity.this, actionstring);
EventData.ActionECarbsPreCheck bolus =
new EventData.ActionECarbsPreCheck(
SafeParse.stringToInt(editCarbs.editText.getText().toString()),
0,
0
);
rxBus.send(new EventWearToMobile(bolus));
showToast(CarbActivity.this, R.string.action_ecarb_confirmation);
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

@ -10,9 +10,10 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.EventData;
/**
* Created by adrian on 04/08/18.
@ -38,6 +39,7 @@ public class ECarbActivity extends ViewSelectorActivity {
finish();
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -86,16 +88,19 @@ public class ECarbActivity 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) -> {
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 = "ecarbs " + SafeParse.stringToInt(editCarbs.editText.getText().toString())
+ " " + SafeParse.stringToInt(editStartTime.editText.getText().toString())
+ " " + SafeParse.stringToInt(editDuration.editText.getText().toString());
DataLayerListenerService.Companion.initiateAction(ECarbActivity.this, actionstring);
EventData.ActionECarbsPreCheck bolus =
new EventData.ActionECarbsPreCheck(
SafeParse.stringToInt(editCarbs.editText.getText().toString()),
SafeParse.stringToInt(editStartTime.editText.getText().toString()),
SafeParse.stringToInt(editDuration.editText.getText().toString())
);
rxBus.send(new EventWearToMobile(bolus));
showToast(ECarbActivity.this, R.string.action_ecarb_confirmation);
finishAffinity();
@ -108,7 +113,7 @@ public class ECarbActivity extends ViewSelectorActivity {
@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

@ -10,9 +10,10 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.EventData;
/**
* Created by adrian on 09/02/17.
@ -34,6 +35,7 @@ public class FillActivity extends ViewSelectorActivity {
finish();
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -67,8 +69,7 @@ public class FillActivity extends ViewSelectorActivity {
//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());
DataLayerListenerService.Companion.initiateAction(FillActivity.this, actionstring);
rxBus.send(new EventWearToMobile(new EventData.ActionFillPreCheck(SafeParse.stringToDouble(editInsulin.editText.getText().toString()))));
showToast(FillActivity.this, R.string.action_fill_confirmation);
finishAffinity();
});

View file

@ -10,9 +10,10 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.EventData;
/**
* Created by adrian on 09/02/17.
@ -42,6 +43,7 @@ public class TempTargetActivity extends ViewSelectorActivity {
finish();
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -112,18 +114,20 @@ public class TempTargetActivity 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 = "temptarget"
+ " " + isMGDL
+ " " + SafeParse.stringToInt(time.editText.getText().toString())
+ " " + SafeParse.stringToDouble(lowRange.editText.getText().toString())
+ " " + (isSingleTarget ? SafeParse.stringToDouble(lowRange.editText.getText().toString()) : SafeParse.stringToDouble(highRange.editText.getText().toString()));
DataLayerListenerService.Companion.initiateAction(TempTargetActivity.this, actionstring);
EventData.ActionTempTargetPreCheck action = new EventData.ActionTempTargetPreCheck(
EventData.ActionTempTargetPreCheck.TempTargetCommand.MANUAL,
isMGDL,
SafeParse.stringToInt(time.editText.getText().toString()),
SafeParse.stringToDouble(lowRange.editText.getText().toString()),
(isSingleTarget ?
SafeParse.stringToDouble(lowRange.editText.getText().toString()) : SafeParse.stringToDouble(highRange.editText.getText().toString()))
);
rxBus.send(new EventWearToMobile(action));
showToast(TempTargetActivity.this, R.string.action_tempt_confirmation);
finishAffinity();
});
@ -135,7 +139,7 @@ public class TempTargetActivity extends ViewSelectorActivity {
@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

@ -10,11 +10,10 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobileAction;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.ActionData;
import info.nightscout.shared.weardata.EventData;
/**
* Created by adrian on 09/02/17.
@ -42,6 +41,7 @@ public class TreatmentActivity extends ViewSelectorActivity {
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -80,12 +80,12 @@ public class TreatmentActivity 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) -> {
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)
ActionData.Bolus bolus = new ActionData.Bolus(SafeParse.stringToDouble(editInsulin.editText.getText().toString()), SafeParse.stringToInt(editCarbs.editText.getText().toString()));
rxBus.send(new EventWearToMobileAction(bolus));
EventData.ActionBolusPreCheck bolus = new EventData.ActionBolusPreCheck(SafeParse.stringToDouble(editInsulin.editText.getText().toString()), SafeParse.stringToInt(editCarbs.editText.getText().toString()));
rxBus.send(new EventWearToMobile(bolus));
showToast(TreatmentActivity.this, R.string.action_treatment_confirmation);
finishAffinity();
});
@ -97,7 +97,7 @@ public class TreatmentActivity extends ViewSelectorActivity {
@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

@ -10,9 +10,10 @@ import android.widget.ImageView;
import java.text.DecimalFormat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.utils.PlusMinusEditText;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.EventData;
/**
* Created by adrian on 09/02/17.
@ -42,6 +43,7 @@ public class WizardActivity extends ViewSelectorActivity {
finish();
}
@SuppressWarnings("deprecation")
private class MyGridViewPagerAdapter extends GridPagerAdapter {
@Override
public int getColumnCount(int arg0) {
@ -82,15 +84,17 @@ public class WizardActivity 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) -> {
final ImageView confirmButton = view.findViewById(R.id.confirmbutton);
confirmButton.setOnClickListener((View v) -> {
if (editPercentage != null) {
percentage = SafeParse.stringToInt(editPercentage.editText.getText().toString());
}
String actionstring = "wizard2 " + SafeParse.stringToInt(editCarbs.editText.getText().toString())
+ " " + percentage;
DataLayerListenerService.Companion.initiateAction(WizardActivity.this, actionstring);
EventData.ActionWizardPreCheck action = new EventData.ActionWizardPreCheck(
SafeParse.stringToInt(editCarbs.editText.getText().toString()),
percentage
);
rxBus.send(new EventWearToMobile(action));
showToast(WizardActivity.this, R.string.action_wizard_confirmation);
finishAffinity();
});
@ -102,7 +106,7 @@ public class WizardActivity extends ViewSelectorActivity {
@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,51 +0,0 @@
package info.nightscout.androidaps.interaction.menus;
import android.content.Intent;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.actions.FillActivity;
import info.nightscout.androidaps.interaction.utils.MenuListActivity;
/**
* Created by adrian on 09/02/17.
*/
public class FillMenuActivity extends MenuListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTitle(R.string.menu_prime_fill);
super.onCreate(savedInstanceState);
}
@Override
protected List<MenuItem> getElements() {
List<MenuItem> menuItems = new ArrayList<>();
menuItems.add(new MenuItem(R.drawable.ic_canula, getString(R.string.action_preset_1)));
menuItems.add(new MenuItem(R.drawable.ic_canula, getString(R.string.action_preset_2)));
menuItems.add(new MenuItem(R.drawable.ic_canula, getString(R.string.action_preset_3)));
menuItems.add(new MenuItem(R.drawable.ic_canula, getString(R.string.action_free_amount)));
return menuItems;
}
@Override
protected void doAction(String action) {
if (getString(R.string.action_preset_1).equals(action)) {
DataLayerListenerService.Companion.initiateAction(this, "fillpreset 1");
} else if (getString(R.string.action_preset_2).equals(action)) {
DataLayerListenerService.Companion.initiateAction(this, "fillpreset 2");
} else if (getString(R.string.action_preset_3).equals(action)) {
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);
this.startActivity(intent);
}
}
}

View file

@ -0,0 +1,34 @@
package info.nightscout.androidaps.interaction.menus
import android.content.Intent
import android.os.Bundle
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventWearToMobile
import info.nightscout.androidaps.interaction.actions.FillActivity
import info.nightscout.androidaps.interaction.utils.MenuListActivity
import info.nightscout.shared.weardata.EventData
class FillMenuActivity : MenuListActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTitle(R.string.menu_prime_fill)
super.onCreate(savedInstanceState)
}
override fun getElements(): List<MenuItem> =
ArrayList<MenuItem>().apply {
add(MenuItem(R.drawable.ic_canula, getString(R.string.action_preset_1)))
add(MenuItem(R.drawable.ic_canula, getString(R.string.action_preset_2)))
add(MenuItem(R.drawable.ic_canula, getString(R.string.action_preset_3)))
add(MenuItem(R.drawable.ic_canula, getString(R.string.action_free_amount)))
}
override fun doAction(action: String) {
when (action) {
getString(R.string.action_preset_1) -> rxBus.send(EventWearToMobile(EventData.ActionFillPresetPreCheck(1)))
getString(R.string.action_preset_2) -> rxBus.send(EventWearToMobile(EventData.ActionFillPresetPreCheck(2)))
getString(R.string.action_preset_3) -> rxBus.send(EventWearToMobile(EventData.ActionFillPresetPreCheck(3)))
getString(R.string.action_free_amount) -> startActivity(Intent(this, FillActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
}
}
}

View file

@ -1,98 +0,0 @@
package info.nightscout.androidaps.interaction.menus;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.AAPSPreferences;
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;
/**
* Created by adrian on 09/02/17.
*/
public class MainMenuActivity extends MenuListActivity {
SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
sp = PreferenceManager.getDefaultSharedPreferences(this);
setTitle(R.string.label_actions_activity);
super.onCreate(savedInstanceState);
DataLayerListenerService.Companion.requestData(this);
}
@Override
protected List<MenuItem> getElements() {
List<MenuItem> menuItems = new ArrayList<>();
if (!sp.getBoolean("wearcontrol", false)) {
menuItems.add(new MenuItem(R.drawable.ic_settings, getString(R.string.menu_settings)));
menuItems.add(new MenuItem(R.drawable.ic_sync, getString(R.string.menu_resync)));
return menuItems;
}
boolean showPrimeFill = sp.getBoolean("primefill", false);
boolean showWizard = sp.getBoolean("showWizard", true);
if (showWizard) menuItems.add(new MenuItem(R.drawable.ic_calculator, getString(R.string.menu_wizard)));
menuItems.add(new MenuItem(R.drawable.ic_e_carbs, getString(R.string.menu_ecarb)));
menuItems.add(new MenuItem(R.drawable.ic_treatment, getString(R.string.menu_treatment)));
menuItems.add(new MenuItem(R.drawable.ic_temptarget, getString(R.string.menu_tempt)));
menuItems.add(new MenuItem(R.drawable.ic_settings, getString(R.string.menu_settings)));
menuItems.add(new MenuItem(R.drawable.ic_status, getString(R.string.menu_status)));
if (showPrimeFill) menuItems.add(new MenuItem(R.drawable.ic_canula, getString(R.string.menu_prime_fill)));
return menuItems;
}
@Override
protected void doAction(String action) {
Intent intent;
if (getString(R.string.menu_settings).equals(action)) {
intent = new Intent(this, AAPSPreferences.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
} else if (getString(R.string.menu_resync).equals(action)) {
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);
this.startActivity(intent);
} else if (getString(R.string.menu_treatment).equals(action)) {
intent = new Intent(this, TreatmentActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
} else if (getString(R.string.menu_wizard).equals(action)) {
intent = new Intent(this, WizardActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
} else if (getString(R.string.menu_status).equals(action)) {
intent = new Intent(this, StatusMenuActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
} else if (getString(R.string.menu_prime_fill).equals(action)) {
intent = new Intent(this, FillMenuActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
} else if (getString(R.string.menu_ecarb).equals(action)) {
intent = new Intent(this, ECarbActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
}
}
}

View file

@ -0,0 +1,56 @@
package info.nightscout.androidaps.interaction.menus
import android.content.Intent
import android.os.Bundle
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventWearToMobile
import info.nightscout.androidaps.interaction.AAPSPreferences
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
import info.nightscout.shared.weardata.EventData
import info.nightscout.shared.weardata.EventData.ActionResendData
class MainMenuActivity : MenuListActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTitle(R.string.label_actions_activity)
super.onCreate(savedInstanceState)
rxBus.send(EventWearToMobile(ActionResendData("MainMenuListActivity")))
}
override fun getElements(): List<MenuItem> =
ArrayList<MenuItem>().apply {
if (!sp.getBoolean(R.string.key_wear_control, false)) {
add(MenuItem(R.drawable.ic_settings, getString(R.string.menu_settings)))
add(MenuItem(R.drawable.ic_sync, getString(R.string.menu_resync)))
} else {
if (sp.getBoolean(R.string.key_show_wizard, true))
add(MenuItem(R.drawable.ic_calculator, getString(R.string.menu_wizard)))
add(MenuItem(R.drawable.ic_e_carbs, getString(R.string.menu_ecarb)))
add(MenuItem(R.drawable.ic_treatment, getString(R.string.menu_treatment)))
add(MenuItem(R.drawable.ic_temptarget, getString(R.string.menu_tempt)))
add(MenuItem(R.drawable.ic_status, getString(R.string.status_cpp)))
add(MenuItem(R.drawable.ic_settings, getString(R.string.menu_settings)))
add(MenuItem(R.drawable.ic_status, getString(R.string.menu_status)))
if (sp.getBoolean(R.string.key_prime_fill, false))
add(MenuItem(R.drawable.ic_canula, getString(R.string.menu_prime_fill)))
}
}
override fun doAction(action: String) {
when (action) {
getString(R.string.menu_settings) -> startActivity(Intent(this, AAPSPreferences::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
getString(R.string.menu_resync) -> rxBus.send(EventWearToMobile(ActionResendData("Re-Sync")))
getString(R.string.status_cpp) -> rxBus.send(EventWearToMobile(EventData.ActionProfileSwitchSendInitialData(System.currentTimeMillis())))
getString(R.string.menu_tempt) -> startActivity(Intent(this, TempTargetActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
getString(R.string.menu_treatment) -> startActivity(Intent(this, TreatmentActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
getString(R.string.menu_wizard) -> startActivity(Intent(this, WizardActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
getString(R.string.menu_status) -> startActivity(Intent(this, StatusMenuActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
getString(R.string.menu_prime_fill) -> startActivity(Intent(this, FillMenuActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
getString(R.string.menu_ecarb) -> startActivity(Intent(this, ECarbActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
}
}
}

View file

@ -1,47 +0,0 @@
package info.nightscout.androidaps.interaction.menus;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.List;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.interaction.utils.MenuListActivity;
/**
* Created by adrian on 09/02/17.
*/
public class StatusMenuActivity extends MenuListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTitle(R.string.menu_status);
super.onCreate(savedInstanceState);
}
@Override
protected List<MenuItem> getElements() {
List<MenuItem> menuitems = new ArrayList<>();
menuitems.add(new MenuItem(R.drawable.ic_status, getString(R.string.status_pump)));
menuitems.add(new MenuItem(R.drawable.ic_loop_closed, getString(R.string.status_loop)));
menuitems.add(new MenuItem(R.drawable.ic_status, getString(R.string.status_cpp)));
menuitems.add(new MenuItem(R.drawable.ic_tdd, getString(R.string.status_tdd)));
return menuitems;
}
@Override
protected void doAction(String action) {
if (getString(R.string.status_pump).equals(action)) {
DataLayerListenerService.Companion.initiateAction(this, "status pump");
} else if (getString(R.string.status_loop).equals(action)) {
DataLayerListenerService.Companion.initiateAction(this, "status loop");
} else if (getString(R.string.status_cpp).equals(action)) {
DataLayerListenerService.Companion.initiateAction(this, "opencpp");
} else if (getString(R.string.status_tdd).equals(action)) {
DataLayerListenerService.Companion.initiateAction(this, "tddstats");
}
}
}

View file

@ -0,0 +1,32 @@
package info.nightscout.androidaps.interaction.menus
import android.os.Bundle
import info.nightscout.androidaps.R
import info.nightscout.androidaps.events.EventWearToMobile
import info.nightscout.androidaps.interaction.utils.MenuListActivity
import info.nightscout.shared.weardata.EventData.ActionLoopStatus
import info.nightscout.shared.weardata.EventData.ActionPumpStatus
import info.nightscout.shared.weardata.EventData.ActionTddStatus
class StatusMenuActivity : MenuListActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTitle(R.string.menu_status)
super.onCreate(savedInstanceState)
}
override fun getElements(): List<MenuItem> =
ArrayList<MenuItem>().apply {
add(MenuItem(R.drawable.ic_status, getString(R.string.status_pump)))
add(MenuItem(R.drawable.ic_loop_closed, getString(R.string.status_loop)))
add(MenuItem(R.drawable.ic_tdd, getString(R.string.status_tdd)))
}
override fun doAction(action: String) {
when (action) {
getString(R.string.status_pump) -> rxBus.send(EventWearToMobile(ActionPumpStatus(System.currentTimeMillis())))
getString(R.string.status_loop) -> rxBus.send(EventWearToMobile(ActionLoopStatus(System.currentTimeMillis())))
getString(R.string.status_tdd) -> rxBus.send(EventWearToMobile(ActionTddStatus(System.currentTimeMillis())))
}
}
}

View file

@ -65,27 +65,27 @@ public class DisplayFormat {
public String shortTrend(final RawDisplayData raw) {
String minutes = "--";
if (raw.datetime > 0) {
minutes = shortTimeSince(raw.datetime);
if (raw.getSingleBg().getTimeStamp() > 0) {
minutes = shortTimeSince(raw.getSingleBg().getTimeStamp());
}
if (minutes.length() + raw.sDelta.length() + deltaSymbol().length() + 1 <= MAX_FIELD_LEN_SHORT) {
return minutes + " " + deltaSymbol() + raw.sDelta;
if (minutes.length() + raw.getSingleBg().getDelta().length() + deltaSymbol().length() + 1 <= MAX_FIELD_LEN_SHORT) {
return minutes + " " + deltaSymbol() + raw.getSingleBg().getDelta();
}
// that only optimizes obvious things like 0 before . or at end, + at beginning
String delta = (new SmallestDoubleString(raw.sDelta)).minimise(MAX_FIELD_LEN_SHORT -1);
String delta = (new SmallestDoubleString(raw.getSingleBg().getDelta())).minimise(MAX_FIELD_LEN_SHORT -1);
if (minutes.length() + delta.length() + deltaSymbol().length() + 1 <= MAX_FIELD_LEN_SHORT) {
return minutes + " " + deltaSymbol() + delta;
}
String shortDelta = (new SmallestDoubleString(raw.sDelta)).minimise(MAX_FIELD_LEN_SHORT -(1+minutes.length()));
String shortDelta = (new SmallestDoubleString(raw.getSingleBg().getDelta())).minimise(MAX_FIELD_LEN_SHORT -(1+minutes.length()));
return minutes + " " + shortDelta;
}
public String longGlucoseLine(final RawDisplayData raw) {
return raw.sSgv + raw.sDirection + " " + deltaSymbol() + (new SmallestDoubleString(raw.sDelta)).minimise(8) + " (" + shortTimeSince(raw.datetime) + ")";
return raw.getSingleBg().getSgvString() + raw.getSingleBg().getSlopeArrow() + " " + deltaSymbol() + (new SmallestDoubleString(raw.getSingleBg().getDelta())).minimise(8) + " (" + shortTimeSince(raw.getSingleBg().getTimeStamp()) + ")";
}
public String longDetailsLine(final RawDisplayData raw) {
@ -95,40 +95,41 @@ public class DisplayFormat {
final int SEP_SHORT_LEN = SEP_SHORT.length();
final String SEP_MIN = " ";
String line = raw.sCOB2 + SEP_LONG + raw.sIOB1 + SEP_LONG + basalRateSymbol()+raw.sBasalRate;
String line =
raw.getStatus().getCob() + SEP_LONG + raw.getStatus().getIobSum() + SEP_LONG + basalRateSymbol()+raw.getStatus().getCurrentBasal();
if (line.length() <= MAX_FIELD_LEN_LONG) {
return line;
}
line = raw.sCOB2 + SEP_SHORT + raw.sIOB1 + SEP_SHORT + raw.sBasalRate;
line = raw.getStatus().getCob() + SEP_SHORT + raw.getStatus().getIobSum() + SEP_SHORT + raw.getStatus().getCurrentBasal();
if (line.length() <= MAX_FIELD_LEN_LONG) {
return line;
}
int remainingMax = MAX_FIELD_LEN_LONG - (raw.sCOB2.length() + raw.sBasalRate.length() + SEP_SHORT_LEN*2);
final String smallestIoB = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(Math.max(MIN_FIELD_LEN_IOB, remainingMax));
line = raw.sCOB2 + SEP_SHORT + smallestIoB + SEP_SHORT + raw.sBasalRate;
int remainingMax = MAX_FIELD_LEN_LONG - (raw.getStatus().getCob().length() + raw.getStatus().getCurrentBasal().length() + SEP_SHORT_LEN*2);
final String smallestIoB = new SmallestDoubleString(raw.getStatus().getIobSum(), SmallestDoubleString.Units.USE).minimise(Math.max(MIN_FIELD_LEN_IOB, remainingMax));
line = raw.getStatus().getCob() + SEP_SHORT + smallestIoB + SEP_SHORT + raw.getStatus().getCurrentBasal();
if (line.length() <= MAX_FIELD_LEN_LONG) {
return line;
}
remainingMax = MAX_FIELD_LEN_LONG - (smallestIoB.length() + raw.sBasalRate.length() + SEP_SHORT_LEN*2);
final String simplifiedCob = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE).minimise(Math.max(MIN_FIELD_LEN_COB, remainingMax));
remainingMax = MAX_FIELD_LEN_LONG - (smallestIoB.length() + raw.getStatus().getCurrentBasal().length() + SEP_SHORT_LEN*2);
final String simplifiedCob = new SmallestDoubleString(raw.getStatus().getCob(), SmallestDoubleString.Units.USE).minimise(Math.max(MIN_FIELD_LEN_COB, remainingMax));
line = simplifiedCob + SEP_SHORT + smallestIoB + SEP_SHORT + raw.sBasalRate;
line = simplifiedCob + SEP_SHORT + smallestIoB + SEP_SHORT + raw.getStatus().getCurrentBasal();
if (line.length() <= MAX_FIELD_LEN_LONG) {
return line;
}
line = simplifiedCob + SEP_MIN + smallestIoB + SEP_MIN + raw.sBasalRate;
line = simplifiedCob + SEP_MIN + smallestIoB + SEP_MIN + raw.getStatus().getCurrentBasal();
return line;
}
public Pair<String, String> detailedIob(RawDisplayData raw) {
final String iob1 = new SmallestDoubleString(raw.sIOB1, SmallestDoubleString.Units.USE).minimise(MAX_FIELD_LEN_SHORT);
final String iob1 = new SmallestDoubleString(raw.getStatus().getIobSum(), SmallestDoubleString.Units.USE).minimise(MAX_FIELD_LEN_SHORT);
String iob2 = "";
if (raw.sIOB2.contains("|")) {
String[] iobs = raw.sIOB2.replace("(", "").replace(")", "").split("\\|");
if (raw.getStatus().getIobDetail().contains("|")) {
String[] iobs = raw.getStatus().getIobDetail().replace("(", "").replace(")", "").split("\\|");
String iobBolus = new SmallestDoubleString(iobs[0]).minimise(MIN_FIELD_LEN_IOB);
if (iobBolus.trim().length() == 0) {
@ -144,7 +145,7 @@ public class DisplayFormat {
}
public Pair<String, String> detailedCob(final RawDisplayData raw) {
SmallestDoubleString cobMini = new SmallestDoubleString(raw.sCOB2, SmallestDoubleString.Units.USE);
SmallestDoubleString cobMini = new SmallestDoubleString(raw.getStatus().getCob(), SmallestDoubleString.Units.USE);
String cob2 = "";
if (cobMini.getExtra().length() > 0) {

View file

@ -1,6 +1,5 @@
package info.nightscout.androidaps.interaction.utils;
import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -9,6 +8,7 @@ import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.wear.widget.CurvedTextView;
import androidx.wear.widget.WearableLinearLayoutManager;
@ -16,13 +16,22 @@ import androidx.wear.widget.WearableRecyclerView;
import java.util.List;
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 08/02/17.
*/
public abstract class MenuListActivity extends Activity {
public abstract class MenuListActivity extends DaggerActivity {
@Inject public RxBus rxBus;
@Inject public SP sp;
List<MenuItem> elements;
protected abstract List<MenuItem> getElements();
@ -96,7 +105,7 @@ public abstract class MenuListActivity extends Activity {
}
}
@Override
@NonNull @Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);

View file

@ -3,22 +3,20 @@ package info.nightscout.androidaps.interaction.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.Nullable;
import com.google.android.gms.wearable.DataMap;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
import info.nightscout.androidaps.Aaps;
import info.nightscout.androidaps.complications.BaseComplicationProviderService;
import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import info.nightscout.shared.weardata.EventData;
/**
* Created by dlvoy on 2019-11-12
@ -26,7 +24,6 @@ import info.nightscout.shared.logging.LTag;
@Singleton
public class Persistence {
private final Context context;
private final AAPSLogger aapsLogger;
private final WearUtil wearUtil;
private final SharedPreferences preferences;
@ -35,7 +32,6 @@ public class Persistence {
@Inject
public Persistence(Context context, AAPSLogger aapsLogger, WearUtil wearUtil) {
this.context = context;
this.aapsLogger = aapsLogger;
this.wearUtil = wearUtil;
preferences = context.getSharedPreferences(COMPLICATION_PROVIDER_PREFERENCES_FILE_KEY, 0);
@ -51,24 +47,6 @@ public class Persistence {
return Base64.encodeToString(input, flags);
}
@Nullable
public DataMap getDataMap(String key) {
if (preferences.contains(key)) {
final String rawB64Data = preferences.getString(key, null);
byte[] rawData = base64decode(rawB64Data, Base64.DEFAULT);
try {
return DataMap.fromByteArray(rawData);
} catch (IllegalArgumentException ex) {
// Should never happen, and if it happen - we ignore and fallback to null
}
}
return null;
}
public void putDataMap(String key, DataMap dataMap) {
preferences.edit().putString(key, base64encodeToString(dataMap.toByteArray(), Base64.DEFAULT)).apply();
}
public String getString(String key, String defaultValue) {
return preferences.getString(key, defaultValue);
}
@ -109,11 +87,67 @@ public class Persistence {
putString(key, joinSet(set, "|"));
}
public void storeDataMap(String key, DataMap dataMap) {
putDataMap(key, dataMap);
public void store(EventData.SingleBg singleBg) {
putString(RawDisplayData.BG_DATA_PERSISTENCE_KEY, singleBg.serialize());
markDataUpdated();
}
@Nullable
public EventData.SingleBg readSingleBg() {
try {
String s = getString(RawDisplayData.BG_DATA_PERSISTENCE_KEY, null);
if (s != null) {
return (EventData.SingleBg) EventData.Companion.deserialize(s);
}
} catch (Exception ignored) {}
return null;
}
@Nullable
public EventData.Status readStatus() {
try {
String s = getString(RawDisplayData.STATUS_PERSISTENCE_KEY, null);
if (s != null) {
return (EventData.Status) EventData.Companion.deserialize(s);
}
} catch (Exception ignored) {}
return null;
}
@Nullable
public EventData.TreatmentData readTreatments() {
try {
String s = getString(RawDisplayData.BASALS_PERSISTENCE_KEY, null);
if (s != null) {
return (EventData.TreatmentData) EventData.Companion.deserialize(s);
}
} catch (Exception ignored) {}
return null;
}
@Nullable
public EventData.GraphData readGraphData() {
try {
String s = getString(RawDisplayData.BG_DATA_PERSISTENCE_KEY, null);
if (s != null) {
return (EventData.GraphData) EventData.Companion.deserialize(s);
}
} catch (Exception ignored) {}
return null;
}
public void store(EventData.GraphData graphData) {
putString(RawDisplayData.GRAPH_DATA_PERSISTENCE_KEY, graphData.serialize());
}
public void store(EventData.TreatmentData treatmentData) {
putString(RawDisplayData.BASALS_PERSISTENCE_KEY, treatmentData.serialize());
}
public void store(EventData.Status status) {
putString(RawDisplayData.STATUS_PERSISTENCE_KEY, status.serialize());
}
public String joinSet(Set<String> set, String separator) {
StringBuilder sb = new StringBuilder();
int i = 0;

View file

@ -2,93 +2,64 @@ package info.nightscout.androidaps.tile
import android.content.Context
import android.content.res.Resources
import android.util.Base64
import android.util.Log
import androidx.preference.PreferenceManager
import com.google.android.gms.wearable.DataMap
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interaction.actions.BackgroundActionActivity
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.EventData
import java.util.*
object QuickWizardSource : TileSource {
override fun getSelectedActions(context: Context): List<Action> {
override fun getSelectedActions(context: Context, sp: SP, aapsLogger: AAPSLogger): List<Action> {
val quickList = mutableListOf<Action>()
val quickMap = getDataMap(context)
val quickMap = getQuickWizardData(sp)
val sfm = secondsFromMidnight()
for (quick in quickMap) {
val validFrom = quick.getInt("from", 0)
val validTo = quick.getInt("to", 0)
val isActive = sfm in validFrom..validTo
val guid = quick.getString("guid", "")
if (isActive && guid != "") {
for (quick in quickMap.entries) {
val isActive = sfm in quick.validFrom..quick.validTo
if (isActive && quick.guid.isNotEmpty()) {
quickList.add(
Action(
buttonText = quick.getString("button_text", "?"),
buttonTextSub = "${quick.getInt("carbs", 0)} g",
buttonText = quick.buttonText,
buttonTextSub = "${quick.carbs} g",
iconRes = R.drawable.ic_quick_wizard,
activityClass = BackgroundActionActivity::class.java.name,
actionString = "quick_wizard $guid",
message = context.resources.getString(R.string.action_quick_wizard_confirmation),
action = EventData.ActionQuickWizardPreCheck(quick.guid),
message = context.resources.getString(R.string.action_quick_wizard_confirmation)
)
)
Log.i(TAG, "getSelectedActions: active " + quick.getString("button_text", "?") + " guid=" + guid)
aapsLogger.info(LTag.WEAR, """getSelectedActions: active ${quick.buttonText} guid=${quick.guid}""")
} else {
Log.i(TAG, "getSelectedActions: not active " + quick.getString("button_text", "?") + " guid=" + guid)
aapsLogger.info(LTag.WEAR, """getSelectedActions: not active ${quick.buttonText} guid=${quick.guid}""")
}
}
return quickList
}
override fun getValidFor(context: Context): Long? {
val quickMap = getDataMap(context)
if (quickMap.size == 0) {
return null
}
override fun getValidFor(sp: SP): Long? {
val quickMap = getQuickWizardData(sp)
if (quickMap.entries.size == 0) return null
val sfm = secondsFromMidnight()
var validTill = 24 * 60 * 60
for (quick in quickMap) {
val validFrom = quick.getInt("from", 0)
val validTo = quick.getInt("to", 0)
val isActive = sfm in validFrom..validTo
val guid = quick.getString("guid", "")
Log.i(TAG, "valid: " + validFrom + "-" + validTo)
if (guid != "") {
if (isActive && validTill > validTo) {
validTill = validTo
}
if (validFrom > sfm && validTill > validFrom) {
validTill = validFrom
}
for (quick in quickMap.entries) {
val isActive = sfm in quick.validFrom..quick.validTo
if (quick.guid.isNotEmpty()) {
if (isActive && validTill > quick.validTo) validTill = quick.validTo
if (quick.validFrom in (sfm + 1) until validTill) validTill = quick.validFrom
}
}
val validWithin = 60
val delta = (validTill - sfm + validWithin) * 1000L
Log.i(TAG, "getValidTill: sfm" + sfm + " till" + validTill + " d=" + delta)
return delta
//aapsLogger.info(LTag.WEAR, "getValidTill: sfm$sfm till$validTill d=$delta")
return (validTill - sfm + validWithin) * 1000L
}
private fun getDataMap(context: Context): ArrayList<DataMap> {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
val key = context.resources.getString(R.string.key_quick_wizard_data_map)
if (sharedPrefs.contains(key)) {
val rawB64Data: String? = sharedPrefs.getString(key, null)
val rawData: ByteArray = Base64.decode(rawB64Data, Base64.DEFAULT)
try {
val map = DataMap.fromByteArray(rawData)
return map.getDataMapArrayList("quick_wizard")
} catch (ex: IllegalArgumentException) {
Log.e(TAG, "getSelectedActions: IllegalArgumentException ", ex)
}
}
return arrayListOf()
}
private fun getQuickWizardData(sp: SP): EventData.QuickWizard =
EventData.deserialize(sp.getString(R.string.key_quick_wizard_data, EventData.QuickWizard(arrayListOf()).serialize())) as EventData.QuickWizard
private fun secondsFromMidnight(): Int {
val c = Calendar.getInstance()
@ -101,8 +72,5 @@ object QuickWizardSource : TileSource {
return (passed / 1000).toInt()
}
override fun getResourceReferences(resources: Resources): List<Int> {
return listOf(R.drawable.ic_quick_wizard)
}
override fun getResourceReferences(resources: Resources): List<Int> = listOf(R.drawable.ic_quick_wizard)
}

View file

@ -5,6 +5,9 @@ import android.content.SharedPreferences
import android.content.res.Resources
import androidx.annotation.DrawableRes
import androidx.preference.PreferenceManager
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.EventData
class StaticAction(
val settingName: String,
@ -12,9 +15,9 @@ class StaticAction(
buttonTextSub: String? = null,
activityClass: String,
@DrawableRes iconRes: Int,
actionString: String? = null,
action: EventData? = null,
message: String? = null,
) : Action(buttonText, buttonTextSub, activityClass, iconRes, actionString, message)
) : Action(buttonText, buttonTextSub, activityClass, iconRes, action, message)
abstract class StaticTileSource : TileSource {
@ -23,7 +26,7 @@ abstract class StaticTileSource : TileSource {
abstract val preferencePrefix: String
abstract fun getDefaultConfig(): Map<String, String>
override fun getSelectedActions(context: Context): List<Action> {
override fun getSelectedActions(context: Context, sp: SP, aapsLogger: AAPSLogger): List<Action> {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
setDefaultSettings(sharedPrefs)
@ -40,7 +43,7 @@ abstract class StaticTileSource : TileSource {
return actionList
}
override fun getValidFor(context: Context): Long? = null
override fun getValidFor(sp: SP): Long? = null
private fun getActionFromPreference(resources: Resources, sharedPrefs: SharedPreferences, index: Int): Action? {
val actionPref = sharedPrefs.getString(preferencePrefix + index, "none")

View file

@ -4,6 +4,7 @@ import android.content.res.Resources
import info.nightscout.androidaps.R
import info.nightscout.androidaps.interaction.actions.BackgroundActionActivity
import info.nightscout.androidaps.interaction.actions.TempTargetActivity
import info.nightscout.shared.weardata.EventData
object TempTargetSource : StaticTileSource() {
@ -19,7 +20,8 @@ object TempTargetSource : StaticTileSource() {
activityClass = BackgroundActionActivity::class.java.name,
message = message,
// actionString = "temptarget false 90 8.0 8.0",
actionString = "temptarget preset activity",
// actionString = "temptarget preset activity",
action = EventData.ActionTempTargetPreCheck(EventData.ActionTempTargetPreCheck.TempTargetCommand.PRESET_ACTIVITY)
),
StaticAction(
settingName = "eating_soon",
@ -28,7 +30,8 @@ object TempTargetSource : StaticTileSource() {
activityClass = BackgroundActionActivity::class.java.name,
message = message,
// actionString = "temptarget false 45 4.5 4.5",
actionString = "temptarget preset eating",
// actionString = "temptarget preset eating",
action = EventData.ActionTempTargetPreCheck(EventData.ActionTempTargetPreCheck.TempTargetCommand.PRESET_EATING)
),
StaticAction(
settingName = "hypo",
@ -37,13 +40,15 @@ object TempTargetSource : StaticTileSource() {
activityClass = BackgroundActionActivity::class.java.name,
message = message,
// actionString = "temptarget false 45 7.0 7.0",
actionString = "temptarget preset hypo",
// actionString = "temptarget preset hypo",
action = EventData.ActionTempTargetPreCheck(EventData.ActionTempTargetPreCheck.TempTargetCommand.PRESET_HYPO)
),
StaticAction(
settingName = "manual",
buttonText = resources.getString(R.string.temp_target_manual),
iconRes = R.drawable.ic_target_manual,
activityClass = TempTargetActivity::class.java.name,
action = null
),
StaticAction(
settingName = "cancel",
@ -51,7 +56,8 @@ object TempTargetSource : StaticTileSource() {
iconRes = R.drawable.ic_target_cancel,
activityClass = BackgroundActionActivity::class.java.name,
message = message,
actionString = "temptarget cancel",
//actionString = "temptarget cancel",
action = EventData.ActionTempTargetPreCheck(EventData.ActionTempTargetPreCheck.TempTargetCommand.CANCEL)
)
)
}

View file

@ -5,11 +5,10 @@ import android.os.Build
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import androidx.wear.tiles.ActionBuilders
import androidx.wear.tiles.ColorBuilders.argb
import androidx.wear.tiles.DeviceParametersBuilders.SCREEN_SHAPE_ROUND
import androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters
import androidx.wear.tiles.DeviceParametersBuilders.SCREEN_SHAPE_ROUND
import androidx.wear.tiles.DimensionBuilders.SpProp
import androidx.wear.tiles.DimensionBuilders.dp
import androidx.wear.tiles.DimensionBuilders.sp
@ -29,11 +28,17 @@ import androidx.wear.tiles.TileService
import androidx.wear.tiles.TimelineBuilders.Timeline
import androidx.wear.tiles.TimelineBuilders.TimelineEntry
import com.google.common.util.concurrent.ListenableFuture
import dagger.android.AndroidInjection
import info.nightscout.androidaps.R
import info.nightscout.androidaps.comm.DataLayerListenerServiceWear
import info.nightscout.shared.logging.AAPSLogger
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.weardata.EventData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.guava.future
import javax.inject.Inject
import kotlin.math.sqrt
private const val SPACING_ACTIONS = 3f
@ -44,8 +49,8 @@ private const val LARGE_SCREEN_WIDTH_DP = 210
interface TileSource {
fun getResourceReferences(resources: android.content.res.Resources): List<Int>
fun getSelectedActions(context: Context): List<Action>
fun getValidFor(context: Context): Long?
fun getSelectedActions(context: Context, sp: SP, aapsLogger: AAPSLogger): List<Action>
fun getValidFor(sp: SP): Long?
}
open class Action(
@ -53,7 +58,7 @@ open class Action(
val buttonTextSub: String? = null,
val activityClass: String,
@DrawableRes val iconRes: Int,
val actionString: String? = null,
val action: EventData? = null,
val message: String? = null,
)
@ -63,12 +68,21 @@ enum class WearControl {
abstract class TileBase : TileService() {
@Inject lateinit var sp: SP
@Inject lateinit var aapsLogger: AAPSLogger
abstract val resourceVersion: String
abstract val source: TileSource
private val serviceJob = Job()
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
// Not derived from DaggerService, do injection here
override fun onCreate() {
AndroidInjection.inject(this)
super.onCreate()
}
override fun onTileRequest(
requestParams: RequestBuilders.TileRequest
): ListenableFuture<Tile> = serviceScope.future {
@ -93,11 +107,11 @@ abstract class TileBase : TileService() {
private fun getSelectedActions(): List<Action> {
// TODO check why thi scan not be don in scope of the coroutine
return source.getSelectedActions(this)
return source.getSelectedActions(this, sp, aapsLogger)
}
private fun validFor(): Long? {
return source.getValidFor(this)
return source.getValidFor(sp)
}
@RequiresApi(Build.VERSION_CODES.N)
@ -172,13 +186,13 @@ abstract class TileBase : TileService() {
val builder = ActionBuilders.AndroidActivity.Builder()
.setClassName(action.activityClass)
.setPackageName(this.packageName)
if (action.actionString != null) {
val actionString = ActionBuilders.AndroidStringExtra.Builder().setValue(action.actionString).build()
builder.addKeyToExtraMapping("actionString", actionString)
if (action.action != null) {
val actionString = ActionBuilders.AndroidStringExtra.Builder().setValue(action.action.serialize()).build()
builder.addKeyToExtraMapping(DataLayerListenerServiceWear.KEY_ACTION, actionString)
}
if (action.message != null) {
val message = ActionBuilders.AndroidStringExtra.Builder().setValue(action.message).build()
builder.addKeyToExtraMapping("message", message)
builder.addKeyToExtraMapping(DataLayerListenerServiceWear.KEY_MESSAGE, message)
}
return ActionBuilders.LaunchAction.Builder()
@ -197,12 +211,8 @@ abstract class TileBase : TileService() {
Modifiers.Builder()
.setBackground(
Background.Builder()
.setColor(
argb(ContextCompat.getColor(baseContext, BUTTON_COLOR))
)
.setCorner(
Corner.Builder().setRadius(dp(circleDiameter / 2)).build()
)
.setColor(argb(ContextCompat.getColor(baseContext, BUTTON_COLOR)))
.setCorner(Corner.Builder().setRadius(dp(circleDiameter / 2)).build())
.build()
)
.setSemantics(
@ -239,9 +249,7 @@ abstract class TileBase : TileService() {
.setFontStyle(
FontStyle.Builder()
.setWeight(FONT_WEIGHT_BOLD)
.setColor(
argb(ContextCompat.getColor(baseContext, R.color.white))
)
.setColor(argb(ContextCompat.getColor(baseContext, R.color.white)))
.setSize(buttonTextSize(deviceParameters, text))
.build()
)
@ -253,9 +261,7 @@ abstract class TileBase : TileService() {
.setText(textSub)
.setFontStyle(
FontStyle.Builder()
.setColor(
argb(ContextCompat.getColor(baseContext, R.color.white))
)
.setColor(argb(ContextCompat.getColor(baseContext, R.color.white)))
.setSize(buttonTextSize(deviceParameters, textSub))
.build()
)
@ -283,11 +289,10 @@ abstract class TileBase : TileService() {
}
private fun getWearControl(): WearControl {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
if (!sharedPrefs.contains("wearcontrol")) {
if (!sp.contains("wearcontrol")) {
return WearControl.NO_DATA
}
val wearControlPref = sharedPrefs.getBoolean("wearcontrol", false)
val wearControlPref = sp.getBoolean("wearcontrol", false)
if (wearControlPref) {
return WearControl.ENABLED
}

View file

@ -1,29 +1,18 @@
package info.nightscout.androidaps.watchfaces;
import android.content.BroadcastReceiver;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.watchface.WatchFaceStyle;
import android.text.format.DateFormat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
@ -32,7 +21,8 @@ import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.google.android.gms.wearable.DataMap;
import androidx.core.content.ContextCompat;
import com.ustwo.clockwise.common.WatchFaceTime;
import com.ustwo.clockwise.common.WatchMode;
import com.ustwo.clockwise.common.WatchShape;
@ -40,25 +30,43 @@ import com.ustwo.clockwise.wearable.WatchFace;
import java.util.ArrayList;
import javax.inject.Inject;
import dagger.android.AndroidInjection;
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.DataLayerListenerService;
import info.nightscout.androidaps.data.TempWatchData;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
import info.nightscout.androidaps.plugins.bus.RxBus;
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 info.nightscout.shared.weardata.EventData;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import lecho.lib.hellocharts.view.LineChartView;
/**
* Created by adrianLxM.
*/
public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPreferenceChangeListener {
public final static IntentFilter INTENT_FILTER;
public static final int SCREENSIZE_SMALL = 280;
public TextView mTime, mSgv, mTimestamp, mDelta, mAvgDelta;
public RelativeLayout mRelativeLayout;
public long sgvLevel = 0;
public int batteryLevel = 1;
@SuppressWarnings("deprecation")
public class BIGChart extends WatchFace {
@Inject RxBus rxBus;
@Inject AapsSchedulers aapsSchedulers;
@Inject AAPSLogger aapsLogger;
@Inject SP sp;
CompositeDisposable disposable = new CompositeDisposable();
private EventData.SingleBg singleBg;
private EventData.Status status;
private EventData.TreatmentData treatmentData;
private EventData.GraphData graphData;
private static final int SCREEN_SIZE_SMALL = 280;
private TextView mTime, mSgv, mTimestamp, mDelta, mAvgDelta;
private RelativeLayout mRelativeLayout;
public int ageLevel = 1;
public int highColor = Color.YELLOW;
public int lowColor = Color.RED;
@ -73,36 +81,21 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
public boolean layoutSet = false;
public BgGraphBuilder bgGraphBuilder;
public LineChartView chart;
public long datetime;
public ArrayList<BgWatchData> bgDataList = new ArrayList<>();
public ArrayList<TempWatchData> tempWatchDataList = new ArrayList<>();
public ArrayList<BasalWatchData> basalWatchDataList = new ArrayList<>();
public ArrayList<BolusWatchData> bolusWatchDataList = new ArrayList<>();
public ArrayList<BgWatchData> predictionList = new ArrayList<>();
public ArrayList<EventData.SingleBg> bgDataList = new ArrayList<>();
public PowerManager.WakeLock wakeLock;
public View layoutView;
private final Point displaySize = new Point();
private int specW, specH;
private int animationAngle = 0;
private boolean isAnimated = false;
private LocalBroadcastManager localBroadcastManager;
private MessageReceiver messageReceiver;
protected SharedPreferences sharedPrefs;
private String rawString = "000 | 000 | 000";
private String batteryString = "--";
private String sgvString = "--";
private String externalStatusString = "no status";
private String cobString = "";
private TextView statusView;
private long chartTapTime = 0L;
private long sgvTapTime = 0L;
@SuppressLint("InflateParams")
@Override
public void onCreate() {
AndroidInjection.inject(this);
super.onCreate();
Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
@ -113,17 +106,71 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
View.MeasureSpec.EXACTLY);
specH = View.MeasureSpec.makeMeasureSpec(displaySize.y,
View.MeasureSpec.EXACTLY);
sharedPrefs = PreferenceManager
.getDefaultSharedPreferences(this);
sharedPrefs.registerOnSharedPreferenceChangeListener(this);
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
DisplayMetrics metrics = getResources().getDisplayMetrics();
if(metrics.widthPixels < SCREENSIZE_SMALL || metrics.heightPixels < SCREENSIZE_SMALL){
if (metrics.widthPixels < SCREEN_SIZE_SMALL || metrics.heightPixels < SCREEN_SIZE_SMALL) {
layoutView = inflater.inflate(R.layout.activity_bigchart_small, null);
} else {
layoutView = inflater.inflate(R.layout.activity_bigchart, null);
}
performViewSetup();
disposable.add(rxBus
.toObservable(EventData.SingleBg.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
aapsLogger.debug(LTag.WEAR, "SingleBg received");
singleBg = event;
mSgv.setText(singleBg.getSgvString());
if (ageLevel() <= 0)
mSgv.setPaintFlags(mSgv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
else mSgv.setPaintFlags(mSgv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
final java.text.DateFormat timeFormat = DateFormat.getTimeFormat(BIGChart.this);
mTime.setText(timeFormat.format(System.currentTimeMillis()));
mDelta.setText(singleBg.getDelta());
mAvgDelta.setText(singleBg.getAvgDelta());
})
);
disposable.add(rxBus
.toObservable(EventData.TreatmentData.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> treatmentData = event)
);
disposable.add(rxBus
.toObservable(EventData.GraphData.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> graphData = event)
);
disposable.add(rxBus
.toObservable(EventData.Status.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
// this event is received as last batch of data
aapsLogger.debug(LTag.WEAR, "Status received");
status = event;
showAgeAndStatus();
addToWatchSet();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
invalidate();
setColor();
})
);
disposable.add(rxBus
.toObservable(EventData.Preferences.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
setColor();
if (layoutSet) {
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
}
invalidate();
})
);
}
@Override
@ -134,54 +181,46 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
public void performViewSetup() {
final WatchViewStub stub = layoutView.findViewById(R.id.watch_view_stub);
IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND);
messageReceiver = new MessageReceiver();
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(messageReceiver, messageFilter);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mTime = stub.findViewById(R.id.watch_time);
mSgv = stub.findViewById(R.id.sgv);
mTimestamp = stub.findViewById(R.id.timestamp);
mDelta = stub.findViewById(R.id.delta);
mAvgDelta = stub.findViewById(R.id.avgdelta);
mRelativeLayout = stub.findViewById(R.id.main_layout);
chart = stub.findViewById(R.id.chart);
statusView = stub.findViewById(R.id.aps_status);
layoutSet = true;
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
}
stub.setOnLayoutInflatedListener(stub1 -> {
mTime = stub1.findViewById(R.id.watch_time);
mSgv = stub1.findViewById(R.id.sgv);
mTimestamp = stub1.findViewById(R.id.timestamp);
mDelta = stub1.findViewById(R.id.delta);
mAvgDelta = stub1.findViewById(R.id.avgdelta);
mRelativeLayout = stub1.findViewById(R.id.main_layout);
chart = stub1.findViewById(R.id.chart);
statusView = stub1.findViewById(R.id.aps_status);
layoutSet = true;
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
});
DataLayerListenerService.Companion.requestData(this);
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("BIGChart:performViewSetup")));
wakeLock.acquire(50);
}
@Override
protected void onTapCommand(int tapType, int x, int y, long eventTime) {
int extra = mSgv!=null?(mSgv.getRight() - mSgv.getLeft())/2:0;
int extra = mSgv != null ? (mSgv.getRight() - mSgv.getLeft()) / 2 : 0;
if (tapType == TAP_TYPE_TAP&&
x >=chart.getLeft() &&
x <= chart.getRight()&&
if (tapType == TAP_TYPE_TAP &&
x >= chart.getLeft() &&
x <= chart.getRight() &&
y >= chart.getTop() &&
y <= chart.getBottom()){
if (eventTime - chartTapTime < 800){
y <= chart.getBottom()) {
if (eventTime - chartTapTime < 800) {
changeChartTimeframe();
}
chartTapTime = eventTime;
} else if (tapType == TAP_TYPE_TAP&&
x + extra >=mSgv.getLeft() &&
x - extra <= mSgv.getRight()&&
} else if (tapType == TAP_TYPE_TAP &&
x + extra >= mSgv.getLeft() &&
x - extra <= mSgv.getRight() &&
y >= mSgv.getTop() &&
y <= mSgv.getBottom()){
if (eventTime - sgvTapTime < 800){
y <= mSgv.getBottom()) {
if (eventTime - sgvTapTime < 800) {
Intent intent = new Intent(this, MainMenuActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
@ -191,17 +230,17 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
}
private void changeChartTimeframe() {
int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3"));
timeframe = (timeframe%5) + 1;
sharedPrefs.edit().putString("chart_timeframe", "" + timeframe).apply();
int timeframe = sp.getInt("chart_timeframe", 3);
timeframe = (timeframe % 5) + 1;
sp.putInt("chart_timeframe", timeframe);
}
protected void onWatchModeChanged(WatchMode watchMode) {
if(lowResMode ^ isLowRes(watchMode)){ //if there was a change in lowResMode
if (lowResMode ^ isLowRes(watchMode)) { //if there was a change in lowResMode
lowResMode = isLowRes(watchMode);
setColor();
} else if (! sharedPrefs.getBoolean("dark", true)){
} else if (!sp.getBoolean("dark", true)) {
//in bright mode: different colours if active:
setColor();
}
@ -213,14 +252,13 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
@Override
protected WatchFaceStyle getWatchFaceStyle(){
protected WatchFaceStyle getWatchFaceStyle() {
return new WatchFaceStyle.Builder(this).setAcceptsTapEvents(true).build();
}
public int ageLevel() {
if(timeSince() <= (1000 * 60 * 12)) {
if (timeSince() <= (1000 * 60 * 12)) {
return 1;
} else {
return 0;
@ -228,40 +266,30 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
}
public double timeSince() {
return System.currentTimeMillis() - datetime;
return System.currentTimeMillis() - singleBg.getTimeStamp();
}
public String readingAge(boolean shortString) {
if (datetime == 0) { return shortString?"--'":"-- Minute ago"; }
int minutesAgo = (int) Math.floor(timeSince()/(1000*60));
if (minutesAgo == 1) {
return minutesAgo + (shortString?"'":" Minute ago");
if (singleBg == null || singleBg.getTimeStamp() == 0) {
return shortString ? "--'" : "-- Minute ago";
}
return minutesAgo + (shortString?"'":" Minutes ago");
int minutesAgo = (int) Math.floor(timeSince() / (1000 * 60));
if (minutesAgo == 1) {
return minutesAgo + (shortString ? "'" : " Minute ago");
}
return minutesAgo + (shortString ? "'" : " Minutes ago");
}
@Override
public void onDestroy() {
if(localBroadcastManager != null && messageReceiver != null){
localBroadcastManager.unregisterReceiver(messageReceiver);}
if (sharedPrefs != null){
sharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
disposable.clear();
super.onDestroy();
}
static {
INTENT_FILTER = new IntentFilter();
INTENT_FILTER.addAction(Intent.ACTION_TIME_TICK);
INTENT_FILTER.addAction(Intent.ACTION_TIMEZONE_CHANGED);
INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
}
@Override
protected void onDraw(Canvas canvas) {
if(layoutSet) {
if (layoutSet) {
this.mRelativeLayout.draw(canvas);
Log.d("onDraw", "draw");
}
}
@ -273,7 +301,7 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
mTime.setText(timeFormat.format(System.currentTimeMillis()));
showAgeAndStatus();
if(ageLevel()<=0) {
if (ageLevel() <= 0) {
mSgv.setPaintFlags(mSgv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
mSgv.setPaintFlags(mSgv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
@ -286,173 +314,25 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
}
}
public class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getBundleExtra("data");
if (layoutSet && bundle !=null) {
DataMap dataMap = DataMap.fromBundle(bundle);
wakeLock.acquire(50);
sgvLevel = dataMap.getLong("sgvLevel");
batteryLevel = dataMap.getInt("batteryLevel");
datetime = dataMap.getLong("timestamp");
rawString = dataMap.getString("rawString");
sgvString = dataMap.getString("sgvString");
batteryString = dataMap.getString("battery");
mSgv.setText(dataMap.getString("sgvString"));
if(ageLevel()<=0) {
mSgv.setPaintFlags(mSgv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
mSgv.setPaintFlags(mSgv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
}
final java.text.DateFormat timeFormat = DateFormat.getTimeFormat(BIGChart.this);
mTime.setText(timeFormat.format(System.currentTimeMillis()));
showAgeAndStatus();
String delta = dataMap.getString("delta");
if (delta.endsWith(" mg/dl")) {
mDelta.setText(delta.substring(0, delta.length() - 6));
} else if (delta.endsWith(" mmol/l")||delta.endsWith(" mmol")) {
mDelta.setText(delta.substring(0, delta.length() - 5));
} else {
mDelta.setText(delta);
}
String avgDelta = dataMap.getString("avgDelta");
if (delta.endsWith(" mg/dl")) {
mAvgDelta.setText(avgDelta.substring(0, avgDelta.length() - 6));
} else if (avgDelta.endsWith(" mmol/l")||avgDelta.endsWith(" mmol")) {
mAvgDelta.setText(avgDelta.substring(0, avgDelta.length() - 5));
} else {
mAvgDelta.setText(avgDelta);
}
if (chart != null) {
addToWatchSet(dataMap);
setupCharts();
}
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
invalidate();
setColor();
//start animation?
// dataMap.getDataMapArrayList("entries") == null -> not on "resend data".
if (!lowResMode && (sharedPrefs.getBoolean("animation", false) && dataMap.getDataMapArrayList("entries") == null && (sgvString.equals("100") || sgvString.equals("5.5") || sgvString.equals("5,5")))) {
startAnimation();
}
}
//status
bundle = intent.getBundleExtra("status");
if (layoutSet && bundle != null) {
DataMap dataMap = DataMap.fromBundle(bundle);
wakeLock.acquire(50);
externalStatusString = dataMap.getString("externalStatusString");
cobString = dataMap.getString("cob");
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
invalidate();
setColor();
}
//basals and temps
bundle = intent.getBundleExtra("basals");
if (layoutSet && bundle != null) {
DataMap dataMap = DataMap.fromBundle(bundle);
wakeLock.acquire(500);
loadBasalsAndTemps(dataMap);
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
invalidate();
setColor();
}
}
}
private void loadBasalsAndTemps(DataMap dataMap) {
ArrayList<DataMap> temps = dataMap.getDataMapArrayList("temps");
if (temps != null) {
tempWatchDataList = new ArrayList<>();
for (DataMap temp : temps) {
TempWatchData twd = new TempWatchData();
twd.startTime = temp.getLong("starttime");
twd.startBasal = temp.getDouble("startBasal");
twd.endTime = temp.getLong("endtime");
twd.endBasal = temp.getDouble("endbasal");
twd.amount = temp.getDouble("amount");
tempWatchDataList.add(twd);
}
}
ArrayList<DataMap> basals = dataMap.getDataMapArrayList("basals");
if (basals != null) {
basalWatchDataList = new ArrayList<>();
for (DataMap basal : basals) {
BasalWatchData bwd = new BasalWatchData();
bwd.startTime = basal.getLong("starttime");
bwd.endTime = basal.getLong("endtime");
bwd.amount = basal.getDouble("amount");
basalWatchDataList.add(bwd);
}
}
ArrayList<DataMap> boluses = dataMap.getDataMapArrayList("boluses");
if (boluses != null) {
bolusWatchDataList = new ArrayList<>();
for (DataMap bolus : boluses) {
BolusWatchData bwd = new BolusWatchData();
bwd.date = bolus.getLong("date");
bwd.bolus = bolus.getDouble("bolus");
bwd.carbs = bolus.getDouble("carbs");
bwd.isSMB = bolus.getBoolean("isSMB");
bwd.isValid = bolus.getBoolean("isValid");
bolusWatchDataList.add(bwd);
}
}
ArrayList<DataMap> predictions = dataMap.getDataMapArrayList("predictions");
if (boluses != null) {
predictionList = new ArrayList<>();
for (DataMap prediction : predictions) {
BgWatchData bwd = new BgWatchData();
bwd.timestamp = prediction.getLong("timestamp");
bwd.sgv = prediction.getDouble("sgv");
bwd.color = prediction.getInt("color");
predictionList.add(bwd);
}
}
}
private void showAgeAndStatus() {
if( mTimestamp != null){
if (mTimestamp != null) {
mTimestamp.setText(readingAge(true));
}
boolean showStatus = sharedPrefs.getBoolean("showExternalStatus", true);
boolean showAvgDelta = sharedPrefs.getBoolean("showAvgDelta", true);
boolean showStatus = sp.getBoolean("showExternalStatus", true);
boolean showAvgDelta = sp.getBoolean("showAvgDelta", true);
if(showAvgDelta){
if (showAvgDelta) {
mAvgDelta.setVisibility(View.VISIBLE);
} else {
mAvgDelta.setVisibility(View.GONE);
}
if(showStatus){
String status = externalStatusString;
if (sharedPrefs.getBoolean("show_cob", true)) {
status = externalStatusString + " " + cobString;
if (showStatus && status != null) {
String status = this.status.getExternalStatus();
if (sp.getBoolean("show_cob", true)) {
status += " " + this.status.getCob();
}
statusView.setText(status);
@ -463,9 +343,9 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
}
public void setColor() {
if(lowResMode){
if (lowResMode) {
setColorLowRes();
} else if (sharedPrefs.getBoolean("dark", true)) {
} else if (sp.getBoolean("dark", true)) {
setColorDark();
} else {
setColorBright();
@ -473,66 +353,6 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key){
setColor();
if(layoutSet){
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
}
invalidate();
}
protected void updateRainbow() {
animationAngle = (animationAngle + 1) % 360;
//Animation matrix:
int[] rainbow = {Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE
, Color.CYAN};
Shader shader = new LinearGradient(0, 0, 0, 20, rainbow,
null, Shader.TileMode.MIRROR);
Matrix matrix = new Matrix();
matrix.setRotate(animationAngle);
shader.setLocalMatrix(matrix);
mSgv.getPaint().setShader(shader);
invalidate();
}
private synchronized boolean isAnimated() {
return isAnimated;
}
private synchronized void setIsAnimated(boolean isAnimated) {
this.isAnimated = isAnimated;
}
void startAnimation() {
Log.d("CircleWatchface", "start startAnimation");
Thread animator = new Thread() {
public void run() {
setIsAnimated(true);
for (int i = 0; i <= 8 * 1000 / 40; i++) {
updateRainbow();
SystemClock.sleep(40);
}
mSgv.getPaint().setShader(null);
setIsAnimated(false);
invalidate();
setColor();
System.gc();
}
};
animator.start();
}
protected void setColorLowRes() {
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime));
statusView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_statusView));
@ -555,40 +375,41 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
}
protected void setColorDark() {
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime));
statusView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_statusView));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_background));
if (sgvLevel == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
} else if (sgvLevel == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
} else if (sgvLevel == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
}
if (singleBg != null) {
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime));
statusView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_statusView));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_background));
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
}
if (ageLevel == 1) {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_Timestamp));
} else {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
if (ageLevel == 1) {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_Timestamp));
} else {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
if (chart != null) {
highColor = ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor);
lowColor = ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor);
midColor = ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor);
gridColour = ContextCompat.getColor(getApplicationContext(), R.color.dark_gridColor);
basalBackgroundColor = ContextCompat.getColor(getApplicationContext(), R.color.basal_dark);
basalCenterColor = ContextCompat.getColor(getApplicationContext(), R.color.basal_light);
pointSize = 2;
setupCharts();
if (chart != null) {
highColor = ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor);
lowColor = ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor);
midColor = ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor);
gridColour = ContextCompat.getColor(getApplicationContext(), R.color.dark_gridColor);
basalBackgroundColor = ContextCompat.getColor(getApplicationContext(), R.color.basal_dark);
basalCenterColor = ContextCompat.getColor(getApplicationContext(), R.color.basal_light);
pointSize = 2;
setupCharts();
}
}
}
@ -598,15 +419,15 @@ public class BIGChart extends WatchFace implements SharedPreferences.OnSharedPre
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_bigchart_time));
statusView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_bigchart_status));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_background));
if (sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
} else if (sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
} else if (sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mAvgDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
@ -636,61 +457,36 @@ 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) {
DataLayerListenerService.Companion.requestData(this); // attempt endTime recover missing data
// attempt endTime recover missing data
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("BIGChart:missedReadingAlert")));
}
}
public void addToWatchSet(DataMap dataMap) {
ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
if (entries != null) {
bgDataList = new ArrayList<BgWatchData>();
for (DataMap entry : entries) {
double sgv = entry.getDouble("sgvDouble");
double high = entry.getDouble("high");
double low = entry.getDouble("low");
long timestamp = entry.getLong("timestamp");
int color = entry.getInt("color", 0);
bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
}
public void addToWatchSet() {
if (graphData != null) {
bgDataList = graphData.getEntries();
} else {
double sgv = dataMap.getDouble("sgvDouble");
double high = dataMap.getDouble("high");
double low = dataMap.getDouble("low");
long timestamp = dataMap.getLong("timestamp");
int color = dataMap.getInt("color", 0);
final int size = bgDataList.size();
if (size > 0) {
if (bgDataList.get(size - 1).timestamp == timestamp)
return; // Ignore duplicates.
}
bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
}
for (int i = 0; i < bgDataList.size(); i++) {
if (bgDataList.get(i).timestamp < (System.currentTimeMillis() - (1000 * 60 * 60 * 5))) {
bgDataList.remove(i); //Get rid of anything more than 5 hours old
break;
}
if (size > 0 && bgDataList.get(size - 1).getTimeStamp() == singleBg.getTimeStamp())
return; // Ignore duplicates.
bgDataList.add(singleBg);
}
}
public void setupCharts() {
if(bgDataList.size() > 0) { //Dont crash things just because we dont have values, people dont like crashy things
int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3"));
if (bgDataList.size() > 0) {
int timeframe = sp.getInt("chart_timeframe", 3);
if (lowResMode) {
bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), bgDataList, predictionList, tempWatchDataList, basalWatchDataList, bolusWatchDataList, pointSize, midColor, gridColour, basalBackgroundColor, basalCenterColor, bolusColor, carbsColor, timeframe);
bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), bgDataList, treatmentData.getPredictions(), treatmentData.getTemps(), treatmentData.getBasals(), treatmentData.getBoluses(), pointSize, midColor, gridColour, basalBackgroundColor, basalCenterColor, bolusColor, carbsColor, timeframe);
} else {
bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), bgDataList, predictionList, tempWatchDataList, basalWatchDataList, bolusWatchDataList, pointSize, highColor, lowColor, midColor, gridColour, basalBackgroundColor, basalCenterColor, bolusColor, carbsColor, timeframe);
bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), bgDataList, treatmentData.getPredictions(), treatmentData.getTemps(), treatmentData.getBasals(), treatmentData.getBoluses(), pointSize, highColor, lowColor, midColor, gridColour, basalBackgroundColor, basalCenterColor, bolusColor, carbsColor, timeframe);
}
chart.setLineChartData(bgGraphBuilder.lineData());
chart.setViewportCalculationEnabled(true);
chart.setMaximumViewport(chart.getMaximumViewport());
} else {
DataLayerListenerService.Companion.requestData(this);
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("BIGChart:setupCharts")));
}
}
}

View file

@ -4,7 +4,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
@ -15,7 +14,6 @@ import android.graphics.Typeface;
import android.os.BatteryManager;
import android.os.PowerManager;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.support.wearable.view.WatchViewStub;
import android.text.format.DateFormat;
import android.view.Display;
@ -28,15 +26,14 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.gms.wearable.DataMap;
import com.ustwo.clockwise.common.WatchFaceTime;
import com.ustwo.clockwise.common.WatchMode;
import com.ustwo.clockwise.common.WatchShape;
import com.ustwo.clockwise.wearable.WatchFace;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
@ -44,12 +41,17 @@ import javax.inject.Inject;
import dagger.android.AndroidInjection;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.androidaps.events.EventWearPreferenceChange;
import info.nightscout.androidaps.events.EventWearToMobile;
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.utils.rx.AapsSchedulers;
import info.nightscout.shared.logging.AAPSLogger;
import info.nightscout.shared.logging.LTag;
import info.nightscout.shared.sharedPreferences.SP;
import info.nightscout.shared.weardata.EventData;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import lecho.lib.hellocharts.view.LineChartView;
/**
@ -58,20 +60,27 @@ import lecho.lib.hellocharts.view.LineChartView;
* Refactored by dlvoy on 2019-11-2019
*/
public abstract class BaseWatchFace extends WatchFace implements SharedPreferences.OnSharedPreferenceChangeListener {
public abstract class BaseWatchFace extends WatchFace {
@Inject WearUtil wearUtil;
@Inject Persistence persistence;
@Inject AAPSLogger aapsLogger;
@Inject RxBus rxBus;
@Inject AapsSchedulers aapsSchedulers;
@Inject SP sp;
public final static IntentFilter INTENT_FILTER;
CompositeDisposable disposable = new CompositeDisposable();
static {
INTENT_FILTER = new IntentFilter();
INTENT_FILTER.addAction(Intent.ACTION_TIME_TICK);
INTENT_FILTER.addAction(Intent.ACTION_TIMEZONE_CHANGED);
INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
}
protected EventData.SingleBg singleBg = new EventData.SingleBg(0, "---", "-", "--", "--", "--"
, 0, 0.0, 0.0, 0.0, 0);
protected EventData.Status status = new EventData.Status("no status", "IOB", "-.--", false, "--g", "-.--U/h", "--", "--", -1, "--", false, 1);
protected EventData.TreatmentData treatmentData = new EventData.TreatmentData(
new ArrayList<>(),
new ArrayList<>(),
new ArrayList<>(),
new ArrayList<>()
);
protected EventData.GraphData graphData = new EventData.GraphData(new ArrayList<>());
static IntentFilter iFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
@ -96,7 +105,6 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
public int pointSize = 2;
public BgGraphBuilder bgGraphBuilder;
public LineChartView chart;
public RawDisplayData rawData;
public PowerManager.WakeLock wakeLock;
// related endTime manual layout
public View layoutView;
@ -104,9 +112,6 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
public boolean forceSquareCanvas = false; // Set to true by the Steampunk watch face.
public String sMinute = "0";
public String sHour = "0";
protected SharedPreferences sharedPrefs;
private LocalBroadcastManager localBroadcastManager;
private MessageReceiver messageReceiver;
private BroadcastReceiver batteryReceiver;
private int colorDarkHigh, colorDarkMid, colorDarkLow;
private java.text.DateFormat timeFormat;
@ -126,7 +131,6 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
colorDarkMid = ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor);
colorDarkLow = ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor);
rawData = new RawDisplayData(wearUtil);
Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
display.getSize(displaySize);
wakeLock = ((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:BaseWatchFace");
@ -137,8 +141,49 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
} else {
specH = View.MeasureSpec.makeMeasureSpec(displaySize.y, View.MeasureSpec.EXACTLY);
}
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
sharedPrefs.registerOnSharedPreferenceChangeListener(this);
disposable.add(rxBus
.toObservable(EventWearPreferenceChange.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
setupBatteryReceiver();
if (event.getChangedKey() != null && event.getChangedKey().equals("delta_granularity"))
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("BaseWatchFace:onSharedPreferenceChanged")));
if (layoutSet) setDataFields();
invalidate();
})
);
disposable.add(rxBus
.toObservable(EventData.SingleBg.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> singleBg = event)
);
disposable.add(rxBus
.toObservable(EventData.TreatmentData.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> treatmentData = event)
);
disposable.add(rxBus
.toObservable(EventData.GraphData.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> graphData = event)
);
disposable.add(rxBus
.toObservable(EventData.Status.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
// this event is received as last batch of data
if (isSimpleUi()) {
if (needUpdate()) {
invalidate();
}
} else {
setupCharts();
setDataFields();
invalidate();
}
})
);
persistence.turnOff();
@ -148,7 +193,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
private void setupBatteryReceiver() {
String setting = sharedPrefs.getString("simplify_ui", "off");
String setting = sp.getString("simplify_ui", "off");
if ((setting.equals("charging") || setting.equals("ambient_charging")) && batteryReceiver == null) {
IntentFilter intentBatteryFilter = new IntentFilter();
intentBatteryFilter.addAction(BatteryManager.ACTION_CHARGING);
@ -215,11 +260,6 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
public void performViewSetup() {
final WatchViewStub layoutStub = layoutView.findViewById(R.id.watch_view_stub);
IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND);
messageReceiver = new MessageReceiver();
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(messageReceiver, messageFilter);
layoutStub.setOnLayoutInflatedListener((WatchViewStub stub) -> {
mTime = stub.findViewById(R.id.watch_time);
@ -274,11 +314,11 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
public double timeSince() {
return System.currentTimeMillis() - rawData.datetime;
return System.currentTimeMillis() - singleBg.getTimeStamp();
}
public String readingAge(boolean shortString) {
if (rawData.datetime == 0) {
if (singleBg.getTimeStamp() == 0) {
return shortString ? "--" : "-- Minute ago";
}
int minutesAgo = (int) Math.floor(timeSince() / (1000 * 60));
@ -290,12 +330,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
@Override
public void onDestroy() {
if (localBroadcastManager != null && messageReceiver != null) {
localBroadcastManager.unregisterReceiver(messageReceiver);
}
if (sharedPrefs != null) {
sharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
disposable.clear();
if (batteryReceiver != null) {
unregisterReceiver(batteryReceiver);
}
@ -326,16 +361,16 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
float xHalf = displaySize.x / 2f;
float yThird = displaySize.y / 3f;
boolean isOutdated = rawData.datetime > 0 && ageLevel() <= 0;
boolean isOutdated = singleBg.getTimeStamp() > 0 && ageLevel() <= 0;
mSvgPaint.setStrikeThruText(isOutdated);
mSvgPaint.setColor(getBgColour(rawData.sgvLevel));
mDirectionPaint.setColor(getBgColour(rawData.sgvLevel));
mSvgPaint.setColor(getBgColour(singleBg.getSgvLevel()));
mDirectionPaint.setColor(getBgColour(singleBg.getSgvLevel()));
String sSvg = rawData.sSgv;
String sSvg = singleBg.getSgvString();
float svgWidth = mSvgPaint.measureText(sSvg);
String sDirection = " " + rawData.sDirection + "\uFE0E";
String sDirection = " " + singleBg.getSgvString() + "\uFE0E";
float directionWidth = mDirectionPaint.measureText(sDirection);
float xSvg = xHalf - (svgWidth + directionWidth) / 2;
@ -382,7 +417,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
private void checkVibrateHourly(WatchFaceTime oldTime, WatchFaceTime newTime) {
boolean hourlyVibratePref = sharedPrefs.getBoolean("vibrate_Hourly", false);
boolean hourlyVibratePref = sp.getBoolean("vibrate_Hourly", false);
if (hourlyVibratePref && layoutSet && newTime.hasHourChanged(oldTime)) {
aapsLogger.info(LTag.WEAR, "hourlyVibratePref", "true --> " + newTime);
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
@ -394,8 +429,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
public void setDataFields() {
setDateAndTime();
if (mSgv != null) {
if (sharedPrefs.getBoolean("showBG", true)) {
mSgv.setText(rawData.sSgv);
if (sp.getBoolean("showBG", true)) {
mSgv.setText(singleBg.getSgvString());
mSgv.setVisibility(View.VISIBLE);
} else {
// Leave the textview there but invisible, as a height holder for the empty space above the white line
@ -407,8 +442,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
strikeThroughSgvIfNeeded();
if (mDirection != null) {
if (sharedPrefs.getBoolean("show_direction", true)) {
mDirection.setText(rawData.sDirection + "\uFE0E");
if (sp.getBoolean("show_direction", true)) {
mDirection.setText(singleBg.getSgvString() + "\uFE0E");
mDirection.setVisibility(View.VISIBLE);
} else {
mDirection.setVisibility(View.GONE);
@ -416,8 +451,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mDelta != null) {
if (sharedPrefs.getBoolean("showDelta", true)) {
mDelta.setText(rawData.sDelta);
if (sp.getBoolean("showDelta", true)) {
mDelta.setText(singleBg.getDelta());
mDelta.setVisibility(View.VISIBLE);
} else {
mDelta.setVisibility(View.GONE);
@ -425,8 +460,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mAvgDelta != null) {
if (sharedPrefs.getBoolean("showAvgDelta", true)) {
mAvgDelta.setText(rawData.sAvgDelta);
if (sp.getBoolean("showAvgDelta", true)) {
mAvgDelta.setText(singleBg.getAvgDelta());
mAvgDelta.setVisibility(View.VISIBLE);
} else {
mAvgDelta.setVisibility(View.GONE);
@ -434,8 +469,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mCOB1 != null && mCOB2 != null) {
mCOB2.setText(rawData.sCOB2);
if (sharedPrefs.getBoolean("show_cob", true)) {
mCOB2.setText(status.getCob());
if (sp.getBoolean("show_cob", true)) {
mCOB1.setVisibility(View.VISIBLE);
mCOB2.setVisibility(View.VISIBLE);
} else {
@ -444,8 +479,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
// Deal with cases where there is only the value shown for COB, and not the label
} else if (mCOB2 != null) {
mCOB2.setText(rawData.sCOB2);
if (sharedPrefs.getBoolean("show_cob", true)) {
mCOB2.setText(status.getCob());
if (sp.getBoolean("show_cob", true)) {
mCOB2.setVisibility(View.VISIBLE);
} else {
mCOB2.setVisibility(View.GONE);
@ -453,15 +488,15 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mIOB1 != null && mIOB2 != null) {
if (sharedPrefs.getBoolean("show_iob", true)) {
if (sp.getBoolean("show_iob", true)) {
mIOB1.setVisibility(View.VISIBLE);
mIOB2.setVisibility(View.VISIBLE);
if (rawData.detailedIOB) {
mIOB1.setText(rawData.sIOB1);
mIOB2.setText(rawData.sIOB2);
if (status.getDetailedIob()) {
mIOB1.setText(status.getIobSum());
mIOB2.setText(status.getIobDetail());
} else {
mIOB1.setText(getString(R.string.activity_IOB));
mIOB2.setText(rawData.sIOB1);
mIOB2.setText(status.getIobSum());
}
} else {
mIOB1.setVisibility(View.GONE);
@ -469,12 +504,12 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
// Deal with cases where there is only the value shown for IOB, and not the label
} else if (mIOB2 != null) {
if (sharedPrefs.getBoolean("show_iob", true)) {
if (sp.getBoolean("show_iob", true)) {
mIOB2.setVisibility(View.VISIBLE);
if (rawData.detailedIOB) {
mIOB2.setText(rawData.sIOB2);
if (status.getDetailedIob()) {
mIOB2.setText(status.getIobDetail());
} else {
mIOB2.setText(rawData.sIOB1);
mIOB2.setText(status.getIobSum());
}
} else {
mIOB2.setText("");
@ -482,11 +517,11 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mTimestamp != null) {
if (sharedPrefs.getBoolean("showAgo", true)) {
if (sp.getBoolean("showAgo", true)) {
if (isAAPSv2 != null) {
mTimestamp.setText(readingAge(true));
} else {
boolean shortString = sharedPrefs.getBoolean("showExternalStatus", true);
boolean shortString = sp.getBoolean("showExternalStatus", true);
mTimestamp.setText(readingAge(shortString));
}
mTimestamp.setVisibility(View.VISIBLE);
@ -496,15 +531,15 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mUploaderBattery != null) {
if (sharedPrefs.getBoolean("show_uploader_battery", true)) {
if (sp.getBoolean("show_uploader_battery", true)) {
if (isAAPSv2 != null) {
mUploaderBattery.setText(rawData.sUploaderBattery + "%");
mUploaderBattery.setText(status.getBattery() + "%");
mUploaderBattery.setVisibility(View.VISIBLE);
} else {
if (sharedPrefs.getBoolean("showExternalStatus", true)) {
mUploaderBattery.setText("U: " + rawData.sUploaderBattery + "%");
if (sp.getBoolean("showExternalStatus", true)) {
mUploaderBattery.setText("U: " + status.getBattery() + "%");
} else {
mUploaderBattery.setText("Uploader: " + rawData.sUploaderBattery + "%");
mUploaderBattery.setText("Uploader: " + status.getBattery() + "%");
}
}
} else {
@ -513,8 +548,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mRigBattery != null) {
if (sharedPrefs.getBoolean("show_rig_battery", false)) {
mRigBattery.setText(rawData.sRigBattery);
if (sp.getBoolean("show_rig_battery", false)) {
mRigBattery.setText(status.getRigBattery());
mRigBattery.setVisibility(View.VISIBLE);
} else {
mRigBattery.setVisibility(View.GONE);
@ -522,8 +557,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mBasalRate != null) {
if (sharedPrefs.getBoolean("show_temp_basal", true)) {
mBasalRate.setText(rawData.sBasalRate);
if (sp.getBoolean("show_temp_basal", true)) {
mBasalRate.setText(status.getCurrentBasal());
mBasalRate.setVisibility(View.VISIBLE);
} else {
mBasalRate.setVisibility(View.GONE);
@ -531,8 +566,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mBgi != null) {
if (rawData.showBGI) {
mBgi.setText(rawData.sBgi);
if (status.getShowBgi()) {
mBgi.setText(status.getBgi());
mBgi.setVisibility(View.VISIBLE);
} else {
mBgi.setVisibility(View.GONE);
@ -540,8 +575,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mStatus != null) {
if (sharedPrefs.getBoolean("showExternalStatus", true)) {
mStatus.setText(rawData.externalStatusString);
if (sp.getBoolean("showExternalStatus", true)) {
mStatus.setText(status.getExternalStatus());
mStatus.setVisibility(View.VISIBLE);
} else {
mStatus.setVisibility(View.GONE);
@ -549,10 +584,10 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mLoop != null) {
if (sharedPrefs.getBoolean("showExternalStatus", true)) {
if (sp.getBoolean("showExternalStatus", true)) {
mLoop.setVisibility(View.VISIBLE);
if (rawData.openApsStatus != -1) {
int mins = (int) ((System.currentTimeMillis() - rawData.openApsStatus) / 1000 / 60);
if (status.getOpenApsStatus() != -1) {
int mins = (int) ((System.currentTimeMillis() - status.getOpenApsStatus()) / 1000 / 60);
mLoop.setText(mins + "'");
if (mins > 14) {
loopLevel = 0;
@ -605,7 +640,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
if (mDate != null && mDay != null && mMonth != null) {
if (sharedPrefs.getBoolean("show_date", false)) {
if (sp.getBoolean("show_date", false)) {
if (mDayName != null) {
mDayName.setText(sdfDayName.format(mDateTime));
}
@ -620,10 +655,10 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
public void setColor() {
dividerMatchesBg = sharedPrefs.getBoolean("match_divider", false);
dividerMatchesBg = sp.getBoolean("match_divider", false);
if (lowResMode) {
setColorLowRes();
} else if (sharedPrefs.getBoolean("dark", true)) {
} else if (sp.getBoolean("dark", true)) {
setColorDark();
} else {
setColorBright();
@ -632,8 +667,8 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
public void strikeThroughSgvIfNeeded() {
if (mSgv != null) {
if (sharedPrefs.getBoolean("showBG", true)) {
if (ageLevel() <= 0 && rawData.datetime > 0) {
if (sp.getBoolean("showBG", true)) {
if (ageLevel() <= 0 && singleBg.getTimeStamp() > 0) {
mSgv.setPaintFlags(mSgv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
mSgv.setPaintFlags(mSgv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
@ -664,7 +699,7 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
private boolean isSimpleUi() {
String simplify = sharedPrefs.getString("simplify_ui", "off");
String simplify = sp.getString("simplify_ui", "off");
if (simplify.equals("off")) {
return false;
}
@ -674,18 +709,6 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
return (simplify.equals("charging") || simplify.equals("ambient_charging")) && isCharging();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
setupBatteryReceiver();
if ("delta_granularity".equals(key)) {
DataLayerListenerService.Companion.requestData(this);
}
if (layoutSet) {
setDataFields();
}
invalidate();
}
protected abstract void setColorDark();
protected abstract void setColorBright();
@ -694,8 +717,9 @@ 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) {
DataLayerListenerService.Companion.requestData(this); // Attempt endTime recover missing data
if (singleBg.getTimeStamp() == 0 || minutes_since >= 16 && ((minutes_since - 16) % 5) == 0) {
// Attempt endTime recover missing data
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("BaseWatchFace:missedReadingAlert")));
}
}
@ -703,12 +727,12 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
if (isSimpleUi()) {
return;
}
if (rawData.bgDataList.size() > 0) { // Dont crash things just because we dont have values, people dont like crashy things
int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3"));
if (chart != null && graphData.getEntries().size() > 0) {
int timeframe = sp.getInt("chart_timeframe", 3);
if (lowResMode) {
bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), rawData, pointSize, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe);
bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), graphData.getEntries(), treatmentData.getPredictions(), treatmentData.getTemps(), treatmentData.getBasals(), treatmentData.getBoluses(), pointSize, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe);
} else {
bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), rawData, pointSize, highColor, lowColor, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe);
bgGraphBuilder = new BgGraphBuilder(getApplicationContext(), graphData.getEntries(), treatmentData.getPredictions(), treatmentData.getTemps(), treatmentData.getBasals(), treatmentData.getBoluses(), pointSize, highColor, lowColor, midColor, gridColor, basalBackgroundColor, basalCenterColor, bolusColor, Color.GREEN, timeframe);
}
chart.setLineChartData(bgGraphBuilder.lineData());
@ -717,39 +741,12 @@ public abstract class BaseWatchFace extends WatchFace implements SharedPreferenc
}
}
public class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
PowerManager.WakeLock wl = wearUtil.getWakeLock("readingPrefs", 50);
final DataMap dataMap = rawData.updateDataFromMessage(intent, wakeLock);
if (chart != null && dataMap != null) {
rawData.addToWatchSet(dataMap);
setupCharts();
}
rawData.updateStatusFromMessage(intent, wakeLock);
rawData.updateBasalsFromMessage(intent);
if (isSimpleUi()) {
if (needUpdate()) {
invalidate();
}
} else {
setupCharts();
setDataFields();
invalidate();
}
wearUtil.releaseWakeLock(wl);
}
}
private boolean needUpdate() {
if (mLastSvg.equals(rawData.sSgv) && mLastDirection.equals(rawData.sDirection)) {
if (mLastSvg.equals(singleBg.getSgvString()) && mLastDirection.equals(singleBg.getSgvString())) {
return false;
}
mLastSvg = rawData.sSgv;
mLastDirection = rawData.sDirection;
mLastSvg = singleBg.getSgvString();
mLastDirection = singleBg.getSgvString();
return true;
}
}

View file

@ -14,17 +14,12 @@ import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import info.nightscout.androidaps.data.BasalWatchData;
import info.nightscout.androidaps.data.BgWatchData;
import info.nightscout.androidaps.data.BolusWatchData;
import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.androidaps.data.TempWatchData;
import info.nightscout.shared.weardata.EventData;
import lecho.lib.hellocharts.model.Axis;
import lecho.lib.hellocharts.model.AxisValue;
import lecho.lib.hellocharts.model.Line;
import lecho.lib.hellocharts.model.LineChartData;
import lecho.lib.hellocharts.model.PointValue;
import lecho.lib.hellocharts.model.Viewport;
/**
* Created by emmablack on 11/15/14.
@ -33,10 +28,10 @@ public class BgGraphBuilder {
public static final double MAX_PREDICTION__TIME_RATIO = (3d / 5);
public static final double UPPER_CUTOFF_SGV = 400;
private final long predictionEndTime;
private final List<BgWatchData> predictionsList;
private final ArrayList<BolusWatchData> bolusWatchDataList;
private final ArrayList<BasalWatchData> basalWatchDataList;
public List<TempWatchData> tempWatchDataList;
private final List<EventData.SingleBg> predictionsList;
private final ArrayList<EventData.TreatmentData.Treatment> bolusWatchDataList;
private final ArrayList<EventData.TreatmentData.Basal> basalWatchDataList;
public List<EventData.TreatmentData.TempBasal> tempWatchDataList;
private final int timespan;
public long end_time;
public long start_time;
@ -44,7 +39,7 @@ public class BgGraphBuilder {
public Context context;
public double highMark;
public double lowMark;
public List<BgWatchData> bgDataList = new ArrayList<BgWatchData>();
public List<EventData.SingleBg> bgDataList;
public int pointSize;
public int highColor;
@ -58,20 +53,24 @@ public class BgGraphBuilder {
public boolean singleLine = false;
private final List<PointValue> inRangeValues = new ArrayList<PointValue>();
private final List<PointValue> highValues = new ArrayList<PointValue>();
private final List<PointValue> lowValues = new ArrayList<PointValue>();
public Viewport viewport;
private final List<PointValue> inRangeValues = new ArrayList<>();
private final List<PointValue> highValues = new ArrayList<>();
private final List<PointValue> lowValues = new ArrayList<>();
//used for low resolution screen.
public BgGraphBuilder(Context context, List<BgWatchData> aBgList, List<BgWatchData> predictionsList, List<TempWatchData> tempWatchDataList, ArrayList<BasalWatchData> basalWatchDataList, ArrayList<BolusWatchData> bolusWatchDataList, int aPointSize, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
this.start_time = System.currentTimeMillis() - (1000 * 60 * 60 * timespan); //timespan hours ago
public BgGraphBuilder(Context context, List<EventData.SingleBg> aBgList,
List<EventData.SingleBg> predictionsList,
List<EventData.TreatmentData.TempBasal> tempWatchDataList,
ArrayList<EventData.TreatmentData.Basal> basalWatchDataList,
ArrayList<EventData.TreatmentData.Treatment> bolusWatchDataList,
int aPointSize, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
this.start_time = System.currentTimeMillis() - (1000L * 60 * 60 * timespan); //timespan
// hours ago
this.bgDataList = aBgList;
this.predictionsList = predictionsList;
this.context = context;
this.highMark = aBgList.get(aBgList.size() - 1).high;
this.lowMark = aBgList.get(aBgList.size() - 1).low;
this.highMark = aBgList.get(aBgList.size() - 1).getHigh();
this.lowMark = aBgList.get(aBgList.size() - 1).getLow();
this.pointSize = aPointSize;
this.singleLine = false;
this.midColor = aMidColor;
@ -80,24 +79,31 @@ public class BgGraphBuilder {
this.timespan = timespan;
this.tempWatchDataList = tempWatchDataList;
this.basalWatchDataList = basalWatchDataList;
this.bolusWatchDataList = (bolusWatchDataList!=null)?bolusWatchDataList:new ArrayList<BolusWatchData>();
this.bolusWatchDataList = (bolusWatchDataList != null) ? bolusWatchDataList : new ArrayList<>();
this.gridColour = gridColour;
this.basalCenterColor = basalCenterColor;
this.basalBackgroundColor = basalBackgroundColor;
this.bolusInvalidColor = bolusInvalidColor;
this.carbsColor = carbsColor;
this.end_time = System.currentTimeMillis() + (1000 * 60 * 6 * timespan); //Now plus 30 minutes padding (for 5 hours. Less if less.)
this.end_time = System.currentTimeMillis() + (1000L * 60 * 6 * timespan); //Now plus 30
// minutes padding (for 5 hours. Less if less.)
this.predictionEndTime = getPredictionEndTime();
this.end_time = (predictionEndTime>end_time)?predictionEndTime:end_time;
this.end_time = Math.max(predictionEndTime, end_time);
}
public BgGraphBuilder(Context context, List<BgWatchData> aBgList, List<BgWatchData> predictionsList, List<TempWatchData> tempWatchDataList, ArrayList<BasalWatchData> basalWatchDataList, ArrayList<BolusWatchData> bolusWatchDataList, int aPointSize, int aHighColor, int aLowColor, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
this.start_time = System.currentTimeMillis() - (1000 * 60 * 60 * timespan); //timespan hours ago
public BgGraphBuilder(Context context, List<EventData.SingleBg> aBgList,
List<EventData.SingleBg> predictionsList,
List<EventData.TreatmentData.TempBasal> tempWatchDataList,
ArrayList<EventData.TreatmentData.Basal> basalWatchDataList,
ArrayList<EventData.TreatmentData.Treatment> bolusWatchDataList,
int aPointSize, int aHighColor, int aLowColor, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
this.start_time = System.currentTimeMillis() - (1000L * 60 * 60 * timespan); //timespan
// hours ago
this.bgDataList = aBgList;
this.predictionsList = predictionsList;
this.context = context;
this.highMark = aBgList.get(aBgList.size() - 1).high;
this.lowMark = aBgList.get(aBgList.size() - 1).low;
this.highMark = aBgList.get(aBgList.size() - 1).getHigh();
this.lowMark = aBgList.get(aBgList.size() - 1).getLow();
this.pointSize = aPointSize;
this.highColor = aHighColor;
this.lowColor = aLowColor;
@ -105,51 +111,16 @@ public class BgGraphBuilder {
this.timespan = timespan;
this.tempWatchDataList = tempWatchDataList;
this.basalWatchDataList = basalWatchDataList;
this.bolusWatchDataList = (bolusWatchDataList!=null)?bolusWatchDataList:new ArrayList<BolusWatchData>();
this.bolusWatchDataList = (bolusWatchDataList != null) ? bolusWatchDataList : new ArrayList<>();
this.gridColour = gridColour;
this.basalCenterColor = basalCenterColor;
this.basalBackgroundColor = basalBackgroundColor;
this.bolusInvalidColor = bolusInvalidColor;
this.carbsColor = carbsColor;
this.end_time = System.currentTimeMillis() + (1000 * 60 * 6 * timespan); //Now plus 30 minutes padding (for 5 hours. Less if less.)
this.end_time = System.currentTimeMillis() + (1000L * 60 * 6 * timespan); //Now plus 30
// minutes padding (for 5 hours. Less if less.)
this.predictionEndTime = getPredictionEndTime();
this.end_time = (predictionEndTime>end_time)?predictionEndTime:end_time;
}
public BgGraphBuilder(Context context, RawDisplayData raw, int aPointSize, int aHighColor, int aLowColor, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
this(context,
raw.bgDataList,
raw.predictionList,
raw.tempWatchDataList,
raw.basalWatchDataList,
raw.bolusWatchDataList,
aPointSize,
aHighColor,
aLowColor,
aMidColor,
gridColour,
basalBackgroundColor,
basalCenterColor,
bolusInvalidColor,
carbsColor,
timespan);
}
public BgGraphBuilder(Context context, RawDisplayData raw, int aPointSize, int aMidColor, int gridColour, int basalBackgroundColor, int basalCenterColor, int bolusInvalidColor, int carbsColor, int timespan) {
this(context,
raw.bgDataList,
raw.predictionList,
raw.tempWatchDataList,
raw.basalWatchDataList,
raw.bolusWatchDataList,
aPointSize,
aMidColor,
gridColour,
basalBackgroundColor,
basalCenterColor,
bolusInvalidColor,
carbsColor,
timespan);
this.end_time = Math.max(predictionEndTime, end_time);
}
public LineChartData lineData() {
@ -162,7 +133,7 @@ public class BgGraphBuilder {
public List<Line> defaultLines() {
addBgReadingValues();
List<Line> lines = new ArrayList<Line>();
List<Line> lines = new ArrayList<>();
lines.add(highLine());
lines.add(lowLine());
lines.add(inRangeValuesLine());
@ -172,41 +143,41 @@ public class BgGraphBuilder {
double minChart = lowMark;
double maxChart = highMark;
for ( BgWatchData bgd:bgDataList) {
if(bgd.sgv > maxChart){
maxChart = bgd.sgv;
for (EventData.SingleBg bgd : bgDataList) {
if (bgd.getSgv() > maxChart) {
maxChart = bgd.getSgv();
}
if(bgd.sgv < minChart){
minChart = bgd.sgv;
if (bgd.getSgv() < minChart) {
minChart = bgd.getSgv();
}
}
double maxBasal = 0.1;
for (BasalWatchData bwd: basalWatchDataList) {
if(bwd.amount > maxBasal){
maxBasal = bwd.amount;
for (EventData.TreatmentData.Basal bwd : basalWatchDataList) {
if (bwd.getAmount() > maxBasal) {
maxBasal = bwd.getAmount();
}
}
double maxTemp = maxBasal;
for (TempWatchData twd: tempWatchDataList) {
if(twd.amount > maxTemp){
maxTemp = twd.amount;
for (EventData.TreatmentData.TempBasal twd : tempWatchDataList) {
if (twd.getAmount() > maxTemp) {
maxTemp = twd.getAmount();
}
}
double factor = (maxChart-minChart)/maxTemp;
double factor = (maxChart - minChart) / maxTemp;
// in case basal is the highest, don't paint it totally at the top.
factor = Math.min(factor, ((maxChart-minChart)/maxBasal)*(2/3d));
factor = Math.min(factor, ((maxChart - minChart) / maxBasal) * (2 / 3d));
boolean highlight = PreferenceManager
.getDefaultSharedPreferences(context)
.getBoolean("highlight_basals", false);
for (TempWatchData twd: tempWatchDataList) {
if(twd.endTime > start_time) {
lines.add(tempValuesLine(twd, (float) minChart, factor, false, highlight?(pointSize+1):pointSize));
if(highlight){
for (EventData.TreatmentData.TempBasal twd : tempWatchDataList) {
if (twd.getEndTime() > start_time) {
lines.add(tempValuesLine(twd, (float) minChart, factor, false, highlight ? (pointSize + 1) : pointSize));
if (highlight) {
lines.add(tempValuesLine(twd, (float) minChart, factor, true, 1));
}
}
@ -224,13 +195,14 @@ public class BgGraphBuilder {
private Line basalLine(float offset, double factor, boolean highlight) {
List<PointValue> pointValues = new ArrayList<PointValue>();
List<PointValue> pointValues = new ArrayList<>();
for (BasalWatchData bwd: basalWatchDataList) {
if(bwd.endTime > start_time) {
long begin = Math.max(start_time, bwd.startTime);
pointValues.add(new PointValue(fuzz(begin), offset + (float) (factor * bwd.amount)));
pointValues.add(new PointValue(fuzz(bwd.endTime), offset + (float) (factor * bwd.amount)));
for (EventData.TreatmentData.Basal bwd : basalWatchDataList) {
if (bwd.getEndTime() > start_time) {
long begin = Math.max(start_time, bwd.getStartTime());
pointValues.add(new PointValue(fuzz(begin), offset + (float) (factor * bwd.getAmount())));
pointValues.add(new PointValue(fuzz(bwd.getEndTime()),
offset + (float) (factor * bwd.getAmount())));
}
}
@ -238,7 +210,7 @@ public class BgGraphBuilder {
basalLine.setHasPoints(false);
basalLine.setColor(basalCenterColor);
basalLine.setPathEffect(new DashPathEffect(new float[]{4f, 3f}, 4f));
basalLine.setStrokeWidth(highlight?2:1);
basalLine.setStrokeWidth(highlight ? 2 : 1);
return basalLine;
@ -246,28 +218,28 @@ public class BgGraphBuilder {
private Line bolusLine(float offset) {
List<PointValue> pointValues = new ArrayList<PointValue>();
List<PointValue> pointValues = new ArrayList<>();
for (BolusWatchData bwd: bolusWatchDataList) {
if(bwd.date > start_time && bwd.date <= end_time && !bwd.isSMB && bwd.isValid && bwd.bolus > 0) {
pointValues.add(new PointValue(fuzz(bwd.date), offset -2));
for (EventData.TreatmentData.Treatment bwd : bolusWatchDataList) {
if (bwd.getDate() > start_time && bwd.getDate() <= end_time && !bwd.isSMB() && bwd.isValid() && bwd.getBolus() > 0) {
pointValues.add(new PointValue(fuzz(bwd.getDate()), offset - 2));
}
}
Line line = new Line(pointValues);
line.setColor(basalCenterColor);
line.setHasLines(false);
line.setPointRadius(pointSize*2);
line.setPointRadius(pointSize * 2);
line.setHasPoints(true);
return line;
}
private Line smbLine(float offset) {
List<PointValue> pointValues = new ArrayList<PointValue>();
List<PointValue> pointValues = new ArrayList<>();
for (BolusWatchData bwd: bolusWatchDataList) {
if(bwd.date > start_time && bwd.date <= end_time && bwd.isSMB && bwd.isValid && bwd.bolus > 0) {
pointValues.add(new PointValue(fuzz(bwd.date), offset -2));
for (EventData.TreatmentData.Treatment bwd : bolusWatchDataList) {
if (bwd.getDate() > start_time && bwd.getDate() <= end_time && bwd.isSMB() && bwd.isValid() && bwd.getBolus() > 0) {
pointValues.add(new PointValue(fuzz(bwd.getDate()), offset - 2));
}
}
Line line = new Line(pointValues);
@ -280,11 +252,11 @@ public class BgGraphBuilder {
private Line bolusInvalidLine(float offset) {
List<PointValue> pointValues = new ArrayList<PointValue>();
List<PointValue> pointValues = new ArrayList<>();
for (BolusWatchData bwd: bolusWatchDataList) {
if(bwd.date > start_time && bwd.date <= end_time && !(bwd.isValid && (bwd.bolus > 0 || bwd.carbs > 0))) {
pointValues.add(new PointValue(fuzz(bwd.date), offset -2));
for (EventData.TreatmentData.Treatment bwd : bolusWatchDataList) {
if (bwd.getDate() > start_time && bwd.getDate() <= end_time && !(bwd.isValid() && (bwd.getBolus() > 0 || bwd.getCarbs() > 0))) {
pointValues.add(new PointValue(fuzz(bwd.getDate()), offset - 2));
}
}
Line line = new Line(pointValues);
@ -297,17 +269,17 @@ public class BgGraphBuilder {
private Line carbsLine(float offset) {
List<PointValue> pointValues = new ArrayList<PointValue>();
List<PointValue> pointValues = new ArrayList<>();
for (BolusWatchData bwd: bolusWatchDataList) {
if(bwd.date > start_time && bwd.date <= end_time && !bwd.isSMB && bwd.isValid && bwd.carbs > 0) {
pointValues.add(new PointValue(fuzz(bwd.date), offset +2));
for (EventData.TreatmentData.Treatment bwd : bolusWatchDataList) {
if (bwd.getDate() > start_time && bwd.getDate() <= end_time && !bwd.isSMB() && bwd.isValid() && bwd.getCarbs() > 0) {
pointValues.add(new PointValue(fuzz(bwd.getDate()), offset + 2));
}
}
Line line = new Line(pointValues);
line.setColor(carbsColor);
line.setHasLines(false);
line.setPointRadius(pointSize*2);
line.setPointRadius(pointSize * 2);
line.setHasPoints(true);
return line;
}
@ -316,13 +288,13 @@ public class BgGraphBuilder {
private void addPredictionLines(List<Line> lines) {
Map<Integer, List<PointValue>> values = new HashMap<>();
long endTime = getPredictionEndTime();
for (BgWatchData bwd : predictionsList) {
if (bwd.timestamp <= endTime) {
double value = Math.min(bwd.sgv, UPPER_CUTOFF_SGV);
if (!values.containsKey(bwd.color)) {
values.put(bwd.color, new ArrayList<>());
for (EventData.SingleBg bwd : predictionsList) {
if (bwd.getTimeStamp() <= endTime) {
double value = Math.min(bwd.getSgv(), UPPER_CUTOFF_SGV);
if (!values.containsKey(bwd.getColor())) {
values.put(bwd.getColor(), new ArrayList<>());
}
values.get(bwd.color).add(new PointValue(fuzz(bwd.timestamp), (float) value));
values.get(bwd.getColor()).add(new PointValue(fuzz(bwd.getTimeStamp()), (float) value));
}
}
for (Map.Entry<Integer, List<PointValue>> entry : values.entrySet()) {
@ -358,7 +330,7 @@ public class BgGraphBuilder {
public Line inRangeValuesLine() {
Line inRangeValuesLine = new Line(inRangeValues);
inRangeValuesLine.setColor(midColor);
if(singleLine) {
if (singleLine) {
inRangeValuesLine.setHasLines(true);
inRangeValuesLine.setHasPoints(false);
inRangeValuesLine.setStrokeWidth(pointSize);
@ -371,19 +343,19 @@ public class BgGraphBuilder {
}
public Line tempValuesLine(TempWatchData twd, float offset, double factor, boolean isHighlightLine, int strokeWidth) {
List<PointValue> lineValues = new ArrayList<PointValue>();
long begin = Math.max(start_time, twd.startTime);
lineValues.add(new PointValue(fuzz(begin), offset + (float) (factor * twd.startBasal)));
lineValues.add(new PointValue(fuzz(begin), offset + (float) (factor * twd.amount)));
lineValues.add(new PointValue(fuzz(twd.endTime), offset + (float) (factor * twd.amount)));
lineValues.add(new PointValue(fuzz(twd.endTime), offset + (float) (factor * twd.endBasal)));
public Line tempValuesLine(EventData.TreatmentData.TempBasal twd, float offset, double factor, boolean isHighlightLine, int strokeWidth) {
List<PointValue> lineValues = new ArrayList<>();
long begin = Math.max(start_time, twd.getStartTime());
lineValues.add(new PointValue(fuzz(begin), offset + (float) (factor * twd.getStartBasal())));
lineValues.add(new PointValue(fuzz(begin), offset + (float) (factor * twd.getAmount())));
lineValues.add(new PointValue(fuzz(twd.getEndTime()), offset + (float) (factor * twd.getAmount())));
lineValues.add(new PointValue(fuzz(twd.getEndTime()), offset + (float) (factor * twd.getEndBasal())));
Line valueLine = new Line(lineValues);
valueLine.setHasPoints(false);
if (isHighlightLine){
if (isHighlightLine) {
valueLine.setColor(basalCenterColor);
valueLine.setStrokeWidth(1);
}else {
} else {
valueLine.setColor(basalBackgroundColor);
valueLine.setStrokeWidth(strokeWidth);
}
@ -391,38 +363,36 @@ public class BgGraphBuilder {
}
private void addBgReadingValues() {
if(singleLine) {
for (BgWatchData bgReading : bgDataList) {
if(bgReading.timestamp > start_time) {
if (bgReading.sgv >= 450) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) 450));
} else if (bgReading.sgv >= highMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= lowMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= 40) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= 11) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) 40));
if (singleLine) {
for (EventData.SingleBg bgReading : bgDataList) {
if (bgReading.getTimeStamp() > start_time) {
if (bgReading.getSgv() >= 450) {
inRangeValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) 450));
} else if (bgReading.getSgv() >= highMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) bgReading.getSgv()));
} else if (bgReading.getSgv() >= lowMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) bgReading.getSgv()));
} else if (bgReading.getSgv() >= 40) {
inRangeValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) bgReading.getSgv()));
} else if (bgReading.getSgv() >= 11) {
inRangeValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) 40));
}
}
}
} else {
for (BgWatchData bgReading : bgDataList) {
if (bgReading.timestamp > start_time) {
if (bgReading.sgv >= 450) {
highValues.add(new PointValue(fuzz(bgReading.timestamp), (float) 450));
} else if (bgReading.sgv >= highMark) {
highValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= lowMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= 40) {
lowValues.add(new PointValue(fuzz(bgReading.timestamp), (float) bgReading.sgv));
} else if (bgReading.sgv >= 11) {
lowValues.add(new PointValue(fuzz(bgReading.timestamp), (float) 40));
for (EventData.SingleBg bgReading : bgDataList) {
if (bgReading.getTimeStamp() > start_time) {
if (bgReading.getSgv() >= 450) {
highValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) 450));
} else if (bgReading.getSgv() >= highMark) {
highValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) bgReading.getSgv()));
} else if (bgReading.getSgv() >= lowMark) {
inRangeValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) bgReading.getSgv()));
} else if (bgReading.getSgv() >= 40) {
lowValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) bgReading.getSgv()));
} else if (bgReading.getSgv() >= 11) {
lowValues.add(new PointValue(fuzz(bgReading.getTimeStamp()), (float) 40));
}
}
}
@ -430,7 +400,7 @@ public class BgGraphBuilder {
}
public Line highLine() {
List<PointValue> highLineValues = new ArrayList<PointValue>();
List<PointValue> highLineValues = new ArrayList<>();
highLineValues.add(new PointValue(fuzz(start_time), (float) highMark));
highLineValues.add(new PointValue(fuzz(end_time), (float) highMark));
Line highLine = new Line(highLineValues);
@ -441,7 +411,7 @@ public class BgGraphBuilder {
}
public Line lowLine() {
List<PointValue> lowLineValues = new ArrayList<PointValue>();
List<PointValue> lowLineValues = new ArrayList<>();
lowLineValues.add(new PointValue(fuzz(start_time), (float) lowMark));
lowLineValues.add(new PointValue(fuzz(end_time), (float) lowMark));
Line lowLine = new Line(lowLineValues);
@ -457,7 +427,7 @@ public class BgGraphBuilder {
public Axis yAxis() {
Axis yAxis = new Axis();
yAxis.setAutoGenerated(true);
List<AxisValue> axisValues = new ArrayList<AxisValue>();
List<AxisValue> axisValues = new ArrayList<>();
yAxis.setValues(axisValues);
yAxis.setHasLines(false);
yAxis.setLineColor(gridColour);
@ -466,13 +436,13 @@ public class BgGraphBuilder {
public Axis xAxis() {
final boolean is24 = DateFormat.is24HourFormat(context);
SimpleDateFormat timeFormat = new SimpleDateFormat(is24? "HH" : "h a");
SimpleDateFormat timeFormat = new SimpleDateFormat(is24 ? "HH" : "h a");
timeFormat.setTimeZone(TimeZone.getDefault());
long timeNow = System.currentTimeMillis();
Axis xAxis = new Axis();
xAxis.setAutoGenerated(false);
List<AxisValue> xAxisValues = new ArrayList<AxisValue>();
List<AxisValue> xAxisValues = new ArrayList<>();
//get the time-tick at the full hour after start_time
GregorianCalendar startGC = new GregorianCalendar();
@ -484,14 +454,14 @@ public class BgGraphBuilder {
long start_hour = startGC.getTimeInMillis();
//Display current time on the graph
SimpleDateFormat longTimeFormat = new SimpleDateFormat(is24? "HH:mm" : "h:mm a");
SimpleDateFormat longTimeFormat = new SimpleDateFormat(is24 ? "HH:mm" : "h:mm a");
xAxisValues.add(new AxisValue(fuzz(timeNow)).setLabel((longTimeFormat.format(timeNow))));
long hourTick = start_hour;
// add all full hours within the timeframe
while (hourTick < end_time){
if(Math.abs(hourTick - timeNow) > (8 * (end_time-start_time)/60)){
while (hourTick < end_time) {
if (Math.abs(hourTick - timeNow) > (8 * (end_time - start_time) / 60)) {
xAxisValues.add(new AxisValue(fuzz(hourTick)).setLabel(timeFormat.format(hourTick)));
} else {
//don't print hour label if too close to now to avoid overlaps
@ -499,7 +469,7 @@ public class BgGraphBuilder {
}
//increment by one hour
hourTick += 60*60*1000;
hourTick += 60 * 60 * 1000;
}
xAxis.setValues(xAxisValues);
@ -513,16 +483,16 @@ public class BgGraphBuilder {
public long getPredictionEndTime() {
long maxPredictionDate = System.currentTimeMillis();
for (BgWatchData prediction :
for (EventData.SingleBg prediction :
predictionsList) {
if (maxPredictionDate < prediction.timestamp) {
maxPredictionDate = prediction.timestamp;
if (maxPredictionDate < prediction.getTimeStamp()) {
maxPredictionDate = prediction.getTimeStamp();
}
}
return (long) Math.min(maxPredictionDate, System.currentTimeMillis() + MAX_PREDICTION__TIME_RATIO *timespan*1000*60*60);
return (long) Math.min(maxPredictionDate, System.currentTimeMillis() + MAX_PREDICTION__TIME_RATIO * timespan * 1000 * 60 * 60);
}
public float fuzz(long value) {
return (float) Math.round(value / fuzzyTimeDenom);
return (float) Math.round(value / fuzzyTimeDenom);
}
}

View file

@ -1,25 +1,15 @@
package info.nightscout.androidaps.watchfaces;
import android.content.BroadcastReceiver;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.support.wearable.watchface.WatchFaceStyle;
import android.util.Log;
import android.util.TypedValue;
import android.view.Display;
import android.view.LayoutInflater;
@ -27,21 +17,40 @@ import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import com.google.android.gms.wearable.DataMap;
import com.ustwo.clockwise.common.WatchFaceTime;
import com.ustwo.clockwise.wearable.WatchFace;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.TreeSet;
import javax.inject.Inject;
import dagger.android.AndroidInjection;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.BgWatchData;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
import info.nightscout.androidaps.plugins.bus.RxBus;
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 info.nightscout.shared.weardata.EventData;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
public class CircleWatchface extends WatchFace implements SharedPreferences.OnSharedPreferenceChangeListener {
public class CircleWatchface extends WatchFace {
@Inject RxBus rxBus;
@Inject AapsSchedulers aapsSchedulers;
@Inject AAPSLogger aapsLogger;
@Inject SP sp;
CompositeDisposable disposable = new CompositeDisposable();
private EventData.SingleBg singleBg = new EventData.SingleBg(0, "---", "-", "--", "--", "--", 0, 0.0, 0.0, 0.0, 0);
private EventData.GraphData graphData;
private EventData.Status status = new EventData.Status("no status", "IOB", "-.--", false, "--g", "-.--U/h", "--", "--", -1, "--", false, 1);
public final float PADDING = 20f;
public final float CIRCLE_WIDTH = 10f;
public final int BIG_HAND_WIDTH = 16;
@ -52,43 +61,27 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
//variables for time
private float angleBig = 0f;
private float angleSMALL = 0f;
private int hour, minute;
private int color;
private final Paint circlePaint = new Paint();
private final Paint removePaint = new Paint();
private RectF rect, rectDelete;
private boolean overlapping;
private int animationAngle = 0;
private boolean isAnimated = false;
public Point displaySize = new Point();
private final MessageReceiver messageReceiver = new MessageReceiver();
private int sgvLevel = 0;
private String sgvString = "999";
private String statusString = "no status";
private int batteryLevel = 0;
private long datetime = 0;
private String direction = "";
private String delta = "";
private String avgDelta = "";
public TreeSet<BgWatchData> bgDataList = new TreeSet<>();
public ArrayList<EventData.SingleBg> bgDataList = new ArrayList<>();
private int specW;
private int specH;
private View myLayout;
protected SharedPreferences sharedPrefs;
private TextView mSgv;
private long sgvTapTime = 0;
@Override
@SuppressLint("InflateParams") @Override
public void onCreate() {
AndroidInjection.inject(this);
super.onCreate();
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
@ -104,19 +97,46 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
specH = View.MeasureSpec.makeMeasureSpec(displaySize.y,
View.MeasureSpec.EXACTLY);
sharedPrefs = PreferenceManager
.getDefaultSharedPreferences(this);
sharedPrefs.registerOnSharedPreferenceChangeListener(this);
//register Message Receiver
LocalBroadcastManager.getInstance(this).registerReceiver(messageReceiver, new IntentFilter(Intent.ACTION_SEND));
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
myLayout = inflater.inflate(R.layout.modern_layout, null);
prepareLayout();
prepareDrawTime();
//ListenerService.requestData(this); //usually connection is not set up yet
disposable.add(rxBus
.toObservable(EventData.SingleBg.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> singleBg = event)
);
disposable.add(rxBus
.toObservable(EventData.GraphData.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> graphData = event)
);
disposable.add(rxBus
.toObservable(EventData.Status.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
// this event is received as last batch of data
aapsLogger.debug(LTag.WEAR, "Status received");
status = event;
addToWatchSet();
prepareLayout();
prepareDrawTime();
invalidate();
})
);
disposable.add(rxBus
.toObservable(EventData.Preferences.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
prepareDrawTime();
prepareLayout();
invalidate();
})
);
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("CircleWatchFace::onCreate")));
wakeLock.release();
}
@ -124,18 +144,13 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
@Override
public void onDestroy() {
if (messageReceiver != null) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(messageReceiver);
}
if (sharedPrefs != null) {
sharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
disposable.clear();
super.onDestroy();
}
@Override
protected synchronized void onDraw(Canvas canvas) {
Log.d("CircleWatchface", "start onDraw");
aapsLogger.debug(LTag.WEAR, "start onDraw");
canvas.drawColor(getBackgroundColor());
drawTime(canvas);
drawOtherStuff(canvas);
@ -145,27 +160,25 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
private synchronized void prepareLayout() {
Log.d("CircleWatchface", "start startPrepareLayout");
aapsLogger.debug(LTag.WEAR, "start startPrepareLayout");
// prepare fields
TextView textView;
mSgv = myLayout.findViewById(R.id.sgvString);
textView = myLayout.findViewById(R.id.sgvString);
if (sharedPrefs.getBoolean("showBG", true)) {
textView.setVisibility(View.VISIBLE);
textView.setText(getSgvString());
textView.setTextColor(getTextColor());
if (sp.getBoolean("showBG", true)) {
mSgv.setVisibility(View.VISIBLE);
mSgv.setText(singleBg.getSgvString());
mSgv.setTextColor(getTextColor());
} else {
//Also possible: View.INVISIBLE instead of View.GONE (no layout change)
textView.setVisibility(View.INVISIBLE);
mSgv.setVisibility(View.INVISIBLE);
}
textView = myLayout.findViewById(R.id.statusString);
if (sharedPrefs.getBoolean("showExternalStatus", true)) {
TextView textView = myLayout.findViewById(R.id.statusString);
if (sp.getBoolean("showExternalStatus", true)) {
textView.setVisibility(View.VISIBLE);
textView.setText(getStatusString());
textView.setText(status.getExternalStatus());
textView.setTextColor(getTextColor());
} else {
@ -174,10 +187,10 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
textView = myLayout.findViewById(R.id.agoString);
if (sharedPrefs.getBoolean("showAgo", true)) {
if (sp.getBoolean("showAgo", true)) {
textView.setVisibility(View.VISIBLE);
if (sharedPrefs.getBoolean("showBigNumbers", false)) {
if (sp.getBoolean("showBigNumbers", false)) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 26);
} else {
((TextView) myLayout.findViewById(R.id.agoString)).setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
@ -190,17 +203,17 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
textView = myLayout.findViewById(R.id.deltaString);
if (sharedPrefs.getBoolean("showDelta", true)) {
if (sp.getBoolean("showDelta", true)) {
textView.setVisibility(View.VISIBLE);
textView.setText(getDelta());
textView.setText(singleBg.getDelta());
textView.setTextColor(getTextColor());
if (sharedPrefs.getBoolean("showBigNumbers", false)) {
if (sp.getBoolean("showBigNumbers", false)) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
} else {
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
}
if (sharedPrefs.getBoolean("showAvgDelta", true)) {
textView.append(" " + getAvgDelta());
if (sp.getBoolean("showAvgDelta", true)) {
textView.append(" " + singleBg.getAvgDelta());
}
} else {
@ -215,8 +228,8 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
public String getMinutes() {
String minutes = "--'";
if (getDatetime() != 0) {
minutes = ((int) Math.floor((System.currentTimeMillis() - getDatetime()) / 60000.0)) + "'";
if (singleBg.getTimeStamp() != 0) {
minutes = ((int) Math.floor((System.currentTimeMillis() - singleBg.getTimeStamp()) / 60000.0)) + "'";
}
return minutes;
}
@ -249,16 +262,16 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
private synchronized void prepareDrawTime() {
Log.d("CircleWatchface", "start prepareDrawTime");
aapsLogger.debug(LTag.WEAR, "start prepareDrawTime");
hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) % 12;
minute = Calendar.getInstance().get(Calendar.MINUTE);
int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) % 12;
int minute = Calendar.getInstance().get(Calendar.MINUTE);
angleBig = (((hour + minute / 60f) / 12f * 360) - 90 - BIG_HAND_WIDTH / 2f + 360) % 360;
angleSMALL = ((minute / 60f * 360) - 90 - SMALL_HAND_WIDTH / 2f + 360) % 360;
color = 0;
switch (getSgvLevel()) {
switch ((int) singleBg.getSgvLevel()) {
case -1:
color = getLowColor();
break;
@ -271,20 +284,7 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
if (isAnimated()) {
//Animation matrix:
int[] rainbow = {Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE
, Color.CYAN};
Shader shader = new LinearGradient(0, 0, 0, 20, rainbow,
null, Shader.TileMode.MIRROR);
Matrix matrix = new Matrix();
matrix.setRotate(animationAngle);
shader.setLocalMatrix(matrix);
circlePaint.setShader(shader);
} else {
circlePaint.setShader(null);
}
circlePaint.setShader(null);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setStrokeWidth(CIRCLE_WIDTH);
@ -299,17 +299,10 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
rect = new RectF(PADDING, PADDING, displaySize.x - PADDING, displaySize.y - PADDING);
rectDelete = new RectF(PADDING - CIRCLE_WIDTH / 2, PADDING - CIRCLE_WIDTH / 2, displaySize.x - PADDING + CIRCLE_WIDTH / 2, displaySize.y - PADDING + CIRCLE_WIDTH / 2);
overlapping = ALWAYS_HIGHLIGT_SMALL || areOverlapping(angleSMALL, angleSMALL + SMALL_HAND_WIDTH + NEAR, angleBig, angleBig + BIG_HAND_WIDTH + NEAR);
Log.d("CircleWatchface", "end prepareDrawTime");
aapsLogger.debug(LTag.WEAR, "end prepareDrawTime");
}
synchronized void animationStep() {
animationAngle = (animationAngle + 1) % 360;
prepareDrawTime();
invalidate();
}
private boolean areOverlapping(float aBegin, float aEnd, float bBegin, float bEnd) {
return
aBegin <= bBegin && aEnd >= bBegin ||
@ -338,7 +331,7 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
// defining color for dark and bright
public int getLowColor() {
if (sharedPrefs.getBoolean("dark", true)) {
if (sp.getBoolean("dark", true)) {
return Color.argb(255, 255, 120, 120);
} else {
return Color.argb(255, 255, 80, 80);
@ -346,7 +339,7 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
public int getInRangeColor() {
if (sharedPrefs.getBoolean("dark", true)) {
if (sp.getBoolean("dark", true)) {
return Color.argb(255, 120, 255, 120);
} else {
return Color.argb(255, 0, 240, 0);
@ -355,7 +348,7 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
public int getHighColor() {
if (sharedPrefs.getBoolean("dark", true)) {
if (sp.getBoolean("dark", true)) {
return Color.argb(255, 255, 255, 120);
} else {
return Color.argb(255, 255, 200, 0);
@ -364,7 +357,7 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
public int getBackgroundColor() {
if (sharedPrefs.getBoolean("dark", true)) {
if (sp.getBoolean("dark", true)) {
return Color.BLACK;
} else {
return Color.WHITE;
@ -373,7 +366,7 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
public int getTextColor() {
if (sharedPrefs.getBoolean("dark", true)) {
if (sp.getBoolean("dark", true)) {
return Color.WHITE;
} else {
return Color.BLACK;
@ -382,23 +375,22 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
public void drawOtherStuff(Canvas canvas) {
Log.d("CircleWatchface", "start onDrawOtherStuff. bgDataList.size(): " + bgDataList.size());
aapsLogger.debug(LTag.WEAR, "start onDrawOtherStuff. bgDataList.size(): " + bgDataList.size());
if (isAnimated()) return; // too many repaints when animated
if (sharedPrefs.getBoolean("showRingHistory", false)) {
if (sp.getBoolean("showRingHistory", false)) {
//Perfect low and High indicators
if (bgDataList.size() > 0) {
addIndicator(canvas, 100, Color.LTGRAY);
addIndicator(canvas, (float) bgDataList.iterator().next().low, getLowColor());
addIndicator(canvas, (float) bgDataList.iterator().next().high, getHighColor());
addIndicator(canvas, (float) bgDataList.iterator().next().getLow(), getLowColor());
addIndicator(canvas, (float) bgDataList.iterator().next().getHigh(), getHighColor());
if (sharedPrefs.getBoolean("softRingHistory", true)) {
for (BgWatchData data : bgDataList) {
if (sp.getBoolean("softRingHistory", true)) {
for (EventData.SingleBg data : bgDataList) {
addReadingSoft(canvas, data);
}
} else {
for (BgWatchData data : bgDataList) {
for (EventData.SingleBg data : bgDataList) {
addReading(canvas, data);
}
}
@ -406,202 +398,15 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
}
public int holdInMemory() {
return 6;
}
public synchronized void addToWatchSet() {
//getters & setters
bgDataList.clear();
if (!sp.getBoolean("showRingHistory", false)) return;
private synchronized int getSgvLevel() {
return sgvLevel;
}
private synchronized void setSgvLevel(int sgvLevel) {
this.sgvLevel = sgvLevel;
}
private synchronized int getBatteryLevel() {
return batteryLevel;
}
private synchronized void setBatteryLevel(int batteryLevel) {
this.batteryLevel = batteryLevel;
}
private synchronized long getDatetime() {
return datetime;
}
private synchronized void setDatetime(long datetime) {
this.datetime = datetime;
}
private synchronized String getDirection() {
return direction;
}
private void setDirection(String direction) {
this.direction = direction;
}
String getSgvString() {
return sgvString;
}
void setSgvString(String sgvString) {
this.sgvString = sgvString;
}
String getStatusString() {
return statusString;
}
void setStatusString(String statusString) {
this.statusString = statusString;
}
public String getDelta() {
return delta;
}
private void setDelta(String delta) {
this.delta = delta;
}
private String getAvgDelta() {
return avgDelta;
}
private void setAvgDelta(String avgDelta) {
this.avgDelta = avgDelta;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
prepareDrawTime();
prepareLayout();
invalidate();
}
private synchronized boolean isAnimated() {
return isAnimated;
}
private synchronized void setIsAnimated(boolean isAnimated) {
this.isAnimated = isAnimated;
}
void startAnimation() {
Log.d("CircleWatchface", "start startAnimation");
Thread animator = new Thread() {
public void run() {
setIsAnimated(true);
for (int i = 0; i <= 8 * 1000 / 40; i++) {
animationStep();
SystemClock.sleep(40);
}
setIsAnimated(false);
prepareDrawTime();
invalidate();
System.gc();
}
};
animator.start();
}
public class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AndroidAPS:MessageReceiver");
wakeLock.acquire(30000);
Bundle bundle = intent.getBundleExtra("data");
if (bundle != null) {
DataMap dataMap = DataMap.fromBundle(bundle);
setSgvLevel((int) dataMap.getLong("sgvLevel"));
Log.d("CircleWatchface", "sgv level : " + getSgvLevel());
setSgvString(dataMap.getString("sgvString"));
Log.d("CircleWatchface", "sgv string : " + getSgvString());
setDelta(dataMap.getString("delta"));
setAvgDelta(dataMap.getString("avgDelta"));
setDatetime(dataMap.getLong("timestamp"));
addToWatchSet(dataMap);
//start animation?
// dataMap.getDataMapArrayList("entries") == null -> not on "resend data".
if (sharedPrefs.getBoolean("animation", false) && dataMap.getDataMapArrayList("entries") == null && (getSgvString().equals("100") || getSgvString().equals("5.5") || getSgvString().equals("5,5"))) {
startAnimation();
}
prepareLayout();
prepareDrawTime();
invalidate();
}
//status
bundle = intent.getBundleExtra("status");
if (bundle != null) {
DataMap dataMap = DataMap.fromBundle(bundle);
wakeLock.acquire(50);
setStatusString(dataMap.getString("externalStatusString"));
prepareLayout();
prepareDrawTime();
invalidate();
}
wakeLock.release();
}
}
public synchronized void addToWatchSet(DataMap dataMap) {
if (!sharedPrefs.getBoolean("showRingHistory", false)) {
bgDataList.clear();
return;
}
Log.d("CircleWatchface", "start addToWatchSet");
ArrayList<DataMap> entries = dataMap.getDataMapArrayList("entries");
if (entries == null) {
double sgv = dataMap.getDouble("sgvDouble");
double high = dataMap.getDouble("high");
double low = dataMap.getDouble("low");
long timestamp = dataMap.getLong("timestamp");
int color = dataMap.getInt("color", 0);
bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
} else if (!sharedPrefs.getBoolean("animation", false)) {
// don't load history at once if animations are set (less resource consumption)
Log.d("addToWatchSet", "entries.size(): " + entries.size());
for (DataMap entry : entries) {
double sgv = entry.getDouble("sgvDouble");
double high = entry.getDouble("high");
double low = entry.getDouble("low");
long timestamp = entry.getLong("timestamp");
int color = entry.getInt("color", 0);
bgDataList.add(new BgWatchData(sgv, high, low, timestamp, color));
}
} else
Log.d("addToWatchSet", "start removing bgDataList.size(): " + bgDataList.size());
HashSet<BgWatchData> removeSet = new HashSet<>();
double threshold = (System.currentTimeMillis() - (1000 * 60 * 5 * holdInMemory()));
for (BgWatchData data : bgDataList) {
if (data.timestamp < threshold) {
removeSet.add(data);
}
}
bgDataList.removeAll(removeSet);
Log.d("addToWatchSet", "after bgDataList.size(): " + bgDataList.size());
removeSet = null;
System.gc();
double threshold = (System.currentTimeMillis() - (1000L * 60 * 30)); // 30 min
for (EventData.SingleBg entry : graphData.getEntries())
if (entry.getTimeStamp() >= threshold) bgDataList.add(entry);
aapsLogger.debug(LTag.WEAR, "addToWatchSet size=" + bgDataList.size());
}
public int darken(int color, double fraction) {
@ -618,10 +423,7 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
private int darkenColor(int color, double fraction) {
//if (sharedPrefs.getBoolean("dark", false)) {
return (int) Math.max(color - (color * fraction), 0);
//}
// return (int)Math.min(color + (color * fraction), 255);
}
@ -659,44 +461,46 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
}
public void addReadingSoft(Canvas canvas, BgWatchData entry) {
public void addReadingSoft(Canvas canvas, EventData.SingleBg entry) {
Log.d("CircleWatchface", "addReadingSoft");
aapsLogger.debug(LTag.WEAR, "addReadingSoft");
double size;
int color = Color.LTGRAY;
if (sharedPrefs.getBoolean("dark", true)) {
if (sp.getBoolean("dark", true)) {
color = Color.DKGRAY;
}
float offsetMultiplier = (((displaySize.x / 2f) - PADDING) / 12f);
float offset = (float) Math.max(1, Math.ceil((System.currentTimeMillis() - entry.timestamp) / (1000 * 60 * 5.0)));
size = bgToAngle((float) entry.sgv);
float offset = (float) Math.max(1,
Math.ceil((System.currentTimeMillis() - entry.getTimeStamp()) / (1000 * 60 * 5.0)));
size = bgToAngle((float) entry.getSgv());
addArch(canvas, offset * offsetMultiplier + 10, color, (float) size);
addArch(canvas, (float) size, offset * offsetMultiplier + 10, getBackgroundColor(), (float) (360 - size));
addArch(canvas, (offset + .8f) * offsetMultiplier + 10, getBackgroundColor(), 360);
}
public void addReading(Canvas canvas, BgWatchData entry) {
Log.d("CircleWatchface", "addReading");
public void addReading(Canvas canvas, EventData.SingleBg entry) {
aapsLogger.debug(LTag.WEAR, "addReading");
double size;
int color = Color.LTGRAY;
int indicatorColor = Color.DKGRAY;
if (sharedPrefs.getBoolean("dark", true)) {
if (sp.getBoolean("dark", true)) {
color = Color.DKGRAY;
indicatorColor = Color.LTGRAY;
}
int barColor = Color.GRAY;
if (entry.sgv >= entry.high) {
if (entry.getSgv() >= entry.getHigh()) {
indicatorColor = getHighColor();
barColor = darken(getHighColor(), .5);
} else if (entry.sgv <= entry.low) {
} else if (entry.getSgv() <= entry.getLow()) {
indicatorColor = getLowColor();
barColor = darken(getLowColor(), .5);
}
float offsetMultiplier = (((displaySize.x / 2f) - PADDING) / 12f);
float offset = (float) Math.max(1, Math.ceil((System.currentTimeMillis() - entry.timestamp) / (1000 * 60 * 5.0)));
size = bgToAngle((float) entry.sgv);
float offset = (float) Math.max(1,
Math.ceil((System.currentTimeMillis() - entry.getTimeStamp()) / (1000 * 60 * 5.0)));
size = bgToAngle((float) entry.getSgv());
addArch(canvas, offset * offsetMultiplier + 11, barColor, (float) size - 2); // Dark Color Bar
addArch(canvas, (float) size - 2, offset * offsetMultiplier + 11, indicatorColor, 2f); // Indicator at end of bar
addArch(canvas, (float) size, offset * offsetMultiplier + 11, color, (float) (360f - size)); // Dark fill
@ -705,8 +509,8 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
@Override
protected void onTapCommand(int tapType, int x, int y, long eventTime) {
int extra = mSgv != null ? (mSgv.getRight() - mSgv.getLeft()) / 2 : 0;
if (mSgv == null) return;
int extra = (mSgv.getRight() - mSgv.getLeft()) / 2;
if (tapType == TAP_TYPE_TAP &&
x + extra >= mSgv.getLeft() &&
@ -726,6 +530,4 @@ public class CircleWatchface extends WatchFace implements SharedPreferences.OnSh
protected WatchFaceStyle getWatchFaceStyle() {
return new WatchFaceStyle.Builder(this).setAcceptsTapEvents(true).build();
}
}

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.watchfaces;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.support.wearable.watchface.WatchFaceStyle;
import android.view.LayoutInflater;
@ -16,7 +17,7 @@ public class Cockpit extends BaseWatchFace {
private long sgvTapTime = 0;
@Override
@SuppressLint("InflateParams") @Override
public void onCreate() {
super.onCreate();
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
@ -48,13 +49,13 @@ public class Cockpit extends BaseWatchFace {
setTextSizes();
if (mHighLight != null && mLowLight != null) {
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mHighLight.setBackgroundResource(R.drawable.airplane_led_yellow_lit);
mLowLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit);
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mHighLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit);
mLowLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit);
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mHighLight.setBackgroundResource(R.drawable.airplane_led_grey_unlit);
mLowLight.setBackgroundResource(R.drawable.airplane_led_red_lit);
}
@ -84,7 +85,7 @@ public class Cockpit extends BaseWatchFace {
protected void setTextSizes() {
if (mIOB2 != null) {
if (rawData.detailedIOB) {
if (status.getDetailedIob()) {
if (bIsRound) {
mIOB2.setTextSize(10);
} else {

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.watchfaces;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.support.wearable.watchface.WatchFaceStyle;
@ -19,10 +20,9 @@ import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
public class DigitalStyle extends BaseWatchFace {
private static final long TIME_TAP_THRESHOLD = 800;
private final long chartTapTime = 0;
private long sgvTapTime = 0;
@Override
@SuppressLint("InflateParams") @Override
public void onCreate() {
super.onCreate();
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
@ -57,13 +57,13 @@ public class DigitalStyle extends BaseWatchFace {
}
protected void setColorDark() {
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
}
@ -74,7 +74,7 @@ public class DigitalStyle extends BaseWatchFace {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
if (rawData.batteryLevel == 1) {
if (status.getBatteryLevel() == 1) {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
} else {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty));
@ -99,10 +99,11 @@ public class DigitalStyle extends BaseWatchFace {
LinearLayout mShapesElements = layoutView.findViewById(R.id.shapes_elements);
if (mShapesElements != null) {
String displayFormatType = (mShapesElements.getContentDescription().toString().startsWith("round") ? "round" : "rect");
String displayStyle=sharedPrefs.getString("digitalstyle_frameStyle", "full");
String displayFrameColor=sharedPrefs.getString("digitalstyle_frameColor", "red");
String displayFrameColorSaturation=sharedPrefs.getString("digitalstyle_frameColorSaturation", "500");
String displayFrameColorOpacity=sharedPrefs.getString("digitalstyle_frameColorOpacity", "1");
String displayStyle=sp.getString("digitalstyle_frameStyle", "full");
String displayFrameColor=sp.getString("digitalstyle_frameColor", "red");
String displayFrameColorSaturation=sp.getString("digitalstyle_frameColorSaturation",
"500");
String displayFrameColorOpacity=sp.getString("digitalstyle_frameColorOpacity", "1");
// Load image with shapes
String styleDrawableName = "digitalstyle_bg_" + displayStyle + "_" + displayFormatType;
@ -133,7 +134,7 @@ public class DigitalStyle extends BaseWatchFace {
}
/* optimize font-size --> when date is off then increase font-size of time */
Boolean isShowDate = sharedPrefs.getBoolean("show_date", false);
Boolean isShowDate = sp.getBoolean("show_date", false);
if (!isShowDate) {
layoutView.findViewById(R.id.date_time).setVisibility(View.GONE);
mHour.setTextSize(62);
@ -148,7 +149,7 @@ public class DigitalStyle extends BaseWatchFace {
mMinute.setLetterSpacing((float) 0);
/* display week number */
Boolean isShowWeekNumber = sharedPrefs.getBoolean("show_weeknumber", false);
Boolean isShowWeekNumber = sp.getBoolean("show_weeknumber", false);
Log.i("---------------------------------","weeknumber refresh ");
TextView mWeekNumber= layoutView.findViewById(R.id.weeknumber);
if (isShowWeekNumber) {

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.watchfaces;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Color;
import androidx.core.content.ContextCompat;
@ -16,7 +17,7 @@ public class Home extends BaseWatchFace {
private long chartTapTime = 0;
private long sgvTapTime = 0;
@Override
@SuppressLint("InflateParams") @Override
public void onCreate() {
super.onCreate();
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
@ -53,9 +54,9 @@ public class Home extends BaseWatchFace {
}
private void changeChartTimeframe() {
int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3"));
int timeframe = sp.getInt("chart_timeframe", 3);
timeframe = (timeframe%5) + 1;
sharedPrefs.edit().putString("chart_timeframe", "" + timeframe).apply();
sp.putString("chart_timeframe", "" + timeframe);
}
@Override
@ -69,15 +70,15 @@ public class Home extends BaseWatchFace {
R.color.dark_background : R.color.dark_statusView));
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_background));
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
@ -90,7 +91,7 @@ public class Home extends BaseWatchFace {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
if (rawData.batteryLevel == 1) {
if (status.getBatteryLevel() == 1) {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), dividerMatchesBg ?
R.color.dark_midColor : R.color.dark_uploaderBattery));
} else {
@ -138,15 +139,15 @@ public class Home extends BaseWatchFace {
mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), dividerMatchesBg ?
R.color.light_background : R.color.light_stripe_background));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_background));
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
@ -158,7 +159,7 @@ public class Home extends BaseWatchFace {
mTimestamp.setTextColor(Color.RED);
}
if (rawData.batteryLevel == 1) {
if (status.getBatteryLevel() == 1) {
mUploaderBattery.setTextColor(dividerMatchesBg ? Color.BLACK : Color.WHITE);
} else {
mUploaderBattery.setTextColor(Color.RED);

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.watchfaces;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Color;
@ -19,7 +20,7 @@ public class Home2 extends BaseWatchFace {
private long chartTapTime = 0;
private long sgvTapTime = 0;
@Override
@SuppressLint("InflateParams") @Override
public void onCreate() {
super.onCreate();
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
@ -57,9 +58,9 @@ public class Home2 extends BaseWatchFace {
}
private void changeChartTimeframe() {
int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3"));
int timeframe = sp.getInt("chart_timeframe", 3);
timeframe = (timeframe % 5) + 1;
sharedPrefs.edit().putString("chart_timeframe", "" + timeframe).apply();
sp.putString("chart_timeframe", "" + timeframe);
}
@Override
@ -89,13 +90,13 @@ public class Home2 extends BaseWatchFace {
setTextSizes();
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
}
@ -106,7 +107,7 @@ public class Home2 extends BaseWatchFace {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
if (rawData.batteryLevel == 1) {
if (status.getBatteryLevel() == 1) {
mUploaderBattery.setTextColor(dividerBatteryOkColor);
} else {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_uploaderBatteryEmpty));
@ -200,13 +201,13 @@ public class Home2 extends BaseWatchFace {
setTextSizes();
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
}
@ -217,7 +218,7 @@ public class Home2 extends BaseWatchFace {
mTimestamp.setTextColor(Color.RED);
}
if (rawData.batteryLevel == 1) {
if (status.getBatteryLevel() == 1) {
mUploaderBattery.setTextColor(dividerTxtColor);
} else {
mUploaderBattery.setTextColor(Color.RED);
@ -255,7 +256,7 @@ public class Home2 extends BaseWatchFace {
if (mIOB1 != null && mIOB2 != null) {
if (rawData.detailedIOB) {
if (status.getDetailedIob()) {
mIOB1.setTextSize(14);
mIOB2.setTextSize(10);
} else {

View file

@ -1,5 +1,6 @@
package info.nightscout.androidaps.watchfaces;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Color;
import androidx.core.content.ContextCompat;
@ -15,7 +16,7 @@ public class LargeHome extends BaseWatchFace {
private long sgvTapTime = 0;
@Override
@SuppressLint("InflateParams") @Override
public void onCreate() {
super.onCreate();
LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
@ -25,8 +26,8 @@ public class LargeHome extends BaseWatchFace {
@Override
protected void onTapCommand(int tapType, int x, int y, long eventTime) {
int extra = mSgv!=null?(mSgv.getRight() - mSgv.getLeft())/2:0;
if (mSgv == null) return;
int extra = (mSgv.getRight() - mSgv.getLeft())/2;
if (tapType == TAP_TYPE_TAP&&
x + extra >=mSgv.getLeft() &&
@ -53,15 +54,15 @@ public class LargeHome extends BaseWatchFace {
R.color.dark_background : R.color.dark_mLinearLayout));
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_background));
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_highColor));
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_midColor));
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_lowColor));
@ -74,7 +75,7 @@ public class LargeHome extends BaseWatchFace {
mTimestamp.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_TimestampOld));
}
if (rawData.batteryLevel == 1) {
if (status.getBatteryLevel() == 1) {
mUploaderBattery.setTextColor(ContextCompat.getColor(getApplicationContext(), dividerMatchesBg ?
R.color.dark_midColor : R.color.dark_uploaderBattery));
} else {
@ -90,15 +91,15 @@ public class LargeHome extends BaseWatchFace {
mLinearLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), dividerMatchesBg ?
R.color.light_background : R.color.light_stripe_background));
mRelativeLayout.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.light_background));
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_highColor));
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_midColor));
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDelta.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
mDirection.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.light_lowColor));
@ -110,7 +111,7 @@ public class LargeHome extends BaseWatchFace {
mTimestamp.setTextColor(Color.RED);
}
if (rawData.batteryLevel == 1) {
if (status.getBatteryLevel() == 1) {
mUploaderBattery.setTextColor(dividerMatchesBg ? Color.BLACK : Color.WHITE);
} else {
mUploaderBattery.setTextColor(Color.RED);
@ -120,15 +121,15 @@ public class LargeHome extends BaseWatchFace {
} else {
mRelativeLayout.setBackgroundColor(Color.BLACK);
mLinearLayout.setBackgroundColor(dividerMatchesBg ? Color.BLACK : Color.LTGRAY);
if (rawData.sgvLevel == 1) {
if (singleBg.getSgvLevel() == 1) {
mSgv.setTextColor(Color.YELLOW);
mDirection.setTextColor(Color.YELLOW);
mDelta.setTextColor(Color.YELLOW);
} else if (rawData.sgvLevel == 0) {
} else if (singleBg.getSgvLevel() == 0) {
mSgv.setTextColor(Color.WHITE);
mDirection.setTextColor(Color.WHITE);
mDelta.setTextColor(Color.WHITE);
} else if (rawData.sgvLevel == -1) {
} else if (singleBg.getSgvLevel() == -1) {
mSgv.setTextColor(Color.RED);
mDirection.setTextColor(Color.RED);
mDelta.setTextColor(Color.RED);

View file

@ -1,29 +1,17 @@
package info.nightscout.androidaps.watchfaces;
import android.content.BroadcastReceiver;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Shader;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.support.wearable.view.WatchViewStub;
import android.support.wearable.watchface.WatchFaceStyle;
import android.text.format.DateFormat;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
@ -32,56 +20,60 @@ import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.google.android.gms.wearable.DataMap;
import androidx.core.content.ContextCompat;
import com.ustwo.clockwise.common.WatchFaceTime;
import com.ustwo.clockwise.common.WatchMode;
import com.ustwo.clockwise.common.WatchShape;
import com.ustwo.clockwise.wearable.WatchFace;
import java.util.ArrayList;
import javax.inject.Inject;
import dagger.android.AndroidInjection;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.BasalWatchData;
import info.nightscout.androidaps.data.BgWatchData;
import info.nightscout.androidaps.data.DataLayerListenerService;
import info.nightscout.androidaps.data.TempWatchData;
import info.nightscout.androidaps.events.EventWearToMobile;
import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
import info.nightscout.androidaps.plugins.bus.RxBus;
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 info.nightscout.shared.weardata.EventData;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
/**
* Created by adrianLxM.
*/
public class NOChart extends WatchFace implements SharedPreferences.OnSharedPreferenceChangeListener {
public final static IntentFilter INTENT_FILTER;
public class NOChart extends WatchFace {
@Inject RxBus rxBus;
@Inject AapsSchedulers aapsSchedulers;
@Inject AAPSLogger aapsLogger;
@Inject SP sp;
CompositeDisposable disposable = new CompositeDisposable();
private EventData.SingleBg singleBg;
private EventData.Status status;
public static final int SCREENSIZE_SMALL = 280;
public TextView mTime, mSgv, mTimestamp, mDelta, mAvgDelta;
public RelativeLayout mRelativeLayout;
public long sgvLevel = 0;
public int batteryLevel = 1;
public int ageLevel = 1;
public boolean lowResMode = false;
public boolean layoutSet = false;
public long datetime;
public ArrayList<BgWatchData> bgDataList = new ArrayList<>();
public ArrayList<TempWatchData> tempWatchDataList = new ArrayList<>();
public ArrayList<BasalWatchData> basalWatchDataList = new ArrayList<>();
public PowerManager.WakeLock wakeLock;
public View layoutView;
private final Point displaySize = new Point();
private int specW, specH;
private int animationAngle = 0;
private boolean isAnimated = false;
private LocalBroadcastManager localBroadcastManager;
private MessageReceiver messageReceiver;
protected SharedPreferences sharedPrefs;
private String sgvString = "--";
private String externalStatusString = "no status";
private TextView statusView;
private long sgvTapTime = 0L;
@Override
@SuppressLint("InflateParams") @Override
public void onCreate() {
AndroidInjection.inject(this);
super.onCreate();
Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
@ -92,17 +84,60 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
View.MeasureSpec.EXACTLY);
specH = View.MeasureSpec.makeMeasureSpec(displaySize.y,
View.MeasureSpec.EXACTLY);
sharedPrefs = PreferenceManager
.getDefaultSharedPreferences(this);
sharedPrefs.registerOnSharedPreferenceChangeListener(this);
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
layoutView = inflater.inflate(R.layout.activity_nochart, null);
DisplayMetrics metrics = getResources().getDisplayMetrics();
if(metrics.widthPixels < SCREENSIZE_SMALL || metrics.heightPixels < SCREENSIZE_SMALL){
if (metrics.widthPixels < SCREENSIZE_SMALL || metrics.heightPixels < SCREENSIZE_SMALL) {
layoutView = inflater.inflate(R.layout.activity_nochart_small, null);
} else {
layoutView = inflater.inflate(R.layout.activity_nochart, null);
}
disposable.add(rxBus
.toObservable(EventData.SingleBg.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
aapsLogger.debug(LTag.WEAR, "SingleBg received");
singleBg = event;
mSgv.setText(singleBg.getSgvString());
if (ageLevel() <= 0)
mSgv.setPaintFlags(mSgv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
else mSgv.setPaintFlags(mSgv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
final java.text.DateFormat timeFormat = DateFormat.getTimeFormat(this);
mTime.setText(timeFormat.format(System.currentTimeMillis()));
mDelta.setText(singleBg.getDelta());
mAvgDelta.setText(singleBg.getAvgDelta());
})
);
disposable.add(rxBus
.toObservable(EventData.Status.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
// this event is received as last batch of data
aapsLogger.debug(LTag.WEAR, "Status received");
status = event;
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
invalidate();
setColor();
})
);
disposable.add(rxBus
.toObservable(EventData.Preferences.class)
.observeOn(aapsSchedulers.getMain())
.subscribe(event -> {
setColor();
if (layoutSet) {
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
}
invalidate();
})
);
performViewSetup();
}
@ -114,44 +149,37 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
public void performViewSetup() {
final WatchViewStub stub = layoutView.findViewById(R.id.watch_view_stub);
IntentFilter messageFilter = new IntentFilter(Intent.ACTION_SEND);
messageReceiver = new MessageReceiver();
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(messageReceiver, messageFilter);
stub.setOnLayoutInflatedListener(new WatchViewStub.OnLayoutInflatedListener() {
@Override
public void onLayoutInflated(WatchViewStub stub) {
mTime = stub.findViewById(R.id.watch_time);
mSgv = stub.findViewById(R.id.sgv);
mTimestamp = stub.findViewById(R.id.timestamp);
mDelta = stub.findViewById(R.id.delta);
mAvgDelta = stub.findViewById(R.id.avgdelta);
mRelativeLayout = stub.findViewById(R.id.main_layout);
statusView = stub.findViewById(R.id.aps_status);
layoutSet = true;
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
}
stub.setOnLayoutInflatedListener(stub1 -> {
mTime = stub1.findViewById(R.id.watch_time);
mSgv = stub1.findViewById(R.id.sgv);
mTimestamp = stub1.findViewById(R.id.timestamp);
mDelta = stub1.findViewById(R.id.delta);
mAvgDelta = stub1.findViewById(R.id.avgdelta);
mRelativeLayout = stub1.findViewById(R.id.main_layout);
statusView = stub1.findViewById(R.id.aps_status);
layoutSet = true;
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
});
DataLayerListenerService.Companion.requestData(this);
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("NOChart" +
":performViewSetup")));
wakeLock.acquire(50);
}
@Override
protected void onTapCommand(int tapType, int x, int y, long eventTime) {
if (mSgv == null) return;
int extra = (mSgv.getRight() - mSgv.getLeft()) / 2;
int extra = mSgv!=null?(mSgv.getRight() - mSgv.getLeft())/2:0;
if (tapType == TAP_TYPE_TAP&&
x + extra >=mSgv.getLeft() &&
x - extra <= mSgv.getRight()&&
if (tapType == TAP_TYPE_TAP &&
x + extra >= mSgv.getLeft() &&
x - extra <= mSgv.getRight() &&
y >= mSgv.getTop() &&
y <= mSgv.getBottom()){
if (eventTime - sgvTapTime < 800){
y <= mSgv.getBottom()) {
if (eventTime - sgvTapTime < 800) {
Intent intent = new Intent(this, MainMenuActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
@ -162,10 +190,10 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
protected void onWatchModeChanged(WatchMode watchMode) {
if(lowResMode ^ isLowRes(watchMode)){ //if there was a change in lowResMode
if (lowResMode ^ isLowRes(watchMode)) { //if there was a change in lowResMode
lowResMode = isLowRes(watchMode);
setColor();
} else if (! sharedPrefs.getBoolean("dark", true)){
} else if (!sp.getBoolean("dark", true)) {
//in bright mode: different colours if active:
setColor();
}
@ -177,14 +205,13 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
@Override
protected WatchFaceStyle getWatchFaceStyle(){
protected WatchFaceStyle getWatchFaceStyle() {
return new WatchFaceStyle.Builder(this).setAcceptsTapEvents(true).build();
}
public int ageLevel() {
if(timeSince() <= (1000 * 60 * 12)) {
if (timeSince() <= (1000 * 60 * 12)) {
return 1;
} else {
return 0;
@ -192,40 +219,30 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
}
public double timeSince() {
return System.currentTimeMillis() - datetime;
return System.currentTimeMillis() - singleBg.getTimeStamp();
}
public String readingAge(boolean shortString) {
if (datetime == 0) { return shortString?"--'":"-- Minute ago"; }
int minutesAgo = (int) Math.floor(timeSince()/(1000*60));
if (minutesAgo == 1) {
return minutesAgo + (shortString?"'":" Minute ago");
if (singleBg == null || singleBg.getTimeStamp() == 0) {
return shortString ? "--'" : "-- Minute ago";
}
return minutesAgo + (shortString?"'":" Minutes ago");
int minutesAgo = (int) Math.floor(timeSince() / (1000 * 60));
if (minutesAgo == 1) {
return minutesAgo + (shortString ? "'" : " Minute ago");
}
return minutesAgo + (shortString ? "'" : " Minutes ago");
}
@Override
public void onDestroy() {
if(localBroadcastManager != null && messageReceiver != null){
localBroadcastManager.unregisterReceiver(messageReceiver);}
if (sharedPrefs != null){
sharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
disposable.clear();
super.onDestroy();
}
static {
INTENT_FILTER = new IntentFilter();
INTENT_FILTER.addAction(Intent.ACTION_TIME_TICK);
INTENT_FILTER.addAction(Intent.ACTION_TIMEZONE_CHANGED);
INTENT_FILTER.addAction(Intent.ACTION_TIME_CHANGED);
}
@Override
protected void onDraw(Canvas canvas) {
if(layoutSet) {
if (layoutSet) {
this.mRelativeLayout.draw(canvas);
Log.d("onDraw", "draw");
}
}
@ -237,7 +254,7 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
mTime.setText(timeFormat.format(System.currentTimeMillis()));
showAgeAndStatus();
if(ageLevel()<=0) {
if (ageLevel() <= 0) {
mSgv.setPaintFlags(mSgv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
mSgv.setPaintFlags(mSgv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
@ -250,142 +267,28 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
}
}
public class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getBundleExtra("data");
if (layoutSet && bundle !=null) {
DataMap dataMap = DataMap.fromBundle(bundle);
wakeLock.acquire(50);
sgvLevel = dataMap.getLong("sgvLevel");
batteryLevel = dataMap.getInt("batteryLevel");
datetime = dataMap.getLong("timestamp");
sgvString = dataMap.getString("sgvString");
mSgv.setText(dataMap.getString("sgvString"));
if(ageLevel()<=0) {
mSgv.setPaintFlags(mSgv.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
mSgv.setPaintFlags(mSgv.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
}
final java.text.DateFormat timeFormat = DateFormat.getTimeFormat(NOChart.this);
mTime.setText(timeFormat.format(System.currentTimeMillis()));
showAgeAndStatus();
String delta = dataMap.getString("delta");
if (delta.endsWith(" mg/dl")) {
mDelta.setText(delta.substring(0, delta.length() - 6));
} else if (delta.endsWith(" mmol/l")||delta.endsWith(" mmol")) {
mDelta.setText(delta.substring(0, delta.length() - 5));
} else {
mDelta.setText(delta);
}
String avgDelta = dataMap.getString("avgDelta");
if (delta.endsWith(" mg/dl")) {
mAvgDelta.setText(avgDelta.substring(0, avgDelta.length() - 6));
} else if (avgDelta.endsWith(" mmol/l")||avgDelta.endsWith(" mmol")) {
mAvgDelta.setText(avgDelta.substring(0, avgDelta.length() - 5));
} else {
mAvgDelta.setText(avgDelta);
}
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
invalidate();
setColor();
//start animation?
// dataMap.getDataMapArrayList("entries") == null -> not on "resend data".
if (!lowResMode && (sharedPrefs.getBoolean("animation", false) && dataMap.getDataMapArrayList("entries") == null && (sgvString.equals("100") || sgvString.equals("5.5") || sgvString.equals("5,5")))) {
startAnimation();
}
}
//status
bundle = intent.getBundleExtra("status");
if (layoutSet && bundle != null) {
DataMap dataMap = DataMap.fromBundle(bundle);
wakeLock.acquire(50);
externalStatusString = dataMap.getString("externalStatusString");
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
invalidate();
setColor();
}
//basals and temps
bundle = intent.getBundleExtra("basals");
if (layoutSet && bundle != null) {
DataMap dataMap = DataMap.fromBundle(bundle);
wakeLock.acquire(500);
loadBasalsAndTemps(dataMap);
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
invalidate();
setColor();
}
}
}
private void loadBasalsAndTemps(DataMap dataMap) {
ArrayList<DataMap> temps = dataMap.getDataMapArrayList("temps");
if (temps != null) {
tempWatchDataList = new ArrayList<>();
for (DataMap temp : temps) {
TempWatchData twd = new TempWatchData();
twd.startTime = temp.getLong("starttime");
twd.startBasal = temp.getDouble("startBasal");
twd.endTime = temp.getLong("endtime");
twd.endBasal = temp.getDouble("endbasal");
twd.amount = temp.getDouble("amount");
tempWatchDataList.add(twd);
}
}
ArrayList<DataMap> basals = dataMap.getDataMapArrayList("basals");
if (basals != null) {
basalWatchDataList = new ArrayList<>();
for (DataMap basal : basals) {
BasalWatchData bwd = new BasalWatchData();
bwd.startTime = basal.getLong("starttime");
bwd.endTime = basal.getLong("endtime");
bwd.amount = basal.getDouble("amount");
basalWatchDataList.add(bwd);
}
}
}
private void showAgeAndStatus() {
if( mTimestamp != null){
if (mTimestamp != null) {
mTimestamp.setText(readingAge(true));
}
boolean showAvgDelta = sharedPrefs.getBoolean("showAvgDelta", true);
boolean showAvgDelta = sp.getBoolean("showAvgDelta", true);
if(showAvgDelta){
if (showAvgDelta) {
mAvgDelta.setVisibility(View.VISIBLE);
} else {
mAvgDelta.setVisibility(View.GONE);
}
statusView.setText(externalStatusString);
if (status != null) {
statusView.setText(status.getExternalStatus());
statusView.setVisibility(View.VISIBLE);
}
}
public void setColor() {
if(lowResMode){
if (lowResMode) {
setColorLowRes();
} else if (sharedPrefs.getBoolean("dark", true)) {
} else if (sp.getBoolean("dark", true)) {
setColorDark();
} else {
setColorBright();
@ -393,62 +296,6 @@ public class NOChart extends WatchFace implements SharedPreferences.OnSharedPref
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key){
setColor();
if(layoutSet){
showAgeAndStatus();
mRelativeLayout.measure(specW, specH);
mRelativeLayout.layout(0, 0, mRelativeLayout.getMeasuredWidth(),
mRelativeLayout.getMeasuredHeight());
}
invalidate();
}
protected void updateRainbow() {
animationAngle = (animationAngle + 1) % 360;
//Animation matrix:
int[] rainbow = {Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE
, Color.CYAN};
Shader shader = new LinearGradient(0, 0, 0, 20, rainbow,
null, Shader.TileMode.MIRROR);
Matrix matrix = new Matrix();
matrix.setRotate(animationAngle);
shader.setLocalMatrix(matrix);
mSgv.getPaint().setShader(shader);
invalidate();
}
private synchronized void setIsAnimated(boolean isAnimated) {
this.isAnimated = isAnimated;
}
void startAnimation() {
Log.d("CircleWatchface", "start startAnimation");
Thread animator = new Thread() {
public void run() {
setIsAnimated(true);
for (int i = 0; i <= 8 * 1000 / 40; i++) {
updateRainbow();
SystemClock.sleep(40);
}
mSgv.getPaint().setShader(null);
setIsAnimated(false);
invalidate();
setColor();
System.gc();
}
};
animator.start();
}
protected void setColorLowRes() {
mTime.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_mTime));
statusView.setTextColor(ContextCompat.getColor(getApplicationContext(), R.color.dark_statusView));
@ -516,9 +363,11 @@ 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) {
DataLayerListenerService.Companion.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) {
// attempt endTime recover missing data
rxBus.send(new EventWearToMobile(new EventData.ActionResendData("NOChart" +
":missedReadingAlert")));
}
}
}

View file

@ -1,13 +1,15 @@
package info.nightscout.androidaps.watchfaces;
import android.annotation.SuppressLint;
import android.content.Intent;
import androidx.core.content.ContextCompat;
import android.support.wearable.watchface.WatchFaceStyle;
import android.view.LayoutInflater;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import androidx.core.content.ContextCompat;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.interaction.menus.MainMenuActivity;
import info.nightscout.shared.SafeParse;
@ -23,7 +25,7 @@ public class Steampunk extends BaseWatchFace {
private float lastEndDegrees = 0f;
private float deltaRotationAngle = 0f;
@Override
@SuppressLint("InflateParams") @Override
public void onCreate() {
forceSquareCanvas = true;
super.onCreate();
@ -35,22 +37,22 @@ public class Steampunk extends BaseWatchFace {
@Override
protected void onTapCommand(int tapType, int x, int y, long eventTime) {
if (tapType == TAP_TYPE_TAP&&
if (tapType == TAP_TYPE_TAP &&
x >= mChartTap.getLeft() &&
x <= mChartTap.getRight()&&
x <= mChartTap.getRight() &&
y >= mChartTap.getTop() &&
y <= mChartTap.getBottom()){
if (eventTime - chartTapTime < 800){
y <= mChartTap.getBottom()) {
if (eventTime - chartTapTime < 800) {
changeChartTimeframe();
}
chartTapTime = eventTime;
} else if (tapType == TAP_TYPE_TAP&&
} else if (tapType == TAP_TYPE_TAP &&
x >= mMainMenuTap.getLeft() &&
x <= mMainMenuTap.getRight()&&
x <= mMainMenuTap.getRight() &&
y >= mMainMenuTap.getTop() &&
y <= mMainMenuTap.getBottom()){
if (eventTime - mainMenuTapTime < 800){
y <= mMainMenuTap.getBottom()) {
if (eventTime - mainMenuTapTime < 800) {
Intent intent = new Intent(this, MainMenuActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
@ -67,7 +69,7 @@ public class Steampunk extends BaseWatchFace {
protected void setColorDark() {
if (mLinearLayout2 != null) {
if (ageLevel() <= 0 && rawData.datetime != 0) {
if (ageLevel() <= 0 && singleBg.getTimeStamp() != 0) {
mLinearLayout2.setBackgroundResource(R.drawable.redline);
mTimestamp.setTextColor(getResources().getColor(R.color.red_600));
} else {
@ -84,30 +86,32 @@ public class Steampunk extends BaseWatchFace {
}
}
if (!rawData.sSgv.equals("---")) {
if (!singleBg.getSgvString().equals("---")) {
float rotationAngle = 0f; //by default, show ? on the dial (? is at 0 degrees on the dial)
if (!rawData.sUnits.equals("-")) {
if (!singleBg.getGlucoseUnits().equals("-")) {
//ensure the glucose dial is the correct units
if (rawData.sUnits.equals("mmol")) {
if (singleBg.getGlucoseUnits().equals("mmol")) {
mGlucoseDial.setImageResource(R.drawable.steampunk_dial_mmol);
} else {
mGlucoseDial.setImageResource(R.drawable.steampunk_dial_mgdl);
}
//convert the Sgv to degrees of rotation
if (rawData.sUnits.equals("mmol")) {
rotationAngle = Float.valueOf(rawData.sSgv) * 18f; //convert to mg/dL, which is equivalent to degrees
if (singleBg.getGlucoseUnits().equals("mmol")) {
rotationAngle = SafeParse.stringToFloat(singleBg.getSgvString()) * 18f; //convert to
// mg/dL, which is equivalent to degrees
} else {
rotationAngle = Float.valueOf(rawData.sSgv); //if glucose a value is received, use it to determine the amount of rotation of the dial.
rotationAngle = SafeParse.stringToFloat(singleBg.getSgvString()); //if glucose a value is received, use it to determine the amount of rotation of the dial.
}
}
if (rotationAngle > 330) rotationAngle = 330; //if the glucose value is higher than 330 then show "HIGH" on the dial. ("HIGH" is at 330 degrees on the dial)
if (rotationAngle != 0 && rotationAngle < 30) rotationAngle = 30; //if the glucose value is lower than 30 show "LOW" on the dial. ("LOW" is at 30 degrees on the dial)
if (rotationAngle > 330)
rotationAngle = 330; //if the glucose value is higher than 330 then show "HIGH" on the dial. ("HIGH" is at 330 degrees on the dial)
if (rotationAngle != 0 && rotationAngle < 30)
rotationAngle = 30; //if the glucose value is lower than 30 show "LOW" on the dial. ("LOW" is at 30 degrees on the dial)
if (lastEndDegrees == 0) lastEndDegrees = rotationAngle;
//rotate glucose dial
@ -124,51 +128,54 @@ public class Steampunk extends BaseWatchFace {
//set the delta gauge and rotate the delta pointer
float deltaIsNegative = 1f; //by default go clockwise
if (!rawData.sAvgDelta.equals("--")) { //if a legitimate delta value is received, then...
if (rawData.sAvgDelta.charAt(0) == '-') deltaIsNegative = -1f; //if the delta is negative, go counter-clockwise
Float AbssAvgDelta = SafeParse.stringToFloat(rawData.sAvgDelta.substring(1)) ; //get rid of the sign so it can be converted to float.
String autogranularity = "0" ; //autogranularity off
if (!singleBg.getAvgDelta().equals("--")) { //if a legitimate delta value is
// received,
// then...
if (singleBg.getAvgDelta().charAt(0) == '-')
deltaIsNegative = -1f; //if the delta is negative, go counter-clockwise
Float AbssAvgDelta = SafeParse.stringToFloat(singleBg.getAvgDelta().substring(1)); //get rid of the sign so it can be converted to float.
String autogranularity = "0"; //autogranularity off
//ensure the delta gauge is the right units and granularity
if (!rawData.sUnits.equals("-")) {
if (rawData.sUnits.equals("mmol")) {
if (sharedPrefs.getString("delta_granularity", "2").equals("4")) { //Auto granularity
if (!singleBg.getGlucoseUnits().equals("-")) {
if (singleBg.getGlucoseUnits().equals("mmol")) {
if (sp.getString("delta_granularity", "2").equals("4")) { //Auto granularity
autogranularity = "1"; // low (init)
if (AbssAvgDelta < 0.3 ) {
autogranularity = "3" ; // high if below 0.3 mmol/l
if (AbssAvgDelta < 0.3) {
autogranularity = "3"; // high if below 0.3 mmol/l
} else if (AbssAvgDelta < 0.5) {
autogranularity = "2" ; // medium if below 0.5 mmol/l
autogranularity = "2"; // medium if below 0.5 mmol/l
}
}
if (sharedPrefs.getString("delta_granularity", "2").equals("1") || autogranularity.equals("1")) { //low
if (sp.getString("delta_granularity", "2").equals("1") || autogranularity.equals("1")) { //low
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_10);
deltaRotationAngle = (AbssAvgDelta * 30f);
}
if (sharedPrefs.getString("delta_granularity", "2").equals("2") || autogranularity.equals("2")) { //medium
if (sp.getString("delta_granularity", "2").equals("2") || autogranularity.equals("2")) { //medium
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_05);
deltaRotationAngle = (AbssAvgDelta * 60f);
}
if (sharedPrefs.getString("delta_granularity", "2").equals("3") || autogranularity.equals("3")) { //high
if (sp.getString("delta_granularity", "2").equals("3") || autogranularity.equals("3")) { //high
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mmol_03);
deltaRotationAngle = (AbssAvgDelta * 100f);
}
} else {
if (sharedPrefs.getString("delta_granularity", "2").equals("4")) { //Auto granularity
if (sp.getString("delta_granularity", "2").equals("4")) { //Auto granularity
autogranularity = "1"; // low (init)
if (AbssAvgDelta < 5 ) {
autogranularity = "3" ; // high if below 5 mg/dl
if (AbssAvgDelta < 5) {
autogranularity = "3"; // high if below 5 mg/dl
} else if (AbssAvgDelta < 10) {
autogranularity = "2" ; // medium if below 10 mg/dl
autogranularity = "2"; // medium if below 10 mg/dl
}
}
if (sharedPrefs.getString("delta_granularity", "2").equals("1") || autogranularity.equals("1")) { //low
if (sp.getString("delta_granularity", "2").equals("1") || autogranularity.equals("1")) { //low
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_20);
deltaRotationAngle = (AbssAvgDelta * 1.5f);
}
if (sharedPrefs.getString("delta_granularity", "2").equals("2") || autogranularity.equals("2")) { //medium
if (sp.getString("delta_granularity", "2").equals("2") || autogranularity.equals("2")) { //medium
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_10);
deltaRotationAngle = (AbssAvgDelta * 3f);
}
if (sharedPrefs.getString("delta_granularity", "2").equals("3") || autogranularity.equals("3")) { //high
if (sp.getString("delta_granularity", "2").equals("3") || autogranularity.equals("3")) { //high
mLinearLayout.setBackgroundResource(R.drawable.steampunk_gauge_mgdl_5);
deltaRotationAngle = (AbssAvgDelta * 6f);
}
@ -179,10 +186,10 @@ public class Steampunk extends BaseWatchFace {
}
//rotate the minute hand.
mMinuteHand.setRotation(Float.valueOf(sMinute) * 6f);
mMinuteHand.setRotation(Float.parseFloat(sMinute) * 6f);
//rotate the hour hand.
mHourHand.setRotation((Float.valueOf(sHour) * 30f) + (Float.valueOf(sMinute) * 0.5f));
mHourHand.setRotation((Float.parseFloat(sHour) * 30f) + (Float.parseFloat(sMinute) * 0.5f));
setTextSizes();
@ -197,7 +204,7 @@ public class Steampunk extends BaseWatchFace {
gridColor = ContextCompat.getColor(getApplicationContext(), R.color.grey_steampunk);
basalBackgroundColor = ContextCompat.getColor(getApplicationContext(), R.color.basal_dark);
basalCenterColor = ContextCompat.getColor(getApplicationContext(), R.color.basal_dark);
if (Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3")) < 3) {
if (sp.getInt("chart_timeframe", 3) < 3) {
pointSize = 2;
} else {
pointSize = 1;
@ -232,7 +239,7 @@ public class Steampunk extends BaseWatchFace {
//top row. large font unless text too big (i.e. detailedIOB)
mCOB2.setTextSize(fontLarge);
mBasalRate.setTextSize(fontLarge);
if (rawData.sIOB2.length() < 7) {
if (status.getIobDetail().length() < 7) {
mIOB2.setTextSize(fontLarge);
} else {
mIOB2.setTextSize(fontSmall);
@ -248,7 +255,8 @@ public class Steampunk extends BaseWatchFace {
}
//if both batteries are shown, make them smaller.
if (sharedPrefs.getBoolean("show_uploader_battery", true) && sharedPrefs.getBoolean("show_rig_battery", false)) {
if (sp.getBoolean("show_uploader_battery", true) && sp.getBoolean(
"show_rig_battery", false)) {
mUploaderBattery.setTextSize(fontSmall);
mRigBattery.setTextSize(fontSmall);
} else {
@ -258,14 +266,14 @@ public class Steampunk extends BaseWatchFace {
}
private void changeChartTimeframe() {
int timeframe = Integer.parseInt(sharedPrefs.getString("chart_timeframe", "3"));
timeframe = (timeframe%5) + 1;
int timeframe = sp.getInt("chart_timeframe", 3);
timeframe = (timeframe % 5) + 1;
if (timeframe < 3) {
pointSize = 2;
} else {
pointSize = 1;
}
setupCharts();
sharedPrefs.edit().putString("chart_timeframe", "" + timeframe).apply();
sp.putString("chart_timeframe", "" + timeframe);
}
}

View file

@ -170,12 +170,6 @@
<string name="bolus_progress_silent_channel_name">AAPS Bolus Progress Silent</string>
<string name="bolus_progress_channel_description">Bolus progress and cancel</string>
<string name="bolus_progress_silent_channel_description">Bolus progress and cancel with less vibrations</string>
<string name="key_quickwizard" translatable="false">QuickWizard</string>
<string name="key_wear_control" translatable="false">wearcontrol</string>
<string name="key_units_mgdl" translatable="false">units_mgdl</string>
<string name="key_boluswizard_percentage" translatable="false">boluswizard_percentage</string>
<string name="key_treatmentssafety_maxcarbs" translatable="false">treatmentssafety_maxcarbs</string>
<string name="key_treatmentssafety_maxbolus" translatable="false">treatmentssafety_maxbolus</string>
<string name="simple_ui_off">Off</string>
<string name="simple_ui_charging">During Charging</string>
<string name="simple_ui_always_on">Always On Mode</string>
@ -189,6 +183,15 @@
<string name="tile_no_config">No config available</string>
<string name="wear_control_not_enabled">Wear controls disabled</string>
<string name="wear_control_no_data">No data available</string>
<string name="key_quick_wizard_data_map" translatable="false">quick_wizard_data_map</string>
<string name="key_quickwizard" translatable="false">QuickWizard</string>
<string name="key_wear_control" translatable="false">wearcontrol</string>
<string name="key_units_mgdl" translatable="false">units_mgdl</string>
<string name="key_boluswizard_percentage" translatable="false">boluswizard_percentage</string>
<string name="key_treatmentssafety_maxcarbs" translatable="false">treatmentssafety_maxcarbs</string>
<string name="key_treatmentssafety_maxbolus" translatable="false">treatmentssafety_maxbolus</string>
<string name="key_quick_wizard_data" translatable="false">quick_wizard_data</string>
<string name="key_prime_fill" translatable="false">primefill</string>
<string name="key_show_wizard" translatable="false">showWizard</string>
</resources>

View file

@ -1,97 +0,0 @@
package info.nightscout.androidaps.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import info.nightscout.androidaps.TestBase;
import info.nightscout.androidaps.interaction.utils.Constants;
import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
public class BgWatchDataTest extends TestBase {
@Test
public void bgWatchDataHashTest() {
// GIVEN
BgWatchData inserted = new BgWatchData(
88.0, 160.0, 90.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 4, 1
);
Set<BgWatchData> set = new HashSet<>();
// THEN
//noinspection ConstantConditions
assertFalse(set.contains(inserted));
set.add(inserted);
assertTrue(set.contains(inserted));
}
/**
* BgWatchData has BIZARRE equals - only timestamp and color are checked!
*/
@Test
public void bgWatchDataEqualsTest() {
// GIVEN
BgWatchData item1 = new BgWatchData(
88.0, 160.0, 90.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS, 1
);
BgWatchData item2sameTimeSameColor = new BgWatchData(
123.0, 190, 90.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS, 1
);
BgWatchData item3sameTimeSameDiffColor = new BgWatchData(
96.0, 190, 90.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS, 0
);
BgWatchData item4differentTime = new BgWatchData(
88.0, 160.0, 90.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 2, 1
);
// THEN
assertEquals(item1, item2sameTimeSameColor);
assertNotEquals(item1, item3sameTimeSameDiffColor);
assertNotEquals(item1, item4differentTime);
}
/**
* BgWatchData is ordered by timestamp, reverse order
*/
@Test
public void bgWatchDataCompareTest() {
// GIVEN
BgWatchData item1 = new BgWatchData(
85, 160.0, 90.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 2, 1
);
BgWatchData item2 = new BgWatchData(
80, 190, 90.0,
WearUtilMocker.REF_NOW, 1
);
BgWatchData item3 = new BgWatchData(
80, 190, 50.0,
WearUtilMocker.REF_NOW + Constants.MINUTE_IN_MS * 5, 0
);
BgWatchData item4 = new BgWatchData(
160, 140, 70.0,
WearUtilMocker.REF_NOW, 0
);
// THEN
assertTrue(item2.compareTo(item1) < 0);
assertTrue(item2.compareTo(item3) > 0);
assertEquals(0, item2.compareTo(item4));
}
}

View file

@ -1,123 +0,0 @@
package info.nightscout.androidaps.data;
import static org.junit.Assert.assertEquals;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.gms.wearable.DataMap;
import org.junit.Test;
import info.nightscout.androidaps.TestBase;
import info.nightscout.androidaps.interaction.utils.Constants;
import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
import info.nightscout.androidaps.testing.mocks.BundleMock;
import info.nightscout.androidaps.testing.mocks.IntentMock;
public class RawDataSgvDisplayDataTest extends TestBase {
//==============================================================================================
// SGV DATA
//==============================================================================================
private DataMap dataMapForData() {
DataMap dataMap = new DataMap();
dataMap.putLong("sgvLevel", 1L);
dataMap.putLong("timestamp", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS);
dataMap.putString("sgvString", "106");
dataMap.putString("slopeArrow", "");
dataMap.putString("delta", "5.4");
dataMap.putString("avgDelta", "3.7");
dataMap.putString("glucoseUnits", "mg/dl");
return dataMap;
}
private void assertDataEmpty(RawDisplayData newRaw) {
assertEquals(newRaw.sgvLevel, 0L);
assertEquals(newRaw.datetime, 0L);
assertEquals(newRaw.sSgv, "---");
assertEquals(newRaw.sDirection, "--");
assertEquals(newRaw.sDelta, "--");
assertEquals(newRaw.sAvgDelta, "--");
assertEquals(newRaw.sUnits, "-");
}
private void assertDataOk(RawDisplayData newRaw) {
assertEquals(newRaw.sgvLevel, 1L);
assertEquals(newRaw.datetime, WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS);
assertEquals(newRaw.sSgv, "106");
assertEquals(newRaw.sDirection, "");
assertEquals(newRaw.sDelta, "5.4");
assertEquals(newRaw.sAvgDelta, "3.7");
assertEquals(newRaw.sUnits, "mg/dl");
}
@Test
public void updateDataFromEmptyPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateFromPersistence(persistence);
// THEN
assertDataEmpty(newRaw);
}
@Test
public void updateDataFromPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
persistence.storeDataMap(RawDisplayData.DATA_PERSISTENCE_KEY, dataMapForData());
newRaw.updateFromPersistence(persistence);
// THEN
assertDataOk(newRaw);
}
@Test
public void partialUpdateDataFromPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
persistence.storeDataMap(RawDisplayData.DATA_PERSISTENCE_KEY, dataMapForData());
newRaw.updateForComplicationsFromPersistence(persistence);
// THEN
assertDataOk(newRaw);
}
@Test
public void updateDataFromMessageTest() {
// GIVEN
Intent intent = IntentMock.mock();
Bundle bundle = BundleMock.mock(dataMapForData());
intent.putExtra("data", bundle);
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateDataFromMessage(intent, null);
// THEN
assertDataOk(newRaw);
}
@Test
public void updateDataFromEmptyMessageTest() {
// GIVEN
Intent intent = IntentMock.mock();
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateDataFromMessage(intent, null);
// THEN
assertDataEmpty(newRaw);
}
}

View file

@ -1,242 +0,0 @@
package info.nightscout.androidaps.data;
import static org.junit.Assert.assertEquals;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.gms.wearable.DataMap;
import org.junit.Test;
import java.util.ArrayList;
import info.nightscout.androidaps.TestBase;
import info.nightscout.androidaps.interaction.utils.Constants;
import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
import info.nightscout.androidaps.testing.mocks.BundleMock;
import info.nightscout.androidaps.testing.mocks.IntentMock;
import info.nightscout.androidaps.testing.utils.BasalWatchDataExt;
import info.nightscout.androidaps.testing.utils.BgWatchDataExt;
import info.nightscout.androidaps.testing.utils.BolusWatchDataExt;
import info.nightscout.androidaps.testing.utils.TempWatchDataExt;
@SuppressWarnings("SpellCheckingInspection")
public class RawDisplayDataBasalsTest extends TestBase {
//==============================================================================================
// BASALS for chart
//==============================================================================================
private DataMap dataMapForBasals() {
DataMap dataMap = new DataMap();
ArrayList<DataMap> temps = new ArrayList<>();
DataMap temp = new DataMap();
temp.putLong("starttime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 20);
temp.putDouble("startBasal", 1.5);
temp.putLong("endtime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 10);
temp.putDouble("endbasal", 1.5);
temp.putDouble("amount", 1.8);
temps.add(temp);
DataMap temp2 = new DataMap();
temp2.putLong("starttime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 10);
temp2.putDouble("startBasal", 1.3);
temp2.putLong("endtime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 2);
temp2.putDouble("endbasal", 1.3);
temp2.putDouble("amount", 2.3);
temps.add(temp2);
dataMap.putDataMapArrayList("temps", temps);
ArrayList<DataMap> basals = new ArrayList<>();
DataMap basal = new DataMap();
basal.putLong("starttime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 20);
basal.putLong("endtime", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 2);
basal.putDouble("amount", 1.2);
basals.add(basal);
dataMap.putDataMapArrayList("basals", basals);
ArrayList<DataMap> boluses = new ArrayList<>();
DataMap bolus = new DataMap();
bolus.putLong("date", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 17);
bolus.putDouble("bolus", 5.5);
bolus.putDouble("carbs", 20.0);
bolus.putBoolean("isSMB", false);
bolus.putBoolean("isValid", true);
boluses.add(bolus);
DataMap bolus2 = new DataMap();
bolus2.putLong("date", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 11);
bolus2.putDouble("bolus", 3.0);
bolus2.putDouble("carbs", 0.0);
bolus2.putBoolean("isSMB", false);
bolus2.putBoolean("isValid", true);
boluses.add(bolus2);
DataMap bolus3 = new DataMap();
bolus3.putLong("date", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 3);
bolus3.putDouble("bolus", 0.0);
bolus3.putDouble("carbs", 15.0);
bolus3.putBoolean("isSMB", true);
bolus3.putBoolean("isValid", false);
boluses.add(bolus3);
dataMap.putDataMapArrayList("boluses", boluses);
ArrayList<DataMap> predictions = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DataMap prediction = new DataMap();
prediction.putLong("timestamp", WearUtilMocker.REF_NOW + Constants.MINUTE_IN_MS * i);
prediction.putDouble("sgv", 160 - 4 * i);
prediction.putInt("color", 0);
predictions.add(prediction);
}
dataMap.putDataMapArrayList("predictions", predictions);
return dataMap;
}
private void assertBasalsEmpty(RawDisplayData newRaw) {
assertEquals(newRaw.tempWatchDataList.size(), 0);
assertEquals(newRaw.basalWatchDataList.size(), 0);
assertEquals(newRaw.bolusWatchDataList.size(), 0);
assertEquals(newRaw.predictionList.size(), 0);
}
private void assertBasalsOk(RawDisplayData newRaw) {
assertEquals(newRaw.tempWatchDataList.size(), 2);
assertEquals(newRaw.basalWatchDataList.size(), 1);
assertEquals(newRaw.bolusWatchDataList.size(), 3);
assertEquals(newRaw.predictionList.size(), 10);
assertEquals(new TempWatchDataExt(newRaw.tempWatchDataList.get(0)), TempWatchDataExt.build(
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 20,
1.5,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 10,
1.5,
1.8
));
assertEquals(new TempWatchDataExt(newRaw.tempWatchDataList.get(1)), TempWatchDataExt.build(
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 10,
1.3,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 2,
1.3,
2.3
));
assertEquals(new BasalWatchDataExt(newRaw.basalWatchDataList.get(0)), BasalWatchDataExt.build(
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 20,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 2,
1.2
));
assertEquals(new BolusWatchDataExt(newRaw.bolusWatchDataList.get(0)), BolusWatchDataExt.build(
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 17,
5.5,
20,
false,
true
));
assertEquals(new BolusWatchDataExt(newRaw.bolusWatchDataList.get(1)), BolusWatchDataExt.build(
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 11,
3,
0,
false,
true
));
assertEquals(new BolusWatchDataExt(newRaw.bolusWatchDataList.get(2)), BolusWatchDataExt.build(
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 3,
0,
15,
true,
false
));
assertEquals(new BgWatchDataExt(newRaw.predictionList.get(3)), BgWatchDataExt.build(
160 - 4 * 3,
WearUtilMocker.REF_NOW + Constants.MINUTE_IN_MS * 3,
0
));
assertEquals(new BgWatchDataExt(newRaw.predictionList.get(7)), BgWatchDataExt.build(
160 - 4 * 7,
WearUtilMocker.REF_NOW + Constants.MINUTE_IN_MS * 7,
0
));
}
@Test
public void updateBasalsFromEmptyPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateFromPersistence(persistence);
// THEN
assertBasalsEmpty(newRaw);
}
@Test
public void updateBasalsFromPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
persistence.storeDataMap(RawDisplayData.BASALS_PERSISTENCE_KEY, dataMapForBasals());
newRaw.updateFromPersistence(persistence);
// THEN
assertBasalsOk(newRaw);
}
@Test
public void partialUpdateBasalsFromPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
persistence.storeDataMap(RawDisplayData.BASALS_PERSISTENCE_KEY, dataMapForBasals());
newRaw.updateForComplicationsFromPersistence(persistence);
// THEN
assertBasalsEmpty(newRaw);
}
@Test
public void updateBasalsFromMessageTest() {
// GIVEN
Intent intent = IntentMock.mock();
Bundle bundle = BundleMock.mock(dataMapForBasals());
intent.putExtra("basals", bundle);
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateBasalsFromMessage(intent);
// THEN
assertBasalsOk(newRaw);
}
@Test
public void updateBasalsFromEmptyMessageTest() {
// GIVEN
Intent intent = IntentMock.mock();
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateBasalsFromMessage(intent);
// THEN
assertBasalsEmpty(newRaw);
}
}

View file

@ -1,135 +0,0 @@
package info.nightscout.androidaps.data;
import static org.junit.Assert.assertEquals;
import com.google.android.gms.wearable.DataMap;
import org.junit.Test;
import java.util.ArrayList;
import info.nightscout.androidaps.TestBase;
import info.nightscout.androidaps.interaction.utils.Constants;
import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
import info.nightscout.androidaps.testing.utils.BgWatchDataExt;
@SuppressWarnings("PointlessArithmeticExpression")
public class RawDisplayDataBgEntriesTest extends TestBase {
//==============================================================================================
// ENTRIES for chart
//==============================================================================================
private DataMap dataMapForEntries() {
DataMap dataMap = new DataMap();
ArrayList<DataMap> entries = new ArrayList<>();
for (int i = 0; i < 12; i++) {
DataMap entry = new DataMap();
entry.putLong("timestamp", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 4 * (16 - i));
entry.putDouble("sgvDouble", 145.0 - 5 * i);
entry.putDouble("high", 170.0);
entry.putDouble("low", 80.0);
entry.putInt("color", 0);
entries.add(entry);
}
dataMap.putDataMapArrayList("entries", entries);
return dataMap;
}
private DataMap dataMapForEntries(long timestamp, double sgv) {
DataMap entry = new DataMap();
entry.putLong("timestamp", timestamp);
entry.putDouble("sgvDouble", sgv);
entry.putDouble("high", 160.0);
entry.putDouble("low", 90.0);
entry.putInt("color", 1);
return entry;
}
@Test
public void addToWatchSetTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
DataMap multipleEntries = dataMapForEntries();
DataMap singleEntry1 = dataMapForEntries(WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 4 * 2, 92);
DataMap singleEntry2 = dataMapForEntries(WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 4 * 1, 88);
// WHEN, THEN
// add list
newRaw.addToWatchSet(multipleEntries);
assertEquals(newRaw.bgDataList.size(), 12);
assertEquals(new BgWatchDataExt(newRaw.bgDataList.get(5)),
new BgWatchDataExt(new BgWatchData(
120.0, 170.0, 80.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 4 * (16 - 5), 0
)));
assertEquals(new BgWatchDataExt(newRaw.bgDataList.get(11)),
new BgWatchDataExt(new BgWatchData(
90.0, 170.0, 80.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 4 * (16 - 11), 0
)));
// add single entries
newRaw.addToWatchSet(singleEntry1);
newRaw.addToWatchSet(singleEntry2);
assertEquals(newRaw.bgDataList.size(), 14);
assertEquals(new BgWatchDataExt(newRaw.bgDataList.get(12)),
new BgWatchDataExt(new BgWatchData(
92.0, 160.0, 90.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 4 * 2, 1
)));
assertEquals(new BgWatchDataExt(newRaw.bgDataList.get(13)),
new BgWatchDataExt(new BgWatchData(
88.0, 160.0, 90.0,
WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 4 * 1, 1
)));
// ignore duplicates
newRaw.addToWatchSet(singleEntry2);
assertEquals(newRaw.bgDataList.size(), 14);
}
@Test
public void addToWatchSetCleanupOldTest() {
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp(), 125));
assertEquals(newRaw.bgDataList.size(), 1);
getWearUtilMocker().progressClock(Constants.HOUR_IN_MS * 2);
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp(), 140));
assertEquals(newRaw.bgDataList.size(), 2);
getWearUtilMocker().progressClock(Constants.HOUR_IN_MS * 1);
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp(), 150));
getWearUtilMocker().progressClock(Constants.HOUR_IN_MS * 1 + Constants.MINUTE_IN_MS * 30);
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp(), 101));
assertEquals(newRaw.bgDataList.size(), 4);
getWearUtilMocker().progressClock(Constants.MINUTE_IN_MS * 30);
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp(), 90));
assertEquals(newRaw.bgDataList.size(), 5);
getWearUtilMocker().progressClock(Constants.HOUR_IN_MS * 1 + Constants.MINUTE_IN_MS * 30);
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp(), 80));
assertEquals(newRaw.bgDataList.size(), 5);
getWearUtilMocker().progressClock(Constants.HOUR_IN_MS * 4);
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp(), 92));
assertEquals(newRaw.bgDataList.size(), 2);
getWearUtilMocker().progressClock(Constants.HOUR_IN_MS * 5 + Constants.MINUTE_IN_MS * 30);
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp(), 107));
assertEquals(newRaw.bgDataList.size(), 1);
getWearUtilMocker().progressClock(Constants.HOUR_IN_MS * 6 + Constants.MINUTE_IN_MS * 30);
newRaw.addToWatchSet(dataMapForEntries(getWearUtil().timestamp() - Constants.HOUR_IN_MS * 6, 138));
assertEquals(newRaw.bgDataList.size(), 0);
}
}

View file

@ -1,160 +0,0 @@
package info.nightscout.androidaps.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.gms.wearable.DataMap;
import org.junit.Before;
import org.junit.Test;
import info.nightscout.androidaps.TestBase;
import info.nightscout.androidaps.interaction.utils.Constants;
import info.nightscout.androidaps.testing.mockers.RawDataMocker;
import info.nightscout.androidaps.testing.mockers.WearUtilMocker;
import info.nightscout.androidaps.testing.mocks.BundleMock;
import info.nightscout.androidaps.testing.mocks.IntentMock;
@SuppressWarnings("SimplifiableAssertion")
public class RawDisplayDataStatusTest extends TestBase {
private RawDataMocker rawDataMocker;
@Before
public void mock() {
rawDataMocker = new RawDataMocker(getWearUtil());
}
@SuppressWarnings("AssertBetweenInconvertibleTypes") @Test
public void toDebugStringTest() {
RawDisplayData raw = rawDataMocker.rawDelta(5, "1.5");
raw.externalStatusString = "placeholder-here";
assertEquals(raw.datetime, WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 5);
assertTrue(raw.toDebugString().contains("placeholder-here"));
}
//==============================================================================================
// STATUS
//==============================================================================================
private DataMap dataMapForStatus() {
DataMap dataMap = new DataMap();
dataMap.putString("currentBasal", "120%");
dataMap.putString("battery", "76");
dataMap.putString("rigBattery", "40%");
dataMap.putBoolean("detailedIob", true);
dataMap.putString("iobSum", "12.5");
dataMap.putString("iobDetail", "(11,2|1,3)");
dataMap.putString("cob", "5(10)g");
dataMap.putString("bgi", "13");
dataMap.putBoolean("showBgi", false);
dataMap.putString("externalStatusString", "");
dataMap.putInt("batteryLevel", 1);
dataMap.putLong("openApsStatus", WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 2);
return dataMap;
}
private void assertStatusEmpty(RawDisplayData newRaw) {
assertEquals(newRaw.sBasalRate, "-.--U/h");
assertEquals(newRaw.sUploaderBattery, "--");
assertEquals(newRaw.sRigBattery, "--");
assertEquals(newRaw.detailedIOB, false);
assertEquals(newRaw.sIOB1, "IOB");
assertEquals(newRaw.sIOB2, "-.--");
assertEquals(newRaw.sCOB1, "Carb");
assertEquals(newRaw.sCOB2, "--g");
assertEquals(newRaw.sBgi, "--");
assertEquals(newRaw.showBGI, false);
assertEquals(newRaw.externalStatusString, "no status");
assertEquals(newRaw.batteryLevel, 1);
assertEquals(newRaw.openApsStatus, -1L);
}
private void assertStatusOk(RawDisplayData newRaw) {
assertEquals(newRaw.sBasalRate, "120%");
assertEquals(newRaw.sUploaderBattery, "76");
assertEquals(newRaw.sRigBattery, "40%");
assertEquals(newRaw.detailedIOB, true);
assertEquals(newRaw.sIOB1, "12.5U");
assertEquals(newRaw.sIOB2, "(11,2|1,3)");
assertEquals(newRaw.sCOB1, "Carb");
assertEquals(newRaw.sCOB2, "5(10)g");
assertEquals(newRaw.sBgi, "13");
assertEquals(newRaw.showBGI, false);
assertEquals(newRaw.externalStatusString, "");
assertEquals(newRaw.batteryLevel, 1);
assertEquals(newRaw.openApsStatus, WearUtilMocker.REF_NOW - Constants.MINUTE_IN_MS * 2);
}
@Test
public void updateStatusFromEmptyPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateFromPersistence(persistence);
// THEN
assertStatusEmpty(newRaw);
}
@Test
public void updateStatusFromPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
persistence.storeDataMap(RawDisplayData.STATUS_PERSISTENCE_KEY, dataMapForStatus());
newRaw.updateFromPersistence(persistence);
// THEN
assertStatusOk(newRaw);
}
@Test
public void partialUpdateStatusFromPersistenceTest() {
// GIVEN
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
persistence.storeDataMap(RawDisplayData.STATUS_PERSISTENCE_KEY, dataMapForStatus());
newRaw.updateForComplicationsFromPersistence(persistence);
// THEN
assertStatusOk(newRaw);
}
@Test
public void updateStatusFromMessageTest() {
// GIVEN
Intent intent = IntentMock.mock();
Bundle bundle = BundleMock.mock(dataMapForStatus());
intent.putExtra("status", bundle);
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateStatusFromMessage(intent, null);
// THEN
assertStatusOk(newRaw);
}
@Test
public void updateStatusFromEmptyMessageTest() {
// GIVEN
Intent intent = IntentMock.mock();
RawDisplayData newRaw = new RawDisplayData(getWearUtil());
// WHEN
newRaw.updateStatusFromMessage(intent, null);
// THEN
assertStatusEmpty(newRaw);
}
}

View file

@ -106,10 +106,10 @@ public class DisplayFormatTest extends TestBase {
@Test
public void shortTrendTest() {
RawDisplayData raw = new RawDisplayData(wearUtil);
RawDisplayData raw = new RawDisplayData();
assertEquals(displayFormat.shortTrend(raw), "-- Δ--");
raw.datetime = wearUtilMocker.backInTime(0, 0, 2, 0);
raw.getSingleBg().setTimeStamp(wearUtilMocker.backInTime(0, 0, 2, 0));
assertEquals(displayFormat.shortTrend(raw), "2' Δ--");
when(sp.getBoolean("complication_unicode", true)).thenReturn(true);

View file

@ -2,12 +2,7 @@ package info.nightscout.androidaps.interaction.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static info.nightscout.androidaps.testing.mockers.WearUtilMocker.REF_NOW;
import com.google.android.gms.wearable.DataMap;
import org.junit.Test;
@ -42,62 +37,6 @@ public class PersistenceTest extends TestBase {
assertTrue(updatedGot);
}
@Test
public void whenDataUpdatedTest() {
// GIVEN
DataMap map = new DataMap();
// WHEN
final long whenNotUpdated = persistence.whenDataUpdated();
persistence.storeDataMap("data-map", map);
final long whenUpdatedFirst = persistence.whenDataUpdated();
getWearUtilMocker().progressClock(60000);
persistence.storeDataMap("data-map", map);
final long whenUpdatedNext = persistence.whenDataUpdated();
// THEN
assertEquals(whenNotUpdated, 0L);
assertEquals(whenUpdatedFirst, REF_NOW);
assertEquals(whenUpdatedNext, REF_NOW + 60000);
}
@Test
public void getDataMapTest() {
// GIVEN
DataMap map = new DataMap();
map.putByteArray("test-key", new byte[]{9, 42, 127, -5});
// WHEN
DataMap notExisting = persistence.getDataMap("not-there");
persistence.storeDataMap("data-map", map);
DataMap restoredMap = persistence.getDataMap("data-map");
assert restoredMap != null;
byte[] restoredMapContents = restoredMap.getByteArray("test-key");
// THEN
assertNull(notExisting);
assertNotNull(restoredMap);
assertTrue(restoredMap.containsKey("test-key"));
assertEquals(restoredMapContents.length, 4);
assertEquals(restoredMapContents[0], (byte) 9);
assertEquals(restoredMapContents[1], (byte) 42);
assertEquals(restoredMapContents[2], (byte) 127);
assertEquals(restoredMapContents[3], (byte) -5);
}
@Test
public void brokenDataMapTest() {
// WHEN
persistence.putString("data-map", "ZmFrZSBkYXRh");
DataMap restoredMap = persistence.getDataMap("data-map");
// THEN
assertNull(restoredMap);
}
@Test
public void setsTest() {
// WHEN

View file

@ -4,15 +4,14 @@ import com.google.android.gms.wearable.DataMap
import info.nightscout.androidaps.TestBase
import info.nightscout.androidaps.testing.mockers.WearUtilMocker
import info.nightscout.androidaps.testing.mocks.BundleMock
import org.hamcrest.CoreMatchers
import org.junit.Assert
import org.junit.Test
import java.util.*
/**
* Created by dlvoy on 22.11.2019.
*/
@Suppress("SpellCheckingInspection") class WearUtilTest : TestBase() {
@Suppress("SpellCheckingInspection")
class WearUtilTest : TestBase() {
@Test fun timestampAndTimeDiffsTest() {

View file

@ -3,70 +3,138 @@ package info.nightscout.androidaps.testing.mockers;
import info.nightscout.androidaps.data.RawDisplayData;
import info.nightscout.androidaps.interaction.utils.WearUtil;
import info.nightscout.shared.SafeParse;
import info.nightscout.shared.weardata.EventData;
@SuppressWarnings("PointlessArithmeticExpression")
public class RawDataMocker {
private final WearUtil wearUtil;
private final WearUtilMocker wearUtilMocker;
public RawDataMocker(WearUtil wearUtil) {
this.wearUtil = wearUtil;
wearUtilMocker = new WearUtilMocker(wearUtil);
}
public RawDisplayData rawSgv(String sgv, int m, String deltaString) {
RawDisplayData raw = new RawDisplayData(wearUtil);
raw.datetime = wearUtilMocker.backInTime(0, 0, m, 0);
raw.sDelta = deltaString;
raw.sSgv = sgv;
RawDisplayData raw = new RawDisplayData();
double delta = SafeParse.stringToDouble(deltaString);
String d;
if (delta <= (-3.5 * 5)) {
raw.sDirection = "\u21ca";
d = "\u21ca";
} else if (delta <= (-2 * 5)) {
raw.sDirection = "\u2193";
d = "\u2193";
} else if (delta <= (-1 * 5)) {
raw.sDirection = "\u2198";
d = "\u2198";
} else if (delta <= (1 * 5)) {
raw.sDirection = "\u2192";
d = "\u2192";
} else if (delta <= (2 * 5)) {
raw.sDirection = "\u2197";
d = "\u2197";
} else if (delta <= (3.5 * 5)) {
raw.sDirection = "\u2191";
d = "\u2191";
} else {
raw.sDirection = "\u21c8";
d = "\u21c8";
}
raw.setSingleBg(
new EventData.SingleBg(
wearUtilMocker.backInTime(0, 0, m, 0),
sgv,
"",
d,
deltaString,
"",
0,
0.0,
0.0,
0.0,
0
)
);
return raw;
}
public RawDisplayData rawDelta(int m, String delta) {
RawDisplayData raw = new RawDisplayData(wearUtil);
raw.datetime = wearUtilMocker.backInTime(0, 0, m, 0);
raw.sDelta = delta;
RawDisplayData raw = new RawDisplayData();
raw.setSingleBg(
new EventData.SingleBg(
wearUtilMocker.backInTime(0, 0, m, 0),
"",
"",
"",
delta,
"",
0,
0.0,
0.0,
0.0,
0
)
);
return raw;
}
public RawDisplayData rawCobIobBr(String cob, String iob, String br) {
RawDisplayData raw = new RawDisplayData(wearUtil);
raw.sCOB2 = cob;
raw.sIOB1 = iob;
raw.sBasalRate = br;
RawDisplayData raw = new RawDisplayData();
raw.setStatus(
new EventData.Status(
"",
iob,
"",
true,
cob,
br,
"",
"",
0L,
"",
true,
0
)
);
return raw;
}
public RawDisplayData rawIob(String iob, String iob2) {
RawDisplayData raw = new RawDisplayData(wearUtil);
raw.sIOB1 = iob;
raw.sIOB2 = iob2;
RawDisplayData raw = new RawDisplayData();
raw.setStatus(
new EventData.Status(
"",
iob,
iob2,
true,
"",
"",
"",
"",
0L,
"",
true,
0
)
);
return raw;
}
public RawDisplayData rawCob(String cob) {
RawDisplayData raw = new RawDisplayData(wearUtil);
raw.sCOB2 = cob;
RawDisplayData raw = new RawDisplayData();
raw.setStatus(
new EventData.Status(
"",
"",
"",
true,
cob,
"",
"",
"",
0L,
"",
true,
0
)
);
return raw;
}

View file

@ -1,59 +0,0 @@
package info.nightscout.androidaps.testing.utils;
import static info.nightscout.androidaps.testing.utils.ExtUtil.assertClassHaveSameFields;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
import info.nightscout.androidaps.data.BasalWatchData;
public class BasalWatchDataExt extends BasalWatchData {
private BasalWatchDataExt() {
super();
}
public BasalWatchDataExt(BasalWatchData ref) {
super();
// since we do not want modify BasalWatchData - we use this wrapper class
// but we make sure it has same fields
assertClassHaveSameFields(BasalWatchData.class, "startTime,endTime,amount");
this.startTime = ref.startTime;
this.endTime = ref.endTime;
this.amount = ref.amount;
}
public static BasalWatchDataExt build(long startTime, long endTime, double amount) {
BasalWatchDataExt bwd = new BasalWatchDataExt();
bwd.startTime = startTime;
bwd.endTime = endTime;
bwd.amount = amount;
return bwd;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof BasalWatchData) {
return (this.startTime == ((BasalWatchData) obj).startTime)
&& (this.endTime == ((BasalWatchData) obj).endTime)
&& (this.amount == ((BasalWatchData) obj).amount);
} else {
return false;
}
}
@NonNull @Override
public String toString() {
return startTime + ", " + endTime + ", " + amount;
}
@Override
public int hashCode() {
return Objects.hash(startTime, endTime, amount);
}
}

View file

@ -1,68 +0,0 @@
package info.nightscout.androidaps.testing.utils;
import static info.nightscout.androidaps.testing.utils.ExtUtil.assertClassHaveSameFields;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
import info.nightscout.androidaps.data.BgWatchData;
@SuppressWarnings("unused")
public class BgWatchDataExt extends BgWatchData {
private BgWatchDataExt() {
super();
}
public BgWatchDataExt(double aSgv, double aHigh, double aLow, long aTimestamp, int aColor) {
super(aSgv, aHigh, aLow, aTimestamp, aColor);
}
public BgWatchDataExt(BgWatchData ref) {
super();
// since we do not want modify BgWatchDataExt - we use this wrapper class
// but we make sure it has same fields
assertClassHaveSameFields(BgWatchData.class, "sgv,high,low,timestamp,color");
this.sgv = ref.sgv;
this.high = ref.high;
this.low = ref.low;
this.timestamp = ref.timestamp;
this.color = ref.color;
}
public static BgWatchDataExt build(double sgv, long timestamp, int color) {
BgWatchDataExt twd = new BgWatchDataExt();
twd.sgv = sgv;
twd.timestamp = timestamp;
twd.color = color;
return twd;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof BgWatchData) {
return (this.sgv == ((BgWatchData) obj).sgv)
&& (this.high == ((BgWatchData) obj).high)
&& (this.low == ((BgWatchData) obj).low)
&& (this.timestamp == ((BgWatchData) obj).timestamp)
&& (this.color == ((BgWatchData) obj).color);
} else {
return false;
}
}
@Override @NonNull
public String toString() {
return sgv + ", " + high + ", " + low + ", " + timestamp + ", " + color;
}
@Override
public int hashCode() {
return Objects.hash(sgv, high, low, timestamp, color);
}
}

View file

@ -1,65 +0,0 @@
package info.nightscout.androidaps.testing.utils;
import static info.nightscout.androidaps.testing.utils.ExtUtil.assertClassHaveSameFields;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
import info.nightscout.androidaps.data.BolusWatchData;
public class BolusWatchDataExt extends BolusWatchData {
private BolusWatchDataExt() {
super();
}
public BolusWatchDataExt(BolusWatchData ref) {
super();
// since we do not want modify BolusWatchData - we use this wrapper class
// but we make sure it has same fields
assertClassHaveSameFields(BolusWatchData.class, "date,bolus,carbs,isSMB,isValid");
this.date = ref.date;
this.bolus = ref.bolus;
this.carbs = ref.carbs;
this.isSMB = ref.isSMB;
this.isValid = ref.isValid;
}
public static BolusWatchDataExt build(long date, double bolus, double carbs, boolean isSMB, boolean isValid) {
BolusWatchDataExt bwd = new BolusWatchDataExt();
bwd.date = date;
bwd.bolus = bolus;
bwd.carbs = carbs;
bwd.isSMB = isSMB;
bwd.isValid = isValid;
return bwd;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof BolusWatchData) {
return (this.date == ((BolusWatchData) obj).date)
&& (this.bolus == ((BolusWatchData) obj).bolus)
&& (this.carbs == ((BolusWatchData) obj).carbs)
&& (this.isSMB == ((BolusWatchData) obj).isSMB)
&& (this.isValid == ((BolusWatchData) obj).isValid);
} else {
return false;
}
}
@NonNull @Override
public String toString() {
return date + ", " + bolus + ", " + carbs + ", " + isSMB + ", " + isValid;
}
@Override
public int hashCode() {
return Objects.hash(date, bolus, carbs, isSMB, isValid);
}
}

View file

@ -1,27 +0,0 @@
package info.nightscout.androidaps.testing.utils;
import static org.junit.Assert.assertEquals;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
class ExtUtil {
static <T> void assertClassHaveSameFields(Class<T> checkedClass, String commaSeparatedFieldList) {
Set<String> parentFields = new HashSet<>();
for (Field f : checkedClass.getDeclaredFields()) {
final String fieldName = f.getName();
// skip runtime-injected fields like $jacocoData
if (fieldName.startsWith("$")) {
continue;
}
parentFields.add(fieldName);
}
Set<String> knownFields = new HashSet<>(Arrays.asList(commaSeparatedFieldList.split(",")));
assertEquals(parentFields, knownFields);
}
}

View file

@ -1,67 +0,0 @@
package info.nightscout.androidaps.testing.utils;
import static info.nightscout.androidaps.testing.utils.ExtUtil.assertClassHaveSameFields;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
import info.nightscout.androidaps.data.TempWatchData;
public class TempWatchDataExt extends TempWatchData {
private TempWatchDataExt() {
super();
}
public TempWatchDataExt(TempWatchData ref) {
super();
// since we do not want modify BolusWatchData - we use this wrapper class
// but we make sure it has same fields
assertClassHaveSameFields(TempWatchData.class, "startTime,startBasal,endTime,endBasal,amount");
this.startTime = ref.startTime;
this.startBasal = ref.startBasal;
this.endTime = ref.endTime;
this.endBasal = ref.endBasal;
this.amount = ref.amount;
}
public static TempWatchDataExt build(long startTime, double startBasal, long endTime,
double endBasal, double amount) {
TempWatchDataExt twd = new TempWatchDataExt();
twd.startTime = startTime;
twd.startBasal = startBasal;
twd.endTime = endTime;
twd.endBasal = endBasal;
twd.amount = amount;
return twd;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof TempWatchData) {
return (this.startTime == ((TempWatchData) obj).startTime)
&& (this.startBasal == ((TempWatchData) obj).startBasal)
&& (this.endTime == ((TempWatchData) obj).endTime)
&& (this.endBasal == ((TempWatchData) obj).endBasal)
&& (this.amount == ((TempWatchData) obj).amount);
} else {
return false;
}
}
@NonNull @Override
public String toString() {
return startTime + ", " + startBasal + ", " + endTime + ", " + endBasal + ", " + amount;
}
@Override
public int hashCode() {
return Objects.hash(startTime, startBasal, endTime, endBasal, amount);
}
}