mirror of https://github.com/tasks/tasks
Refactor notification scheduling
* Remove foreground service * Use expedited work to trigger notifications * Remove miscellaneous notification channelpull/2872/head
parent
95c351e9fd
commit
3cd0295b71
@ -1,73 +0,0 @@
|
|||||||
package org.tasks.injection
|
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.tasks.R
|
|
||||||
import org.tasks.analytics.Firebase
|
|
||||||
import org.tasks.notifications.NotificationManager
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
abstract class InjectingService : Service() {
|
|
||||||
private val job = SupervisorJob()
|
|
||||||
private val scope = CoroutineScope(Dispatchers.Default + job)
|
|
||||||
|
|
||||||
@Inject lateinit var firebase: Firebase
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
startForeground()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
|
||||||
startForeground()
|
|
||||||
scope.launch {
|
|
||||||
try {
|
|
||||||
doWork()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
firebase.reportException(e)
|
|
||||||
} finally {
|
|
||||||
done(startId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return START_NOT_STICKY
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun done(startId: Int) {
|
|
||||||
scheduleNext()
|
|
||||||
stopSelf(startId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
stopForeground(true)
|
|
||||||
job.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startForeground() {
|
|
||||||
startForeground(notificationId, buildNotification())
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract val notificationId: Int
|
|
||||||
protected abstract val notificationBody: Int
|
|
||||||
|
|
||||||
private fun buildNotification(): Notification {
|
|
||||||
return NotificationCompat.Builder(
|
|
||||||
this, NotificationManager.NOTIFICATION_CHANNEL_MISCELLANEOUS)
|
|
||||||
.setSound(null)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
|
||||||
.setSmallIcon(R.drawable.ic_check_white_24dp)
|
|
||||||
.setContentTitle(getString(R.string.app_name))
|
|
||||||
.setContentText(getString(notificationBody))
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun scheduleNext() {}
|
|
||||||
|
|
||||||
protected abstract suspend fun doWork()
|
|
||||||
}
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
package org.tasks.jobs
|
|
||||||
|
|
||||||
import com.google.common.collect.Ordering
|
|
||||||
import com.google.common.collect.TreeMultimap
|
|
||||||
import com.google.common.primitives.Ints
|
|
||||||
import org.tasks.preferences.Preferences
|
|
||||||
import org.tasks.time.DateTime
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class NotificationQueue @Inject constructor(
|
|
||||||
private val preferences: Preferences,
|
|
||||||
private val workManager: WorkManager
|
|
||||||
) {
|
|
||||||
private val jobs =
|
|
||||||
TreeMultimap.create<Long, AlarmEntry>(Ordering.natural()) { l, r ->
|
|
||||||
Ints.compare(l.hashCode(), r.hashCode())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun add(entry: AlarmEntry) = add(listOf(entry))
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun add(entries: Iterable<AlarmEntry>) {
|
|
||||||
val originalFirstTime = firstTime()
|
|
||||||
entries.forEach { jobs.put(it.time, it) }
|
|
||||||
if (originalFirstTime != firstTime()) {
|
|
||||||
scheduleNext(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun clear() {
|
|
||||||
jobs.clear()
|
|
||||||
workManager.cancelNotifications()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancelForTask(taskId: Long) {
|
|
||||||
val firstTime = firstTime()
|
|
||||||
jobs.values().filter { it.taskId == taskId }.forEach { remove(listOf(it)) }
|
|
||||||
if (firstTime != firstTime()) {
|
|
||||||
scheduleNext(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@get:Synchronized
|
|
||||||
val overdueJobs: List<AlarmEntry>
|
|
||||||
get() = jobs.keySet()
|
|
||||||
.headSet(DateTime().startOfMinute().plusMinutes(1).millis)
|
|
||||||
.flatMap { jobs[it] }
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun scheduleNext() = scheduleNext(false)
|
|
||||||
|
|
||||||
private fun scheduleNext(cancelCurrent: Boolean) {
|
|
||||||
if (jobs.isEmpty) {
|
|
||||||
if (cancelCurrent) {
|
|
||||||
workManager.cancelNotifications()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
workManager.scheduleNotification(nextScheduledTime())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun firstTime() = if (jobs.isEmpty) 0L else jobs.asMap().firstKey()
|
|
||||||
|
|
||||||
fun nextScheduledTime(): Long {
|
|
||||||
val next = firstTime()
|
|
||||||
return if (next > 0) preferences.adjustForQuietHours(next) else 0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun size() = jobs.size()
|
|
||||||
|
|
||||||
fun getJobs() = jobs.values().toList()
|
|
||||||
|
|
||||||
fun isEmpty() = jobs.isEmpty
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun remove(entries: List<AlarmEntry>): Boolean {
|
|
||||||
var success = true
|
|
||||||
for (entry in entries) {
|
|
||||||
success = success and (!jobs.containsEntry(entry.time, entry) || jobs.remove(
|
|
||||||
entry.time,
|
|
||||||
entry
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return success
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package org.tasks.jobs
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class NotificationReceiver: BroadcastReceiver() {
|
||||||
|
@Inject lateinit var workManager: WorkManager
|
||||||
|
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
workManager.triggerNotifications(expedited = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,50 +0,0 @@
|
|||||||
package org.tasks.jobs
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.IBinder
|
|
||||||
import com.todoroo.andlib.utility.AndroidUtilities
|
|
||||||
import com.todoroo.astrid.alarms.AlarmService
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import org.tasks.Notifier
|
|
||||||
import org.tasks.R
|
|
||||||
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
|
|
||||||
import org.tasks.data.AlarmDao
|
|
||||||
import org.tasks.injection.InjectingService
|
|
||||||
import org.tasks.preferences.Preferences
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class NotificationService : InjectingService() {
|
|
||||||
@Inject lateinit var preferences: Preferences
|
|
||||||
@Inject lateinit var notifier: Notifier
|
|
||||||
@Inject lateinit var notificationQueue: NotificationQueue
|
|
||||||
@Inject lateinit var alarmDao: AlarmDao
|
|
||||||
@Inject lateinit var alarmService: AlarmService
|
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? = null
|
|
||||||
|
|
||||||
override val notificationId = -1
|
|
||||||
|
|
||||||
override val notificationBody = R.string.building_notifications
|
|
||||||
|
|
||||||
override suspend fun doWork() {
|
|
||||||
AndroidUtilities.assertNotMainThread()
|
|
||||||
if (!preferences.isCurrentlyQuietHours) {
|
|
||||||
val overdueJobs = notificationQueue.overdueJobs
|
|
||||||
if (!notificationQueue.remove(overdueJobs)) {
|
|
||||||
throw RuntimeException("Failed to remove jobs from queue")
|
|
||||||
}
|
|
||||||
notifier.triggerNotifications(overdueJobs.map { it.toNotification() })
|
|
||||||
overdueJobs
|
|
||||||
.filter { it.type == TYPE_SNOOZE }
|
|
||||||
.takeIf { it.isNotEmpty() }
|
|
||||||
?.map { it.id }
|
|
||||||
?.let { alarmDao.deleteByIds(it) }
|
|
||||||
overdueJobs
|
|
||||||
.map { it.taskId }
|
|
||||||
.let { alarmService.scheduleAlarms(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun scheduleNext() = notificationQueue.scheduleNext()
|
|
||||||
}
|
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
package org.tasks.jobs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.hilt.work.HiltWorker
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.todoroo.andlib.utility.DateUtilities.now
|
||||||
|
import com.todoroo.astrid.alarms.AlarmService
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import org.tasks.Notifier
|
||||||
|
import org.tasks.analytics.Firebase
|
||||||
|
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
|
||||||
|
import org.tasks.data.AlarmDao
|
||||||
|
import org.tasks.date.DateTimeUtils.toDateTime
|
||||||
|
import org.tasks.preferences.Preferences
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@HiltWorker
|
||||||
|
class NotificationWork @AssistedInject constructor(
|
||||||
|
@Assisted context: Context,
|
||||||
|
@Assisted workerParams: WorkerParameters,
|
||||||
|
firebase: Firebase,
|
||||||
|
private val workManager: WorkManager,
|
||||||
|
private val alarmService: AlarmService,
|
||||||
|
private val alarmDao: AlarmDao,
|
||||||
|
private val preferences: Preferences,
|
||||||
|
private val notifier: Notifier,
|
||||||
|
) : RepeatingWorker(context, workerParams, firebase) {
|
||||||
|
private var nextAlarm: Long = 0
|
||||||
|
|
||||||
|
override suspend fun run(): Result {
|
||||||
|
if (preferences.isCurrentlyQuietHours) {
|
||||||
|
nextAlarm = preferences.adjustForQuietHours(now())
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
repeat(3) {
|
||||||
|
val (overdue, future) = alarmService.getAlarms()
|
||||||
|
if (overdue.isNotEmpty()) {
|
||||||
|
overdue
|
||||||
|
.sortedBy { it.time }
|
||||||
|
.also { alarms ->
|
||||||
|
alarms
|
||||||
|
.filter { it.type == TYPE_SNOOZE }
|
||||||
|
.map { it.id }
|
||||||
|
.let { alarmDao.deleteByIds(it) }
|
||||||
|
}
|
||||||
|
.map { it.toNotification() }
|
||||||
|
.let { notifier.triggerNotifications(it) }
|
||||||
|
} else {
|
||||||
|
nextAlarm = future.minOfOrNull { it.time } ?: 0
|
||||||
|
Timber.d("nextAlarm=${nextAlarm.toDateTime()}")
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
firebase.reportException(IllegalStateException("Should have returned already"))
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun scheduleNext() {
|
||||||
|
if (nextAlarm > 0) {
|
||||||
|
workManager.scheduleNotification(preferences.adjustForQuietHours(nextAlarm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,263 +0,0 @@
|
|||||||
package org.tasks.jobs
|
|
||||||
|
|
||||||
import org.junit.After
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.mockito.AdditionalAnswers
|
|
||||||
import org.mockito.ArgumentMatchers
|
|
||||||
import org.mockito.Mockito
|
|
||||||
import org.tasks.Freeze.Companion.freezeAt
|
|
||||||
import org.tasks.data.Alarm.Companion.TYPE_DATE_TIME
|
|
||||||
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
|
|
||||||
import org.tasks.preferences.Preferences
|
|
||||||
import org.tasks.time.DateTime
|
|
||||||
import org.tasks.time.DateTimeUtils
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class NotificationQueueTest {
|
|
||||||
private lateinit var queue: NotificationQueue
|
|
||||||
private lateinit var workManager: WorkManager
|
|
||||||
private lateinit var preferences: Preferences
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun before() {
|
|
||||||
preferences = Mockito.mock(Preferences::class.java)
|
|
||||||
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong()))
|
|
||||||
.then(AdditionalAnswers.returnsFirstArg<Any>())
|
|
||||||
workManager = Mockito.mock(WorkManager::class.java)
|
|
||||||
queue = NotificationQueue(preferences, workManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
fun after() {
|
|
||||||
Mockito.verifyNoMoreInteractions(workManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun removeAlarmDoesntAffectOtherAlarm() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
queue.add(AlarmEntry(2, 2, now, TYPE_DATE_TIME))
|
|
||||||
queue.remove(listOf(AlarmEntry(1, 1, now, TYPE_DATE_TIME)))
|
|
||||||
freezeAt(now) {
|
|
||||||
assertEquals(
|
|
||||||
listOf(AlarmEntry(2, 2, now, TYPE_DATE_TIME)),
|
|
||||||
queue.overdueJobs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun removeByTaskDoesntAffectOtherAlarm() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
queue.add(AlarmEntry(2, 2, now, TYPE_DATE_TIME))
|
|
||||||
queue.cancelForTask(1)
|
|
||||||
freezeAt(now) {
|
|
||||||
assertEquals(
|
|
||||||
listOf(AlarmEntry(2, 2, now, TYPE_DATE_TIME)),
|
|
||||||
queue.overdueJobs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun rescheduleForFirstJob() {
|
|
||||||
queue.add(AlarmEntry(1, 2, 3, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun dontRescheduleForLaterJobs() {
|
|
||||||
queue.add(AlarmEntry(1, 2, 3, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(2, 3, 4, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(3)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun rescheduleForNewerJob() {
|
|
||||||
queue.add(AlarmEntry(1, 1, 2, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(1, 1, 1, TYPE_DATE_TIME))
|
|
||||||
val order = Mockito.inOrder(workManager)
|
|
||||||
order.verify(workManager).scheduleNotification(2)
|
|
||||||
order.verify(workManager).scheduleNotification(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun rescheduleWhenCancelingOnlyJob() {
|
|
||||||
queue.add(AlarmEntry(1, 1, 2, TYPE_DATE_TIME))
|
|
||||||
queue.cancelForTask(1)
|
|
||||||
val order = Mockito.inOrder(workManager)
|
|
||||||
order.verify(workManager).scheduleNotification(2)
|
|
||||||
order.verify(workManager).cancelNotifications()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun rescheduleWhenCancelingFirstJob() {
|
|
||||||
queue.add(AlarmEntry(1, 1, 1, 0))
|
|
||||||
queue.add(AlarmEntry(2, 2, 2, 0))
|
|
||||||
queue.cancelForTask(1)
|
|
||||||
val order = Mockito.inOrder(workManager)
|
|
||||||
order.verify(workManager).scheduleNotification(1)
|
|
||||||
order.verify(workManager).scheduleNotification(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun dontRescheduleWhenCancelingLaterJob() {
|
|
||||||
queue.add(AlarmEntry(1, 1, 1, 0))
|
|
||||||
queue.add(AlarmEntry(2, 2, 2, 0))
|
|
||||||
queue.cancelForTask(2)
|
|
||||||
Mockito.verify(workManager).scheduleNotification(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun nextScheduledTimeIsZeroWhenQueueIsEmpty() {
|
|
||||||
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong()))
|
|
||||||
.thenReturn(1234L)
|
|
||||||
assertEquals(0, queue.nextScheduledTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun adjustNextScheduledTimeForQuietHours() {
|
|
||||||
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong()))
|
|
||||||
.thenReturn(1234L)
|
|
||||||
queue.add(AlarmEntry(1, 1, 1, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(1234)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun overdueJobsAreReturned() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(2, 1, now + ONE_MINUTE, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
freezeAt(now) {
|
|
||||||
assertEquals(
|
|
||||||
listOf(AlarmEntry(1, 1, now, TYPE_DATE_TIME)), queue.overdueJobs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun twoOverdueJobsAtSameTimeReturned() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(2, 2, now, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
freezeAt(now) {
|
|
||||||
assertEquals(
|
|
||||||
setOf(
|
|
||||||
AlarmEntry(1, 1, now, TYPE_DATE_TIME),
|
|
||||||
AlarmEntry(2, 2, now, TYPE_DATE_TIME)
|
|
||||||
),
|
|
||||||
queue.overdueJobs.toSet()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun twoOverdueJobsAtDifferentTimes() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
freezeAt(now + 2 * ONE_MINUTE) {
|
|
||||||
assertEquals(
|
|
||||||
listOf(
|
|
||||||
AlarmEntry(1, 1, now, TYPE_DATE_TIME),
|
|
||||||
AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME)
|
|
||||||
),
|
|
||||||
queue.overdueJobs
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun overdueJobsAreRemoved() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
freezeAt(now) {
|
|
||||||
queue.remove(queue.overdueJobs)
|
|
||||||
}
|
|
||||||
assertEquals(
|
|
||||||
listOf(AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME)), queue.getJobs()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun multipleOverduePeriodsLapsed() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(2, 2, now + ONE_MINUTE, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(3, 3, now + 2 * ONE_MINUTE, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
freezeAt(now + ONE_MINUTE) {
|
|
||||||
queue.remove(queue.overdueJobs)
|
|
||||||
}
|
|
||||||
assertEquals(
|
|
||||||
listOf(AlarmEntry(3, 3, now + 2 * ONE_MINUTE, TYPE_DATE_TIME)), queue.getJobs()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun clearShouldCancelExisting() {
|
|
||||||
queue.add(AlarmEntry(1, 1, 1, 0))
|
|
||||||
queue.clear()
|
|
||||||
val order = Mockito.inOrder(workManager)
|
|
||||||
order.verify(workManager).scheduleNotification(1)
|
|
||||||
order.verify(workManager).cancelNotifications()
|
|
||||||
assertEquals(0, queue.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ignoreInvalidCancelForByAlarm() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
queue.remove(listOf(AlarmEntry(2, 2, now, TYPE_DATE_TIME)))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ignoreInvalidCancelForTask() {
|
|
||||||
val now = DateTimeUtils.currentTimeMillis()
|
|
||||||
queue.add(AlarmEntry(1, 1, now, TYPE_DATE_TIME))
|
|
||||||
queue.cancelForTask(2)
|
|
||||||
Mockito.verify(workManager).scheduleNotification(now)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun allDuringSameMinuteAreOverdue() {
|
|
||||||
val now = DateTime(2017, 9, 3, 0, 14, 6, 455)
|
|
||||||
val due = DateTime(2017, 9, 3, 0, 14, 0, 0)
|
|
||||||
val snooze = DateTime(2017, 9, 3, 0, 14, 59, 999)
|
|
||||||
queue.add(AlarmEntry(1, 1, due.millis, TYPE_DATE_TIME))
|
|
||||||
queue.add(AlarmEntry(2, 2, snooze.millis, TYPE_SNOOZE))
|
|
||||||
queue.add(AlarmEntry(3, 3, due.plusMinutes(1).millis, TYPE_DATE_TIME))
|
|
||||||
Mockito.verify(workManager).scheduleNotification(due.millis)
|
|
||||||
freezeAt(now) {
|
|
||||||
val overdueJobs = queue.overdueJobs
|
|
||||||
assertEquals(
|
|
||||||
listOf(
|
|
||||||
AlarmEntry(1, 1, due.millis, TYPE_DATE_TIME),
|
|
||||||
AlarmEntry(2, 2, snooze.millis, TYPE_SNOOZE)
|
|
||||||
),
|
|
||||||
overdueJobs
|
|
||||||
)
|
|
||||||
queue.remove(overdueJobs)
|
|
||||||
assertEquals(
|
|
||||||
listOf(AlarmEntry(3, 3, due.plusMinutes(1).millis, TYPE_DATE_TIME)),
|
|
||||||
queue.getJobs()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val ONE_MINUTE = TimeUnit.MINUTES.toMillis(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue