diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java deleted file mode 100644 index 2af3ef34b6..0000000000 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.java +++ /dev/null @@ -1,14 +0,0 @@ -package info.nightscout.androidaps.interaction.utils; - -public class Constants { - - public static final long SECOND_IN_MS = 1000; - public static final long MINUTE_IN_MS = 60000; - public static final long HOUR_IN_MS = 3600000; - public static final long DAY_IN_MS = 86400000; - public static final long WEEK_IN_MS = DAY_IN_MS * 7; - public static final long MONTH_IN_MS = DAY_IN_MS * 30; - - public static final long STALE_MS = Constants.MINUTE_IN_MS * 12; - -} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.kt new file mode 100644 index 0000000000..3c8ce54949 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Constants.kt @@ -0,0 +1,13 @@ +package info.nightscout.androidaps.interaction.utils + +@Suppress("unused") +object Constants { + + const val SECOND_IN_MS: Long = 1000 + const val MINUTE_IN_MS: Long = 60000 + const val HOUR_IN_MS: Long = 3600000 + const val DAY_IN_MS: Long = 86400000 + const val WEEK_IN_MS = DAY_IN_MS * 7 + const val MONTH_IN_MS = DAY_IN_MS * 30 + const val STALE_MS = MINUTE_IN_MS * 12 +} \ No newline at end of file diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java index 966b921db5..e74b659446 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/DisplayFormat.java @@ -12,7 +12,8 @@ public class DisplayFormat { @Inject SP sp; @Inject WearUtil wearUtil; - @Inject DisplayFormat() {} + @Inject DisplayFormat() { + } /** * Maximal and minimal lengths of fields/labels shown in complications, in characters @@ -74,12 +75,12 @@ public class DisplayFormat { } // that only optimizes obvious things like 0 before . or at end, + at beginning - String delta = (new SmallestDoubleString(raw.getSingleBg().getDelta())).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.getSingleBg().getDelta())).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; } @@ -96,7 +97,7 @@ public class DisplayFormat { final String SEP_MIN = " "; String line = - raw.getStatus().getCob() + SEP_LONG + raw.getStatus().getIobSum() + SEP_LONG + basalRateSymbol()+raw.getStatus().getCurrentBasal(); + raw.getStatus().getCob() + SEP_LONG + raw.getStatus().getIobSum() + SEP_LONG + basalRateSymbol() + raw.getStatus().getCurrentBasal(); if (line.length() <= MAX_FIELD_LEN_LONG) { return line; } @@ -105,14 +106,14 @@ public class DisplayFormat { return line; } - int remainingMax = MAX_FIELD_LEN_LONG - (raw.getStatus().getCob().length() + raw.getStatus().getCurrentBasal().length() + SEP_SHORT_LEN*2); + 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.getStatus().getCurrentBasal().length() + SEP_SHORT_LEN*2); + 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.getStatus().getCurrentBasal(); @@ -135,13 +136,13 @@ public class DisplayFormat { if (iobBolus.trim().length() == 0) { iobBolus = "--"; } - String iobBasal = new SmallestDoubleString(iobs[1]).minimise((MAX_FIELD_LEN_SHORT -1) - Math.max(MIN_FIELD_LEN_IOB, iobBolus.length())); + String iobBasal = new SmallestDoubleString(iobs[1]).minimise((MAX_FIELD_LEN_SHORT - 1) - Math.max(MIN_FIELD_LEN_IOB, iobBolus.length())); if (iobBasal.trim().length() == 0) { iobBasal = "--"; } - iob2 = iobBolus+" "+iobBasal; + iob2 = iobBolus + " " + iobBasal; } - return Pair.create(iob1, iob2); + return new Pair(iob1, iob2); } public Pair detailedCob(final RawDisplayData raw) { @@ -152,6 +153,6 @@ public class DisplayFormat { cob2 = cobMini.getExtra() + cobMini.getUnits(); } final String cob1 = cobMini.minimise(MAX_FIELD_LEN_SHORT); - return Pair.create(cob1, cob2); + return new Pair(cob1, cob2); } } diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java deleted file mode 100644 index 5a268d3b7e..0000000000 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.java +++ /dev/null @@ -1,115 +0,0 @@ -package info.nightscout.androidaps.interaction.utils; - -import android.os.PowerManager; - -import java.util.concurrent.ConcurrentHashMap; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import info.nightscout.androidaps.BuildConfig; -import info.nightscout.androidaps.utils.DateUtil; -import info.nightscout.shared.logging.AAPSLogger; -import info.nightscout.shared.logging.LTag; - -/** - * Created for xDrip by jamorham on 07/03/2018 - * Adapted for AAPS by dlvoy on 2019-11-11 - * - * Tasks which are fired from events can be scheduled here and only execute when they become idle - * and are not being rescheduled within their wait window. - * - */ - -@Singleton -public class Inevitable { - - @Inject WearUtil wearUtil; - @Inject AAPSLogger aapsLogger; - @Inject DateUtil dateUtil; - - @Inject Inevitable() {} - - private static final int MAX_QUEUE_TIME = (int) Constants.MINUTE_IN_MS * 6; - private static final boolean debug = BuildConfig.DEBUG; - - private final ConcurrentHashMap tasks = new ConcurrentHashMap<>(); - - public void task(final String id, long idle_for, Runnable runnable) { - if (idle_for > MAX_QUEUE_TIME) { - throw new RuntimeException(id + " Requested time: " + idle_for + " beyond max queue time"); - } - final Task task = tasks.get(id); - if (task != null) { - // if it already exists then extend the time - task.extendTime(idle_for); - - if (debug) - aapsLogger.debug(LTag.WEAR, "Extending time for: " + id + " to " + dateUtil.dateAndTimeAndSecondsString(task.when)); - } else { - // otherwise create new task - if (runnable == null) return; // extension only if already exists - tasks.put(id, new Task(id, idle_for, runnable)); - - if (debug) { - aapsLogger.debug(LTag.WEAR, - "Creating task: " + id + " due: " + dateUtil.dateAndTimeAndSecondsString(tasks.get(id).when)); - } - - // create a thread to wait and execute in background - final Thread t = new Thread(() -> { - final PowerManager.WakeLock wl = wearUtil.getWakeLock(id, MAX_QUEUE_TIME + 5000); - try { - boolean running = true; - // wait for task to be due or killed - while (running) { - wearUtil.threadSleep(500); - final Task thisTask = tasks.get(id); - running = thisTask != null && !thisTask.poll(); - } - } finally { - wearUtil.releaseWakeLock(wl); - } - }); - t.setPriority(Thread.MIN_PRIORITY); - t.start(); - } - } - - public void kill(final String id) { - tasks.remove(id); - } - - private class Task { - private long when; - private final Runnable what; - private final String id; - - Task(String id, long offset, Runnable what) { - this.what = what; - this.id = id; - extendTime(offset); - } - - public void extendTime(long offset) { - this.when = wearUtil.timestamp() + offset; - } - - public boolean poll() { - final long till = wearUtil.msTill(when); - if (till < 1) { - if (debug) aapsLogger.debug(LTag.WEAR, "Executing task! " + this.id); - tasks.remove(this.id); // early remove to allow overlapping scheduling - what.run(); - return true; - } else if (till > MAX_QUEUE_TIME) { - aapsLogger.debug(LTag.WEAR, "Task: " + this.id + " In queue too long: " + till); - tasks.remove(this.id); - return true; - } - return false; - } - - } - -} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.kt new file mode 100644 index 0000000000..410f358499 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Inevitable.kt @@ -0,0 +1,103 @@ +package info.nightscout.androidaps.interaction.utils + +import info.nightscout.androidaps.BuildConfig +import info.nightscout.androidaps.utils.DateUtil +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Created for xDrip by jamorham on 07/03/2018 + * Adapted for AAPS by dlvoy on 2019-11-11 + * + * Tasks which are fired from events can be scheduled here and only execute when they become idle + * and are not being rescheduled within their wait window. + * + */ +@Singleton +class Inevitable @Inject internal constructor() { + + @Inject lateinit var wearUtil: WearUtil + @Inject lateinit var aapsLogger: AAPSLogger + @Inject lateinit var dateUtil: DateUtil + + private val tasks = ConcurrentHashMap() + fun task(id: String, idle_for: Long, runnable: Runnable?) { + if (idle_for > MAX_QUEUE_TIME) { + throw RuntimeException("$id Requested time: $idle_for beyond max queue time") + } + val task = tasks[id] + if (task != null) { + // if it already exists then extend the time + task.extendTime(idle_for) + if (debug) aapsLogger.debug(LTag.WEAR, "Extending time for: " + id + " to " + dateUtil.dateAndTimeAndSecondsString(task.`when`)) + } else { + // otherwise create new task + if (runnable == null) return // extension only if already exists + tasks[id] = Task(id, idle_for, runnable) + if (debug) { + aapsLogger.debug( + LTag.WEAR, + "Creating task: " + id + " due: " + dateUtil.dateAndTimeAndSecondsString(tasks[id]!!.`when`) + ) + } + + // create a thread to wait and execute in background + val t = Thread { + val wl = wearUtil.getWakeLock(id, MAX_QUEUE_TIME + 5000) + try { + var running = true + // wait for task to be due or killed + while (running) { + wearUtil.threadSleep(500) + val thisTask = tasks[id] + running = thisTask != null && !thisTask.poll() + } + } finally { + wearUtil.releaseWakeLock(wl) + } + } + t.priority = Thread.MIN_PRIORITY + t.start() + } + } + + fun kill(id: String) { + tasks.remove(id) + } + + private inner class Task(private val id: String, offset: Long, private val what: Runnable) { + + var `when`: Long = 0 + fun extendTime(offset: Long) { + `when` = wearUtil.timestamp() + offset + } + + fun poll(): Boolean { + val till = wearUtil.msTill(`when`) + if (till < 1) { + if (debug) aapsLogger.debug(LTag.WEAR, "Executing task! $id") + tasks.remove(id) // early remove to allow overlapping scheduling + what.run() + return true + } else if (till > MAX_QUEUE_TIME) { + aapsLogger.debug(LTag.WEAR, "Task: $id In queue too long: $till") + tasks.remove(id) + return true + } + return false + } + + init { + extendTime(offset) + } + } + + companion object { + + private const val MAX_QUEUE_TIME = Constants.MINUTE_IN_MS.toInt() * 6 + private val debug = BuildConfig.DEBUG + } +} \ No newline at end of file diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.java deleted file mode 100644 index 4207cbd743..0000000000 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.java +++ /dev/null @@ -1,43 +0,0 @@ -package info.nightscout.androidaps.interaction.utils; - -import java.util.Objects; - -/** - * Same as android Pair, but clean room java class - does not require Android SDK for tests - */ -public class Pair { - - public final F first; - public final S second; - - public Pair(F first, S second) { - this.first = first; - this.second = second; - } - - public static Pair create(F f, S s) { - return new Pair<>(f, s); - } - - @Override - public boolean equals(java.lang.Object o) { - if (o instanceof Pair) { - return ((Pair) o).first.equals(first) && ((Pair) o).second.equals(second); - } else { - return false; - } - } - - @Override - public String toString() { - return "First: \""+first.toString()+"\" Second: \""+second.toString()+"\""; - } - - @Override - public int hashCode() { - return Objects.hash(first, second); - } - -} - - diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.kt new file mode 100644 index 0000000000..9d087b0bc8 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/Pair.kt @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.interaction.utils + +import java.util.* + +/** + * Same as android Pair, but clean room java class - does not require Android SDK for tests + */ +class Pair(val first: F, val second: S) { + + override fun equals(other: Any?): Boolean = + if (other is Pair<*, *>) other.first == first && other.second == second + else false + + override fun toString(): String = "First: \"" + first.toString() + "\" Second: \"" + second.toString() + "\"" + override fun hashCode(): Int = Objects.hash(first, second) + + companion object { + + fun create(f: F, s: S): Pair = Pair(f, s) + } +} \ No newline at end of file diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java deleted file mode 100644 index 38bb5b011e..0000000000 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.java +++ /dev/null @@ -1,96 +0,0 @@ -package info.nightscout.androidaps.interaction.utils; - -import android.content.Context; -import android.os.Bundle; -import android.os.PowerManager; - -import com.google.android.gms.wearable.DataMap; - -import java.util.HashMap; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Singleton; - -import info.nightscout.shared.logging.AAPSLogger; -import info.nightscout.shared.logging.LTag; - -/** - * Created by andy on 3/5/19. - * Adapted by dlvoy on 2019-11-06 using code from jamorham JoH class - */ - -@Singleton -public class WearUtil { - - @Inject public Context context; - @Inject public AAPSLogger aapsLogger; - - @Inject public WearUtil() { - } - - private final boolean debug_wakelocks = false; - private final Map rateLimits = new HashMap<>(); - - //============================================================================================== - // Time related util methods - //============================================================================================== - - public long timestamp() { - return System.currentTimeMillis(); - } - - public long msSince(long when) { - return (timestamp() - when); - } - - public long msTill(long when) { - return (when - timestamp()); - } - - //============================================================================================== - // Thread and power management utils - //============================================================================================== - - // return true if below rate limit - public synchronized boolean isBelowRateLimit(String named, int onceForSeconds) { - // check if over limit - if ((rateLimits.containsKey(named)) && (timestamp() - rateLimits.get(named) < (onceForSeconds * 1000))) { - aapsLogger.debug(LTag.WEAR, named + " rate limited to one for " + onceForSeconds + " seconds"); - return false; - } - // not over limit - rateLimits.put(named, timestamp()); - return true; - } - - public PowerManager.WakeLock getWakeLock(final String name, int millis) { - final PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AAPS::" + name); - wl.acquire(millis); - if (debug_wakelocks) - aapsLogger.debug(LTag.WEAR, "getWakeLock: " + name + " " + wl); - return wl; - } - - public void releaseWakeLock(PowerManager.WakeLock wl) { - if (debug_wakelocks) aapsLogger.debug(LTag.WEAR, "releaseWakeLock: " + wl.toString()); - if (wl == null) return; - if (wl.isHeld()) wl.release(); - } - - public void threadSleep(long millis) { - try { - Thread.sleep(millis); - } catch (InterruptedException e) { - // we simply ignore if sleep was interrupted - } - } - - /** - * Taken out to helper method to allow testing - */ - public DataMap bundleToDataMap(Bundle bundle) { - return DataMap.fromBundle(bundle); - } -} diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.kt b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.kt new file mode 100644 index 0000000000..9945246c50 --- /dev/null +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/utils/WearUtil.kt @@ -0,0 +1,82 @@ +package info.nightscout.androidaps.interaction.utils + +import android.content.Context +import android.os.Bundle +import android.os.PowerManager +import com.google.android.gms.wearable.DataMap +import info.nightscout.shared.logging.AAPSLogger +import info.nightscout.shared.logging.LTag +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Created by andy on 3/5/19. + * Adapted by dlvoy on 2019-11-06 using code from jamorham JoH class + */ +@Singleton +class WearUtil @Inject constructor() { + + @Inject lateinit var context: Context + @Inject lateinit var aapsLogger: AAPSLogger + + private val debugWakelocks = false + private val rateLimits: MutableMap = HashMap() + + //============================================================================================== + // Time related util methods + //============================================================================================== + fun timestamp(): Long { + return System.currentTimeMillis() + } + + fun msSince(`when`: Long): Long { + return timestamp() - `when` + } + + fun msTill(`when`: Long): Long { + return `when` - timestamp() + } + + //============================================================================================== + // Thread and power management utils + //============================================================================================== + // return true if below rate limit + @Synchronized fun isBelowRateLimit(named: String, onceForSeconds: Int): Boolean { + // check if over limit + if (rateLimits.containsKey(named) && timestamp() - rateLimits[named]!! < onceForSeconds * 1000) { + aapsLogger.debug(LTag.WEAR, "$named rate limited to one for $onceForSeconds seconds") + return false + } + // not over limit + rateLimits[named] = timestamp() + return true + } + + fun getWakeLock(name: String, millis: Int): PowerManager.WakeLock { + val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager + val wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AAPS::$name") + wl.acquire(millis.toLong()) + if (debugWakelocks) aapsLogger.debug(LTag.WEAR, "getWakeLock: $name $wl") + return wl + } + + fun releaseWakeLock(wl: PowerManager.WakeLock?) { + if (debugWakelocks) aapsLogger.debug(LTag.WEAR, "releaseWakeLock: " + wl.toString()) + if (wl?.isHeld == true) wl.release() + } + + fun threadSleep(millis: Long) { + try { + Thread.sleep(millis) + } catch (e: InterruptedException) { + // we simply ignore if sleep was interrupted + } + } + + /** + * Taken out to helper method to allow testing + */ + fun bundleToDataMap(bundle: Bundle?): DataMap { + return DataMap.fromBundle(bundle) + } +} \ No newline at end of file diff --git a/wear/src/test/java/info/nightscout/androidaps/testing/mockers/WearUtilMocker.kt b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/WearUtilMocker.kt index c9fa0b1ca5..27199dc97c 100644 --- a/wear/src/test/java/info/nightscout/androidaps/testing/mockers/WearUtilMocker.kt +++ b/wear/src/test/java/info/nightscout/androidaps/testing/mockers/WearUtilMocker.kt @@ -64,7 +64,7 @@ class WearUtilMocker(private val wearUtil: WearUtil) { if (v is String) map.putString(key, v) if (v is Array<*>) map.putStringArray(key, v as Array) if (v is ArrayList<*>) { - if (!v.isEmpty()) { + if (v.isNotEmpty()) { if (v[0] is Int) { map.putIntegerArrayList(key, v as ArrayList) }