From e1b10af05a49f5fabc6029ccf1c406a7aab1e98c Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Tue, 10 Apr 2018 17:40:53 +0200 Subject: [PATCH] Correcting a concurrent modification crash --- app/build.gradle.kts | 1 + .../quasseldroid/ui/chat/ChatActivity.kt | 8 +- .../chat/buffers/BufferViewConfigFragment.kt | 2 +- .../ui/chat/input/ChatlineFragment.kt | 10 +-- .../ui/chat/messages/MessageListFragment.kt | 2 +- .../kuschku/quasseldroid/util/AvatarHelper.kt | 18 ---- .../compatibility/AndroidHandlerService.kt | 83 +++++++++++++++++++ 7 files changed, 93 insertions(+), 31 deletions(-) delete mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a53f267fc..0a7df83a4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -126,6 +126,7 @@ dependencies { } // Utility + implementation("io.reactivex.rxjava2", "rxandroid", "2.0.2") implementation("io.reactivex.rxjava2", "rxjava", "2.1.9") implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb") implementation("org.jetbrains", "annotations", "16.0.1") diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt index 5928f4594..07a553b5c 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt @@ -106,7 +106,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc setSupportActionBar(toolbar) - viewModel.buffer_liveData.observe(this, Observer { + viewModel.buffer.toLiveData().observe(this, Observer { if (it != null && drawerLayout.isDrawerOpen(Gravity.START)) { drawerLayout.closeDrawer(Gravity.START, true) } @@ -151,8 +151,8 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } } - viewModel.errors.observe(this, Observer { error -> - error?.let { + viewModel.errors.toLiveData().observe(this, Observer { error -> + error?.orNull()?.let { when (it) { is HandshakeMessage.ClientInitReject -> MaterialDialog.Builder(this) @@ -232,7 +232,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc } }) - viewModel.connectionProgress_liveData.observe(this, Observer { + viewModel.connectionProgress.toLiveData().observe(this, Observer { val (state, progress, max) = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0) when (state) { ConnectionState.CONNECTED -> { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt index cc52089d3..63fbb4182 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt @@ -230,7 +230,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() { ) chatList.adapter = listAdapter - viewModel.selectedBuffer_liveData.observe(this, Observer { buffer -> + viewModel.selectedBuffer.toLiveData().observe(this, Observer { buffer -> if (buffer != null) { val menu = actionMode?.menu if (menu != null) { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt index 98c1dbe69..255e89229 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/ChatlineFragment.kt @@ -19,10 +19,7 @@ import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.AppearanceSettings import de.kuschku.quasseldroid.settings.AutoCompleteSettings import de.kuschku.quasseldroid.settings.MessageSettings -import de.kuschku.quasseldroid.util.helper.invoke -import de.kuschku.quasseldroid.util.helper.lineSequence -import de.kuschku.quasseldroid.util.helper.retint -import de.kuschku.quasseldroid.util.helper.visibleIf +import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.irc.format.IrcFormatSerializer import de.kuschku.quasseldroid.util.service.ServiceBoundFragment @@ -126,9 +123,8 @@ class ChatlineFragment : ServiceBoundFragment() { historyPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED } messageHistory.adapter = messageHistoryAdapter - viewModel.recentlySentMessages_liveData.observe( - this, Observer(messageHistoryAdapter::submitList) - ) + viewModel.recentlySentMessages.toLiveData() + .observe(this, Observer(messageHistoryAdapter::submitList)) fun send() { if (chatline.text.isNotBlank()) { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt index 28dc3c523..68bafbb81 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt @@ -274,7 +274,7 @@ class MessageListFragment : ServiceBoundFragment() { } } - val lastMessageId = viewModel.buffer_liveData.switchMapNotNull { + val lastMessageId = viewModel.buffer.toLiveData().switchMapNotNull { database.message().lastMsgId(it) } diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt deleted file mode 100644 index 60a5af730..000000000 --- a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt +++ /dev/null @@ -1,18 +0,0 @@ -package de.kuschku.quasseldroid.util - -import de.kuschku.libquassel.quassel.syncables.IrcUser -import de.kuschku.libquassel.util.irc.HostmaskHelper -import de.kuschku.quasseldroid.persistence.QuasselDatabase - -object AvatarHelper { - fun avatar(message: QuasselDatabase.DatabaseMessage? = null, user: IrcUser? = null): String? { - val ident = message?.sender?.let(HostmaskHelper::user) - ?: user?.user() - ?: return null - - val userId = Regex("[us]id(\\d+)").matchEntire(ident)?.groupValues?.lastOrNull() - ?: return null - - return "https://static.irccloud-cdn.com/avatar-redirect/$userId" - } -} diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/compatibility/AndroidHandlerService.kt b/app/src/main/java/de/kuschku/quasseldroid/util/compatibility/AndroidHandlerService.kt index b61c3b793..3da80f703 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/util/compatibility/AndroidHandlerService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/util/compatibility/AndroidHandlerService.kt @@ -2,10 +2,18 @@ package de.kuschku.quasseldroid.util.compatibility import android.os.Handler import android.os.HandlerThread +import android.os.Message import android.os.Process import de.kuschku.libquassel.util.compatibility.HandlerService +import io.reactivex.Scheduler +import io.reactivex.disposables.Disposable +import io.reactivex.disposables.Disposables +import io.reactivex.plugins.RxJavaPlugins +import java.util.concurrent.TimeUnit class AndroidHandlerService : HandlerService { + override lateinit var scheduler: Scheduler + override fun serialize(f: () -> Unit) { serializeHandler.post(f) } @@ -56,6 +64,8 @@ class AndroidHandlerService : HandlerService { deserializeHandler = Handler(deserializeThread.looper) writeHandler = Handler(writeThread.looper) backendHandler = Handler(backendThread.looper) + + scheduler = HandlerScheduler(backendHandler) } override var exceptionHandler: Thread.UncaughtExceptionHandler? = null @@ -67,4 +77,77 @@ class AndroidHandlerService : HandlerService { writeThread.quit() backendThread.quit() } + + internal class HandlerScheduler(private val handler: Handler) : Scheduler() { + override fun scheduleDirect(run: Runnable, delay: Long, unit: TimeUnit): Disposable { + val scheduled = ScheduledRunnable(handler, RxJavaPlugins.onSchedule(run)) + handler.postDelayed(scheduled, unit.toMillis(delay)) + return scheduled + } + + override fun createWorker(): Scheduler.Worker { + return HandlerWorker(handler) + } + + private class HandlerWorker constructor(private val handler: Handler) : Scheduler.Worker() { + @Volatile + private var disposed: Boolean = false + + override fun schedule(run: Runnable, delay: Long, unit: TimeUnit): Disposable { + if (disposed) { + return Disposables.disposed() + } + + val scheduled = ScheduledRunnable(handler, RxJavaPlugins.onSchedule(run)) + + val message = Message.obtain(handler, scheduled) + message.obj = this // Used as token for batch disposal of this worker's runnables. + + handler.sendMessageDelayed(message, unit.toMillis(delay)) + + // Re-check disposed state for removing in case we were racing a call to dispose(). + if (disposed) { + handler.removeCallbacks(scheduled) + return Disposables.disposed() + } + + return scheduled + } + + override fun dispose() { + disposed = true + handler.removeCallbacksAndMessages(this /* token */) + } + + override fun isDisposed(): Boolean { + return disposed + } + } + + private class ScheduledRunnable internal constructor(private val handler: Handler, + private val delegate: Runnable) : Runnable, + Disposable { + + @Volatile + private var disposed: Boolean = false + + override fun run() { + try { + delegate.run() + } catch (t: Throwable) { + RxJavaPlugins.onError(t) + } + + } + + override fun dispose() { + disposed = true + handler.removeCallbacks(this) + } + + override fun isDisposed(): Boolean { + return disposed + } + } + } } -- GitLab