diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cc7f881..2c41ccd 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -198,6 +198,10 @@
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
+
+
- if (msg.encoding == Plaintext.Encoding.SIMPLE) {
- conversationService.getSubject(listOf(msg))?.let { subject ->
- msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray())
- messageRepo.save(msg)
- Thread.yield()
+ service.process(SimpleJob(
+ messageRepo.count(),
+ { messageRepo.findNextLegacyMessages(it) },
+ { msg ->
+ if (msg.encoding == Plaintext.Encoding.SIMPLE) {
+ conversationService.getSubject(listOf(msg))?.let { subject ->
+ msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray())
+ messageRepo.save(msg)
+ Thread.yield()
+ }
}
- }
- }
- if (!messages.isEmpty()) {
- previous = messages.last()
- }
- } while (!messages.isEmpty())
-
- uiThread {
- Toast.makeText(
- ctx,
- R.string.cleanup_notification_end,
- Toast.LENGTH_LONG
- ).show()
- conversationInit.isEnabled = true
+ },
+ R.drawable.ic_notification_batch,
+ R.string.emulate_conversations_batch
+ ))
}
}
- return@OnPreferenceClickListener true
+
+ override fun onServiceDisconnected(name: ComponentName) {
+ }
}
- private fun emulateConversationChangeListener(conversationInit: Preference?) = Preference.OnPreferenceChangeListener { preference, newValue ->
+ private fun conversationInitClickListener() = Preference.OnPreferenceClickListener {
+ val ctx = activity?.applicationContext
+ ?: throw IllegalStateException("Context not available")
+ ctx.bindService(Intent(ctx, BatchProcessorService::class.java), connection, Context.BIND_AUTO_CREATE)
+ true
+ }
+
+ private fun emulateConversationChangeListener(conversationInit: Preference?) = Preference.OnPreferenceChangeListener { _, newValue ->
conversationInit?.isEnabled = newValue as Boolean
true
}
diff --git a/app/src/main/java/ch/dissem/apps/abit/notification/BatchNotification.kt b/app/src/main/java/ch/dissem/apps/abit/notification/BatchNotification.kt
new file mode 100644
index 0000000..aadd9af
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/notification/BatchNotification.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2016 Christian Basler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.dissem.apps.abit.notification
+
+import android.content.Context
+import android.support.v4.app.NotificationCompat
+import ch.dissem.apps.abit.R
+import ch.dissem.apps.abit.service.Job
+
+/**
+ * Ongoing notification while proof of work is in progress.
+ */
+class BatchNotification(ctx: Context) : AbstractNotification(ctx) {
+
+ private val builder = NotificationCompat.Builder(ctx, ONGOING_CHANNEL_ID)
+ .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ .setUsesChronometer(true)
+
+ init {
+ initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent)
+ notification = builder.build()
+ }
+
+ override val notificationId = ONGOING_NOTIFICATION_ID
+
+ fun update(job: Job): BatchNotification {
+
+ builder.setContentTitle(ctx.getString(job.description))
+ .setSmallIcon(job.icon)
+ .setProgress(job.numberOfItems, job.numberOfProcessedItems, job.numberOfItems <= 0)
+
+ notification = builder.build()
+ show()
+ return this
+ }
+
+ companion object {
+ const val ONGOING_NOTIFICATION_ID = 4
+ }
+}
diff --git a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt
index 174f9d1..4fd1963 100644
--- a/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt
+++ b/app/src/main/java/ch/dissem/apps/abit/repository/AndroidMessageRepository.kt
@@ -47,6 +47,13 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
super.findMessages(label, offset, limit)
}
+ fun count() = DatabaseUtils.queryNumEntries(
+ sql.readableDatabase,
+ TABLE_NAME,
+ null,
+ null
+ ).toInt()
+
override fun countUnread(label: Label?) = when {
label === LABEL_ARCHIVE -> 0
label == null -> DatabaseUtils.queryNumEntries(
diff --git a/app/src/main/java/ch/dissem/apps/abit/service/BatchProcessorService.kt b/app/src/main/java/ch/dissem/apps/abit/service/BatchProcessorService.kt
new file mode 100644
index 0000000..bf87598
--- /dev/null
+++ b/app/src/main/java/ch/dissem/apps/abit/service/BatchProcessorService.kt
@@ -0,0 +1,117 @@
+package ch.dissem.apps.abit.service
+
+import android.app.Service
+import android.content.Intent
+import android.os.Binder
+import android.support.annotation.DrawableRes
+import android.support.annotation.StringRes
+import android.support.v4.content.ContextCompat
+import ch.dissem.apps.abit.notification.BatchNotification
+import ch.dissem.apps.abit.notification.BatchNotification.Companion.ONGOING_NOTIFICATION_ID
+import org.jetbrains.anko.doAsync
+import java.util.*
+
+class BatchProcessorService : Service() {
+ private lateinit var notification: BatchNotification
+
+ override fun onCreate() {
+ notification = BatchNotification(this)
+ }
+
+ override fun onBind(intent: Intent) = BatchBinder(this)
+
+ class BatchBinder internal constructor(val service: BatchProcessorService) : Binder() {
+ private val notification = service.notification
+
+ fun process(job: Job) = synchronized(queue) {
+ ContextCompat.startForegroundService(
+ service,
+ Intent(service, BatchProcessorService::class.java)
+ )
+ service.startForeground(
+ ONGOING_NOTIFICATION_ID,
+ notification.notification
+ )
+ if (!working) {
+ working = true
+ service.processQueue(job)
+ } else {
+ queue.add(job)
+ }
+ }
+
+ }
+
+ private fun processQueue(job: Job) {
+ doAsync {
+ var next: Job? = job
+ while (next != null) {
+ next.process(notification)
+
+ synchronized(queue) {
+ next = queue.poll()
+ if (next == null) {
+ working = false
+ stopForeground(true)
+ stopSelf()
+ }
+ }
+ }
+
+ }
+ }
+
+ companion object {
+ private var working = false
+ private val queue = LinkedList()
+ }
+}
+
+interface Job {
+ val icon: Int
+ @DrawableRes get
+
+ val description: Int
+ @StringRes get
+
+ val numberOfItems: Int
+ var numberOfProcessedItems: Int
+
+ /**
+ * Runs the job. This shouldn't happen in a separate thread, as this is handled by the service.
+ */
+ fun process(notification: BatchNotification)
+}
+
+data class SimpleJob(
+ override val numberOfItems: Int,
+ /**
+ * Provides the next batch of items, given the last item of the previous batch,
+ * or null for the first batch.
+ */
+ private val provider: (T?) -> List,
+ /**
+ * Processes an item.
+ */
+ private val processor: (T) -> Unit,
+ override val icon: Int,
+ override val description: Int
+) : Job {
+ override var numberOfProcessedItems: Int = 0
+
+ override fun process(notification: BatchNotification) {
+ notification.update(this)
+ var batch = provider.invoke(null)
+ while (batch.isNotEmpty()) {
+ Thread.yield()
+ batch.forEach {
+ processor.invoke(it)
+ Thread.yield()
+ }
+ numberOfProcessedItems += batch.size
+ notification.update(this)
+ batch = provider.invoke(batch.last())
+ }
+ }
+
+}
diff --git a/app/src/main/res/drawable/ic_notification_batch.xml b/app/src/main/res/drawable/ic_notification_batch.xml
new file mode 100644
index 0000000..fc8b546
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notification_batch.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 630db20..d57db8f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -140,4 +140,5 @@ As an alternative you could configure a trusted node in the settings, but as of
Guess conversations
Use subject to determine which messages belong together. The order will likely be wrong.
Group existing messages by subject
+ Grouping existing messages by subject