diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cbd8970d024d66dd1ee40aeb23226a863cf9430e..a7bdf862067406ba26803706100c91216fa85a46 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="de.kuschku.quasseldroid_ng"> <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application android:name=".QuasseldroidNG" diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt index 93b188ec0f2d12f6dbd05ce385a8702dfaf0d39a..7783f763f3ca5e3dce215f7f81342e8b0da4dfad 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.kt @@ -3,52 +3,132 @@ package de.kuschku.quasseldroid_ng.service import android.annotation.SuppressLint import android.arch.lifecycle.LifecycleService import android.arch.lifecycle.Observer -import android.content.Intent -import android.content.SharedPreferences +import android.content.* +import android.net.ConnectivityManager import android.os.Binder -import android.support.v7.preference.PreferenceManager import de.kuschku.libquassel.protocol.* import de.kuschku.libquassel.session.* import de.kuschku.quasseldroid_ng.BuildConfig +import de.kuschku.quasseldroid_ng.Keys import de.kuschku.quasseldroid_ng.R +import de.kuschku.quasseldroid_ng.persistence.AccountDatabase import de.kuschku.quasseldroid_ng.persistence.QuasselBacklogStorage import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase import de.kuschku.quasseldroid_ng.ui.settings.data.ConnectionSettings import de.kuschku.quasseldroid_ng.ui.settings.data.Settings import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread +import de.kuschku.quasseldroid_ng.util.NotificationManager import de.kuschku.quasseldroid_ng.util.compatibility.AndroidHandlerService +import de.kuschku.quasseldroid_ng.util.helper.sharedPreferences import de.kuschku.quasseldroid_ng.util.helper.toLiveData +import io.reactivex.Observable +import io.reactivex.functions.BiFunction +import io.reactivex.subjects.BehaviorSubject import org.threeten.bp.Instant import java.security.cert.X509Certificate import java.util.concurrent.TimeUnit import javax.net.ssl.X509TrustManager -class QuasselService : LifecycleService(), SharedPreferences.OnSharedPreferenceChangeListener { +class QuasselService : LifecycleService(), + SharedPreferences.OnSharedPreferenceChangeListener { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { + update() + } + + private fun update() { val connectionSettings = Settings.connection(this) - if (this.connectionSettings.showNotification != connectionSettings.showNotification) { + if (this.connectionSettings?.showNotification != connectionSettings.showNotification) { this.connectionSettings = connectionSettings - updateNotification(connectionSettings.showNotification) + updateNotificationStatus() + } + + val (accountId, reconnect) = this.sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) { + Pair( + getLong(Keys.Status.selectedAccount, -1), + getBoolean(Keys.Status.reconnect, false) + ) + } + if (this.accountId != accountId || this.reconnect != reconnect) { + this.accountId = accountId + this.reconnect = reconnect + + updateConnection(accountId, reconnect) } } - private lateinit var notificationManager: de.kuschku.quasseldroid_ng.util.NotificationManager + private var accountId: Long = -1 + private var reconnect: Boolean = false + + private lateinit var notificationManager: NotificationManager + private var notificationHandle: NotificationManager.Handle? = null + private var progress = Triple(ConnectionState.DISCONNECTED, 0, 0) - private fun updateNotification(showNotification: Boolean) { - if (showNotification) { - val (id, notification) = notificationManager.notificationBackground() - startForeground(id, notification) + private fun updateNotificationStatus() { + if (connectionSettings?.showNotification == true) { + val notificationHandle = notificationManager.notificationBackground() + this.notificationHandle = notificationHandle + updateNotification(notificationHandle) + startForeground(notificationHandle.id, notificationHandle.builder.build()) } else { stopForeground(true) } } + private fun updateNotification(handle: NotificationManager.Handle) { + val (state, progress, max) = this.progress + when (state) { + ConnectionState.DISCONNECTED -> { + handle.builder.setContentTitle(getString(R.string.label_status_disconnected)) + handle.builder.setProgress(0, 0, false) + } + ConnectionState.CONNECTING -> { + handle.builder.setContentTitle(getString(R.string.label_status_connecting)) + handle.builder.setProgress(max, progress, true) + } + ConnectionState.HANDSHAKE -> { + handle.builder.setContentTitle(getString(R.string.label_status_handshake)) + handle.builder.setProgress(max, progress, true) + } + ConnectionState.INIT -> { + handle.builder.setContentTitle(getString(R.string.label_status_init)) + handle.builder.setProgress(max, progress, false) + } + ConnectionState.CONNECTED -> { + handle.builder.setContentTitle(getString(R.string.label_status_connected)) + handle.builder.setProgress(0, 0, false) + } + } + } + + private fun updateConnection(accountId: Long, reconnect: Boolean) { + handler.post { + val account = if (accountId == -1L || !reconnect) { + null + } else { + AccountDatabase.Creator.init(this).accounts().findById(accountId) + } + + if (account == null) { + sessionManager.state.toLiveData() + backendImplementation.disconnect(true) + stopSelf() + } else { + backendImplementation.connectUnlessConnected( + SocketAddress(account.host, account.port), + account.user, + account.pass, + true + ) + } + } + } + private lateinit var sessionManager: SessionManager private lateinit var clientData: ClientData - private lateinit var connectionSettings: ConnectionSettings + private var connectionSettings: ConnectionSettings? = null private val trustManager = object : X509TrustManager { @SuppressLint("TrustAllX509TrustManager") @@ -127,6 +207,15 @@ class QuasselService : LifecycleService(), SharedPreferences.OnSharedPreferenceC private lateinit var database: QuasselDatabase + private val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (context != null && intent != null) { + connectivity.onNext(Unit) + } + } + } + private val connectivity = BehaviorSubject.createDefault(Unit) + override fun onCreate() { handler.onCreate() super.onCreate() @@ -142,25 +231,42 @@ class QuasselService : LifecycleService(), SharedPreferences.OnSharedPreferenceC ), supportedProtocols = listOf(Protocol.Datastream) ) - sessionManager.state - .filter { it == ConnectionState.DISCONNECTED } + + sessionManager.connectionProgress.toLiveData().observe(this, Observer { + this.progress = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0) + val handle = this.notificationHandle + if (handle != null) { + updateNotification(handle) + notificationManager.notify(handle) + } + }) + + Observable.combineLatest( + sessionManager.state.filter { it == ConnectionState.DISCONNECTED }, + connectivity, + BiFunction { a: ConnectionState, b: Unit -> a to b }) .delay(200, TimeUnit.MILLISECONDS) .throttleFirst(1, TimeUnit.SECONDS) .toLiveData() .observe( this, Observer { - sessionManager.reconnect() + sessionManager.reconnect(true) } ) - connectionSettings = Settings.connection(this) + sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) { + registerOnSharedPreferenceChangeListener(this@QuasselService) + } + sharedPreferences { + registerOnSharedPreferenceChangeListener(this@QuasselService) + } - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(this) + registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) - notificationManager = de.kuschku.quasseldroid_ng.util.NotificationManager(this) + notificationManager = NotificationManager(this) notificationManager.init() - updateNotification(connectionSettings.showNotification) + + update() } override fun onBind(intent: Intent?): QuasselBinder { diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt index ad10acecc968079a04fd78cfb67c64ed184875d9..a5f4eb6fac183ccec97c4d82536e5207937864e1 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt @@ -21,19 +21,20 @@ import com.sothree.slidinguppanel.SlidingUpPanelLayout import de.kuschku.libquassel.protocol.Message import de.kuschku.libquassel.protocol.Message_Type import de.kuschku.libquassel.session.ConnectionState -import de.kuschku.libquassel.session.SocketAddress import de.kuschku.libquassel.util.and import de.kuschku.libquassel.util.or import de.kuschku.quasseldroid_ng.Keys import de.kuschku.quasseldroid_ng.R -import de.kuschku.quasseldroid_ng.persistence.AccountDatabase import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase import de.kuschku.quasseldroid_ng.ui.settings.SettingsActivity import de.kuschku.quasseldroid_ng.ui.settings.data.BacklogSettings import de.kuschku.quasseldroid_ng.ui.settings.data.Settings import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread -import de.kuschku.quasseldroid_ng.util.helper.* +import de.kuschku.quasseldroid_ng.util.helper.editApply +import de.kuschku.quasseldroid_ng.util.helper.invoke +import de.kuschku.quasseldroid_ng.util.helper.let +import de.kuschku.quasseldroid_ng.util.helper.sharedPreferences import de.kuschku.quasseldroid_ng.util.service.ServiceBoundActivity import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar @@ -133,57 +134,22 @@ class ChatActivity : ServiceBoundActivity() { ) drawerToggle.syncState() - backend.observeSticky( - this, Observer { backendValue -> - if (backendValue != null) { - val database = AccountDatabase.Creator.init(this) - handler.post { - if (accountId == -1L) { - setResult(Activity.RESULT_OK) - finish() - } - val account = database.accounts().findById(accountId) - if (account == null) { - setResult(Activity.RESULT_OK) - finish() - } else { - backendValue.connectUnlessConnected( - SocketAddress(account.host, account.port), - account.user, - account.pass, - true - ) - } + viewModel.connectionProgress.observe(this, Observer { it -> + val (state, progress, max) = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0) + when (state) { + ConnectionState.CONNECTED, ConnectionState.DISCONNECTED -> { + progressBar.hide() + } + ConnectionState.INIT -> { + progressBar.isIndeterminate = true + } + else -> { + progressBar.isIndeterminate = false + progressBar.progress = progress + progressBar.max = max } } - } - ) - - viewModel.connectionState.observe( - this, Observer { - val status = it ?: ConnectionState.DISCONNECTED - - if (status == ConnectionState.CONNECTED) { - progressBar.progress = 1 - progressBar.max = 1 - } else { - progressBar.isIndeterminate = status != ConnectionState.INIT - } - - progressBar.toggle( - status != ConnectionState.CONNECTED && status != ConnectionState.DISCONNECTED - ) - } - ) - - viewModel.initState.observe( - this, Observer { - val (progress, max) = it ?: 0 to 0 - - progressBar.max = max - progressBar.progress = progress - } - ) + }) editorPanel.addPanelSlideListener(panelSlideListener) editorPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED @@ -291,10 +257,11 @@ class ChatActivity : ServiceBoundActivity() { } R.id.disconnect -> { handler.post { - getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE).editApply { - putBoolean(Keys.Status.reconnect, false) + sharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE) { + editApply { + putBoolean(Keys.Status.reconnect, false) + } } - backend()?.disconnect(true) setResult(Activity.RESULT_OK) finish() } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt index 954983940daef9a70ab70da2805e63b2208ffd2f..d1214a1e62ca526e56a96f7ef41918db40aeb526 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt @@ -48,8 +48,7 @@ class QuasselViewModel : ViewModel() { val sessionManager = backend.map { it.sessionManager() } val session = sessionManager.switchMapRx { it.session } - val connectionState = sessionManager.switchMapRx { it.state } - val initState = sessionManager.switchMapRx { it.initStatus } + val connectionProgress = sessionManager.switchMapRx { it.connectionProgress } private val bufferViewManager = session.map(ISession::bufferViewManager) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/NotificationManager.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/NotificationManager.kt index 6977a21982f2242eb0f16ee33811f76e778b4ef2..74b7ad5384d3d251824e8994b030b94cf6c8527b 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/NotificationManager.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/NotificationManager.kt @@ -1,7 +1,6 @@ package de.kuschku.quasseldroid_ng.util import android.annotation.TargetApi -import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent @@ -9,6 +8,7 @@ import android.content.Context import android.content.Intent import android.os.Build import android.support.v4.app.NotificationCompat +import android.support.v4.app.NotificationManagerCompat import de.kuschku.quasseldroid_ng.R import de.kuschku.quasseldroid_ng.ui.setup.accounts.AccountSelectionActivity import de.kuschku.quasseldroid_ng.util.helper.editApply @@ -49,7 +49,7 @@ class NotificationManager(private val context: Context) { id } - fun notificationBackground(): Pair<Int, Notification> { + fun notificationBackground(): Handle { val notification = NotificationCompat.Builder( context.applicationContext, context.getString(R.string.notification_channel_background) @@ -60,15 +60,21 @@ class NotificationManager(private val context: Context) { Intent(context.applicationContext, AccountSelectionActivity::class.java), 0 ) ) - .setContentText(context.getString(R.string.label_running_in_background)) .setSmallIcon(R.mipmap.ic_launcher_recents) .setPriority(NotificationCompat.PRIORITY_MIN) - .build() + return Handle(BACKGROUND_NOTIFICATION_ID, notification) + } - return BACKGROUND_NOTIFICATION_ID to notification + fun notify(handle: Handle) { + NotificationManagerCompat.from(context).notify(handle.id, handle.builder.build()) } companion object { val BACKGROUND_NOTIFICATION_ID = Int.MAX_VALUE } + + data class Handle( + val id: Int, + val builder: NotificationCompat.Builder + ) } \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ContextHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ContextHelper.kt index c1dd7d784a2d017ed4818af6363744cf382ebcbf..4d656cc54e30e11d38a3127dcc5d3831efc090b4 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ContextHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/ContextHelper.kt @@ -42,5 +42,10 @@ fun Context.getCompatColor(@ColorRes id: Int): Int { } -fun <T> Context.sharedPreferences(f: SharedPreferences.() -> T) = - PreferenceManager.getDefaultSharedPreferences(this).f() \ No newline at end of file +fun <T> Context.sharedPreferences(name: String? = null, mode: Int = 0, + f: SharedPreferences.() -> T) = + if (name == null) { + PreferenceManager.getDefaultSharedPreferences(this).f() + } else { + getSharedPreferences(name, mode).f() + } \ No newline at end of file diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt deleted file mode 100644 index 87ecb1c85784704d50856c08d9eef5068f651814..0000000000000000000000000000000000000000 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt +++ /dev/null @@ -1,8 +0,0 @@ -package de.kuschku.quasseldroid_ng.util.helper - -import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar - -fun MaterialContentLoadingProgressBar.toggle(visible: Boolean) { - if (visible) show() - else hide() -} \ 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 34fc3bcec6315076ff1e79ee64403e1982f3e213..96a8e3ab66e7476d32851a536d810e3970af4f36 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,11 +13,16 @@ <string name="label_filter_messages">Filter Messages</string> <string name="label_input_history">Input History</string> <string name="label_placeholder">Write a messageā¦</string> - <string name="label_running_in_background">Running in background</string> <string name="label_save">Save</string> <string name="label_select_multiple">Select</string> <string name="label_settings">Settings</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_background_title">Background</string> <string name="notification_channel_highlight" translatable="false">highlight</string> 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 2732d93f1e98f0a93a1a35af1b24c9d1442de5d9..a657f67f5c6b81f4780c2c0b22687e89a031b015 100644 --- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt @@ -11,6 +11,7 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler import de.kuschku.libquassel.util.compatibility.log import de.kuschku.libquassel.util.helpers.or import io.reactivex.Observable +import io.reactivex.functions.BiFunction import io.reactivex.subjects.BehaviorSubject import javax.net.ssl.SSLSession import javax.net.ssl.X509TrustManager @@ -53,9 +54,40 @@ class SessionManager(offlineSession: ISession, val backlogStorage: BacklogStorag override fun close() = session.or(lastSession).close() + private var lastClientData: ClientData? = null + private var lastTrustManager: X509TrustManager? = null + private var lastAddress: SocketAddress? = null + private var lastHandlerService: (() -> HandlerService)? = null + private var lastUserData: Pair<String, String>? = null + private var lastShouldReconnect = false + + private var inProgressSession = BehaviorSubject.createDefault(ISession.NULL) + private var lastSession: ISession = offlineSession + override val state: Observable<ConnectionState> = inProgressSession.switchMap { it.state } + + override val initStatus: Observable<Pair<Int, Int>> = inProgressSession.switchMap { it.initStatus } + val session: Observable<ISession> = state.map { connectionState -> + if (connectionState == ConnectionState.CONNECTED) + inProgressSession.value + else + lastSession + } + + val connectionProgress: Observable<Triple<ConnectionState, Int, Int>> = Observable.combineLatest( + state, initStatus, + BiFunction<ConnectionState, Pair<Int, Int>, Triple<ConnectionState, Int, Int>> { t1, t2 -> + Triple(t1, t2.first, t2.second) + }) + init { log(LoggingHandler.LogLevel.INFO, "Session", "Session created") + state.subscribe { + if (state == ConnectionState.CONNECTED) { + lastSession.close() + } + } + // This should preload them Invokers } @@ -93,15 +125,8 @@ class SessionManager(offlineSession: ISession, val backlogStorage: BacklogStorag ) } - private var lastClientData: ClientData? = null - private var lastTrustManager: X509TrustManager? = null - private var lastAddress: SocketAddress? = null - private var lastHandlerService: (() -> HandlerService)? = null - private var lastUserData: Pair<String, String>? = null - private var lastShouldReconnect = false - - fun reconnect() { - if (lastShouldReconnect) { + fun reconnect(forceReconnect: Boolean = false) { + if (lastShouldReconnect || forceReconnect) { val clientData = lastClientData val trustManager = lastTrustManager val address = lastAddress @@ -109,8 +134,10 @@ class SessionManager(offlineSession: ISession, val backlogStorage: BacklogStorag val userData = lastUserData if (clientData != null && trustManager != null && address != null && handlerService != null && userData != null) { - ifDisconnected { - connect(clientData, trustManager, address, handlerService, userData) + if (state.or( + ConnectionState.DISCONNECTED + ) == ConnectionState.DISCONNECTED || forceReconnect) { + connect(clientData, trustManager, address, handlerService, userData, forceReconnect) } } } @@ -122,15 +149,4 @@ class SessionManager(offlineSession: ISession, val backlogStorage: BacklogStorag inProgressSession.value.close() inProgressSession.onNext(ISession.NULL) } - - private var inProgressSession = BehaviorSubject.createDefault(ISession.NULL) - private var lastSession: ISession = offlineSession - override val state: Observable<ConnectionState> = inProgressSession.switchMap { it.state } - override val initStatus: Observable<Pair<Int, Int>> = inProgressSession.switchMap { it.initStatus } - val session: Observable<ISession> = state.map { connectionState -> - if (connectionState == ConnectionState.CONNECTED) - inProgressSession.value - else - lastSession - } }