From 199168cf0a2b74c6cb89263a3ca182ccaa8a84ce Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Thu, 13 Dec 2018 10:22:22 +0100
Subject: [PATCH] Implements more userfriendly error messages for connection
 errors

---
 .../quasseldroid/ui/chat/ChatActivity.kt      |  52 +++
 .../quasseldroid/util/backport/OsConstants.kt | 419 ++++++++++++++++++
 .../main/java/libcore/io/ErrnoException.java  |  32 ++
 app/src/main/res/values-de/strings_error.xml  |   5 +
 .../main/res/values-fr-rCA/strings_error.xml  |   1 +
 app/src/main/res/values-lt/strings_error.xml  |   1 +
 app/src/main/res/values-pt/strings_error.xml  |   1 +
 app/src/main/res/values-sr/strings_error.xml  |   1 +
 app/src/main/res/values/strings_error.xml     |   5 +
 .../libquassel/connection/CoreConnection.kt   |   6 +-
 .../connection/ProtocolVersionException.kt    |   9 +
 .../de/kuschku/libquassel/session/ISession.kt |   3 +
 .../de/kuschku/libquassel/session/Session.kt  |  17 +-
 .../libquassel/session/SessionManager.kt      |   5 +
 .../viewmodel/QuasselViewModel.kt             |   4 +
 15 files changed, 558 insertions(+), 3 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/backport/OsConstants.kt
 create mode 100644 app/src/main/java/libcore/io/ErrnoException.java
 create mode 100644 lib/src/main/java/de/kuschku/libquassel/connection/ProtocolVersionException.kt

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 6eb904134..d6bd0e34c 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
@@ -28,6 +28,7 @@ import android.graphics.Canvas
 import android.graphics.drawable.Drawable
 import android.os.Build
 import android.os.Bundle
+import android.system.ErrnoException
 import android.text.Html
 import android.view.ActionMode
 import android.view.Menu
@@ -36,6 +37,7 @@ import android.view.View
 import android.widget.EditText
 import android.widget.LinearLayout
 import android.widget.TextView
+import android.widget.Toast
 import androidx.appcompat.app.ActionBarDrawerToggle
 import androidx.appcompat.widget.AppCompatImageView
 import androidx.appcompat.widget.Toolbar
@@ -55,6 +57,7 @@ import com.bumptech.glide.request.target.SimpleTarget
 import com.bumptech.glide.request.transition.Transition
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import de.kuschku.libquassel.connection.ConnectionState
+import de.kuschku.libquassel.connection.ProtocolVersionException
 import de.kuschku.libquassel.connection.QuasselSecurityException
 import de.kuschku.libquassel.protocol.Buffer_Type
 import de.kuschku.libquassel.protocol.Message
@@ -85,6 +88,7 @@ import de.kuschku.quasseldroid.ui.setup.accounts.selection.AccountSelectionActiv
 import de.kuschku.quasseldroid.ui.setup.user.UserSetupActivity
 import de.kuschku.quasseldroid.util.ColorContext
 import de.kuschku.quasseldroid.util.avatars.AvatarHelper
+import de.kuschku.quasseldroid.util.backport.OsConstants
 import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.missingfeatures.MissingFeaturesDialog
@@ -100,6 +104,8 @@ import org.threeten.bp.Instant
 import org.threeten.bp.ZoneId
 import org.threeten.bp.format.DateTimeFormatter
 import org.threeten.bp.format.FormatStyle
+import java.net.ConnectException
+import java.net.UnknownHostException
 import java.security.cert.CertificateExpiredException
 import java.security.cert.CertificateNotYetValidException
 import javax.inject.Inject
@@ -541,6 +547,52 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
       }
     })
 
+    // Connection errors that should show up as toast
+    viewModel.connectionErrors.toLiveData().observe(this, Observer { error ->
+      error?.let {
+        val cause = it.cause
+        when {
+          it is UnknownHostException         -> {
+            val host = it.message?.replace("Host is unresolved: ", "")
+
+            Toast.makeText(this,
+                           getString(R.string.label_error_unknown_host, host),
+                           Toast.LENGTH_LONG).show()
+          }
+          it is ProtocolVersionException     -> {
+            Toast.makeText(this,
+                           getString(R.string.label_error_invalid_protocol_version,
+                                     it.protocol.version),
+                           Toast.LENGTH_LONG).show()
+          }
+          it is ConnectException &&
+          cause is libcore.io.ErrnoException -> {
+            val errorCode = OsConstants.errnoName(cause.errno)
+            val errorName = OsConstants.strerror(cause.errno)
+
+            Toast.makeText(this,
+                           getString(R.string.label_error_connection, errorName, errorCode),
+                           Toast.LENGTH_LONG).show()
+          }
+          it is ConnectException &&
+          Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
+          cause is ErrnoException            -> {
+            val errorCode = OsConstants.errnoName(cause.errno)
+            val errorName = OsConstants.strerror(cause.errno)
+
+            Toast.makeText(this,
+                           getString(R.string.label_error_connection, errorName, errorCode),
+                           Toast.LENGTH_LONG).show()
+          }
+          else                               -> {
+            Toast.makeText(this,
+                           getString(R.string.label_error_connection_closed),
+                           Toast.LENGTH_LONG).show()
+          }
+        }
+      }
+    })
+
     // After initial connect, open the drawer
     viewModel.connectionProgress
       .filter { (it, _, _) -> it == ConnectionState.CONNECTED }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/backport/OsConstants.kt b/app/src/main/java/de/kuschku/quasseldroid/util/backport/OsConstants.kt
