Convert NotificationQueue to Kotlin

pull/996/head
Alex Baker 4 years ago
parent e63f8721e7
commit e482a881f9

@ -83,7 +83,8 @@ install:
script:
- ./gradlew :app:lintGoogleplayRelease
- ./gradlew -Pcoverage :app:jacocoTestGoogleplayDebugUnitTestReport :app:createGoogleplayDebugAndroidTestCoverageReport
- ./gradlew :app:jacocoTestReport
- ./gradlew -Pcoverage :app:createGoogleplayDebugAndroidTestCoverageReport
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock

@ -160,6 +160,7 @@ dependencies {
debugImplementation("com.squareup.leakcanary:leakcanary-android:${Versions.leakcanary}")
implementation("org.jetbrains.kotlin:kotlin-stdlib:${Versions.kotlin}")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.2")
implementation("com.squareup.okhttp3:okhttp:${Versions.okhttp}")
implementation("com.google.code.gson:gson:2.8.6")
implementation("com.google.android.material:material:1.1.0")

@ -35,10 +35,9 @@ import javax.inject.Inject
@RunWith(AndroidJUnit4::class)
class ReminderServiceTest : InjectingTestCase() {
@Inject
lateinit var preferences: Preferences
@Inject
lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences
@Inject lateinit var taskDao: TaskDao
private lateinit var service: ReminderService
private lateinit var random: Random
private lateinit var jobs: NotificationQueue

@ -1,117 +0,0 @@
package org.tasks.jobs;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Lists.newArrayList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.common.collect.TreeMultimap;
import com.google.common.primitives.Ints;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import org.tasks.injection.ApplicationScope;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
@ApplicationScope
public class NotificationQueue {
private final TreeMultimap<Long, NotificationQueueEntry> jobs =
TreeMultimap.create(Ordering.natural(), (l, r) -> Ints.compare(l.hashCode(), r.hashCode()));
private final Preferences preferences;
private final WorkManager workManager;
@Inject
public NotificationQueue(Preferences preferences, WorkManager workManager) {
this.preferences = preferences;
this.workManager = workManager;
}
public synchronized <T extends NotificationQueueEntry> void add(T entry) {
add(Collections.singletonList(entry));
}
public synchronized <T extends NotificationQueueEntry> void add(Iterable<T> entries) {
long originalFirstTime = firstTime();
for (T entry : filter(entries, notNull())) {
jobs.put(entry.getTime(), entry);
}
if (originalFirstTime != firstTime()) {
scheduleNext(true);
}
}
public synchronized void clear() {
jobs.clear();
workManager.cancelNotifications();
}
public synchronized void cancelAlarm(long alarmId) {
cancel(AlarmEntry.class, alarmId);
}
public synchronized void cancelReminder(long taskId) {
cancel(ReminderEntry.class, taskId);
}
private void cancel(Class<? extends NotificationQueueEntry> c, long id) {
long firstTime = firstTime();
remove(newArrayList(filter(jobs.values(), r -> r.getClass().equals(c) && r.getId() == id)));
if (firstTime != firstTime()) {
scheduleNext(true);
}
}
synchronized List<? extends NotificationQueueEntry> getOverdueJobs() {
List<NotificationQueueEntry> result = new ArrayList<>();
long cutoff = new DateTime().startOfMinute().plusMinutes(1).getMillis();
for (Long key : jobs.keySet().headSet(cutoff)) {
result.addAll(jobs.get(key));
}
return result;
}
synchronized void scheduleNext() {
scheduleNext(false);
}
private void scheduleNext(boolean cancelCurrent) {
if (jobs.isEmpty()) {
if (cancelCurrent) {
workManager.cancelNotifications();
}
} else {
workManager.scheduleNotification(nextScheduledTime());
}
}
private long firstTime() {
return jobs.isEmpty() ? 0 : jobs.asMap().firstKey();
}
long nextScheduledTime() {
long next = firstTime();
return next > 0 ? preferences.adjustForQuietHours(next) : 0;
}
int size() {
return jobs.size();
}
List<NotificationQueueEntry> getJobs() {
return ImmutableList.copyOf(jobs.values());
}
public synchronized boolean remove(List<? extends NotificationQueueEntry> entries) {
boolean success = true;
for (NotificationQueueEntry entry : entries) {
success &= !jobs.containsEntry(entry.getTime(), entry) || jobs.remove(entry.getTime(), entry);
}
return success;
}
}

@ -0,0 +1,91 @@
package org.tasks.jobs
import com.google.common.collect.Ordering
import com.google.common.collect.TreeMultimap
import com.google.common.primitives.Ints
import kotlinx.collections.immutable.toImmutableList
import org.tasks.injection.ApplicationScope
import org.tasks.preferences.Preferences
import org.tasks.time.DateTime
import java.util.*
import javax.inject.Inject
@ApplicationScope
class NotificationQueue @Inject constructor(private val preferences: Preferences, private val workManager: WorkManager) {
private val jobs = TreeMultimap.create(Ordering.natural<Long>(), Comparator { l: NotificationQueueEntry, r: NotificationQueueEntry -> Ints.compare(l.hashCode(), r.hashCode()) })
@Synchronized
fun <T : NotificationQueueEntry> add(entry: T) = add(listOf(entry))
@Synchronized
fun <T : NotificationQueueEntry> add(entries: Iterable<T?>) {
val originalFirstTime = firstTime()
entries.filterNotNull().forEach { jobs.put(it.time, it) }
if (originalFirstTime != firstTime()) {
scheduleNext(true)
}
}
@Synchronized
fun clear() {
jobs.clear()
workManager.cancelNotifications()
}
@Synchronized
fun cancelAlarm(alarmId: Long) = cancel(AlarmEntry::class.java, alarmId)
@Synchronized
fun cancelReminder(taskId: Long) = cancel(ReminderEntry::class.java, taskId)
private fun cancel(c: Class<out NotificationQueueEntry>, id: Long) {
val firstTime = firstTime()
jobs.values()
.filter { it.javaClass == c && it.id == id }
.forEach { remove(listOf(it)) }
if (firstTime != firstTime()) {
scheduleNext(true)
}
}
@get:Synchronized
val overdueJobs: List<NotificationQueueEntry>
get() {
return 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().toImmutableList()
@Synchronized
fun remove(entries: List<NotificationQueueEntry>): Boolean {
var success = true
for (entry in entries) {
success = success and (!jobs.containsEntry(entry.time, entry) || jobs.remove(entry.time, entry))
}
return success
}
}

@ -6,7 +6,7 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.AdditionalAnswers
import org.mockito.Matchers
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
import org.tasks.Freeze.Companion.freezeAt
import org.tasks.preferences.Preferences
@ -22,7 +22,7 @@ class NotificationQueueTest {
@Before
fun before() {
preferences = Mockito.mock(Preferences::class.java)
Mockito.`when`(preferences.adjustForQuietHours(Matchers.anyLong())).then(AdditionalAnswers.returnsFirstArg<Any>())
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong())).then(AdditionalAnswers.returnsFirstArg<Any>())
workManager = Mockito.mock(WorkManager::class.java)
queue = NotificationQueue(preferences, workManager)
}
@ -142,13 +142,13 @@ class NotificationQueueTest {
@Test
fun nextScheduledTimeIsZeroWhenQueueIsEmpty() {
Mockito.`when`(preferences.adjustForQuietHours(Matchers.anyLong())).thenReturn(1234L)
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong())).thenReturn(1234L)
assertEquals(0, queue.nextScheduledTime())
}
@Test
fun adjustNextScheduledTimeForQuietHours() {
Mockito.`when`(preferences.adjustForQuietHours(Matchers.anyLong())).thenReturn(1234L)
Mockito.`when`(preferences.adjustForQuietHours(ArgumentMatchers.anyLong())).thenReturn(1234L)
queue.add(ReminderEntry(1, 1, 1))
Mockito.verify(workManager).scheduleNotification(1234)
}
@ -203,7 +203,7 @@ class NotificationQueueTest {
freezeAt(now) {
queue.remove(queue.overdueJobs)
}
assertEquals(listOf(ReminderEntry(2, now + ONE_MINUTE, ReminderService.TYPE_DUE)), queue.jobs)
assertEquals(listOf(ReminderEntry(2, now + ONE_MINUTE, ReminderService.TYPE_DUE)), queue.getJobs())
}
@Test
@ -217,7 +217,7 @@ class NotificationQueueTest {
queue.remove(queue.overdueJobs)
}
assertEquals(
listOf(ReminderEntry(3, now + 2 * ONE_MINUTE, ReminderService.TYPE_DUE)), queue.jobs)
listOf(ReminderEntry(3, now + 2 * ONE_MINUTE, ReminderService.TYPE_DUE)), queue.getJobs())
}
@Test
@ -257,7 +257,7 @@ class NotificationQueueTest {
queue.remove(overdueJobs)
assertEquals(
listOf(ReminderEntry(3, due.plusMinutes(1).millis, ReminderService.TYPE_DUE)),
queue.jobs)
queue.getJobs())
}
}

Loading…
Cancel
Save