From f3249dca20affb8497dfa1be7beb44885ec06ce9 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Thu, 22 Mar 2018 18:40:27 +0100
Subject: [PATCH] Cleanup, reorganization

---
 .../quasseldroid/service/QuasselService.kt    |  31 ++--
 .../quasseldroid/ui/chat/ChatActivity.kt      | 168 +++++++++---------
 .../chat/buffers/BufferViewConfigFragment.kt  |  13 +-
 .../ui/chat/messages/MessageAdapter.kt        |  11 +-
 .../ui/chat/messages/MessageListFragment.kt   | 102 +++++------
 .../ui/chat/nicks/NickListFragment.kt         |   9 -
 .../compatibility/AndroidHandlerService.kt    |  38 ++--
 .../util/service/ServiceBoundActivity.kt      |  38 +++-
 .../util/service/ServiceBoundFragment.kt      |  13 ++
 app/src/main/res/values-de/strings.xml        |   6 -
 app/src/main/res/values-de/strings_error.xml  |   6 +
 app/src/main/res/values-de/strings_status.xml |   9 +
 app/src/main/res/values/strings.xml           |   6 -
 app/src/main/res/values/strings_error.xml     |   6 +
 app/src/main/res/values/strings_status.xml    |   9 +
 15 files changed, 252 insertions(+), 213 deletions(-)
 create mode 100644 app/src/main/res/values-de/strings_error.xml
 create mode 100644 app/src/main/res/values-de/strings_status.xml
 create mode 100644 app/src/main/res/values/strings_error.xml
 create mode 100644 app/src/main/res/values/strings_status.xml

diff --git a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
index 4c823409e..0ab29d260 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/service/QuasselService.kt
@@ -16,7 +16,6 @@ import de.kuschku.quasseldroid.persistence.QuasselBacklogStorage
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.ConnectionSettings
 import de.kuschku.quasseldroid.settings.Settings
-import de.kuschku.quasseldroid.util.AndroidHandlerThread
 import de.kuschku.quasseldroid.util.QuasseldroidNotificationManager
 import de.kuschku.quasseldroid.util.compatibility.AndroidHandlerService
 import de.kuschku.quasseldroid.util.helper.editApply