new file mode 100644
index 000000000..2d62f5c04
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/backport/OsConstants.kt
@@ -0,0 +1,419 @@
+package de.kuschku.quasseldroid.util.backport
+
+object OsConstants {
+
+  const val EPERM = 1
+  const val ENOENT = 2
+  const val ESRCH = 3
+  const val EINTR = 4
+  const val EIO = 5
+  const val ENXIO = 6
+  const val E2BIG = 7
+  const val ENOEXEC = 8
+  const val EBADF = 9
+  const val ECHILD = 10
+  const val EAGAIN = 11
+  const val ENOMEM = 12
+  const val EACCES = 13
+  const val EFAULT = 14
+  const val ENOTBLK = 15
+  const val EBUSY = 16
+  const val EEXIST = 17
+  const val EXDEV = 18
+  const val ENODEV = 19
+  const val ENOTDIR = 20
+  const val EISDIR = 21
+  const val EINVAL = 22
+  const val ENFILE = 23
+  const val EMFILE = 24
+  const val ENOTTY = 25
+  const val ETXTBSY = 26
+  const val EFBIG = 27
+  const val ENOSPC = 28
+  const val ESPIPE = 29
+  const val EROFS = 30
+  const val EMLINK = 31
+  const val EPIPE = 32
+  const val EDOM = 33
+  const val ERANGE = 34
+  const val EDEADLK = 35
+  const val ENAMETOOLONG = 36
+  const val ENOLCK = 37
+  const val ENOSYS = 38
+  const val ENOTEMPTY = 39
+  const val ELOOP = 40
+  const val EWOULDBLOCK = 11
+  const val ENOMSG = 42
+  const val EIDRM = 43
+  const val ECHRNG = 44
+  const val EL2NSYNC = 45
+  const val EL3HLT = 46
+  const val EL3RST = 47
+  const val ELNRNG = 48
+  const val EUNATCH = 49
+  const val ENOCSI = 50
+  const val EL2HLT = 51
+  const val EBADE = 52
+  const val EBADR = 53
+  const val EXFULL = 54
+  const val ENOANO = 55
+  const val EBADRQC = 56
+  const val EBADSLT = 57
+  const val EDEADLOCK = 35
+  const val EBFONT = 59
+  const val ENOSTR = 60
+  const val ENODATA = 61
+  const val ETIME = 62
+  const val ENOSR = 63
+  const val ENONET = 64
+  const val ENOPKG = 65
+  const val EREMOTE = 66
+  const val ENOLINK = 67
+  const val EADV = 68
+  const val ESRMNT = 69
+  const val ECOMM = 70
+  const val EPROTO = 71
+  const val EMULTIHOP = 72
+  const val EDOTDOT = 73
+  const val EBADMSG = 74
+  const val EOVERFLOW = 75
+  const val ENOTUNIQ = 76
+  const val EBADFD = 77
+  const val EREMCHG = 78
+  const val ELIBACC = 79
+  const val ELIBBAD = 80
+  const val ELIBSCN = 81
+  const val ELIBMAX = 82
+  const val ELIBEXEC = 83
+  const val EILSEQ = 84
+  const val ERESTART = 85
+  const val ESTRPIPE = 86
+  const val EUSERS = 87
+  const val ENOTSOCK = 88
+  const val EDESTADDRREQ = 89
+  const val EMSGSIZE = 90
+  const val EPROTOTYPE = 91
+  const val ENOPROTOOPT = 92
+  const val EPROTONOSUPPORT = 93
+  const val ESOCKTNOSUPPORT = 94
+  const val EOPNOTSUPP = 95
+  const val EPFNOSUPPORT = 96
+  const val EAFNOSUPPORT = 97
+  const val EADDRINUSE = 98
+  const val EADDRNOTAVAIL = 99
+  const val ENETDOWN = 100
+  const val ENETUNREACH = 101
+  const val ENETRESET = 102
+  const val ECONNABORTED = 103
+  const val ECONNRESET = 104
+  const val ENOBUFS = 105
+  const val EISCONN = 106
+  const val ENOTCONN = 107
+  const val ESHUTDOWN = 108
+  const val ETOOMANYREFS = 109
+  const val ETIMEDOUT = 110
+  const val ECONNREFUSED = 111
+  const val EHOSTDOWN = 112
+  const val EHOSTUNREACH = 113
+  const val EALREADY = 114
+  const val EINPROGRESS = 115
+  const val ESTALE = 116
+  const val EUCLEAN = 117
+  const val ENOTNAM = 118
+  const val ENAVAIL = 119
+  const val EISNAM = 120
+  const val EREMOTEIO = 121
+  const val EDQUOT = 122
+  const val ENOMEDIUM = 123
+  const val EMEDIUMTYPE = 124
+  const val ECANCELED = 125
+  const val ENOKEY = 126
+  const val EKEYEXPIRED = 127
+  const val EKEYREVOKED = 128
+  const val EKEYREJECTED = 129
+  const val EOWNERDEAD = 130
+  const val ENOTRECOVERABLE = 131
+  const val ERFKILL = 132
+  const val EHWPOISON = 133
+  const val ENOTSUP = 95
+
+  fun errnoName(errno: Int): String? {
+    return when (errno) {
+      EPERM           -> "EPERM"
+      ENOENT          -> "ENOENT"
+      ESRCH           -> "ESRCH"
+      EINTR           -> "EINTR"
+      EIO             -> "EIO"
+      ENXIO           -> "ENXIO"
+      E2BIG           -> "E2BIG"
+      ENOEXEC         -> "ENOEXEC"
+      EBADF           -> "EBADF"
+      ECHILD          -> "ECHILD"
+      EAGAIN          -> "EAGAIN"
+      ENOMEM          -> "ENOMEM"
+      EACCES          -> "EACCES"
+      EFAULT          -> "EFAULT"
+      ENOTBLK         -> "ENOTBLK"
+      EBUSY           -> "EBUSY"
+      EEXIST          -> "EEXIST"
+      EXDEV           -> "EXDEV"
+      ENODEV          -> "ENODEV"
+      ENOTDIR         -> "ENOTDIR"
+      EISDIR          -> "EISDIR"
+      EINVAL          -> "EINVAL"
+      ENFILE          -> "ENFILE"
+      EMFILE          -> "EMFILE"
+      ENOTTY          -> "ENOTTY"
+      ETXTBSY         -> "ETXTBSY"
+      EFBIG           -> "EFBIG"
+      ENOSPC          -> "ENOSPC"
+      ESPIPE          -> "ESPIPE"
+      EROFS           -> "EROFS"
+      EMLINK          -> "EMLINK"
+      EPIPE           -> "EPIPE"
+      EDOM            -> "EDOM"
+      ERANGE          -> "ERANGE"
+      EDEADLK         -> "EDEADLK"
+      ENAMETOOLONG    -> "ENAMETOOLONG"
+      ENOLCK          -> "ENOLCK"
+      ENOSYS          -> "ENOSYS"
+      ENOTEMPTY       -> "ENOTEMPTY"
+      ELOOP           -> "ELOOP"
+      EWOULDBLOCK     -> "EWOULDBLOCK"
+      ENOMSG          -> "ENOMSG"
+      EIDRM           -> "EIDRM"
+      ECHRNG          -> "ECHRNG"
+      EL2NSYNC        -> "EL2NSYNC"
+      EL3HLT          -> "EL3HLT"
+      EL3RST          -> "EL3RST"
+      ELNRNG          -> "ELNRNG"
+      EUNATCH         -> "EUNATCH"
+      ENOCSI          -> "ENOCSI"
+      EL2HLT          -> "EL2HLT"
+      EBADE           -> "EBADE"
+      EBADR           -> "EBADR"
+      EXFULL          -> "EXFULL"
+      ENOANO          -> "ENOANO"
+      EBADRQC         -> "EBADRQC"
+      EBADSLT         -> "EBADSLT"
+      EDEADLOCK       -> "EDEADLOCK"
+      EBFONT          -> "EBFONT"
+      ENOSTR          -> "ENOSTR"
+      ENODATA         -> "ENODATA"
+      ETIME           -> "ETIME"
+      ENOSR           -> "ENOSR"
+      ENONET          -> "ENONET"
+      ENOPKG          -> "ENOPKG"
+      EREMOTE         -> "EREMOTE"
+      ENOLINK         -> "ENOLINK"
+      EADV            -> "EADV"
+      ESRMNT          -> "ESRMNT"
+      ECOMM           -> "ECOMM"
+      EPROTO          -> "EPROTO"
+      EMULTIHOP       -> "EMULTIHOP"
+      EDOTDOT         -> "EDOTDOT"
+      EBADMSG         -> "EBADMSG"
+      EOVERFLOW       -> "EOVERFLOW"
+      ENOTUNIQ        -> "ENOTUNIQ"
+      EBADFD          -> "EBADFD"
+      EREMCHG         -> "EREMCHG"
+      ELIBACC         -> "ELIBACC"
+      ELIBBAD         -> "ELIBBAD"
+      ELIBSCN         -> "ELIBSCN"
+      ELIBMAX         -> "ELIBMAX"
+      ELIBEXEC        -> "ELIBEXEC"
+      EILSEQ          -> "EILSEQ"
+      ERESTART        -> "ERESTART"
+      ESTRPIPE        -> "ESTRPIPE"
+      EUSERS          -> "EUSERS"
+      ENOTSOCK        -> "ENOTSOCK"
+      EDESTADDRREQ    -> "EDESTADDRREQ"
+      EMSGSIZE        -> "EMSGSIZE"
+      EPROTOTYPE      -> "EPROTOTYPE"
+      ENOPROTOOPT     -> "ENOPROTOOPT"
+      EPROTONOSUPPORT -> "EPROTONOSUPPORT"
+      ESOCKTNOSUPPORT -> "ESOCKTNOSUPPORT"
+      EOPNOTSUPP      -> "EOPNOTSUPP"
+      EPFNOSUPPORT    -> "EPFNOSUPPORT"
+      EAFNOSUPPORT    -> "EAFNOSUPPORT"
+      EADDRINUSE      -> "EADDRINUSE"
+      EADDRNOTAVAIL   -> "EADDRNOTAVAIL"
+      ENETDOWN        -> "ENETDOWN"
+      ENETUNREACH     -> "ENETUNREACH"
+      ENETRESET       -> "ENETRESET"
+      ECONNABORTED    -> "ECONNABORTED"
+      ECONNRESET      -> "ECONNRESET"
+      ENOBUFS         -> "ENOBUFS"
+      EISCONN         -> "EISCONN"
+      ENOTCONN        -> "ENOTCONN"
+      ESHUTDOWN       -> "ESHUTDOWN"
+      ETOOMANYREFS    -> "ETOOMANYREFS"
+      ETIMEDOUT       -> "ETIMEDOUT"
+      ECONNREFUSED    -> "ECONNREFUSED"
+      EHOSTDOWN       -> "EHOSTDOWN"
+      EHOSTUNREACH    -> "EHOSTUNREACH"
+      EALREADY        -> "EALREADY"
+      EINPROGRESS     -> "EINPROGRESS"
+      ESTALE          -> "ESTALE"
+      EUCLEAN         -> "EUCLEAN"
+      ENOTNAM         -> "ENOTNAM"
+      ENAVAIL         -> "ENAVAIL"
+      EISNAM          -> "EISNAM"
+      EREMOTEIO       -> "EREMOTEIO"
+      EDQUOT          -> "EDQUOT"
+      ENOMEDIUM       -> "ENOMEDIUM"
+      EMEDIUMTYPE     -> "EMEDIUMTYPE"
+      ECANCELED       -> "ECANCELED"
+      ENOKEY          -> "ENOKEY"
+      EKEYEXPIRED     -> "EKEYEXPIRED"
+      EKEYREVOKED     -> "EKEYREVOKED"
+      EKEYREJECTED    -> "EKEYREJECTED"
+      EOWNERDEAD      -> "EOWNERDEAD"
+      ENOTRECOVERABLE -> "ENOTRECOVERABLE"
+      ERFKILL         -> "ERFKILL"
+      EHWPOISON       -> "EHWPOISON"
+      ENOTSUP         -> "ENOTSUP"
+      else            -> null
+    }
+  }
+
+  fun strerror(errno: Int): String? {
+    return when (errno) {
+      EPERM           -> "Operation not permitted"
+      ENOENT          -> "No such file or directory"
+      ESRCH           -> "No such process"
+      EINTR           -> "Interrupted system call"
+      EIO             -> "Input/output error"
+      ENXIO           -> "No such device or address"
+      E2BIG           -> "Argument list too long"
+      ENOEXEC         -> "Exec format error"
+      EBADF           -> "Bad file descriptor"
+      ECHILD          -> "No child processes"
+      EAGAIN          -> "Resource temporarily unavailable"
+      ENOMEM          -> "Cannot allocate memory"
+      EACCES          -> "Permission denied"
+      EFAULT          -> "Bad address"
+      ENOTBLK         -> "Block device required"
+      EBUSY           -> "Device or resource busy"
+      EEXIST          -> "File exists"
+      EXDEV           -> "Invalid cross-device link"
+      ENODEV          -> "No such device"
+      ENOTDIR         -> "Not a directory"
+      EISDIR          -> "Is a directory"
+      EINVAL          -> "Invalid argument"
+      ENFILE          -> "Too many open files in system"
+      EMFILE          -> "Too many open files"
+      ENOTTY          -> "Inappropriate ioctl for device"
+      ETXTBSY         -> "Text file busy"
+      EFBIG           -> "File too large"
+      ENOSPC          -> "No space left on device"
+      ESPIPE          -> "Illegal seek"
+      EROFS           -> "Read-only file system"
+      EMLINK          -> "Too many links"
+      EPIPE           -> "Broken pipe"
+      EDOM            -> "Numerical argument out of domain"
+      ERANGE          -> "Numerical result out of range"
+      EDEADLK         -> "Resource deadlock avoided"
+      ENAMETOOLONG    -> "File name too long"
+      ENOLCK          -> "No locks available"
+      ENOSYS          -> "Function not implemented"
+      ENOTEMPTY       -> "Directory not empty"
+      ELOOP           -> "Too many levels of symbolic links"
+      EWOULDBLOCK     -> "Resource temporarily unavailable"
+      ENOMSG          -> "No message of desired type"
+      EIDRM           -> "Identifier removed"
+      ECHRNG          -> "Channel number out of range"
+      EL2NSYNC        -> "Level 2 not synchronized"
+      EL3HLT          -> "Level 3 halted"
+      EL3RST          -> "Level 3 reset"
+      ELNRNG          -> "Link number out of range"
+      EUNATCH         -> "Protocol driver not attached"
+      ENOCSI          -> "No CSI structure available"
+      EL2HLT          -> "Level 2 halted"
+      EBADE           -> "Invalid exchange"
+      EBADR           -> "Invalid request descriptor"
+      EXFULL          -> "Exchange full"
+      ENOANO          -> "No anode"
+      EBADRQC         -> "Invalid request code"
+      EBADSLT         -> "Invalid slot"
+      EDEADLOCK       -> "Resource deadlock avoided"
+      EBFONT          -> "Bad font file format"
+      ENOSTR          -> "Device not a stream"
+      ENODATA         -> "No data available"
+      ETIME           -> "Timer expired"
+      ENOSR           -> "Out of streams resources"
+      ENONET          -> "Machine is not on the network"
+      ENOPKG          -> "Package not installed"
+      EREMOTE         -> "Object is remote"
+      ENOLINK         -> "Link has been severed"
+      EADV            -> "Advertise error"
+      ESRMNT          -> "Srmount error"
+      ECOMM           -> "Communication error on send"
+      EPROTO          -> "Protocol error"
+      EMULTIHOP       -> "Multihop attempted"
+      EDOTDOT         -> "RFS specific error"
+      EBADMSG         -> "Bad message"
+      EOVERFLOW       -> "Value too large for defined data type"
+      ENOTUNIQ        -> "Name not unique on network"
+      EBADFD          -> "File descriptor in bad state"
+      EREMCHG         -> "Remote address changed"
+      ELIBACC         -> "Can not access a needed shared library"
+      ELIBBAD         -> "Accessing a corrupted shared library"
+      ELIBSCN         -> ".lib section in a.out corrupted"
+      ELIBMAX         -> "Attempting to link in too many shared libraries"
+      ELIBEXEC        -> "Cannot exec a shared library directly"
+      EILSEQ          -> "Invalid or incomplete multibyte or wide character"
+      ERESTART        -> "Interrupted system call should be restarted"
+      ESTRPIPE        -> "Streams pipe error"
+      EUSERS          -> "Too many users"
+      ENOTSOCK        -> "Socket operation on non-socket"
+      EDESTADDRREQ    -> "Destination address required"
+      EMSGSIZE        -> "Message too long"
+      EPROTOTYPE      -> "Protocol wrong type for socket"
+      ENOPROTOOPT     -> "Protocol not available"
+      EPROTONOSUPPORT -> "Protocol not supported"
+      ESOCKTNOSUPPORT -> "Socket type not supported"
+      EOPNOTSUPP      -> "Operation not supported"
+      EPFNOSUPPORT    -> "Protocol family not supported"
+      EAFNOSUPPORT    -> "Address family not supported by protocol"
+      EADDRINUSE      -> "Address already in use"
+      EADDRNOTAVAIL   -> "Cannot assign requested address"
+      ENETDOWN        -> "Network is down"
+      ENETUNREACH     -> "Network is unreachable"
+      ENETRESET       -> "Network dropped connection on reset"
+      ECONNABORTED    -> "Software caused connection abort"
+      ECONNRESET      -> "Connection reset by peer"
+      ENOBUFS         -> "No buffer space available"
+      EISCONN         -> "Transport endpoint is already connected"
+      ENOTCONN        -> "Transport endpoint is not connected"
+      ESHUTDOWN       -> "Cannot send after transport endpoint shutdown"
+      ETOOMANYREFS    -> "Too many references: cannot splice"
+      ETIMEDOUT       -> "Connection timed out"
+      ECONNREFUSED    -> "Connection refused"
+      EHOSTDOWN       -> "Host is down"
+      EHOSTUNREACH    -> "No route to host"
+      EALREADY        -> "Operation already in progress"
+      EINPROGRESS     -> "Operation now in progress"
+      ESTALE          -> "Stale file handle"
+      EUCLEAN         -> "Structure needs cleaning"
+      ENOTNAM         -> "Not a XENIX named type file"
+      ENAVAIL         -> "No XENIX semaphores available"
+      EISNAM          -> "Is a named type file"
+      EREMOTEIO       -> "Remote I/O error"
+      EDQUOT          -> "Disk quota exceeded"
+      ENOMEDIUM       -> "No medium found"
+      EMEDIUMTYPE     -> "Wrong medium type"
+      ECANCELED       -> "Operation canceled"
+      ENOKEY          -> "Required key not available"
+      EKEYEXPIRED     -> "Key has expired"
+      EKEYREVOKED     -> "Key has been revoked"
+      EKEYREJECTED    -> "Key was rejected by service"
+      EOWNERDEAD      -> "Owner died"
+      ENOTRECOVERABLE -> "State not recoverable"
+      ERFKILL         -> "Operation not possible due to RF-kill"
+      EHWPOISON       -> "Memory page has hardware error"
+      ENOTSUP         -> "Operation not supported"
+      else            -> null
+    }
+  }
+}
diff --git a/app/src/main/java/libcore/io/ErrnoException.java b/app/src/main/java/libcore/io/ErrnoException.java
new file mode 100644
index 000000000..16960d6d0
--- /dev/null
+++ b/app/src/main/java/libcore/io/ErrnoException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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 libcore.io;
+
+public final class ErrnoException extends Exception {
+  public final int errno;
+  private final String functionName;
+
+  public ErrnoException(String functionName, int errno) {
+    this.functionName = functionName;
+    this.errno = errno;
+  }
+
+  public ErrnoException(String functionName, int errno, Throwable cause) {
+    super(cause);
+    this.functionName = functionName;
+    this.errno = errno;
+  }
+}
diff --git a/app/src/main/res/values-de/strings_error.xml b/app/src/main/res/values-de/strings_error.xml
index 478f31c54..152a8daad 100644
--- a/app/src/main/res/values-de/strings_error.xml
+++ b/app/src/main/res/values-de/strings_error.xml
@@ -39,4 +39,9 @@
     <code>%1$s</code><br/>
     ist nicht gültig für %2$s.</p>
   ]]></string>
