Skip to content

Commit 1cc26e7

Browse files
authored
Merge pull request #99 from Joshix-1/master
Timezone aware scheduling of Alarms
2 parents 6a749ee + 6297777 commit 1cc26e7

File tree

5 files changed

+107
-107
lines changed

5 files changed

+107
-107
lines changed

app/src/main/kotlin/org/fossify/clock/activities/ReminderActivity.kt

+16-9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.fossify.commons.helpers.MINUTE_SECONDS
2424
import org.fossify.commons.helpers.SILENT
2525
import org.fossify.commons.helpers.isOreoMr1Plus
2626
import org.fossify.commons.helpers.isOreoPlus
27+
import java.util.Calendar
2728

2829
class ReminderActivity : SimpleActivity() {
2930
companion object {
@@ -273,23 +274,29 @@ class ReminderActivity : SimpleActivity() {
273274
private fun snoozeAlarm(overrideSnoozeDuration: Int? = null) {
274275
destroyEffects()
275276
if (overrideSnoozeDuration != null) {
276-
setupAlarmClock(alarm!!, overrideSnoozeDuration * MINUTE_SECONDS)
277-
wasAlarmSnoozed = true
278-
finishActivity()
277+
scheduleSnoozedAlarm(overrideSnoozeDuration)
279278
} else if (config.useSameSnooze) {
280-
setupAlarmClock(alarm!!, config.snoozeTime * MINUTE_SECONDS)
281-
wasAlarmSnoozed = true
282-
finishActivity()
279+
scheduleSnoozedAlarm(config.snoozeTime)
283280
} else {
284281
showPickSecondsDialog(config.snoozeTime * MINUTE_SECONDS, true, cancelCallback = { finishActivity() }) {
285282
config.snoozeTime = it / MINUTE_SECONDS
286-
setupAlarmClock(alarm!!, it)
287-
wasAlarmSnoozed = true
288-
finishActivity()
283+
scheduleSnoozedAlarm(config.snoozeTime)
289284
}
290285
}
291286
}
292287

288+
private fun scheduleSnoozedAlarm(snoozeMinutes: Int) {
289+
setupAlarmClock(
290+
alarm = alarm!!,
291+
triggerTimeMillis = Calendar.getInstance()
292+
.apply { add(Calendar.MINUTE, snoozeMinutes) }
293+
.timeInMillis
294+
)
295+
296+
wasAlarmSnoozed = true
297+
finishActivity()
298+
}
299+
293300
private fun finishActivity() {
294301
if (!wasAlarmSnoozed && alarm != null) {
295302
cancelAlarmClock(alarm!!)

app/src/main/kotlin/org/fossify/clock/activities/SnoozeReminderActivity.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import org.fossify.clock.extensions.setupAlarmClock
99
import org.fossify.clock.helpers.ALARM_ID
1010
import org.fossify.commons.extensions.showPickSecondsDialog
1111
import org.fossify.commons.helpers.MINUTE_SECONDS
12+
import java.util.Calendar
1213

1314
class SnoozeReminderActivity : AppCompatActivity() {
1415
override fun onCreate(savedInstanceState: Bundle?) {
@@ -18,7 +19,12 @@ class SnoozeReminderActivity : AppCompatActivity() {
1819
hideNotification(id)
1920
showPickSecondsDialog(config.snoozeTime * MINUTE_SECONDS, true, cancelCallback = { dialogCancelled() }) {
2021
config.snoozeTime = it / MINUTE_SECONDS
21-
setupAlarmClock(alarm, it)
22+
setupAlarmClock(
23+
alarm = alarm,
24+
triggerTimeMillis = Calendar.getInstance()
25+
.apply { add(Calendar.SECOND, it) }
26+
.timeInMillis
27+
)
2228
finishActivity()
2329
}
2430
}

app/src/main/kotlin/org/fossify/clock/extensions/Context.kt

+37-63
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import android.os.Handler
1313
import android.os.Looper
1414
import android.os.PowerManager
1515
import android.text.SpannableString
16-
import android.text.format.DateFormat
1716
import android.text.style.RelativeSizeSpan
1817
import android.widget.Toast
1918
import androidx.core.app.AlarmManagerCompat
@@ -36,7 +35,8 @@ import org.fossify.commons.helpers.*
3635
import java.text.SimpleDateFormat
3736
import java.util.Calendar
3837
import java.util.Locale
39-
import kotlin.math.pow
38+
import kotlin.math.ceil
39+
import kotlin.time.Duration.Companion.milliseconds
4040
import kotlin.time.Duration.Companion.minutes
4141

4242
val Context.config: Config get() = Config.newInstance(applicationContext)
@@ -100,59 +100,44 @@ fun Context.createNewTimer(): Timer {
100100
}
101101

102102
fun Context.scheduleNextAlarm(alarm: Alarm, showToast: Boolean) {
103-
val calendar = Calendar.getInstance()
104-
calendar.firstDayOfWeek = Calendar.MONDAY
105-
val currentTimeInMinutes = getCurrentDayMinutes()
103+
val triggerTimeMillis = getTimeOfNextAlarm(alarm)?.timeInMillis ?: return
104+
setupAlarmClock(alarm = alarm, triggerTimeMillis = triggerTimeMillis)
106105

107-
if (alarm.days == TODAY_BIT) {
108-
val triggerInMinutes = alarm.timeInMinutes - currentTimeInMinutes
109-
setupAlarmClock(alarm, triggerInMinutes * 60 - calendar.get(Calendar.SECOND))
110-
111-
if (showToast) {
112-
showRemainingTimeMessage(triggerInMinutes)
113-
}
114-
} else if (alarm.days == TOMORROW_BIT) {
115-
val triggerInMinutes = alarm.timeInMinutes - currentTimeInMinutes + DAY_MINUTES
116-
setupAlarmClock(alarm, triggerInMinutes * 60 - calendar.get(Calendar.SECOND))
106+
if (showToast) {
107+
val now = Calendar.getInstance()
108+
val triggerInMillis = triggerTimeMillis - now.timeInMillis
109+
showRemainingTimeMessage(triggerInMillis)
110+
}
111+
}
117112

118-
if (showToast) {
119-
showRemainingTimeMessage(triggerInMinutes)
120-
}
113+
fun Context.showRemainingTimeMessage(triggerInMillis: Long) {
114+
val totalSeconds = triggerInMillis.milliseconds.inWholeSeconds.toInt()
115+
val remainingTime = if (totalSeconds >= MINUTE_SECONDS) {
116+
val roundedMinutes = ceil(totalSeconds / MINUTE_SECONDS.toFloat()).toInt()
117+
formatMinutesToTimeString(roundedMinutes)
121118
} else {
122-
for (i in 0..7) {
123-
val currentDay = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
124-
val isCorrectDay = alarm.days and 2.0.pow(currentDay).toInt() != 0
125-
if (isCorrectDay && (alarm.timeInMinutes > currentTimeInMinutes || i > 0)) {
126-
val triggerInMinutes = alarm.timeInMinutes - currentTimeInMinutes + (i * DAY_MINUTES)
127-
setupAlarmClock(alarm, triggerInMinutes * 60 - calendar.get(Calendar.SECOND))
128-
129-
if (showToast) {
130-
showRemainingTimeMessage(triggerInMinutes)
131-
}
132-
break
133-
} else {
134-
calendar.add(Calendar.DAY_OF_MONTH, 1)
135-
}
136-
}
119+
formatSecondsToTimeString(totalSeconds)
137120
}
138-
}
139121

140-
fun Context.showRemainingTimeMessage(totalMinutes: Int) {
141-
val fullString = String.format(getString(org.fossify.commons.R.string.time_remaining), formatMinutesToTimeString(totalMinutes))
142-
toast(fullString, Toast.LENGTH_LONG)
122+
toast(
123+
msg = String.format(
124+
getString(org.fossify.commons.R.string.time_remaining), remainingTime
125+
),
126+
length = Toast.LENGTH_LONG
127+
)
143128
}
144129

145-
fun Context.setupAlarmClock(alarm: Alarm, triggerInSeconds: Int) {
130+
fun Context.setupAlarmClock(alarm: Alarm, triggerTimeMillis: Long) {
146131
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
147-
val targetMS = System.currentTimeMillis() + triggerInSeconds * 1000
132+
148133
try {
149-
AlarmManagerCompat.setAlarmClock(alarmManager, targetMS, getOpenAlarmTabIntent(), getAlarmIntent(alarm))
134+
AlarmManagerCompat.setAlarmClock(alarmManager, triggerTimeMillis, getOpenAlarmTabIntent(), getAlarmIntent(alarm))
150135

151136
// show a notification to allow dismissing the alarm 10 minutes before it actually triggers
152-
val dismissalTriggerTime = if (targetMS - System.currentTimeMillis() < 10.minutes.inWholeMilliseconds) {
137+
val dismissalTriggerTime = if (triggerTimeMillis - System.currentTimeMillis() < 10.minutes.inWholeMilliseconds) {
153138
System.currentTimeMillis() + 500
154139
} else {
155-
targetMS - 10.minutes.inWholeMilliseconds
140+
triggerTimeMillis - 10.minutes.inWholeMilliseconds
156141
}
157142
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, 0, dismissalTriggerTime, getEarlyAlarmDismissalIntent(alarm))
158143
} catch (e: Exception) {
@@ -277,35 +262,26 @@ fun Context.getClosestEnabledAlarmString(callback: (result: String) -> Unit) {
277262
return@getEnabledAlarms
278263
}
279264

265+
val now = Calendar.getInstance()
280266
val nextAlarmList = enabledAlarms
281-
.mapNotNull { getTimeUntilNextAlarm(it.timeInMinutes, it.days) }
267+
.mapNotNull(::getTimeOfNextAlarm)
268+
.filter { it > now }
282269

283-
if (nextAlarmList.isEmpty()) {
284-
callback("")
285-
}
286-
287-
var closestAlarmTime = Int.MAX_VALUE
288-
nextAlarmList.forEach { time ->
289-
if (time < closestAlarmTime) {
290-
closestAlarmTime = time
291-
}
292-
}
293-
294-
if (closestAlarmTime == Int.MAX_VALUE) {
270+
val closestAlarmTime = nextAlarmList.minOrNull()
271+
if (closestAlarmTime == null) {
295272
callback("")
273+
return@getEnabledAlarms
296274
}
297275

298-
val calendar = Calendar.getInstance().apply { firstDayOfWeek = Calendar.MONDAY }
299-
calendar.add(Calendar.MINUTE, closestAlarmTime)
300-
val dayOfWeekIndex = (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
276+
val dayOfWeekIndex = (closestAlarmTime.get(Calendar.DAY_OF_WEEK) + 5) % 7
301277
val dayOfWeek = resources.getStringArray(org.fossify.commons.R.array.week_days_short)[dayOfWeekIndex]
302278
val pattern = if (config.use24HourFormat) {
303279
FORMAT_24H
304280
} else {
305281
FORMAT_12H
306282
}
307283

308-
val formattedTime = SimpleDateFormat(pattern, Locale.getDefault()).format(calendar.time)
284+
val formattedTime = SimpleDateFormat(pattern, Locale.getDefault()).format(closestAlarmTime.time)
309285
callback("$dayOfWeek $formattedTime")
310286
}
311287
}
@@ -359,7 +335,7 @@ fun Context.getTimerNotification(timer: Timer, pendingIntent: PendingIntent, add
359335
if (isOreoPlus()) {
360336
try {
361337
notificationManager.deleteNotificationChannel(channelId)
362-
} catch (e: Exception) {
338+
} catch (_: Exception) {
363339
}
364340

365341
val audioAttributes = AudioAttributes.Builder()
@@ -385,10 +361,8 @@ fun Context.getTimerNotification(timer: Timer, pendingIntent: PendingIntent, add
385361
}
386362
}
387363

388-
val title = if (timer.label.isEmpty()) {
364+
val title = timer.label.ifEmpty {
389365
getString(R.string.timer)
390-
} else {
391-
timer.label
392366
}
393367

394368
val reminderActivityIntent = getReminderActivityIntent()

app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt

+40-32
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
package org.fossify.clock.helpers
22

33
import org.fossify.clock.extensions.isBitSet
4+
import org.fossify.clock.models.Alarm
45
import org.fossify.clock.models.MyTimeZone
5-
import org.fossify.commons.helpers.*
6+
import org.fossify.commons.helpers.FRIDAY_BIT
7+
import org.fossify.commons.helpers.MONDAY_BIT
8+
import org.fossify.commons.helpers.SATURDAY_BIT
9+
import org.fossify.commons.helpers.SUNDAY_BIT
10+
import org.fossify.commons.helpers.THURSDAY_BIT
11+
import org.fossify.commons.helpers.TUESDAY_BIT
12+
import org.fossify.commons.helpers.WEDNESDAY_BIT
13+
import org.fossify.commons.helpers.isPiePlus
614
import java.util.Calendar
715
import java.util.Date
816
import java.util.TimeZone
@@ -125,7 +133,13 @@ fun getPassedSeconds(): Int {
125133
return ((calendar.timeInMillis + offset) / 1000).toInt()
126134
}
127135

128-
fun formatTime(showSeconds: Boolean, use24HourFormat: Boolean, hours: Int, minutes: Int, seconds: Int): String {
136+
fun formatTime(
137+
showSeconds: Boolean,
138+
use24HourFormat: Boolean,
139+
hours: Int,
140+
minutes: Int,
141+
seconds: Int,
142+
): String {
129143
val hoursFormat = if (use24HourFormat) "%02d" else "%01d"
130144
var format = "$hoursFormat:%02d"
131145

@@ -254,39 +268,33 @@ fun getAllTimeZones() = arrayListOf(
254268
MyTimeZone(89, "GMT+13:00 Tongatapu", "Pacific/Tongatapu")
255269
)
256270

257-
fun getTimeUntilNextAlarm(alarmTimeInMinutes: Int, days: Int): Int? {
258-
val calendar = Calendar.getInstance()
259-
calendar.firstDayOfWeek = Calendar.MONDAY
260-
val currentTimeInMinutes = calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)
261-
val currentDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - Calendar.MONDAY
271+
fun getTimeOfNextAlarm(alarm: Alarm): Calendar? {
272+
return getTimeOfNextAlarm(alarm.timeInMinutes, alarm.days)
273+
}
262274

263-
var minTimeDifferenceInMinutes = Int.MAX_VALUE
275+
fun getTimeOfNextAlarm(alarmTimeInMinutes: Int, days: Int): Calendar? {
276+
val nextAlarmTime = Calendar.getInstance().apply {
277+
firstDayOfWeek = Calendar.MONDAY // why is this here? seems unnecessary
278+
set(Calendar.HOUR_OF_DAY, alarmTimeInMinutes / 60)
279+
set(Calendar.MINUTE, alarmTimeInMinutes % 60)
280+
set(Calendar.SECOND, 0)
281+
set(Calendar.MILLISECOND, 0)
282+
}
264283

265-
for (i in 0..6) {
266-
val alarmDayOfWeek = (currentDayOfWeek + i) % 7
267-
if (isAlarmEnabledForDay(alarmDayOfWeek, days)) {
268-
val timeDifferenceInMinutes = getTimeDifferenceInMinutes(currentTimeInMinutes, alarmTimeInMinutes, i)
269-
if (timeDifferenceInMinutes < minTimeDifferenceInMinutes) {
270-
minTimeDifferenceInMinutes = timeDifferenceInMinutes
284+
return when (days) {
285+
TODAY_BIT -> nextAlarmTime // do nothing, alarm is today
286+
TOMORROW_BIT -> nextAlarmTime.apply { add(Calendar.DAY_OF_MONTH, 1) }
287+
else -> {
288+
val now = Calendar.getInstance()
289+
repeat(8) {
290+
val currentDay = (nextAlarmTime.get(Calendar.DAY_OF_WEEK) + 5) % 7
291+
if (days.isBitSet(currentDay) && now < nextAlarmTime) {
292+
return nextAlarmTime
293+
} else {
294+
nextAlarmTime.add(Calendar.DAY_OF_MONTH, 1)
295+
}
271296
}
297+
null
272298
}
273299
}
274-
275-
return if (minTimeDifferenceInMinutes != Int.MAX_VALUE) {
276-
minTimeDifferenceInMinutes
277-
} else {
278-
null
279-
}
280-
}
281-
282-
fun isAlarmEnabledForDay(day: Int, alarmDays: Int) = alarmDays.isBitSet(day)
283-
284-
fun getTimeDifferenceInMinutes(currentTimeInMinutes: Int, alarmTimeInMinutes: Int, daysUntilAlarm: Int): Int {
285-
val minutesInADay = 24 * 60
286-
val minutesUntilAlarm = daysUntilAlarm * minutesInADay + alarmTimeInMinutes
287-
return if (minutesUntilAlarm > currentTimeInMinutes) {
288-
minutesUntilAlarm - currentTimeInMinutes
289-
} else {
290-
minutesInADay - (currentTimeInMinutes - minutesUntilAlarm)
291-
}
292300
}

app/src/main/kotlin/org/fossify/clock/services/SnoozeService.kt

+7-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ import org.fossify.clock.extensions.dbHelper
77
import org.fossify.clock.extensions.hideNotification
88
import org.fossify.clock.extensions.setupAlarmClock
99
import org.fossify.clock.helpers.ALARM_ID
10-
import org.fossify.commons.helpers.MINUTE_SECONDS
10+
import java.util.Calendar
1111

1212
class SnoozeService : IntentService("Snooze") {
1313
override fun onHandleIntent(intent: Intent?) {
1414
val id = intent!!.getIntExtra(ALARM_ID, -1)
1515
val alarm = dbHelper.getAlarmWithId(id) ?: return
1616
hideNotification(id)
17-
setupAlarmClock(alarm, config.snoozeTime * MINUTE_SECONDS)
17+
setupAlarmClock(
18+
alarm = alarm,
19+
triggerTimeMillis = Calendar.getInstance()
20+
.apply { add(Calendar.MINUTE, config.snoozeTime) }
21+
.timeInMillis
22+
)
1823
}
1924
}

0 commit comments

Comments
 (0)