Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timezone aware scheduling of Alarms #99

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.fossify.commons.helpers.MINUTE_SECONDS
import org.fossify.commons.helpers.SILENT
import org.fossify.commons.helpers.isOreoMr1Plus
import org.fossify.commons.helpers.isOreoPlus
import java.util.Calendar

class ReminderActivity : SimpleActivity() {
companion object {
Expand Down Expand Up @@ -267,23 +268,23 @@ class ReminderActivity : SimpleActivity() {
private fun snoozeAlarm(overrideSnoozeDuration: Int? = null) {
destroyEffects()
if (overrideSnoozeDuration != null) {
setupAlarmClock(alarm!!, overrideSnoozeDuration * MINUTE_SECONDS)
wasAlarmSnoozed = true
finishActivity()
snoozeAlarm { add(Calendar.MINUTE, overrideSnoozeDuration) }
} else if (config.useSameSnooze) {
setupAlarmClock(alarm!!, config.snoozeTime * MINUTE_SECONDS)
wasAlarmSnoozed = true
finishActivity()
snoozeAlarm { add(Calendar.MINUTE, config.snoozeTime) }
} else {
showPickSecondsDialog(config.snoozeTime * MINUTE_SECONDS, true, cancelCallback = { finishActivity() }) {
config.snoozeTime = it / MINUTE_SECONDS
setupAlarmClock(alarm!!, it)
wasAlarmSnoozed = true
finishActivity()
snoozeAlarm { add(Calendar.SECOND, it) }
}
}
}

private fun snoozeAlarm(block: Calendar.() -> Unit) {
setupAlarmClock(alarm!!, block.run { Calendar.getInstance() })
wasAlarmSnoozed = true
finishActivity()
}

private fun finishActivity() {
if (!wasAlarmSnoozed && alarm != null) {
cancelAlarmClock(alarm!!)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.fossify.clock.extensions.setupAlarmClock
import org.fossify.clock.helpers.ALARM_ID
import org.fossify.commons.extensions.showPickSecondsDialog
import org.fossify.commons.helpers.MINUTE_SECONDS
import java.util.Calendar

class SnoozeReminderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -18,7 +19,7 @@ class SnoozeReminderActivity : AppCompatActivity() {
hideNotification(id)
showPickSecondsDialog(config.snoozeTime * MINUTE_SECONDS, true, cancelCallback = { dialogCancelled() }) {
config.snoozeTime = it / MINUTE_SECONDS
setupAlarmClock(alarm, it)
setupAlarmClock(alarm, Calendar.getInstance().apply { add(Calendar.SECOND, it) })
finishActivity()
}
}
Expand Down
68 changes: 17 additions & 51 deletions app/src/main/kotlin/org/fossify/clock/extensions/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -100,40 +100,13 @@ fun Context.createNewTimer(): Timer {
}

fun Context.scheduleNextAlarm(alarm: Alarm, showToast: Boolean) {
val calendar = Calendar.getInstance()
calendar.firstDayOfWeek = Calendar.MONDAY
val currentTimeInMinutes = getCurrentDayMinutes()
val triggerTime = getTimeOfNextAlarm(alarm.timeInMinutes, alarm.days) ?: return
setupAlarmClock(alarm, triggerTime)

if (alarm.days == TODAY_BIT) {
val triggerInMinutes = alarm.timeInMinutes - currentTimeInMinutes
setupAlarmClock(alarm, triggerInMinutes * 60 - calendar.get(Calendar.SECOND))

if (showToast) {
showRemainingTimeMessage(triggerInMinutes)
}
} else if (alarm.days == TOMORROW_BIT) {
val triggerInMinutes = alarm.timeInMinutes - currentTimeInMinutes + DAY_MINUTES
setupAlarmClock(alarm, triggerInMinutes * 60 - calendar.get(Calendar.SECOND))

if (showToast) {
showRemainingTimeMessage(triggerInMinutes)
}
} else {
for (i in 0..7) {
val currentDay = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
val isCorrectDay = alarm.days and 2.0.pow(currentDay).toInt() != 0
if (isCorrectDay && (alarm.timeInMinutes > currentTimeInMinutes || i > 0)) {
val triggerInMinutes = alarm.timeInMinutes - currentTimeInMinutes + (i * DAY_MINUTES)
setupAlarmClock(alarm, triggerInMinutes * 60 - calendar.get(Calendar.SECOND))

if (showToast) {
showRemainingTimeMessage(triggerInMinutes)
}
break
} else {
calendar.add(Calendar.DAY_OF_MONTH, 1)
}
}
if (showToast) {
val now = Calendar.getInstance()
val triggerInMillis = triggerTime.timeInMillis - now.timeInMillis
showRemainingTimeMessage((triggerInMillis / (1000 * 60)).toInt())
}
}

Expand All @@ -142,9 +115,10 @@ fun Context.showRemainingTimeMessage(totalMinutes: Int) {
toast(fullString, Toast.LENGTH_LONG)
}

fun Context.setupAlarmClock(alarm: Alarm, triggerInSeconds: Int) {
fun Context.setupAlarmClock(alarm: Alarm, triggerTime: Calendar) {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val targetMS = System.currentTimeMillis() + triggerInSeconds * 1000

val targetMS = triggerTime.timeInMillis
try {
AlarmManagerCompat.setAlarmClock(alarmManager, targetMS, getOpenAlarmTabIntent(), getAlarmIntent(alarm))

Expand Down Expand Up @@ -277,35 +251,27 @@ fun Context.getClosestEnabledAlarmString(callback: (result: String) -> Unit) {
return@getEnabledAlarms
}

val now = Calendar.getInstance()
val nextAlarmList = enabledAlarms
.mapNotNull { getTimeUntilNextAlarm(it.timeInMinutes, it.days) }
.mapNotNull { getTimeOfNextAlarm(it.timeInMinutes, it.days) }
.filter { it > now }

if (nextAlarmList.isEmpty()) {
callback("")
}
val closestAlarmTime = nextAlarmList.minOrNull()

var closestAlarmTime = Int.MAX_VALUE
nextAlarmList.forEach { time ->
if (time < closestAlarmTime) {
closestAlarmTime = time
}
}

if (closestAlarmTime == Int.MAX_VALUE) {
if (closestAlarmTime == null) {
callback("")
return@getEnabledAlarms
}

val calendar = Calendar.getInstance().apply { firstDayOfWeek = Calendar.MONDAY }
calendar.add(Calendar.MINUTE, closestAlarmTime)
val dayOfWeekIndex = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
val dayOfWeekIndex = (closestAlarmTime.get(Calendar.DAY_OF_WEEK) + 5) % 7
val dayOfWeek = resources.getStringArray(org.fossify.commons.R.array.week_days_short)[dayOfWeekIndex]
val pattern = if (DateFormat.is24HourFormat(this)) {
"HH:mm"
} else {
"h:mm a"
}

val formattedTime = SimpleDateFormat(pattern, Locale.getDefault()).format(calendar.time)
val formattedTime = SimpleDateFormat(pattern, Locale.getDefault()).format(closestAlarmTime.time)
callback("$dayOfWeek $formattedTime")
}
}
Expand Down
66 changes: 32 additions & 34 deletions app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,14 @@ fun formatTime(showSeconds: Boolean, use24HourFormat: Boolean, hours: Int, minut
fun getTomorrowBit(): Int {
val calendar = Calendar.getInstance()
calendar.add(Calendar.DAY_OF_WEEK, 1)
val dayOfWeek = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
return 2.0.pow(dayOfWeek).toInt()
val day = calendar.get(Calendar.DAY_OF_WEEK)
return getBitForCalendarDay(day)
}

fun getTodayBit(): Int {
val calendar = Calendar.getInstance()
val dayOfWeek = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
return 2.0.pow(dayOfWeek).toInt()
val day = calendar.get(Calendar.DAY_OF_WEEK)
return getBitForCalendarDay(day)
}

fun getBitForCalendarDay(day: Int): Int {
Expand Down Expand Up @@ -222,39 +222,37 @@ fun getAllTimeZones() = arrayListOf(
MyTimeZone(89, "GMT+13:00 Tongatapu", "Pacific/Tongatapu")
)

fun getTimeUntilNextAlarm(alarmTimeInMinutes: Int, days: Int): Int? {
val calendar = Calendar.getInstance()
calendar.firstDayOfWeek = Calendar.MONDAY
val currentTimeInMinutes = calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)
val currentDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - Calendar.MONDAY
fun getTimeOfNextAlarm(alarmTimeInMinutes: Int, days: Int): Calendar? {
val nextAlarmTime = Calendar.getInstance()
nextAlarmTime.firstDayOfWeek = Calendar.MONDAY

val hour = alarmTimeInMinutes / 60
val minute = alarmTimeInMinutes % 60

var minTimeDifferenceInMinutes = Int.MAX_VALUE
nextAlarmTime.set(Calendar.HOUR_OF_DAY, hour)
nextAlarmTime.set(Calendar.MINUTE, minute)
nextAlarmTime.set(Calendar.SECOND, 0)
nextAlarmTime.set(Calendar.MILLISECOND, 0)

for (i in 0..6) {
val alarmDayOfWeek = (currentDayOfWeek + i) % 7
if (isAlarmEnabledForDay(alarmDayOfWeek, days)) {
val timeDifferenceInMinutes = getTimeDifferenceInMinutes(currentTimeInMinutes, alarmTimeInMinutes, i)
if (timeDifferenceInMinutes < minTimeDifferenceInMinutes) {
minTimeDifferenceInMinutes = timeDifferenceInMinutes
return when (days) {
TODAY_BIT -> {
// do nothing, alarm is today
nextAlarmTime
}
TOMORROW_BIT -> {
nextAlarmTime.apply { add(Calendar.DAY_OF_MONTH, 1) }
}
else -> {
val now = Calendar.getInstance()
repeat(8) {
val currentDay = (nextAlarmTime.get(Calendar.DAY_OF_WEEK) + 5) % 7
if (days.isBitSet(currentDay) && now < nextAlarmTime) {
return nextAlarmTime
} else {
nextAlarmTime.add(Calendar.DAY_OF_MONTH, 1)
}
}
null
}
}

return if (minTimeDifferenceInMinutes != Int.MAX_VALUE) {
minTimeDifferenceInMinutes
} else {
null
}
}

fun isAlarmEnabledForDay(day: Int, alarmDays: Int) = alarmDays.isBitSet(day)

fun getTimeDifferenceInMinutes(currentTimeInMinutes: Int, alarmTimeInMinutes: Int, daysUntilAlarm: Int): Int {
val minutesInADay = 24 * 60
val minutesUntilAlarm = daysUntilAlarm * minutesInADay + alarmTimeInMinutes
return if (minutesUntilAlarm > currentTimeInMinutes) {
minutesUntilAlarm - currentTimeInMinutes
} else {
minutesInADay - (currentTimeInMinutes - minutesUntilAlarm)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import org.fossify.clock.extensions.hideNotification
import org.fossify.clock.extensions.setupAlarmClock
import org.fossify.clock.helpers.ALARM_ID
import org.fossify.commons.helpers.MINUTE_SECONDS
import java.util.Calendar

class SnoozeService : IntentService("Snooze") {
override fun onHandleIntent(intent: Intent?) {
val id = intent!!.getIntExtra(ALARM_ID, -1)
val alarm = dbHelper.getAlarmWithId(id) ?: return
hideNotification(id)
setupAlarmClock(alarm, config.snoozeTime * MINUTE_SECONDS)
setupAlarmClock(alarm, Calendar.getInstance().apply { add(Calendar.MINUTE, config.snoozeTime) })
}
}
Loading