+
+  <string name="label_error_unknown_host">Host nicht gefunden: %1$s</string>
+  <string name="label_error_invalid_protocol_version">Unbekanntes Protokoll: %1$d</string>
+  <string name="label_error_connection">Fehler beim Herstellen der Verbindung: %1$s (%2$s)</string>
+  <string name="label_error_connection_closed">Fehler: Verbindung wurde unerwartet getrennt</string>
 </resources>
diff --git a/app/src/main/res/values-fr-rCA/strings_error.xml b/app/src/main/res/values-fr-rCA/strings_error.xml
index 07a4007dd..746990039 100644
--- a/app/src/main/res/values-fr-rCA/strings_error.xml
+++ b/app/src/main/res/values-fr-rCA/strings_error.xml
@@ -38,4 +38,5 @@
     <code>%1$s</code><br/>
     n\'est pas valide pour %2$s.</p>
 ]]></string>
+
 </resources>
diff --git a/app/src/main/res/values-lt/strings_error.xml b/app/src/main/res/values-lt/strings_error.xml
index e2f958c7a..dab1e0ed7 100644
--- a/app/src/main/res/values-lt/strings_error.xml
+++ b/app/src/main/res/values-lt/strings_error.xml
@@ -38,4 +38,5 @@
     <code>%1$s</code><br/>
     negalioja %2$s.</p>
   ]]></string>