@@ -98,7 +97,7 @@ class QuasselService : LifecycleService(),
     when (state) {
       ConnectionState.DISCONNECTED -> {
         handle.builder.setContentTitle(getString(R.string.label_status_disconnected))
-        handle.builder.setProgress(0, 0, false)
+        handle.builder.setProgress(0, 0, true)
       }
       ConnectionState.CONNECTING   -> {
         handle.builder.setContentTitle(getString(R.string.label_status_connecting))
@@ -117,11 +116,15 @@ class QuasselService : LifecycleService(),
         handle.builder.setContentTitle(getString(R.string.label_status_connected))
         handle.builder.setProgress(0, 0, false)
       }
+      ConnectionState.CLOSED       -> {
+        handle.builder.setContentTitle(getString(R.string.label_status_closed))
+        handle.builder.setProgress(0, 0, false)
+      }
     }
   }
 
   private fun updateConnection(accountId: Long, reconnect: Boolean) {
-    handler.post {
+    handlerService.backend {
       val account = if (accountId != -1L && reconnect) {
         AccountDatabase.Creator.init(this).accounts().findById(accountId)
       } else {
@@ -172,8 +175,7 @@ class QuasselService : LifecycleService(),
     override fun connect(address: SocketAddress, user: String, pass: String, reconnect: Boolean) {
       disconnect()
       sessionManager.connect(
-        clientData, trustManager, address, ::AndroidHandlerService,
-        user to pass, reconnect
+        clientData, trustManager, address, user to pass, reconnect
       )
     }
 
@@ -186,30 +188,30 @@ class QuasselService : LifecycleService(),
     }
   }
 
-  private val handler = AndroidHandlerThread("Backend")
+  private val handlerService = AndroidHandlerService()
 
   private val asyncBackend = object : Backend {
     override fun connectUnlessConnected(address: SocketAddress, user: String, pass: String,
                                         reconnect: Boolean) {
-      handler.post {
+      handlerService.backend {
         backendImplementation.connectUnlessConnected(address, user, pass, reconnect)
       }
     }
 
     override fun connect(address: SocketAddress, user: String, pass: String, reconnect: Boolean) {
-      handler.post {
+      handlerService.backend {
         backendImplementation.connect(address, user, pass, reconnect)
       }
     }
 
     override fun reconnect() {
-      handler.post {
+      handlerService.backend {
         backendImplementation.reconnect()
       }
     }
 
     override fun disconnect(forever: Boolean) {
-      handler.post {
+      handlerService.backend {
         backendImplementation.disconnect(forever)
         if (forever) {
           stopSelf()
@@ -232,10 +234,9 @@ class QuasselService : LifecycleService(),
   private val connectivity = BehaviorSubject.createDefault(Unit)
 
   override fun onCreate() {
-    handler.onCreate()
     super.onCreate()
     database = QuasselDatabase.Creator.init(application)
-    sessionManager = SessionManager(ISession.NULL, QuasselBacklogStorage(database))
+    sessionManager = SessionManager(ISession.NULL, QuasselBacklogStorage(database), handlerService)
     clientData = ClientData(
       identifier = "${resources.getString(R.string.app_name)} ${BuildConfig.VERSION_NAME}",
       buildDate = Instant.ofEpochSecond(BuildConfig.GIT_COMMIT_DATE),
@@ -249,7 +250,7 @@ class QuasselService : LifecycleService(),
 
     sessionManager.connectionProgress.toLiveData().observe(this, Observer {
       if (this.progress.first != it?.first && it?.first == ConnectionState.CONNECTED) {
-        handler.post {
+        handlerService.backend {
           database.message().clearMessages()
         }
       }
@@ -262,7 +263,7 @@ class QuasselService : LifecycleService(),
     })
 
     Observable.combineLatest(
-      sessionManager.state.filter { it == ConnectionState.DISCONNECTED },
+      sessionManager.state.filter { it == ConnectionState.DISCONNECTED || it == ConnectionState.CLOSED },
       connectivity,
       BiFunction { a: ConnectionState, b: Unit -> a to b })
       .delay(200, TimeUnit.MILLISECONDS)
@@ -299,8 +300,6 @@ class QuasselService : LifecycleService(),
     unregisterReceiver(receiver)
 
     notificationHandle?.let { notificationManager.remove(it) }
-
-    handler.onDestroy()
     super.onDestroy()
   }
 
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 f0056f480..169011eae 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
@@ -15,6 +15,7 @@ import android.support.v7.widget.DefaultItemAnimator
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.RecyclerView
 import android.support.v7.widget.Toolbar
+import android.text.Html
 import android.view.Gravity
 import android.view.Menu
 import android.view.MenuItem
@@ -25,6 +26,7 @@ import com.afollestad.materialdialogs.MaterialDialog
 import com.sothree.slidinguppanel.SlidingUpPanelLayout
 import de.kuschku.libquassel.protocol.Message
 import de.kuschku.libquassel.protocol.Message_Type
+import de.kuschku.libquassel.protocol.message.HandshakeMessage
 import de.kuschku.libquassel.quassel.syncables.interfaces.IAliasManager
 import de.kuschku.libquassel.session.ConnectionState
 import de.kuschku.libquassel.util.and
@@ -37,11 +39,7 @@ import de.kuschku.quasseldroid.settings.Settings
 import de.kuschku.quasseldroid.ui.chat.input.Editor
 import de.kuschku.quasseldroid.ui.chat.input.MessageHistoryAdapter
 import de.kuschku.quasseldroid.ui.settings.SettingsActivity
-import de.kuschku.quasseldroid.ui.setup.accounts.AccountSelectionActivity
-import de.kuschku.quasseldroid.util.AndroidHandlerThread
-import de.kuschku.quasseldroid.util.helper.editApply
 import de.kuschku.quasseldroid.util.helper.invoke
-import de.kuschku.quasseldroid.util.helper.sharedPreferences
 import de.kuschku.quasseldroid.util.service.ServiceBoundActivity
 import de.kuschku.quasseldroid.util.ui.MaterialContentLoadingProgressBar
 import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
@@ -68,8 +66,6 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
 
   private lateinit var drawerToggle: ActionBarDrawerToggle
 
-  private val handler = AndroidHandlerThread("Chat")
-
   private lateinit var viewModel: QuasselViewModel
 
   private lateinit var database: QuasselDatabase
@@ -101,7 +97,6 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
   }
 
   override fun onCreate(savedInstanceState: Bundle?) {
-    handler.onCreate()
     super.onCreate(savedInstanceState)
     setContentView(R.layout.activity_main)
     ButterKnife.bind(this)
@@ -124,7 +119,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       findViewById(R.id.formatting_toolbar),
       { lines ->
         viewModel.session { session ->
-          viewModel.getBuffer().value?.let { bufferId ->
+          viewModel.buffer { bufferId ->
             session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
               val output = mutableListOf<IAliasManager.Command>()
               for ((stripped, formatted) in lines) {
@@ -159,8 +154,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
 
     setSupportActionBar(toolbar)
 
-    viewModel.getBuffer().observe(
-      this, Observer {
+    viewModel.buffer.observe(this, Observer {
       if (it != null && drawerLayout.isDrawerOpen(Gravity.START)) {
         drawerLayout.closeDrawer(Gravity.START, true)
       }
@@ -178,19 +172,47 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       drawerToggle.syncState()
     }
 
+    viewModel.errors.observe(this, Observer {
+      when (it) {
+        is HandshakeMessage.ClientInitReject  ->
+          MaterialDialog.Builder(this)
+            .title(R.string.label_error_init)
+            .content(Html.fromHtml(it.errorString))
+            .neutralText(R.string.label_close)
+            .build()
+            .show()
+        is HandshakeMessage.CoreSetupReject   ->
+          MaterialDialog.Builder(this)
+            .title(R.string.label_error_setup)
+            .content(Html.fromHtml(it.errorString))
+            .neutralText(R.string.label_close)
+            .build()
+            .show()
+        is HandshakeMessage.ClientLoginReject ->
+          MaterialDialog.Builder(this)
+            .title(R.string.label_error_login)
+            .content(Html.fromHtml(it.errorString))
+            .neutralText(R.string.label_close)
+            .build()
+            .show()
+      }
+    })
+
     viewModel.connectionProgress.observe(this, Observer { it ->
       val (state, progress, max) = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0)
       when (state) {
-        ConnectionState.CONNECTED, ConnectionState.DISCONNECTED -> {
+        ConnectionState.CONNECTED,
+        ConnectionState.DISCONNECTED,
+        ConnectionState.CLOSED -> {
           progressBar.hide()
         }
-        ConnectionState.INIT                                    -> {
+        ConnectionState.INIT   -> {
           // Show indeterminate when no progress has been made yet
           progressBar.isIndeterminate = progress == 0 || max == 0
           progressBar.progress = progress
           progressBar.max = max
         }
-        else                                                    -> {
+        else                   -> {
           progressBar.isIndeterminate = true
         }
       }
@@ -209,19 +231,19 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
 
   override fun onSaveInstanceState(outState: Bundle?) {
     super.onSaveInstanceState(outState)
-    outState?.putInt("OPEN_BUFFER", viewModel.getBuffer().value ?: -1)
+    outState?.putInt("OPEN_BUFFER", viewModel.buffer.value ?: -1)
   }
 
   override fun onSaveInstanceState(outState: Bundle?, outPersistentState: PersistableBundle?) {
     super.onSaveInstanceState(outState, outPersistentState)
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-      outPersistentState?.putInt("OPEN_BUFFER", viewModel.getBuffer().value ?: -1)
+      outPersistentState?.putInt("OPEN_BUFFER", viewModel.buffer.value ?: -1)
     }
   }
 
   override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
     super.onRestoreInstanceState(savedInstanceState)
-    viewModel.setBuffer(savedInstanceState?.getInt("OPEN_BUFFER", -1) ?: -1)
+    viewModel.buffer.value = savedInstanceState?.getInt("OPEN_BUFFER", -1) ?: -1
   }
 
   @TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -230,7 +252,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     super.onRestoreInstanceState(savedInstanceState, persistentState)
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
       val fallback = persistentState?.getInt("OPEN_BUFFER", -1) ?: -1
-      viewModel.setBuffer(savedInstanceState?.getInt("OPEN_BUFFER", fallback) ?: fallback)
+      viewModel.buffer.value = savedInstanceState?.getInt("OPEN_BUFFER", fallback) ?: fallback
     }
   }
 
@@ -240,83 +262,65 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
   }
 
   override fun onOptionsItemSelected(item: MenuItem?) = when (item?.itemId) {
-    android.R.id.home -> {
+    android.R.id.home    -> {
       drawerToggle.onOptionsItemSelected(item)
     }
 
     R.id.filter_messages -> {
-      handler.post {
-        val buffer = viewModel.getBuffer().value
-        if (buffer != null) {
-          val filtered = Message_Type.of(database.filtered().get(accountId, buffer) ?: 0)
-          val flags = intArrayOf(
-            Message.MessageType.Join.bit or Message.MessageType.NetsplitJoin.bit,
-            Message.MessageType.Part.bit,
-            Message.MessageType.Quit.bit or Message.MessageType.NetsplitQuit.bit,
-            Message.MessageType.Nick.bit,
-            Message.MessageType.Mode.bit,
-            Message.MessageType.Topic.bit
-          )
-          val selectedIndices = flags.withIndex().mapNotNull { (index, flag) ->
-            if ((filtered and flag).isNotEmpty()) {
-              index
-            } else {
-              null
-            }
-          }.toTypedArray()
-
-          runOnUiThread {
-            MaterialDialog.Builder(this)
-              .title(R.string.label_filter_messages)
-              .items(R.array.message_filter_types)
-              .itemsIds(flags)
-              .itemsCallbackMultiChoice(selectedIndices, { _, _, _ -> false })
-              .positiveText(R.string.label_select_multiple)
-              .negativeText(R.string.label_cancel)
-              .onPositive { dialog, _ ->
-                val selected = dialog.selectedIndices ?: emptyArray()
-                handler.post {
-                  val newlyFiltered = selected
-                    .map { flags[it] }
-                    .fold(Message_Type.of()) { acc, i -> acc or i }
-
-                  database.filtered().replace(
-                    QuasselDatabase.Filtered(accountId, buffer, newlyFiltered.value)
-                  )
-                }
-              }.negativeColorAttr(R.attr.colorTextPrimary)
-              .backgroundColorAttr(R.attr.colorBackgroundCard)
-              .contentColorAttr(R.attr.colorTextPrimary)
-              .build()
-              .show()
+      viewModel.buffer { buffer ->
+        val filtered = Message_Type.of(database.filtered().get(accountId, buffer) ?: 0)
+        val flags = intArrayOf(
+          Message.MessageType.Join.bit or Message.MessageType.NetsplitJoin.bit,
+          Message.MessageType.Part.bit,
+          Message.MessageType.Quit.bit or Message.MessageType.NetsplitQuit.bit,
+          Message.MessageType.Nick.bit,
+          Message.MessageType.Mode.bit,
+          Message.MessageType.Topic.bit
+        )
+        val selectedIndices = flags.withIndex().mapNotNull { (index, flag) ->
+          if ((filtered and flag).isNotEmpty()) {
+            index
+          } else {
+            null
           }
-        }
+        }.toTypedArray()
+
+        MaterialDialog.Builder(this)
+          .title(R.string.label_filter_messages)
+          .items(R.array.message_filter_types)
+          .itemsIds(flags)
+          .itemsCallbackMultiChoice(selectedIndices, { _, _, _ -> false })
+          .positiveText(R.string.label_select_multiple)
+          .negativeText(R.string.label_cancel)
+          .onPositive { dialog, _ ->
+            val selected = dialog.selectedIndices ?: emptyArray()
+            runInBackground {
+              val newlyFiltered = selected
+                .map { flags[it] }
+                .fold(Message_Type.of()) { acc, i -> acc or i }
+
+              database.filtered().replace(
+                QuasselDatabase.Filtered(accountId, buffer, newlyFiltered.value)
+              )
+            }
+          }.negativeColorAttr(R.attr.colorTextPrimary)
+          .backgroundColorAttr(R.attr.colorBackgroundCard)
+          .contentColorAttr(R.attr.colorTextPrimary)
+          .build()
+          .show()
       }
       true
     }
-    R.id.settings -> {
+    R.id.settings        -> {
       startActivity(Intent(applicationContext, SettingsActivity::class.java))
       true
     }
-    R.id.disconnect -> {
-      handler.post {
-        sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) {
-          editApply {
-            putBoolean(Keys.Status.reconnect, false)
-          }
-        }
-
-        val intent = Intent(this, AccountSelectionActivity::class.java)
-        intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
-        startActivityForResult(intent, REQUEST_SELECT_ACCOUNT)
-      }
+    R.id.disconnect      -> {
+      val editor1 = getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE).edit()
+      editor1.putBoolean(Keys.Status.reconnect, false)
+      editor1.commit()
       true
     }
-    else -> super.onOptionsItemSelected(item)
-  }
-
-  override fun onDestroy() {
-    handler.onDestroy()
-    super.onDestroy()
+    else                 -> super.onOptionsItemSelected(item)
   }
 }
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 46176a771..ded3967bd 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
@@ -20,7 +20,6 @@ import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.Settings
-import de.kuschku.quasseldroid.util.AndroidHandlerThread
 import de.kuschku.quasseldroid.util.helper.map
 import de.kuschku.quasseldroid.util.helper.zip
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
@@ -29,8 +28,6 @@ import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
 import de.kuschku.quasseldroid.viewmodel.data.BufferHiddenState
 
 class BufferViewConfigFragment : ServiceBoundFragment() {
-  private val handlerThread = AndroidHandlerThread("ChatList")
-
   @BindView(R.id.chatListToolbar)
   lateinit var chatListToolbar: Toolbar
 
@@ -158,7 +155,6 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
   private lateinit var listAdapter: BufferListAdapter
 
   override fun onCreate(savedInstanceState: Bundle?) {
-    handlerThread.onCreate()
     super.onCreate(savedInstanceState)
 
     viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
@@ -220,7 +216,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
       },
       viewModel.selectedBufferId,
       viewModel.collapsedNetworks,
-      handlerThread::post,
+      ::runInBackground,
       activity!!::runOnUiThread,
       clickListener,
       longClickListener
@@ -312,16 +308,11 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     return view
   }
 
-  override fun onDestroy() {
-    handlerThread.onDestroy()
-    super.onDestroy()
-  }
-
   private val clickListener: ((BufferId) -> Unit)? = {
     if (actionMode != null) {
       longClickListener?.invoke(it)
     } else {
-      viewModel.setBuffer(it)
+      viewModel.buffer.value = it
     }
   }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
index 8728b4629..06730680b 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
@@ -53,14 +53,9 @@ class MessageAdapter(
     }
   }
 
-  override fun getItemViewType(position: Int): Int {
-    val item = getItem(position)
-    if (item != null) {
-      return viewType(Message_Flags.of(item.type), Message_Flags.of(item.flag))
-    } else {
-      return 0
-    }
-  }
+  override fun getItemViewType(position: Int) = getItem(position)?.let {
+    viewType(Message_Flags.of(it.type), Message_Flags.of(it.flag))
+  } ?: 0
 
   private fun viewType(type: Message_Types, flags: Message_Flags) =
     if (flags.hasFlag(Message_Flag.Highlight)) {
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 44fc8178a..7f84073c2 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
@@ -21,8 +21,10 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.BacklogSettings
 import de.kuschku.quasseldroid.settings.Settings
-import de.kuschku.quasseldroid.util.AndroidHandlerThread
-import de.kuschku.quasseldroid.util.helper.*
+import de.kuschku.quasseldroid.util.helper.invoke
+import de.kuschku.quasseldroid.util.helper.switchMapNotNull
+import de.kuschku.quasseldroid.util.helper.toggle
+import de.kuschku.quasseldroid.util.helper.zip
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
 import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
 
@@ -36,8 +38,6 @@ class MessageListFragment : ServiceBoundFragment() {
   private lateinit var viewModel: QuasselViewModel
   private lateinit var appearanceSettings: AppearanceSettings
 
-  private val handler = AndroidHandlerThread("Chat")
-
   private lateinit var database: QuasselDatabase
 
   private lateinit var linearLayoutManager: LinearLayoutManager
@@ -49,7 +49,6 @@ class MessageListFragment : ServiceBoundFragment() {
   private lateinit var backlogSettings: BacklogSettings
 
   override fun onCreate(savedInstanceState: Bundle?) {
-    handler.onCreate()
     super.onCreate(savedInstanceState)
     viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
     appearanceSettings = Settings.appearance(activity!!)
@@ -87,7 +86,7 @@ class MessageListFragment : ServiceBoundFragment() {
       })
 
     database = QuasselDatabase.Creator.init(context!!.applicationContext)
-    val data = viewModel.getBuffer().switchMapNotNull { buffer ->
+    val data = viewModel.buffer.switchMapNotNull { buffer ->
       database.filtered().listen(accountId, buffer).switchMapNotNull { filtered ->
         LivePagedListBuilder(
           database.message().findByBufferIdPaged(buffer, filtered),
@@ -101,62 +100,50 @@ class MessageListFragment : ServiceBoundFragment() {
       }
     }
 
-    handler.post {
-      val lastMessageId = viewModel.getBuffer().switchMapNotNull {
-        database.message().lastMsgId(it)
-      }
-
-      viewModel.sessionManager.zip(lastMessageId).observe(
-        this, Observer {
-        handler.post {
-          val session = it?.first
-          val message = it?.second
-          val bufferSyncer = session?.bufferSyncer
-          if (message != null && bufferSyncer != null && previousMessageId != message.messageId) {
-            markAsRead(bufferSyncer, message.bufferId, message.messageId)
-            previousMessageId = message.messageId
-          }
-        }
-      })
-
-      viewModel.sessionManager.zip(viewModel.getBuffer()).observe(
-        this, Observer {
-        val previous = lastBuffer ?: -1
-        val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition()
-        val firstVisibleMessageId = adapter[firstVisibleItemPosition]?.messageId
-        handler.post {
-          val session = it?.first
-          val buffer = it?.second
-          val bufferSyncer = session?.bufferSyncer
-          if (buffer != null && bufferSyncer != null) {
-            if (buffer != previous) {
-              onBufferChange(previous, buffer, firstVisibleMessageId, bufferSyncer)
-            }
-            lastBuffer = buffer
-          }
-        }
-      })
+    val lastMessageId = viewModel.buffer.switchMapNotNull {
+      database.message().lastMsgId(it)
     }
 
-    viewModel.markerLine.observe(
+    viewModel.sessionManager.zip(lastMessageId).observe(
       this, Observer {
+      runInBackground {
+        val session = it?.first
+        val message = it?.second
+        val bufferSyncer = session?.bufferSyncer
+        if (message != null && bufferSyncer != null && previousMessageId != message.messageId) {
+          markAsRead(bufferSyncer, message.bufferId, message.messageId)
+          previousMessageId = message.messageId
+        }
+      }
+    })
+
+    viewModel.markerLine.observe(this, Observer {
       adapter.markerLinePosition = it
       adapter.notifyDataSetChanged()
     })
 
     var lastBuffer = -1
-    data.observe(
-      this, Observer { list ->
+    data.observe(this, Observer { list ->
       val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition()
-      val buffer = viewModel.getBuffer().value ?: -1
-      if (buffer != lastBuffer) {
-        lastBuffer = buffer
-        adapter.clearCache()
-      }
-      adapter.submitList(list)
-      if (firstVisibleItemPosition < 2) {
-        activity?.runOnUiThread { messageList.scrollToPosition(0) }
-        handler.postDelayed({ activity?.runOnUiThread { messageList.scrollToPosition(0) } }, 16)
+      val firstVisibleMessageId = adapter[firstVisibleItemPosition]?.messageId
+      runInBackground {
+        val buffer = viewModel.buffer.value ?: -1
+        if (buffer != lastBuffer) {
+          backend.value?.sessionManager()?.bufferSyncer?.let { bufferSyncer ->
+            onBufferChange(lastBuffer, buffer, firstVisibleMessageId, bufferSyncer)
+          }
+          lastBuffer = buffer
+          adapter.clearCache()
+        }
+        adapter.submitList(list)
+        if (firstVisibleItemPosition < 2) {
+          activity?.runOnUiThread { messageList.scrollToPosition(0) }
+          runInBackgroundDelayed(16) {
+            activity?.runOnUiThread {
+              messageList.scrollToPosition(0)
+            }
+          }
+        }
       }
     })
     scrollDown.hide()
@@ -194,8 +181,8 @@ class MessageListFragment : ServiceBoundFragment() {
   }
 
   private fun loadMore() {
-    handler.post {
-      viewModel.getBuffer().let { bufferId ->
+    runInBackground {
+      viewModel.buffer { bufferId ->
         viewModel.sessionManager()?.backlogManager?.requestBacklog(
           bufferId = bufferId,
           last = database.message().findFirstByBufferId(
@@ -206,9 +193,4 @@ class MessageListFragment : ServiceBoundFragment() {
       }
     }
   }
-
-  override fun onDestroy() {
-    handler.onDestroy()
-    super.onDestroy()
-  }
 }
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
index 8f708872b..81b6ad62a 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
@@ -15,7 +15,6 @@ import de.kuschku.libquassel.util.irc.IrcCaseMappers
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.Settings
-import de.kuschku.quasseldroid.util.AndroidHandlerThread
 import de.kuschku.quasseldroid.util.helper.map
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
@@ -24,8 +23,6 @@ import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
 class NickListFragment : ServiceBoundFragment() {
   private lateinit var viewModel: QuasselViewModel
 
-  private val handlerThread = AndroidHandlerThread("NickList")
-
   @BindView(R.id.nickList)
   lateinit var nickList: RecyclerView
 
@@ -33,7 +30,6 @@ class NickListFragment : ServiceBoundFragment() {
   private lateinit var appearanceSettings: AppearanceSettings
 
   override fun onCreate(savedInstanceState: Bundle?) {
-    handlerThread.onCreate()
     super.onCreate(savedInstanceState)
 
     viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
@@ -76,11 +72,6 @@ class NickListFragment : ServiceBoundFragment() {
     return view
   }
 
-  override fun onDestroy() {
-    handlerThread.onDestroy()
-    super.onDestroy()
-  }
-
   private val clickListener: ((String) -> Unit)? = {
     // TODO
   }
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 8046286da..7044fd697 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
@@ -6,48 +6,64 @@ import android.os.Process
 import de.kuschku.libquassel.util.compatibility.HandlerService
 
 class AndroidHandlerService : HandlerService {
-  override fun parse(f: () -> Unit) {
-    parseHandler.post(f)
+  override fun serialize(f: () -> Unit) {
+    serializeHandler.post(f)
+  }
+
+  override fun deserialize(f: () -> Unit) {
+    serializeHandler.post(f)
   }
 
   override fun write(f: () -> Unit) {
     writeHandler.post(f)
   }
 
-  override fun handle(f: () -> Unit) {
+  override fun backend(f: () -> Unit) {
     backendHandler.post(f)
   }
 
-  private val parseThread = HandlerThread("parse", Process.THREAD_PRIORITY_DISPLAY)
+  override fun backendDelayed(delayMillis: Long, f: () -> Unit) {
+    backendHandler.postDelayed(f, delayMillis)
+  }
+
+  private val serializeThread = HandlerThread("serialize", Process.THREAD_PRIORITY_BACKGROUND)
+  private val deserializeThread = HandlerThread("deserialize", Process.THREAD_PRIORITY_BACKGROUND)
   private val writeThread = HandlerThread("write", Process.THREAD_PRIORITY_BACKGROUND)
-  private val backendThread = HandlerThread("backend", Process.THREAD_PRIORITY_DISPLAY)
+  private val backendThread = HandlerThread("backend", Process.THREAD_PRIORITY_BACKGROUND)
 
-  private val parseHandler: Handler
+  private val serializeHandler: Handler
+  private val deserializeHandler: Handler
   private val writeHandler: Handler
   private val backendHandler: Handler
 
   private val internalExceptionHandler = Thread.UncaughtExceptionHandler { thread: Thread, throwable: Throwable ->
-    exceptionHandler?.uncaughtException(thread, throwable)
+    val exceptionHandler = exceptionHandler ?: Thread.getDefaultUncaughtExceptionHandler()
+    exceptionHandler.uncaughtException(thread, throwable)
   }
 
   init {
-    parseThread.uncaughtExceptionHandler = internalExceptionHandler
+    serializeThread.uncaughtExceptionHandler = internalExceptionHandler
+    deserializeThread.uncaughtExceptionHandler = internalExceptionHandler
     writeThread.uncaughtExceptionHandler = internalExceptionHandler
     backendThread.uncaughtExceptionHandler = internalExceptionHandler
 
-    parseThread.start()
+    serializeThread.start()
+    deserializeThread.start()
     writeThread.start()
     backendThread.start()
 
-    parseHandler = Handler(parseThread.looper)
+    serializeHandler = Handler(serializeThread.looper)
+    deserializeHandler = Handler(deserializeThread.looper)
     writeHandler = Handler(writeThread.looper)
     backendHandler = Handler(backendThread.looper)
   }
 
   override var exceptionHandler: Thread.UncaughtExceptionHandler? = null
+    get() = field ?: Thread.getDefaultUncaughtExceptionHandler()
 
   override fun quit() {
-    parseThread.quit()
+    serializeThread.quit()
+    deserializeThread.quit()
     writeThread.quit()
     backendThread.quit()
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt
index 37f301572..cbf32061a 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundActivity.kt
@@ -10,12 +10,15 @@ import android.support.annotation.ColorRes
 import android.support.annotation.DrawableRes
 import android.support.v7.app.AppCompatActivity
 import de.kuschku.libquassel.session.Backend
+import de.kuschku.libquassel.util.compatibility.LoggingHandler
+import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
 import de.kuschku.quasseldroid.Keys
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.ConnectionSettings
 import de.kuschku.quasseldroid.settings.Settings
 import de.kuschku.quasseldroid.ui.setup.accounts.AccountSelectionActivity
+import de.kuschku.quasseldroid.util.helper.invoke
 import de.kuschku.quasseldroid.util.helper.sharedPreferences
 import de.kuschku.quasseldroid.util.helper.updateRecentsHeaderIfExisting
 
@@ -30,6 +33,18 @@ abstract class ServiceBoundActivity : AppCompatActivity(),
   protected val backend: LiveData<Backend?>
     get() = connection.backend
 
+  protected fun runInBackground(f: () -> Unit) {
+    connection.backend {
+      it.sessionManager().handlerService.backend(f)
+    }
+  }
+
+  protected fun runInBackgroundDelayed(delayMillis: Long, f: () -> Unit) {
+    connection.backend {
+      it.sessionManager().handlerService.backendDelayed(delayMillis, f)
+    }
+  }
+
   protected lateinit var appearanceSettings: AppearanceSettings
   protected lateinit var connectionSettings: ConnectionSettings
   protected var accountId: Long = -1
@@ -85,11 +100,20 @@ abstract class ServiceBoundActivity : AppCompatActivity(),
     accountId = getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE)
       ?.getLong(Keys.Status.selectedAccount, -1) ?: -1
 
-    if (!sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) {
-        getBoolean(Keys.Status.reconnect, false)
-      } || accountId == -1L) {
+    val reconnect = sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) {
+      getBoolean(Keys.Status.reconnect, false)
+    }
+    val accountIdValid = accountId != -1L
+
+    log(
+      LoggingHandler.LogLevel.ERROR, "DEBUG",
+      "reconnect: $reconnect, accountIdValid: $accountIdValid"
+    )
+
+    if (!reconnect || !accountIdValid) {
 
       if (!startedSelection) {
+        log(LoggingHandler.LogLevel.ERROR, "DEBUG", "started: $REQUEST_SELECT_ACCOUNT")
         startActivityForResult(
           Intent(this, AccountSelectionActivity::class.java), REQUEST_SELECT_ACCOUNT
         )
@@ -102,6 +126,12 @@ abstract class ServiceBoundActivity : AppCompatActivity(),
 
   override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
     super.onActivityResult(requestCode, resultCode, data)
+
+    log(
+      LoggingHandler.LogLevel.ERROR, "DEBUG",
+      "request: $requestCode result: $resultCode, data: $data"
+    )
+
     if (requestCode == REQUEST_SELECT_ACCOUNT) {
       startedSelection = false
 
@@ -117,6 +147,6 @@ abstract class ServiceBoundActivity : AppCompatActivity(),
   }
 
   companion object {
-    const val REQUEST_SELECT_ACCOUNT = 0
+    const val REQUEST_SELECT_ACCOUNT = 1
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt
index 2896a08a3..b6e16dbf6 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/service/ServiceBoundFragment.kt
@@ -6,6 +6,7 @@ import android.os.Bundle
 import android.support.v4.app.Fragment
 import de.kuschku.libquassel.session.Backend
 import de.kuschku.quasseldroid.Keys
+import de.kuschku.quasseldroid.util.helper.invoke
 
 abstract class ServiceBoundFragment : Fragment() {
   private var connection = BackendServiceConnection()
@@ -13,6 +14,18 @@ abstract class ServiceBoundFragment : Fragment() {
   protected val backend: LiveData<Backend?>
     get() = connection.backend
 
+  protected fun runInBackground(f: () -> Unit) {
+    connection.backend {
+      it.sessionManager().handlerService.backend(f)
+    }
+  }
+
+  protected fun runInBackgroundDelayed(delayMillis: Long, f: () -> Unit) {
+    connection.backend {
+      it.sessionManager().handlerService.backendDelayed(delayMillis, f)
+    }
+  }
+
   protected var accountId: Long = -1
 
   override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 562cb0029..2dce73ad1 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -28,12 +28,6 @@
   <string name="label_unhide">Nicht mehr ausblenden</string>
   <string name="label_yes">Ja</string>
 
-  <string name="label_status_disconnected">Nicht verbunden</string>
-  <string name="label_status_connecting">Verbindung wird hergestellt</string>
-  <string name="label_status_handshake">Handshake</string>
-  <string name="label_status_init">Initialisieren</string>
-  <string name="label_status_connected">Verbunden</string>
-
   <string name="notification_channel_connection_title">Verbindung</string>
   <string name="notification_channel_highlight_title">Erwähnungen</string>
 
diff --git a/app/src/main/res/values-de/strings_error.xml b/app/src/main/res/values-de/strings_error.xml
new file mode 100644
index 000000000..0ff779063
--- /dev/null
+++ b/app/src/main/res/values-de/strings_error.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <string name="label_error_login">Loginfehler</string>
+  <string name="label_error_setup">Einrichtungsfehler</string>
+  <string name="label_error_init">Verbindungsfehler</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings_status.xml b/app/src/main/res/values-de/strings_status.xml
new file mode 100644
index 000000000..46c91580a
--- /dev/null
+++ b/app/src/main/res/values-de/strings_status.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <string name="label_status_disconnected">Nicht verbunden</string>
+  <string name="label_status_connecting">Verbindung wird hergestellt</string>
+  <string name="label_status_handshake">Protokoll wird ausgehandelt</string>
+  <string name="label_status_init">Initialisiere Daten</string>
+  <string name="label_status_connected">Verbunden</string>
+  <string name="label_status_closed">Verbindung getrennt</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e0fb82803..b6e4470b9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -27,12 +27,6 @@
   <string name="label_unhide">Make Visible</string>
   <string name="label_yes">Yes</string>
 
-  <string name="label_status_disconnected">Disconnected</string>
-  <string name="label_status_connecting">Connecting</string>
-  <string name="label_status_handshake">Handshake</string>
-  <string name="label_status_init">Init</string>
-  <string name="label_status_connected">Connected</string>
-
   <string name="notification_channel_background" translatable="false">background</string>
   <string name="notification_channel_connection_title">Connection</string>
   <string name="notification_channel_highlight" translatable="false">highlight</string>
diff --git a/app/src/main/res/values/strings_error.xml b/app/src/main/res/values/strings_error.xml
new file mode 100644
index 000000000..7e9e24a25
--- /dev/null
+++ b/app/src/main/res/values/strings_error.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <string name="label_error_login">Login Error</string>
+  <string name="label_error_setup">Setup Error</string>
+  <string name="label_error_init">Connection Error</string>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings_status.xml b/app/src/main/res/values/strings_status.xml
new file mode 100644
index 000000000..ec34c73fb
--- /dev/null
+++ b/app/src/main/res/values/strings_status.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <string name="label_status_disconnected">Disconnected</string>
+  <string name="label_status_connecting">Connecting</string>
+  <string name="label_status_handshake">Handshake</string>
+  <string name="label_status_init">Init</string>
+  <string name="label_status_connected">Connected</string>
+  <string name="label_status_closed">Connection Lost</string>
+</resources>
\ No newline at end of file
-- 
GitLab