+
 </resources>
diff --git a/app/src/main/res/values-pt/strings_error.xml b/app/src/main/res/values-pt/strings_error.xml
index 083d35819..425d28f17 100644
--- a/app/src/main/res/values-pt/strings_error.xml
+++ b/app/src/main/res/values-pt/strings_error.xml
@@ -38,4 +38,5 @@
     <code>%1$s</code><br/>
     não é válido para %2$s.</p>
   ]]></string>
+
 </resources>
diff --git a/app/src/main/res/values-sr/strings_error.xml b/app/src/main/res/values-sr/strings_error.xml
index 8c80d94b4..21d16fed2 100644
--- a/app/src/main/res/values-sr/strings_error.xml
+++ b/app/src/main/res/values-sr/strings_error.xml
@@ -38,4 +38,5 @@
     <code>%1$s</code><br/>
     nije važeći za %2$s.</p>
   ]]></string>
+
 </resources>
diff --git a/app/src/main/res/values/strings_error.xml b/app/src/main/res/values/strings_error.xml
index 78f73c922..48336e144 100644
--- a/app/src/main/res/values/strings_error.xml
+++ b/app/src/main/res/values/strings_error.xml
@@ -38,4 +38,9 @@
     <code>%1$s</code><br/>
     is not valid for %2$s.</p>
   ]]></string>
+
+  <string name="label_error_unknown_host">Host not found: %1$s</string>
+  <string name="label_error_invalid_protocol_version">Invalid protocol: %1$d</string>
+  <string name="label_error_connection">Error encountered during connection: %1$s (%2$s)</string>
+  <string name="label_error_connection_closed">Error: Connection was unexpectedly closed</string>
 </resources>
diff --git a/lib/src/main/java/de/kuschku/libquassel/connection/CoreConnection.kt b/lib/src/main/java/de/kuschku/libquassel/connection/CoreConnection.kt
index 4a6284f5a..fd70f1edb 100644
--- a/lib/src/main/java/de/kuschku/libquassel/connection/CoreConnection.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/CoreConnection.kt
@@ -56,7 +56,8 @@ class CoreConnection(
   private val hostnameVerifier: HostnameVerifier,
   private val address: SocketAddress,
   private val handlerService: HandlerService,
-  private val securityExceptionCallback: (QuasselSecurityException) -> Unit
+  private val securityExceptionCallback: (QuasselSecurityException) -> Unit,
+  private val exceptionCallback: (Throwable) -> Unit
 ) : Thread(), Closeable {
   companion object {
     private const val TAG = "CoreConnection"
@@ -146,7 +147,7 @@ class CoreConnection(
         )
       }
       else -> {
-        throw IllegalArgumentException("Invalid Protocol Version: $protocol")
+        throw ProtocolVersionException(protocol)
       }
     }
   }
@@ -241,6 +242,7 @@ class CoreConnection(
         log(WARN, TAG, "Error encountered in connection", e)
         log(WARN, TAG, "Last sent message: ${MessageRunnable.lastSent.get()}")
         close()
+        exceptionCallback(e)
       }
     }
   }
diff --git a/lib/src/main/java/de/kuschku/libquassel/connection/ProtocolVersionException.kt b/lib/src/main/java/de/kuschku/libquassel/connection/ProtocolVersionException.kt
new file mode 100644
index 000000000..64942c9be
--- /dev/null
+++ b/lib/src/main/java/de/kuschku/libquassel/connection/ProtocolVersionException.kt
@@ -0,0 +1,9 @@
+package de.kuschku.libquassel.connection
+
+import de.kuschku.libquassel.quassel.ProtocolInfo
+import java.net.ConnectException
+
+class ProtocolVersionException(val protocol: ProtocolInfo) : ConnectException() {
+  override val message: String?
+    get() = "Invalid Protocol Version: $protocol"
+}
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
index 11e5244d5..d9316a931 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
@@ -61,6 +61,7 @@ interface ISession : Closeable {
 
   val proxy: SignalProxy
   val error: Flowable<Error>
+  val connectionError: Flowable<Throwable>
   val lag: Observable<Long>
 
   fun login(user: String, pass: String)
@@ -69,6 +70,8 @@ interface ISession : Closeable {
     val NULL = object : ISession {
       override val proxy: SignalProxy = SignalProxy.NULL
       override val error = BehaviorSubject.create<Error>().toFlowable(BackpressureStrategy.BUFFER)
+      override val connectionError = BehaviorSubject.create<Throwable>().toFlowable(
+        BackpressureStrategy.BUFFER)
       override val state = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED)
       override val features: Features = Features(
         QuasselFeatures.empty(),
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
index e7710038f..b723f7a86 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -58,13 +58,24 @@ class Session(
     get() = coreConnection.sslSession
 
   private val coreConnection = CoreConnection(
-    this, clientData, features, trustManager, hostnameVerifier, address, handlerService, ::handle
+    this,
+    clientData,
+    features,
+    trustManager,
+    hostnameVerifier,
+    address,
+    handlerService,
+    ::handle,
+    ::handleConnectionError
   )
   override val state = coreConnection.state
 
   private val _error = PublishSubject.create<Error>()
   override val error = _error.toFlowable(BackpressureStrategy.BUFFER)
 
+  private val _connectionError = PublishSubject.create<Throwable>()
+  override val connectionError = _connectionError.toFlowable(BackpressureStrategy.LATEST)
+
   override val aliasManager = AliasManager(this)
   override val backlogManager = BacklogManager(this, backlogStorage)
   override val bufferViewManager = BufferViewManager(this)
@@ -148,6 +159,10 @@ class Session(
     _error.onNext(Error.SslError(f))
   }
 
+  fun handleConnectionError(f: Throwable) {
+    _connectionError.onNext(f)
+  }
+
   fun addNetwork(networkId: NetworkId) {
     val network = Network(networkId, this)
     networks[networkId] = network
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
index fccae0ba2..9fd414ee3 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -74,6 +74,11 @@ class SessionManager(
       .toFlowable(BackpressureStrategy.LATEST)
       .switchMap(ISession::error)
 
+  val connectionError: Flowable<Throwable>
+    get() = inProgressSession
+      .toFlowable(BackpressureStrategy.LATEST)
+      .switchMap(ISession::connectionError)
+
   val connectionProgress: Observable<Triple<ConnectionState, Int, Int>> = Observable.combineLatest(
     state, initStatus,
     BiFunction<ConnectionState, Pair<Int, Int>, Triple<ConnectionState, Int, Int>> { t1, t2 ->
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
index e4332e416..48212a5ad 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
@@ -99,6 +99,10 @@ class QuasselViewModel : ViewModel() {
     it.orNull()?.error ?: Flowable.empty()
   }
 
+  val connectionErrors = sessionManager.toFlowable(BackpressureStrategy.LATEST).switchMap {
+    it.orNull()?.connectionError ?: Flowable.empty()
+  }
+
   val sslSession = session.flatMapSwitchMap(ISession::sslSession)
 
   val coreInfo = session.mapMapNullable(ISession::coreInfo).mapSwitchMap(CoreInfo::liveInfo)
-- 
GitLab