From d8c8a65d3f6538f1eb23dc6308438e0794a1ba1b Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Fri, 27 Mar 2020 11:09:01 +0100 Subject: [PATCH 01/44] Add support for configuring multiple OH targets. Only one can be active at a time, though. Closes #10 Signed-off-by: Danny Baumann --- .../habdroid/core/CloudMessagingHelper.kt | 4 +- .../habdroid/core/UpdateBroadcastReceiver.kt | 68 ++- .../core/connection/AbstractConnection.kt | 11 +- .../core/connection/ConnectionFactory.kt | 71 ++-- .../core/connection/DefaultConnection.kt | 7 +- .../core/connection/DemoConnection.kt | 3 +- .../habdroid/model/ServerConfiguration.kt | 103 +++++ .../habdroid/ui/ConnectionWebViewClient.kt | 4 +- .../org/openhab/habdroid/ui/MainActivity.kt | 213 +++++++--- .../habdroid/ui/PreferencesActivity.kt | 402 +++++++++++------- .../openhab/habdroid/util/ExtensionFuncs.kt | 8 + .../openhab/habdroid/util/PrefExtensions.kt | 26 +- .../org/openhab/habdroid/util/PrefKeys.kt | 20 +- .../main/res/drawable/ic_menu_down_24dp.xml | 7 + .../src/main/res/drawable/ic_menu_up_24dp.xml | 7 + mobile/src/main/res/layout/drawer_header.xml | 46 ++ mobile/src/main/res/menu/left_drawer.xml | 73 ++-- mobile/src/main/res/menu/server_editor.xml | 27 ++ mobile/src/main/res/values/strings.xml | 6 + .../res/xml/local_connection_preferences.xml | 9 +- mobile/src/main/res/xml/preferences.xml | 26 +- .../res/xml/remote_connection_preferences.xml | 9 +- .../src/main/res/xml/server_preferences.xml | 38 ++ 23 files changed, 824 insertions(+), 364 deletions(-) create mode 100644 mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt create mode 100644 mobile/src/main/res/drawable/ic_menu_down_24dp.xml create mode 100644 mobile/src/main/res/drawable/ic_menu_up_24dp.xml create mode 100644 mobile/src/main/res/layout/drawer_header.xml create mode 100644 mobile/src/main/res/menu/server_editor.xml create mode 100644 mobile/src/main/res/xml/server_preferences.xml diff --git a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt index 1961222f35..e95e778e49 100644 --- a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt +++ b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt @@ -25,7 +25,7 @@ import org.openhab.habdroid.util.HttpClient import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.getHumanReadableErrorMessage import org.openhab.habdroid.util.getPrefs -import org.openhab.habdroid.util.getStringOrEmpty +import org.openhab.habdroid.util.getRemoteUrl object CloudMessagingHelper { private val TAG = CloudMessagingHelper::class.java.simpleName @@ -58,7 +58,7 @@ object CloudMessagingHelper { context.getString(R.string.push_notification_status_disabled), R.drawable.ic_bell_off_outline_grey_24dp ) - context.getPrefs().getStringOrEmpty(PrefKeys.REMOTE_URL).isEmpty() -> PushNotificationStatus( + context.getPrefs().getRemoteUrl().isEmpty() -> PushNotificationStatus( context.getString(R.string.push_notification_status_no_remote_configured), R.drawable.ic_bell_off_outline_grey_24dp ) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt index 1df3dbe0ef..ee17bb2519 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt @@ -28,6 +28,8 @@ import org.openhab.habdroid.BuildConfig import org.openhab.habdroid.R import org.openhab.habdroid.background.EventListenerService import org.openhab.habdroid.background.tiles.AbstractTileService +import org.openhab.habdroid.model.ServerConfiguration +import org.openhab.habdroid.model.ServerPath import org.openhab.habdroid.model.putIconResource import org.openhab.habdroid.model.toOH2IconResource import org.openhab.habdroid.ui.PreferencesActivity @@ -37,6 +39,7 @@ import org.openhab.habdroid.util.getDayNightMode import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrNull +import org.openhab.habdroid.util.putConfiguredServerIds class UpdateBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -48,32 +51,28 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { prefs.edit { if (prefs.getInt(PrefKeys.COMPARABLE_VERSION, 0) <= UPDATE_LOCAL_CREDENTIALS) { Log.d(TAG, "Checking for putting local username/password to remote username/password.") - if (prefs.getStringOrNull(PrefKeys.REMOTE_USERNAME) == null) { - putString(PrefKeys.REMOTE_USERNAME, - prefs.getStringOrNull(PrefKeys.LOCAL_USERNAME)) + if (prefs.getStringOrNull("default_openhab_remote_username") == null) { + putString("default_openhab_remote_username", prefs.getStringOrNull("default_openhab_username")) } - if (prefs.getStringOrNull(PrefKeys.REMOTE_PASSWORD) == null) { - putString(PrefKeys.REMOTE_PASSWORD, - prefs.getStringOrNull(PrefKeys.LOCAL_PASSWORD)) + if (prefs.getStringOrNull("default_openhab_remote_password") == null) { + putString("default_openhab_remote_password", prefs.getStringOrNull("default_openhab_password")) } } if (prefs.getInt(PrefKeys.COMPARABLE_VERSION, 0) <= SECURE_CREDENTIALS) { Log.d(TAG, "Put username/password to encrypted prefs.") context.getSecretPrefs().edit { - putString(PrefKeys.LOCAL_USERNAME, - prefs.getStringOrNull(PrefKeys.LOCAL_USERNAME)) - putString(PrefKeys.LOCAL_PASSWORD, - prefs.getStringOrNull(PrefKeys.LOCAL_PASSWORD)) - putString(PrefKeys.REMOTE_USERNAME, - prefs.getStringOrNull(PrefKeys.REMOTE_USERNAME)) - putString(PrefKeys.REMOTE_PASSWORD, - prefs.getStringOrNull(PrefKeys.REMOTE_PASSWORD)) + putString("default_openhab_username", prefs.getStringOrNull("default_openhab_username")) + putString("default_openhab_password", prefs.getStringOrNull("default_openhab_password")) + putString("default_openhab_remote_username", + prefs.getStringOrNull("default_openhab_remote_username")) + putString("default_openhab_remote_password", + prefs.getStringOrNull("default_openhab_remote_password")) } // Clear from unencrypted prefs - remove(PrefKeys.LOCAL_USERNAME) - remove(PrefKeys.LOCAL_PASSWORD) - remove(PrefKeys.REMOTE_USERNAME) - remove(PrefKeys.REMOTE_PASSWORD) + remove("default_openhab_username") + remove("default_openhab_password") + remove("default_openhab_remote_username") + remove("default_openhab_remote_password") } if (prefs.getInt(PrefKeys.COMPARABLE_VERSION, 0) <= DARK_MODE) { Log.d(TAG, "Migrate to day/night themes") @@ -113,6 +112,38 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { Log.d(TAG, "Update widgets") ItemUpdateWidget.updateAllWidgets(context) } + if (prefs.getInt(PrefKeys.COMPARABLE_VERSION, 0) <= MULTI_SERVER_SUPPORT) { + // if local or remote server URL are set, convert them to a server named 'openHAB' + val localUrl = prefs.getStringOrNull("default_openhab_url") + val remoteUrl = prefs.getStringOrNull("default_openhab_alturl") + if (localUrl != null || remoteUrl != null) { + val secretPrefs = context.getSecretPrefs() + val localPath = localUrl?.let { url -> ServerPath(url, + secretPrefs.getStringOrNull("default_openhab_username"), + secretPrefs.getStringOrNull("default_openhab_password") + ) } + val remotePath = remoteUrl?.let { url -> ServerPath(url, + secretPrefs.getStringOrNull("default_openhab_remote_username"), + secretPrefs.getStringOrNull("default_openhab_remote_password") + ) } + val config = ServerConfiguration(1, "openHAB", localPath, remotePath, + prefs.getStringOrNull("default_openhab_sslclientcert")) + config.saveToPrefs(prefs, secretPrefs) + prefs.edit { + putConfiguredServerIds(setOf(config.id)) + putInt(PrefKeys.ACTIVE_SERVER_ID, config.id) + remove("default_openhab_url") + remove("default_openhab_alturl") + remove("default_openhab_sslclientcert") + } + secretPrefs.edit { + remove("default_openhab_username") + remove("default_openhab_password") + remove("default_openhab_remote_username") + remove("default_openhab_remote_password") + } + } + } updateComparableVersion(this) } @@ -132,6 +163,7 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { private const val SECURE_CREDENTIALS = 190 private const val DARK_MODE = 200 private const val WIDGET_ICON = 250 + private const val MULTI_SERVER_SUPPORT = 274 fun updateComparableVersion(editor: SharedPreferences.Editor) { editor.putInt(PrefKeys.COMPARABLE_VERSION, BuildConfig.VERSION_CODE).apply() diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/AbstractConnection.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/AbstractConnection.kt index 5924813e60..b39a870568 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/AbstractConnection.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/AbstractConnection.kt @@ -14,6 +14,7 @@ package org.openhab.habdroid.core.connection import okhttp3.OkHttpClient +import org.openhab.habdroid.model.ServerPath import org.openhab.habdroid.util.HttpClient import java.net.InetAddress @@ -48,17 +49,15 @@ abstract class AbstractConnection : Connection { internal constructor( httpClient: OkHttpClient, connectionType: Int, - baseUrl: String, - username: String?, - password: String? + path: ServerPath ) { val httpClientWithSocketFactory = httpClient.newBuilder() .socketFactory(socketFactory) .build() - this.username = username - this.password = password - this.baseUrl = baseUrl + this.username = path.userName + this.password = path.password + this.baseUrl = path.url this.connectionType = connectionType this.httpClient = HttpClient(httpClientWithSocketFactory, baseUrl, username, password) } diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt index a5d41f5752..85304ae5aa 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt @@ -35,14 +35,15 @@ import org.openhab.habdroid.core.connection.exception.ConnectionException import org.openhab.habdroid.core.connection.exception.ConnectionNotInitializedException import org.openhab.habdroid.core.connection.exception.NetworkNotAvailableException import org.openhab.habdroid.core.connection.exception.NoUrlInformationException +import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.util.CacheManager import org.openhab.habdroid.util.PrefKeys +import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.isDebugModeEnabled import org.openhab.habdroid.util.isDemoModeEnabled -import org.openhab.habdroid.util.toNormalizedUrl import java.net.Socket import java.security.Principal import java.security.PrivateKey @@ -157,16 +158,23 @@ class ConnectionFactory internal constructor( if (key == PrefKeys.DEBUG_MESSAGES) { updateHttpLoggerSettings() } - if (key in CLIENT_CERT_UPDATE_TRIGGERING_KEYS) { + val serverId = sharedPreferences.getActiveServerId() + if (key in UPDATE_TRIGGERING_KEYS || + CLIENT_CERT_UPDATE_TRIGGERING_PREFIXES.any { prefix -> key == PrefKeys.buildServerKey(serverId, prefix) } + ) { updateHttpClientForClientCert(false) } - if (key in UPDATE_TRIGGERING_KEYS) launch { - updateConnections() + if (key in UPDATE_TRIGGERING_KEYS || + UPDATE_TRIGGERING_PREFIXES.any { prefix -> key == PrefKeys.buildServerKey(serverId, prefix) } + ) launch { + // if the active server changed, we need to invalidate the old connection immediately, + // as we don't want the user to see old server data while we're validating the new one + updateConnections(key == PrefKeys.ACTIVE_SERVER_ID) } } @VisibleForTesting - fun updateConnections() { + fun updateConnections(callListenersImmediately: Boolean = false) { if (prefs.isDemoModeEnabled()) { if (localConnection is DemoConnection) { // demo mode already was enabled @@ -177,14 +185,13 @@ class ConnectionFactory internal constructor( updateState(true, available = localConnection, availableFailureReason = null, cloudInitialized = true, cloud = null, cloudFailureReason = null) } else { - localConnection = makeConnection(Connection.TYPE_LOCAL, - PrefKeys.LOCAL_URL, - PrefKeys.LOCAL_USERNAME, PrefKeys.LOCAL_PASSWORD) - remoteConnection = makeConnection(Connection.TYPE_REMOTE, - PrefKeys.REMOTE_URL, - PrefKeys.REMOTE_USERNAME, PrefKeys.REMOTE_PASSWORD) - - updateState(false, null, null, false, null, null) + val config = ServerConfiguration.load(prefs, secretPrefs, prefs.getActiveServerId()) + localConnection = + config?.localPath?.let { path -> DefaultConnection(httpClient, Connection.TYPE_LOCAL, path) } + remoteConnection = + config?.remotePath?.let { path -> DefaultConnection(httpClient, Connection.TYPE_REMOTE, path) } + + updateState(callListenersImmediately, null, null, false, null, null) triggerConnectionUpdateIfNeeded() } } @@ -202,8 +209,12 @@ class ConnectionFactory internal constructor( } private fun updateHttpClientForClientCert(forceUpdate: Boolean) { - val clientCertAlias = if (prefs.isDemoModeEnabled()) // No client cert in demo mode - null else prefs.getStringOrNull(PrefKeys.SSL_CLIENT_CERT) + val clientCertAlias = if (prefs.isDemoModeEnabled()) { + // No client cert in demo mode + null + } else { + prefs.getStringOrNull(PrefKeys.buildServerKey(prefs.getActiveServerId(), PrefKeys.SSL_CLIENT_CERT_PREFIX)) + } val keyManagers = if (clientCertAlias != null) arrayOf(ClientKeyManager(context, clientCertAlias)) else null @@ -298,21 +309,6 @@ class ConnectionFactory internal constructor( } } - private fun makeConnection( - type: Int, - urlKey: String, - userNameKey: String, - passwordKey: String - ): AbstractConnection? { - val url = prefs.getStringOrNull(urlKey)?.toNormalizedUrl() - if (url.isNullOrEmpty()) { - return null - } - return DefaultConnection(httpClient, type, url, - secretPrefs.getStringOrNull(userNameKey), - secretPrefs.getStringOrNull(passwordKey)) - } - private suspend fun checkAvailableConnection(local: Connection?, remote: Connection?): Connection { val available = connectionHelper.currentConnections .sortedBy { type -> when (type) { @@ -420,15 +416,16 @@ class ConnectionFactory internal constructor( companion object { private val TAG = ConnectionFactory::class.java.simpleName - private val CLIENT_CERT_UPDATE_TRIGGERING_KEYS = listOf( - PrefKeys.DEMO_MODE, PrefKeys.SSL_CLIENT_CERT - ) private val UPDATE_TRIGGERING_KEYS = listOf( - PrefKeys.LOCAL_URL, PrefKeys.REMOTE_URL, - PrefKeys.LOCAL_USERNAME, PrefKeys.LOCAL_PASSWORD, - PrefKeys.REMOTE_USERNAME, PrefKeys.REMOTE_PASSWORD, - PrefKeys.SSL_CLIENT_CERT, PrefKeys.DEMO_MODE + PrefKeys.DEMO_MODE, PrefKeys.ACTIVE_SERVER_ID + ) + private val UPDATE_TRIGGERING_PREFIXES = listOf( + PrefKeys.LOCAL_URL_PREFIX, PrefKeys.REMOTE_URL_PREFIX, + PrefKeys.LOCAL_USERNAME_PREFIX, PrefKeys.LOCAL_PASSWORD_PREFIX, + PrefKeys.REMOTE_USERNAME_PREFIX, PrefKeys.REMOTE_PASSWORD_PREFIX, + PrefKeys.SSL_CLIENT_CERT_PREFIX ) + private val CLIENT_CERT_UPDATE_TRIGGERING_PREFIXES = listOf(PrefKeys.SSL_CLIENT_CERT_PREFIX) @VisibleForTesting lateinit var instance: ConnectionFactory diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/DefaultConnection.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/DefaultConnection.kt index b553ee418f..3bb762a461 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/DefaultConnection.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/DefaultConnection.kt @@ -18,6 +18,7 @@ import android.util.Log import kotlinx.coroutines.delay import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient +import org.openhab.habdroid.model.ServerPath import org.openhab.habdroid.util.bindToNetworkIfPossible import java.io.IOException import java.net.InetSocketAddress @@ -30,10 +31,8 @@ open class DefaultConnection : AbstractConnection { internal constructor( httpClient: OkHttpClient, connectionType: Int, - baseUrl: String, - username: String?, - password: String? - ) : super(httpClient, connectionType, baseUrl, username, password) + path: ServerPath + ) : super(httpClient, connectionType, path) internal constructor(baseConnection: AbstractConnection, connectionType: Int) : super(baseConnection, connectionType) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/DemoConnection.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/DemoConnection.kt index 1320cdce04..7eed22a271 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/DemoConnection.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/DemoConnection.kt @@ -14,6 +14,7 @@ package org.openhab.habdroid.core.connection import okhttp3.OkHttpClient +import org.openhab.habdroid.model.ServerPath class DemoConnection internal constructor(httpClient: OkHttpClient) : - AbstractConnection(httpClient, Connection.TYPE_REMOTE, "https://demo.openhab.org:8443/", null, null) + AbstractConnection(httpClient, Connection.TYPE_REMOTE, ServerPath("https://demo.openhab.org:8443/", null, null)) diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt new file mode 100644 index 0000000000..8086bd2b94 --- /dev/null +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.habdroid.model + +import android.content.SharedPreferences +import android.os.Parcelable +import androidx.core.content.edit +import kotlinx.android.parcel.Parcelize +import org.openhab.habdroid.util.PrefKeys +import org.openhab.habdroid.util.orDefaultIfEmpty +import org.openhab.habdroid.util.toNormalizedUrl + +@Parcelize +data class ServerPath( + val url: String, + val userName: String?, + val password: String? +) : Parcelable { + fun hasAuthentication() = !userName.isNullOrEmpty() && !password.isNullOrEmpty() + + companion object { + internal fun load( + prefs: SharedPreferences, + secretPrefs: SharedPreferences, + serverId: Int, + urlKeyPrefix: String, + userNamePrefix: String, + passwordPrefix: String + ): ServerPath? { + val url = prefs.getString(PrefKeys.buildServerKey(serverId, urlKeyPrefix), null).toNormalizedUrl() + ?: return null + return ServerPath( + url, + secretPrefs.getString(PrefKeys.buildServerKey(serverId, userNamePrefix), null), + secretPrefs.getString(PrefKeys.buildServerKey(serverId, passwordPrefix), null) + ) + } + } +} + +@Parcelize +data class ServerConfiguration( + val id: Int, + val name: String, + val localPath: ServerPath?, + val remotePath: ServerPath?, + val sslClientCert: String? +) : Parcelable { + fun saveToPrefs(prefs: SharedPreferences, secretPrefs: SharedPreferences) { + prefs.edit { + putString(PrefKeys.buildServerKey(id, PrefKeys.SERVER_NAME_PREFIX), name) + putString(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_URL_PREFIX), localPath?.url) + putString(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_URL_PREFIX), remotePath?.url) + putString(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX), sslClientCert) + } + secretPrefs.edit { + putString(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_USERNAME_PREFIX), localPath?.userName) + putString(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_PASSWORD_PREFIX), localPath?.password) + putString(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_USERNAME_PREFIX), remotePath?.userName) + putString(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_PASSWORD_PREFIX), remotePath?.password) + } + } + fun removeFromPrefs(prefs: SharedPreferences, secretPrefs: SharedPreferences) { + prefs.edit { + remove(PrefKeys.buildServerKey(id, PrefKeys.SERVER_NAME_PREFIX)) + remove(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_URL_PREFIX)) + remove(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_URL_PREFIX)) + remove(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX)) + } + secretPrefs.edit { + remove(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_USERNAME_PREFIX)) + remove(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_PASSWORD_PREFIX)) + remove(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_USERNAME_PREFIX)) + remove(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_PASSWORD_PREFIX)) + } + } + + companion object { + fun load(prefs: SharedPreferences, secretPrefs: SharedPreferences, id: Int): ServerConfiguration? { + val localPath = ServerPath.load(prefs, secretPrefs, id, + PrefKeys.LOCAL_URL_PREFIX, PrefKeys.LOCAL_USERNAME_PREFIX, PrefKeys.LOCAL_PASSWORD_PREFIX) + val remotePath = ServerPath.load(prefs, secretPrefs, id, + PrefKeys.REMOTE_URL_PREFIX, PrefKeys.REMOTE_USERNAME_PREFIX, PrefKeys.REMOTE_PASSWORD_PREFIX) + val serverName = prefs.getString(PrefKeys.buildServerKey(id, PrefKeys.SERVER_NAME_PREFIX), null) + if ((localPath == null && remotePath == null) || serverName.isNullOrEmpty()) { + return null + } + val clientCert = prefs.getString(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX), null) + return ServerConfiguration(id, serverName, localPath, remotePath, clientCert) + } + } +} diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/ConnectionWebViewClient.kt b/mobile/src/main/java/org/openhab/habdroid/ui/ConnectionWebViewClient.kt index 89b5c9eaee..0bd8324f7b 100755 --- a/mobile/src/main/java/org/openhab/habdroid/ui/ConnectionWebViewClient.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/ConnectionWebViewClient.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.launch import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.Connection import org.openhab.habdroid.util.PrefKeys +import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.isDemoModeEnabled @@ -80,7 +81,8 @@ open class ConnectionWebViewClient( return } - val alias = prefs.getStringOrNull(PrefKeys.SSL_CLIENT_CERT) + val serverId = prefs.getActiveServerId() + val alias = prefs.getStringOrNull(PrefKeys.buildServerKey(serverId, PrefKeys.SSL_CLIENT_CERT_PREFIX)) Log.d(TAG, "Using alias $alias") if (alias == null) { request.cancel() diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index 21efd96c19..dab0d1c5f2 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -40,6 +40,8 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.WindowManager +import android.widget.ImageView +import android.widget.TextView import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.appcompat.app.ActionBarDrawerToggle @@ -53,6 +55,8 @@ import androidx.core.graphics.drawable.toDrawable import androidx.core.text.inSpans import androidx.core.view.GravityCompat import androidx.core.view.forEach +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.core.widget.ContentLoadingProgressBar import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.RecyclerView @@ -84,6 +88,8 @@ import org.openhab.habdroid.core.connection.exception.ConnectionNotInitializedEx import org.openhab.habdroid.core.connection.exception.NetworkNotAvailableException import org.openhab.habdroid.core.connection.exception.NoUrlInformationException import org.openhab.habdroid.model.LinkedPage +import org.openhab.habdroid.model.ServerConfiguration +import org.openhab.habdroid.model.ServerPath import org.openhab.habdroid.model.ServerProperties import org.openhab.habdroid.model.Sitemap import org.openhab.habdroid.model.sortedWithDefaultName @@ -101,10 +107,15 @@ import org.openhab.habdroid.util.ScreenLockMode import org.openhab.habdroid.util.Util import org.openhab.habdroid.util.areSitemapsShownInDrawer import org.openhab.habdroid.util.determineDataUsagePolicy +import org.openhab.habdroid.util.getActiveServerId +import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getDefaultSitemap +import org.openhab.habdroid.util.getGroupItems import org.openhab.habdroid.util.getHumanReadableErrorMessage +import org.openhab.habdroid.util.getNextAvailableServerId import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getRemoteUrl +import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.hasPermissions import org.openhab.habdroid.util.isDebugModeEnabled @@ -120,6 +131,9 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { private lateinit var drawerLayout: DrawerLayout private lateinit var drawerToggle: ActionBarDrawerToggle private lateinit var drawerMenu: Menu + private lateinit var drawerModeSelectorContainer: View + private lateinit var drawerModeToggle: ImageView + private lateinit var drawerServerNameView: TextView private var drawerIconTintList: ColorStateList? = null lateinit var viewPool: RecyclerView.RecycledViewPool private set @@ -139,6 +153,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { private var isStarted: Boolean = false private var shortcutManager: ShortcutManager? = null private val backgroundTasksManager = BackgroundTasksManager() + private var inServerSelectionMode = false override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) @@ -204,8 +219,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { showSitemapSelectionDialog() } - updateSitemapAndHabPanelDrawerItems() - updateNotificationDrawerItem() + updateSitemapDrawerEntries() } processIntent(intent) @@ -250,8 +264,8 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { ConnectionFactory.addListener(this) + updateDrawerServerEntries() onAvailableConnectionChanged() - updateNotificationDrawerItem() if (connection != null && serverProperties == null) { controller.clearServerCommunicationFailure() @@ -360,11 +374,11 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { return } if (data.getBooleanExtra(PreferencesActivity.RESULT_EXTRA_SITEMAP_CLEARED, false)) { - updateSitemapAndHabPanelDrawerItems() + updateSitemapDrawerEntries() executeOrStoreAction(PendingAction.ChooseSitemap()) } if (data.getBooleanExtra(PreferencesActivity.RESULT_EXTRA_SITEMAP_DRAWER_CHANGED, false)) { - updateSitemapAndHabPanelDrawerItems() + updateSitemapDrawerEntries() } if (data.getBooleanExtra(PreferencesActivity.RESULT_EXTRA_THEME_CHANGED, false)) { recreate() @@ -414,9 +428,12 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { failureReason = e } - updateNotificationDrawerItem() + if (ConnectionFactory.cloudConnectionOrNull != null) { + manageNotificationShortcut(true) + } if (newConnection != null && newConnection === connection) { + updateDrawerItemVisibility() return } @@ -472,7 +489,8 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } viewPool.clear() - updateSitemapAndHabPanelDrawerItems() + updateSitemapDrawerEntries() + updateDrawerItemVisibility() invalidateOptionsMenu() updateTitle() } @@ -488,7 +506,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { override fun onCloudConnectionChanged(connection: CloudConnection?) { RemoteLog.d(TAG, "onCloudConnectionChanged()") - updateNotificationDrawerItem() + updateDrawerItemVisibility() handlePendingAction() } @@ -541,7 +559,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { retryJob?.cancel(CancellationException("queryServerProperties() was called")) val successCb: (ServerProperties) -> Unit = { props -> serverProperties = props - updateSitemapAndHabPanelDrawerItems() + updateSitemapDrawerEntries() if (props.sitemaps.isEmpty()) { Log.e(TAG, "openHAB returned empty Sitemap list") controller.indicateServerCommunicationFailure(getString(R.string.error_empty_sitemap_list)) @@ -578,9 +596,11 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { val port = info.port.toString() Log.d(TAG, "Service resolved: $address port: $port") - prefs.edit { - putString(PrefKeys.LOCAL_URL, "https://$address:$port") - } + val config = ServerConfiguration(prefs.getNextAvailableServerId(), "openHAB", + ServerPath("https://$address:$port", null, null), + null, null + ) + config.saveToPrefs(prefs, getSecretPrefs()) } else { Log.d(TAG, "onServiceResolveFailed()") controller.indicateMissingConfiguration(true, false) @@ -639,11 +659,15 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { serverProperties!!, connection!!, { props -> serverProperties = props - updateSitemapAndHabPanelDrawerItems() + updateSitemapDrawerEntries() }, this@MainActivity::handlePropertyFetchFailure) } } + override fun onDrawerClosed(drawerView: View) { + super.onDrawerClosed(drawerView) + updateDrawerMode(false) + } }) drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START) @@ -700,73 +724,132 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } } } - if (item.groupId == GROUP_ID_SITEMAPS) { + if (item.groupId == R.id.sitemaps) { val sitemap = serverProperties?.sitemaps?.firstOrNull { s -> s.name.hashCode() == item.itemId } if (sitemap != null) { controller.openSitemap(sitemap) handled = true } } + if (item.groupId == R.id.servers) { + prefs.edit { + putInt(PrefKeys.ACTIVE_SERVER_ID, item.itemId) + } + updateServerNameInDrawer() + // Menu views aren't updated in a click handler, so defer the menu update + launch { + updateDrawerMode(false) + } + handled = true + } handled } + + drawerModeSelectorContainer = drawerView.inflateHeaderView(R.layout.drawer_header) + drawerModeToggle = drawerModeSelectorContainer.findViewById(R.id.drawer_mode_switcher) + drawerServerNameView = drawerModeSelectorContainer.findViewById(R.id.server_name) + drawerModeToggle.setOnClickListener { updateDrawerMode(!inServerSelectionMode) } } - private fun updateNotificationDrawerItem() { - val notificationsItem = drawerMenu.findItem(R.id.notifications) - val hasCloudConnection = ConnectionFactory.cloudConnectionOrNull != null - notificationsItem.isVisible = hasCloudConnection - if (hasCloudConnection) { - manageNotificationShortcut(true) + private fun updateDrawerServerEntries() { + // Remove existing items from server group + drawerMenu.getGroupItems(R.id.servers) + .forEach { item -> drawerMenu.removeItem(item.itemId) } + + // Add new items + val configs = prefs.getConfiguredServerIds() + .mapNotNull { id -> ServerConfiguration.load(prefs, getSecretPrefs(), id) } + configs.forEachIndexed { index, config -> drawerMenu.add(R.id.servers, config.id, index, config.name) } + + if (configs.size > 1) { + drawerModeSelectorContainer.isVisible = true + } else { + drawerModeSelectorContainer.isGone = true + inServerSelectionMode = false } + + updateServerNameInDrawer() + updateDrawerItemVisibility() } - private fun updateSitemapAndHabPanelDrawerItems() { - val sitemapsItem = drawerMenu.findItem(R.id.sitemaps) + private fun updateSitemapDrawerEntries() { val defaultSitemapItem = drawerMenu.findItem(R.id.default_sitemap) - val habPanelItem = drawerMenu.findItem(R.id.habpanel) - val nfcItem = drawerMenu.findItem(R.id.nfc) - val props = serverProperties - if (props == null) { - sitemapsItem.isVisible = false - defaultSitemapItem.isVisible = false - habPanelItem.isVisible = false - nfcItem.isVisible = false + val sitemaps = serverProperties?.sitemaps?.sortedWithDefaultName(prefs.getDefaultSitemap(connection)) + + drawerMenu.getGroupItems(R.id.sitemaps) + .filter { item -> item !== defaultSitemapItem } + .forEach { item -> drawerMenu.removeItem(item.itemId) } + + if (sitemaps?.isNotEmpty() != true) { + return + } + + if (prefs.areSitemapsShownInDrawer()) { + sitemaps.forEachIndexed { index, sitemap -> + val item = drawerMenu.add(R.id.sitemaps, sitemap.name.hashCode(), index, sitemap.label) + loadSitemapIcon(sitemap, item) + } } else { - habPanelItem.isVisible = props.hasHabPanelInstalled() - nfcItem.isVisible = NfcAdapter.getDefaultAdapter(this) != null || Util.isEmulator() - manageHabPanelShortcut(props.hasHabPanelInstalled()) - val sitemaps = props.sitemaps.sortedWithDefaultName(prefs.getDefaultSitemap(connection)) - - if (sitemaps.isEmpty()) { - sitemapsItem.isVisible = false - defaultSitemapItem.isVisible = false - } else if (prefs.areSitemapsShownInDrawer()) { - sitemapsItem.isVisible = true - defaultSitemapItem.isVisible = false - val menu = sitemapsItem.subMenu - menu.clear() - - sitemaps.forEachIndexed { index, sitemap -> - val item = menu.add(GROUP_ID_SITEMAPS, sitemap.name.hashCode(), index, sitemap.label) - loadSitemapIcon(sitemap, item) - } + val sitemap = serverProperties?.sitemaps?.firstOrNull { s -> + s.name == prefs.getDefaultSitemap(connection) + } + if (sitemap != null) { + defaultSitemapItem.title = sitemap.label + loadSitemapIcon(sitemap, defaultSitemapItem) } else { - sitemapsItem.isVisible = false - defaultSitemapItem.isVisible = true - - val sitemap = serverProperties?.sitemaps?.firstOrNull { s -> - s.name == prefs.getDefaultSitemap(connection) - } - if (sitemap != null) { - defaultSitemapItem.title = sitemap.label - loadSitemapIcon(sitemap, defaultSitemapItem) - } else { - defaultSitemapItem.title = getString(R.string.mainmenu_openhab_selectsitemap) - defaultSitemapItem.icon = - applyDrawerIconTint(ContextCompat.getDrawable(this, R.drawable.ic_openhab_appicon_24dp)) - } + defaultSitemapItem.title = getString(R.string.mainmenu_openhab_selectsitemap) + defaultSitemapItem.icon = + applyDrawerIconTint(ContextCompat.getDrawable(this, R.drawable.ic_openhab_appicon_24dp)) } } + + updateDrawerItemVisibility() + } + + private fun updateServerNameInDrawer() { + val activeConfig = ServerConfiguration.load(prefs, getSecretPrefs(), prefs.getActiveServerId()) + drawerServerNameView.text = activeConfig?.name + } + + private fun updateDrawerItemVisibility() { + val serverItems = drawerMenu.getGroupItems(R.id.servers) + drawerMenu.setGroupVisible(R.id.servers, serverItems.size > 1 && inServerSelectionMode) + + if (serverProperties?.sitemaps?.isNotEmpty() == true && !inServerSelectionMode) { + drawerMenu.setGroupVisible(R.id.sitemaps, true) + + val defaultSitemapItem = drawerMenu.findItem(R.id.default_sitemap) + defaultSitemapItem.isVisible = !prefs.areSitemapsShownInDrawer() + } else { + drawerMenu.setGroupVisible(R.id.sitemaps, false) + } + + if (inServerSelectionMode) { + drawerMenu.setGroupVisible(R.id.options, false) + } else { + drawerMenu.setGroupVisible(R.id.options, true) + + val notificationsItem = drawerMenu.findItem(R.id.notifications) + notificationsItem.isVisible = ConnectionFactory.cloudConnectionOrNull != null + + val habPanelItem = drawerMenu.findItem(R.id.habpanel) + habPanelItem.isVisible = serverProperties?.hasHabPanelInstalled() == true + + val nfcItem = drawerMenu.findItem(R.id.nfc) + nfcItem.isVisible = serverProperties != null && + (NfcAdapter.getDefaultAdapter(this) != null || Util.isEmulator()) + } + } + + private fun updateDrawerMode(inServerMode: Boolean) { + if (inServerMode == inServerSelectionMode) { + return + } + inServerSelectionMode = inServerMode + drawerModeToggle.setImageResource( + if (inServerSelectionMode) R.drawable.ic_menu_up_24dp else R.drawable.ic_menu_down_24dp + ) + updateDrawerItemVisibility() } private fun loadSitemapIcon(sitemap: Sitemap, item: MenuItem) { @@ -860,7 +943,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { updateDefaultSitemap(result, connection) } } - updateSitemapAndHabPanelDrawerItems() + updateSitemapDrawerEntries() return result } @@ -884,7 +967,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { updateDefaultSitemap(sitemap, connection) } controller.openSitemap(sitemap) - updateSitemapAndHabPanelDrawerItems() + updateSitemapDrawerEntries() } .show() } @@ -1178,7 +1261,5 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { // Activities request codes private const val REQUEST_CODE_SETTINGS = 1001 private const val REQUEST_CODE_PERMISSIONS = 1002 - // Drawer item codes - private const val GROUP_ID_SITEMAPS = 1 } } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 527dafa95b..23ed038b6f 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -17,7 +17,6 @@ import android.app.Activity import android.app.KeyguardManager import android.content.Context import android.content.Intent -import android.content.SharedPreferences import android.content.pm.PackageManager import android.media.RingtoneManager import android.net.Uri @@ -44,7 +43,7 @@ import androidx.fragment.app.commit import androidx.preference.EditTextPreference import androidx.preference.ListPreference import androidx.preference.Preference -import androidx.preference.PreferenceDataStore +import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference import androidx.preference.SwitchPreferenceCompat @@ -65,6 +64,8 @@ import org.openhab.habdroid.core.CloudMessagingHelper import org.openhab.habdroid.core.connection.CloudConnection import org.openhab.habdroid.core.connection.ConnectionFactory import org.openhab.habdroid.model.Item +import org.openhab.habdroid.model.ServerConfiguration +import org.openhab.habdroid.model.ServerPath import org.openhab.habdroid.model.ServerProperties import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget import org.openhab.habdroid.ui.preference.CustomInputTypePreference @@ -75,7 +76,10 @@ import org.openhab.habdroid.util.CacheManager import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.ToastType import org.openhab.habdroid.util.Util +import org.openhab.habdroid.ui.preference.SslClientCertificatePreference +import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getDayNightMode +import org.openhab.habdroid.util.getNextAvailableServerId import org.openhab.habdroid.util.getNotificationTone import org.openhab.habdroid.util.getPreference import org.openhab.habdroid.util.getPrefixForBgTasks @@ -86,6 +90,7 @@ import org.openhab.habdroid.util.getStringOrFallbackIfEmpty import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.hasPermissions import org.openhab.habdroid.util.isTaskerPluginEnabled +import org.openhab.habdroid.util.putConfiguredServerIds import org.openhab.habdroid.util.showToast import org.openhab.habdroid.util.updateDefaultSitemap @@ -174,25 +179,6 @@ class PreferencesActivity : AbstractBaseActivity() { parentActivity.supportActionBar?.setTitle(titleResId) } - protected fun isConnectionHttps(url: String?): Boolean { - return url != null && url.startsWith("https://") - } - - private fun hasConnectionBasicAuthentication(user: String?, password: String?): Boolean { - return !user.isNullOrEmpty() && !password.isNullOrEmpty() - } - - private fun hasClientCertificate(): Boolean { - return prefs.getStringOrEmpty(PrefKeys.SSL_CLIENT_CERT).isNotEmpty() - } - - protected fun isConnectionSecure(url: String?, user: String?, password: String?): Boolean { - if (!isConnectionHttps(url)) { - return false - } - return hasConnectionBasicAuthentication(user, password) || hasClientCertificate() - } - override fun onDisplayPreferenceDialog(preference: Preference?) { if (preference == null) { return @@ -244,14 +230,9 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onStart() { super.onStart() - updateConnectionSummary(PrefKeys.SUBSCREEN_LOCAL_CONNECTION, - PrefKeys.LOCAL_URL, PrefKeys.LOCAL_USERNAME, - PrefKeys.LOCAL_PASSWORD) - updateConnectionSummary(PrefKeys.SUBSCREEN_REMOTE_CONNECTION, - PrefKeys.REMOTE_URL, PrefKeys.REMOTE_USERNAME, - PrefKeys.REMOTE_PASSWORD) updateScreenLockStateAndSummary(prefs.getStringOrFallbackIfEmpty(PrefKeys.SCREEN_LOCK, getString(R.string.settings_screen_lock_off_value))) + populateServerPrefs() ConnectionFactory.addListener(this) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { updateTileSummary() @@ -266,8 +247,7 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences) - val localConnPref = getPreference(PrefKeys.SUBSCREEN_LOCAL_CONNECTION) - val remoteConnPref = getPreference(PrefKeys.SUBSCREEN_REMOTE_CONNECTION) + val addServerPref = getPreference("add_server") val sendDeviceInfoPref = getPreference(PrefKeys.SUBSCREEN_SEND_DEVICE_INFO) notificationPollingPref = getPreference(PrefKeys.FOSS_NOTIFICATIONS_ENABLED) as NotificationPollingPreference @@ -305,14 +285,11 @@ class PreferencesActivity : AbstractBaseActivity() { updateVibrationPreferenceIcon(vibrationPref, prefs.getStringOrNull(PrefKeys.NOTIFICATION_VIBRATION)) - localConnPref.setOnPreferenceClickListener { - parentActivity.openSubScreen(LocalConnectionSettingsFragment()) - false - } - - remoteConnPref.setOnPreferenceClickListener { - parentActivity.openSubScreen(RemoteConnectionSettingsFragment()) - false + addServerPref.setOnPreferenceClickListener { + val nextServerId = prefs.getNextAvailableServerId() + val f = ServerEditorFragment.newInstance(ServerConfiguration(nextServerId, "", null, null, null)) + parentActivity.openSubScreen(f) + true } sendDeviceInfoPref.setOnPreferenceClickListener { @@ -460,6 +437,32 @@ class PreferencesActivity : AbstractBaseActivity() { } } + private fun populateServerPrefs() { + val connCategory = getPreference("connection") as PreferenceCategory + (0 until connCategory.preferenceCount) + .map { index -> connCategory.getPreference(index) } + .filter { pref -> pref.key?.startsWith("server_") == true } + .forEach { pref -> connCategory.removePreference(pref) } + + prefs.getConfiguredServerIds().forEach { serverId -> + val config = ServerConfiguration.load(prefs, secretPrefs, serverId) + if (config != null) { + val pref = Preference(context) + pref.title = "Server $serverId" + pref.summary = config.name + pref.key = "server_$serverId" + pref.order = 10 * serverId + pref.setOnPreferenceClickListener { + parentActivity.openSubScreen(ServerEditorFragment.newInstance(config)) + true + } + connCategory.addPreference(pref) + // The pref needs to be attached for doing this + pref.dependency = PrefKeys.DEMO_MODE + } + } + } + private fun clearImageCache(context: Context) { // Get launch intent for application val restartIntent = context.packageManager.getLaunchIntentForPackage(context.packageName) @@ -535,26 +538,6 @@ class PreferencesActivity : AbstractBaseActivity() { R.drawable.ic_vibration_grey_24dp) } - private fun updateConnectionSummary( - subscreenPrefKey: String, - urlPrefKey: String, - userPrefKey: String, - passwordPrefKey: String - ) { - val pref = getPreference(subscreenPrefKey) - val url = prefs.getStringOrEmpty(urlPrefKey) - val beautyUrl = beautifyUrl(url) - val userName = secretPrefs.getStringOrNull(userPrefKey) - val password = secretPrefs.getStringOrNull(passwordPrefKey) - val summary = when { - url.isEmpty() -> getString(R.string.info_not_set) - isConnectionSecure(url, userName, password) -> - getString(R.string.settings_connection_summary, beautyUrl) - else -> getString(R.string.settings_insecure_connection_summary, beautyUrl) - } - pref.summary = summary - } - @RequiresApi(Build.VERSION_CODES.N) private fun updateTileSummary() { val activeTileCount = (1..AbstractTileService.TILE_COUNT) @@ -587,6 +570,143 @@ class PreferencesActivity : AbstractBaseActivity() { companion object { private const val REQUEST_CODE_RINGTONE = 1000 + } + } + + class ServerEditorFragment : AbstractSettingsFragment() { + private lateinit var config: ServerConfiguration + + override val titleResId: Int get() = R.string.settings_edit_server + + override fun onCreate(savedInstanceState: Bundle?) { + config = requireArguments().getParcelable("config")!! + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.server_editor, menu) + val saveItem = menu.findItem(R.id.save) + saveItem.isEnabled = config.name.isNotEmpty() && (config.localPath != null || config.remotePath != null) + val deleteItem = menu.findItem(R.id.delete) + deleteItem.isVisible = prefs.getConfiguredServerIds().contains(config.id) + } + + override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) { + R.id.save -> { + config.saveToPrefs(prefs, secretPrefs) + val serverIdSet = prefs.getConfiguredServerIds() + if (!serverIdSet.contains(config.id)) { + serverIdSet.add(config.id) + prefs.edit { + putConfiguredServerIds(serverIdSet) + if (serverIdSet.size == 1) { + putInt(PrefKeys.ACTIVE_SERVER_ID, config.id) + } + } + } + parentActivity.invalidateOptionsMenu() + parentFragmentManager.popBackStack() // close ourself + true + } + R.id.delete -> { + // TODO: confirmation prompt + config.removeFromPrefs(prefs, secretPrefs) + val serverIdSet = prefs.getConfiguredServerIds() + serverIdSet.remove(config.id) + prefs.edit { + putConfiguredServerIds(serverIdSet) + if (prefs.getInt(PrefKeys.ACTIVE_SERVER_ID, 0) == config.id) { + putInt(PrefKeys.ACTIVE_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) + } + } + parentFragmentManager.popBackStack() // close ourself + true + } + else -> super.onOptionsItemSelected(item) + } + + override fun onStart() { + super.onStart() + updateConnectionSummary("local", config.localPath) + updateConnectionSummary("remote", config.remotePath) + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.server_preferences) + + val serverNamePref = getPreference("name") as EditTextPreference + serverNamePref.text = config.name + serverNamePref.setOnPreferenceChangeListener { _, newValue -> + config = ServerConfiguration(config.id, newValue as String, + config.localPath, config.remotePath, config.sslClientCert) + parentActivity.invalidateOptionsMenu() + true + } + + val localConnPref = getPreference("local") + localConnPref.setOnPreferenceClickListener { + parentActivity.openSubScreen(ConnectionSettingsFragment.newInstance( + localConnPref.key, + config.localPath, + R.xml.local_connection_preferences, + R.string.settings_openhab_connection, + R.string.settings_openhab_url_summary, + this + )) + false + } + + val remoteConnPref = getPreference("remote") + remoteConnPref.setOnPreferenceClickListener { + parentActivity.openSubScreen(ConnectionSettingsFragment.newInstance( + remoteConnPref.key, + config.remotePath, + R.xml.remote_connection_preferences, + R.string.settings_openhab_alt_connection, + R.string.settings_openhab_alturl_summary, + this + )) + false + } + + val clientCertPref = getPreference("clientcert") as SslClientCertificatePreference + clientCertPref.setOnPreferenceChangeListener { _, newValue -> + config = ServerConfiguration(config.id, config.name, + config.localPath, config.remotePath, newValue as String?) + true + } + } + + fun onPathChanged(key: String, path: ServerPath) { + if (key == "local") { + config = ServerConfiguration(config.id, config.name, path, config.remotePath, config.sslClientCert) + } else { + config = ServerConfiguration(config.id, config.name, config.localPath, path, config.sslClientCert) + } + parentActivity.invalidateOptionsMenu() + } + + private fun updateConnectionSummary(key: String, path: ServerPath?) { + val pref = getPreference(key) + val beautyUrl = beautifyUrl(path?.url.orEmpty()) + pref.summary = when { + path == null || path.url.isEmpty() -> + getString(R.string.info_not_set) + path.url.startsWith("https://") && (path.hasAuthentication() || config.sslClientCert != null) -> + getString(R.string.settings_connection_summary, beautyUrl) + else -> + getString(R.string.settings_insecure_connection_summary, beautyUrl) + } + } + + companion object { + fun newInstance(config: ServerConfiguration): ServerEditorFragment { + val f = ServerEditorFragment() + f.arguments = bundleOf("config" to config) + return f + } @VisibleForTesting fun beautifyUrl(url: String): String { val host = url.toHttpUrlOrNull()?.host ?: url @@ -595,27 +715,36 @@ class PreferencesActivity : AbstractBaseActivity() { } } - internal abstract class ConnectionSettingsFragment : AbstractSettingsFragment() { - private lateinit var urlPreference: Preference - private lateinit var userNamePreference: Preference - private lateinit var passwordPreference: Preference - protected fun initPreferences( - urlPrefKey: String, - userNamePrefKey: String, - passwordPrefKey: String, - @StringRes urlSummaryFormatResId: Int - ) { - urlPreference = initEditor(urlPrefKey, prefs, R.drawable.ic_earth_grey_24dp) { value -> + internal class ConnectionSettingsFragment : AbstractSettingsFragment() { + override val titleResId: Int @StringRes get() = requireArguments().getInt("title") + + private lateinit var urlPreference: EditTextPreference + private lateinit var userNamePreference: EditTextPreference + private lateinit var passwordPreference: EditTextPreference + private lateinit var parent: ServerEditorFragment + private lateinit var path: ServerPath + + override fun onAttach(context: Context) { + super.onAttach(context) + parent = parentFragmentManager.getFragment(requireArguments(), "parent") as ServerEditorFragment + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(requireArguments().getInt("prefs")) + + path = requireArguments().getParcelable("path") ?: ServerPath("", null, null) + + urlPreference = initEditor("url", path.url, R.drawable.ic_earth_grey_24dp) { value -> val actualValue = if (!value.isNullOrEmpty()) value else getString(R.string.info_not_set) - getString(urlSummaryFormatResId, actualValue) + getString(requireArguments().getInt("urlsummary"), actualValue) } - userNamePreference = initEditor(userNamePrefKey, secretPrefs, + userNamePreference = initEditor("username", path.userName, R.drawable.ic_person_outline_grey_24dp) { value -> if (!value.isNullOrEmpty()) value else getString(R.string.info_not_set) } - passwordPreference = initEditor(passwordPrefKey, secretPrefs, + passwordPreference = initEditor("password", path.password, R.drawable.ic_shield_key_outline_grey_24dp) { value -> getString(when { value.isNullOrEmpty() -> R.string.info_not_set @@ -624,37 +753,39 @@ class PreferencesActivity : AbstractBaseActivity() { }) } - updateIconColors(urlPreference.getPrefValue(), - userNamePreference.getPrefValue(), passwordPreference.getPrefValue()) + updateIconColors(urlPreference.text, userNamePreference.text, passwordPreference.text) } private fun initEditor( key: String, - prefsForValue: SharedPreferences, + initialValue: String?, @DrawableRes iconResId: Int, summaryGenerator: (value: String?) -> CharSequence - ): Preference { - val preference: Preference = preferenceScreen.findPreference(key)!! - preference.preferenceDataStore = SharedPrefsDataStore(prefsForValue) + ): EditTextPreference { + val preference = preferenceScreen.findPreference(key)!! preference.icon = DrawableCompat.wrap(ContextCompat.getDrawable(preference.context, iconResId)!!) + preference.text = initialValue preference.setOnPreferenceChangeListener { pref, newValue -> - updateIconColors(getActualValue(pref, newValue, urlPreference), - getActualValue(pref, newValue, userNamePreference), - getActualValue(pref, newValue, passwordPreference)) + val url = if (pref === urlPreference) newValue as String else urlPreference.text + val userName = if (pref === userNamePreference) newValue as String else userNamePreference.text + val password = if (pref === passwordPreference) newValue as String else passwordPreference.text + + updateIconColors(url, userName, password) pref.summary = summaryGenerator(newValue as String) + + if (!url.isNullOrEmpty()) { + val path = ServerPath(url, userName, password) + parent.onPathChanged(requireArguments().getString("key", ""), path) + } true } - preference.summary = summaryGenerator(prefsForValue.getStringOrEmpty(key)) + preference.summary = summaryGenerator(initialValue) return preference } - private fun getActualValue(pref: Preference, newValue: Any, reference: Preference?): String? { - return if (pref === reference) newValue as String else reference.getPrefValue() - } - private fun updateIconColors(url: String?, userName: String?, password: String?) { updateIconColor(urlPreference) { when { - isConnectionHttps(url) -> R.color.pref_icon_green + url?.startsWith("https://") == true -> R.color.pref_icon_green !url.isNullOrEmpty() -> R.color.pref_icon_red else -> null } } @@ -679,25 +810,28 @@ class PreferencesActivity : AbstractBaseActivity() { DrawableCompat.setTintList(pref.icon, null) } } - } - - internal class LocalConnectionSettingsFragment : ConnectionSettingsFragment() { - override val titleResId: Int @StringRes get() = R.string.settings_openhab_connection - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.local_connection_preferences) - initPreferences(PrefKeys.LOCAL_URL, PrefKeys.LOCAL_USERNAME, - PrefKeys.LOCAL_PASSWORD, R.string.settings_openhab_url_summary) - } - } - - internal class RemoteConnectionSettingsFragment : ConnectionSettingsFragment() { - override val titleResId: Int @StringRes get() = R.string.settings_openhab_alt_connection - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.remote_connection_preferences) - initPreferences(PrefKeys.REMOTE_URL, PrefKeys.REMOTE_USERNAME, - PrefKeys.REMOTE_PASSWORD, R.string.settings_openhab_alturl_summary) + companion object { + fun newInstance( + key: String, + serverPath: ServerPath?, + prefsResId: Int, + titleResId: Int, + urlSummaryResId: Int, + parent: ServerEditorFragment + ): ConnectionSettingsFragment { + val f = ConnectionSettingsFragment() + val args = bundleOf( + "key" to key, + "path" to serverPath, + "prefs" to prefsResId, + "title" to titleResId, + "urlsummary" to urlSummaryResId + ) + parent.parentFragmentManager.putFragment(args, "parent", parent) + f.arguments = args + return f + } } } @@ -1051,66 +1185,6 @@ class PreferencesActivity : AbstractBaseActivity() { } } -fun Preference?.getPrefValue(defaultValue: String? = null): String? { - if (this == null) { - return defaultValue - } - preferenceDataStore?.let { - return it.getString(key, defaultValue) - } - return sharedPreferences.getString(key, defaultValue) -} - -class SharedPrefsDataStore constructor(val prefs: SharedPreferences) : PreferenceDataStore() { - override fun getBoolean(key: String?, defValue: Boolean): Boolean { - return prefs.getBoolean(key, defValue) - } - - override fun getInt(key: String?, defValue: Int): Int { - return prefs.getInt(key, defValue) - } - - override fun getLong(key: String?, defValue: Long): Long { - return prefs.getLong(key, defValue) - } - - override fun getFloat(key: String?, defValue: Float): Float { - return prefs.getFloat(key, defValue) - } - - override fun getString(key: String?, defValue: String?): String? { - return prefs.getString(key, defValue) - } - - override fun getStringSet(key: String?, defValues: MutableSet?): MutableSet { - return prefs.getStringSet(key, defValues) ?: mutableSetOf() - } - - override fun putBoolean(key: String?, value: Boolean) { - prefs.edit { putBoolean(key, value) } - } - - override fun putInt(key: String?, value: Int) { - prefs.edit { putInt(key, value) } - } - - override fun putLong(key: String?, value: Long) { - prefs.edit { putLong(key, value) } - } - - override fun putFloat(key: String?, value: Float) { - prefs.edit { putFloat(key, value) } - } - - override fun putString(key: String?, value: String?) { - prefs.edit { putString(key, value) } - } - - override fun putStringSet(key: String?, values: MutableSet?) { - prefs.edit { putStringSet(key, values) } - } -} - interface CustomDialogPreference { fun createDialog(): DialogFragment } diff --git a/mobile/src/main/java/org/openhab/habdroid/util/ExtensionFuncs.kt b/mobile/src/main/java/org/openhab/habdroid/util/ExtensionFuncs.kt index 48bc451f1d..81bc6858f2 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/ExtensionFuncs.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/ExtensionFuncs.kt @@ -30,6 +30,8 @@ import android.util.Log import android.util.TypedValue import androidx.annotation.AttrRes import androidx.annotation.ColorInt +import android.view.Menu +import android.view.MenuItem import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.net.toUri @@ -448,3 +450,9 @@ fun Intent.isResolvable(context: Context): Boolean { * Removes trailing `.0` from float */ fun Float.beautify() = if (this == this.toInt().toFloat()) this.toInt().toString() else this.toString() + +fun Menu.getGroupItems(groupId: Int): List { + return (0 until size()) + .map { index -> getItem(index) } + .filter { item -> item.groupId == groupId } +} diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt index 50fc9ed356..06ca27504c 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt @@ -37,12 +37,30 @@ enum class ScreenLockMode { Enabled } +fun SharedPreferences.getActiveServerId(): Int { + return getInt(PrefKeys.ACTIVE_SERVER_ID, 0) +} + +fun SharedPreferences.getNextAvailableServerId(): Int { + return getStringSet(PrefKeys.SERVER_IDS, null) + ?.lastOrNull() + .orDefaultIfEmpty("0") + .let { idString -> idString.toInt() + 1 } +} + +fun SharedPreferences.getConfiguredServerIds(): MutableSet { + return getStringSet(PrefKeys.SERVER_IDS, null) + ?.map { id -> id.toInt() } + ?.toMutableSet() + ?: mutableSetOf() +} + fun SharedPreferences.getLocalUrl(): String { - return getStringOrEmpty(PrefKeys.LOCAL_URL) + return getStringOrNull(PrefKeys.buildServerKey(getActiveServerId(), PrefKeys.LOCAL_URL_PREFIX)).orEmpty() } fun SharedPreferences.getRemoteUrl(): String { - return getStringOrEmpty(PrefKeys.REMOTE_URL) + return getStringOrNull(PrefKeys.buildServerKey(getActiveServerId(), PrefKeys.REMOTE_URL_PREFIX)).orEmpty() } fun SharedPreferences.getDefaultSitemap(connection: Connection?): String { @@ -180,6 +198,10 @@ fun SharedPreferences.getNotificationVibrationPattern(context: Context): LongArr } } +fun SharedPreferences.Editor.putConfiguredServerIds(ids: Set) { + putStringSet(PrefKeys.SERVER_IDS, ids.map { id -> id.toString() }.toSet()) +} + fun SharedPreferences.Editor.updateDefaultSitemap(sitemap: Sitemap?, connection: Connection?) { if (connection is DemoConnection) { return diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt index 91a9147428..cdab0261e6 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt @@ -18,13 +18,17 @@ object PrefKeys { * Settings keys */ const val DEMO_MODE = "default_openhab_demomode" - const val LOCAL_URL = "default_openhab_url" - const val LOCAL_USERNAME = "default_openhab_username" - const val LOCAL_PASSWORD = "default_openhab_password" - const val REMOTE_URL = "default_openhab_alturl" - const val REMOTE_USERNAME = "default_openhab_remote_username" - const val REMOTE_PASSWORD = "default_openhab_remote_password" - const val SSL_CLIENT_CERT = "default_openhab_sslclientcert" + const val SERVER_IDS = "server_ids" + const val ACTIVE_SERVER_ID = "active_server_id" + const val SERVER_NAME_PREFIX = "server_name_" + const val LOCAL_URL_PREFIX = "local_url_" + const val LOCAL_USERNAME_PREFIX = "local_username_" + const val LOCAL_PASSWORD_PREFIX = "local_password_" + const val REMOTE_URL_PREFIX = "remote_url_" + const val REMOTE_USERNAME_PREFIX = "remote_username_" + const val REMOTE_PASSWORD_PREFIX = "remote_password_" + const val SSL_CLIENT_CERT_PREFIX = "sslclientcert_" + fun buildServerKey(id: Int, prefix: String) = "$prefix$id" const val SITEMAP_NAME = "default_openhab_sitemap" const val SITEMAP_LABEL = "default_openhab_sitemap_label" @@ -83,8 +87,6 @@ object PrefKeys { /** * PreferencesActivity subpages */ - const val SUBSCREEN_LOCAL_CONNECTION = "default_openhab_local_connection" - const val SUBSCREEN_REMOTE_CONNECTION = "default_openhab_remote_connection" const val SUBSCREEN_SEND_DEVICE_INFO = "send_device_info" const val SUBSCREEN_TILE = "tiles" diff --git a/mobile/src/main/res/drawable/ic_menu_down_24dp.xml b/mobile/src/main/res/drawable/ic_menu_down_24dp.xml new file mode 100644 index 0000000000..5dd32f9cfb --- /dev/null +++ b/mobile/src/main/res/drawable/ic_menu_down_24dp.xml @@ -0,0 +1,7 @@ + + + diff --git a/mobile/src/main/res/drawable/ic_menu_up_24dp.xml b/mobile/src/main/res/drawable/ic_menu_up_24dp.xml new file mode 100644 index 0000000000..0778d5d28e --- /dev/null +++ b/mobile/src/main/res/drawable/ic_menu_up_24dp.xml @@ -0,0 +1,7 @@ + + + diff --git a/mobile/src/main/res/layout/drawer_header.xml b/mobile/src/main/res/layout/drawer_header.xml new file mode 100644 index 0000000000..197fc8b17d --- /dev/null +++ b/mobile/src/main/res/layout/drawer_header.xml @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/mobile/src/main/res/menu/left_drawer.xml b/mobile/src/main/res/menu/left_drawer.xml index 41edd5cd29..c491fc0e13 100644 --- a/mobile/src/main/res/menu/left_drawer.xml +++ b/mobile/src/main/res/menu/left_drawer.xml @@ -1,35 +1,48 @@ - - - - - - - - - + android:title="@string/mainmenu_openhab_sitemaps" + android:checkableBehavior="none"> + + + + + + + + + + diff --git a/mobile/src/main/res/menu/server_editor.xml b/mobile/src/main/res/menu/server_editor.xml new file mode 100644 index 0000000000..ab6116deb0 --- /dev/null +++ b/mobile/src/main/res/menu/server_editor.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 06a362dfdc..03c4e99e91 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -93,6 +93,12 @@ kiosk on Show all Sitemaps in side menu + Server name + Add server + Edit server + Save + Delete + Servers Voice commands diff --git a/mobile/src/main/res/xml/local_connection_preferences.xml b/mobile/src/main/res/xml/local_connection_preferences.xml index d0161615b6..18d1bd3f3c 100644 --- a/mobile/src/main/res/xml/local_connection_preferences.xml +++ b/mobile/src/main/res/xml/local_connection_preferences.xml @@ -5,22 +5,25 @@ android:title="@string/settings_openhab_connection"> diff --git a/mobile/src/main/res/xml/preferences.xml b/mobile/src/main/res/xml/preferences.xml index ff3a448b5d..6376ea4ad7 100644 --- a/mobile/src/main/res/xml/preferences.xml +++ b/mobile/src/main/res/xml/preferences.xml @@ -2,31 +2,21 @@ - + + android:title="@string/settings_openhab_demomode" + android:order="0" /> - - + android:order="999" /> diff --git a/mobile/src/main/res/xml/server_preferences.xml b/mobile/src/main/res/xml/server_preferences.xml new file mode 100644 index 0000000000..7202887f67 --- /dev/null +++ b/mobile/src/main/res/xml/server_preferences.xml @@ -0,0 +1,38 @@ + + + + + + + + + From ecbae25e93a34d4387556d5aadee69b4f5277aee Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Tue, 21 Jul 2020 21:15:40 +0200 Subject: [PATCH 02/44] Improvements * Add confirmation dialog for deletion * Mark 'add server' pref as beta * Fix default sitemap selection * Other small improvements Fixes #10 Closes #1900 Signed-off-by: mueller-ma --- .../habdroid/core/UpdateBroadcastReceiver.kt | 10 +- .../habdroid/model/ServerConfiguration.kt | 40 ++++- .../org/openhab/habdroid/ui/MainActivity.kt | 34 ++--- .../habdroid/ui/PreferencesActivity.kt | 139 ++++++++++-------- .../habdroid/ui/preference/BetaPreference.kt | 44 ++++++ .../openhab/habdroid/util/PrefExtensions.kt | 23 ++- .../org/openhab/habdroid/util/PrefKeys.kt | 6 +- .../main/res/drawable/rounded_corner_box.xml | 15 ++ mobile/src/main/res/layout/pref_beta.xml | 10 ++ mobile/src/main/res/values/strings.xml | 4 + mobile/src/main/res/xml/preferences.xml | 6 +- .../src/main/res/xml/server_preferences.xml | 4 + 12 files changed, 223 insertions(+), 112 deletions(-) create mode 100644 mobile/src/main/java/org/openhab/habdroid/ui/preference/BetaPreference.kt create mode 100644 mobile/src/main/res/drawable/rounded_corner_box.xml create mode 100644 mobile/src/main/res/layout/pref_beta.xml diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt index ee17bb2519..601cbfeb58 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt @@ -28,6 +28,7 @@ import org.openhab.habdroid.BuildConfig import org.openhab.habdroid.R import org.openhab.habdroid.background.EventListenerService import org.openhab.habdroid.background.tiles.AbstractTileService +import org.openhab.habdroid.model.DefaultSitemap import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.model.ServerPath import org.openhab.habdroid.model.putIconResource @@ -126,8 +127,15 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { secretPrefs.getStringOrNull("default_openhab_remote_username"), secretPrefs.getStringOrNull("default_openhab_remote_password") ) } + val defaultSitemapName = prefs.getStringOrNull("default_openhab_sitemap") + val defaultSitemapLabel = prefs.getStringOrNull("default_openhab_sitemap_label") + val defaultSitemap = if (defaultSitemapName.isNullOrEmpty() || defaultSitemapLabel == null) { + null + } else { + DefaultSitemap(defaultSitemapName, defaultSitemapLabel) + } val config = ServerConfiguration(1, "openHAB", localPath, remotePath, - prefs.getStringOrNull("default_openhab_sslclientcert")) + prefs.getStringOrNull("default_openhab_sslclientcert"), defaultSitemap) config.saveToPrefs(prefs, secretPrefs) prefs.edit { putConfiguredServerIds(setOf(config.id)) diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt index 8086bd2b94..6ae9d45ad9 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt @@ -18,7 +18,7 @@ import android.os.Parcelable import androidx.core.content.edit import kotlinx.android.parcel.Parcelize import org.openhab.habdroid.util.PrefKeys -import org.openhab.habdroid.util.orDefaultIfEmpty +import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.toNormalizedUrl @Parcelize @@ -38,12 +38,12 @@ data class ServerPath( userNamePrefix: String, passwordPrefix: String ): ServerPath? { - val url = prefs.getString(PrefKeys.buildServerKey(serverId, urlKeyPrefix), null).toNormalizedUrl() + val url = prefs.getStringOrNull(PrefKeys.buildServerKey(serverId, urlKeyPrefix)).toNormalizedUrl() ?: return null return ServerPath( url, - secretPrefs.getString(PrefKeys.buildServerKey(serverId, userNamePrefix), null), - secretPrefs.getString(PrefKeys.buildServerKey(serverId, passwordPrefix), null) + secretPrefs.getStringOrNull(PrefKeys.buildServerKey(serverId, userNamePrefix)), + secretPrefs.getStringOrNull(PrefKeys.buildServerKey(serverId, passwordPrefix)) ) } } @@ -55,7 +55,8 @@ data class ServerConfiguration( val name: String, val localPath: ServerPath?, val remotePath: ServerPath?, - val sslClientCert: String? + val sslClientCert: String?, + val defaultSitemap: DefaultSitemap? ) : Parcelable { fun saveToPrefs(prefs: SharedPreferences, secretPrefs: SharedPreferences) { prefs.edit { @@ -64,6 +65,7 @@ data class ServerConfiguration( putString(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_URL_PREFIX), remotePath?.url) putString(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX), sslClientCert) } + saveDefaultSitemap(prefs, id, defaultSitemap) secretPrefs.edit { putString(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_USERNAME_PREFIX), localPath?.userName) putString(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_PASSWORD_PREFIX), localPath?.password) @@ -77,6 +79,8 @@ data class ServerConfiguration( remove(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_URL_PREFIX)) remove(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_URL_PREFIX)) remove(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX)) + remove(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_NAME_PREFIX)) + remove(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_LABEL_PREFIX)) } secretPrefs.edit { remove(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_USERNAME_PREFIX)) @@ -92,12 +96,32 @@ data class ServerConfiguration( PrefKeys.LOCAL_URL_PREFIX, PrefKeys.LOCAL_USERNAME_PREFIX, PrefKeys.LOCAL_PASSWORD_PREFIX) val remotePath = ServerPath.load(prefs, secretPrefs, id, PrefKeys.REMOTE_URL_PREFIX, PrefKeys.REMOTE_USERNAME_PREFIX, PrefKeys.REMOTE_PASSWORD_PREFIX) - val serverName = prefs.getString(PrefKeys.buildServerKey(id, PrefKeys.SERVER_NAME_PREFIX), null) + val serverName = prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.SERVER_NAME_PREFIX)) if ((localPath == null && remotePath == null) || serverName.isNullOrEmpty()) { return null } - val clientCert = prefs.getString(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX), null) - return ServerConfiguration(id, serverName, localPath, remotePath, clientCert) + val clientCert = prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX)) + return ServerConfiguration(id, serverName, localPath, remotePath, clientCert, getDefaultSitemap(prefs, id)) + } + + fun saveDefaultSitemap(prefs: SharedPreferences, id: Int, defaultSitemap: DefaultSitemap?) { + prefs.edit { + putString(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_NAME_PREFIX), defaultSitemap?.name) + putString(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_LABEL_PREFIX), defaultSitemap?.label) + } + } + + fun getDefaultSitemap(prefs: SharedPreferences, id: Int): DefaultSitemap? { + val defaultSitemapName = prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_NAME_PREFIX)) + val defaultSitemapLabel = prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_LABEL_PREFIX)) + return if (defaultSitemapName != null && defaultSitemapLabel != null) { + DefaultSitemap(defaultSitemapName, defaultSitemapLabel) + } else { + null + } } } } + +@Parcelize +data class DefaultSitemap(val name: String, val label: String) : Parcelable diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index dab0d1c5f2..56d3b8855c 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -596,9 +596,9 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { val port = info.port.toString() Log.d(TAG, "Service resolved: $address port: $port") - val config = ServerConfiguration(prefs.getNextAvailableServerId(), "openHAB", + val config = ServerConfiguration(prefs.getNextAvailableServerId(), getString(R.string.openhab), ServerPath("https://$address:$port", null, null), - null, null + null, null, null ) config.saveToPrefs(prefs, getSecretPrefs()) } else { @@ -713,12 +713,12 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } R.id.default_sitemap -> { val sitemap = serverProperties?.sitemaps?.firstOrNull { s -> - s.name == prefs.getDefaultSitemap(connection) + s.name == prefs.getDefaultSitemap(connection)?.name } if (sitemap != null) { controller.openSitemap(sitemap) handled = true - } else if (prefs.getDefaultSitemap(connection).isEmpty()) { + } else if (prefs.getDefaultSitemap(connection) != null) { executeOrStoreAction(PendingAction.ChooseSitemap()) handled = true } @@ -774,7 +774,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { private fun updateSitemapDrawerEntries() { val defaultSitemapItem = drawerMenu.findItem(R.id.default_sitemap) - val sitemaps = serverProperties?.sitemaps?.sortedWithDefaultName(prefs.getDefaultSitemap(connection)) + val sitemaps = serverProperties?.sitemaps?.sortedWithDefaultName(prefs.getDefaultSitemap(connection)?.name.orEmpty()) drawerMenu.getGroupItems(R.id.sitemaps) .filter { item -> item !== defaultSitemapItem } @@ -791,7 +791,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } } else { val sitemap = serverProperties?.sitemaps?.firstOrNull { s -> - s.name == prefs.getDefaultSitemap(connection) + s.name == prefs.getDefaultSitemap(connection)?.name.orEmpty() } if (sitemap != null) { defaultSitemapItem.title = sitemap.label @@ -921,7 +921,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } private fun selectConfiguredSitemapFromList(): Sitemap? { - val configuredSitemap = prefs.getDefaultSitemap(connection) + val configuredSitemap = prefs.getDefaultSitemap(connection)?.name.orEmpty() val sitemaps = serverProperties?.sitemaps val result = when { sitemaps == null -> null @@ -934,16 +934,14 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } Log.d(TAG, "Configured sitemap is '$configuredSitemap', selected $result") - prefs.edit { - if (result == null && configuredSitemap.isNotEmpty()) { - // clear old configuration - updateDefaultSitemap(null, connection) - } else if (result != null && (configuredSitemap.isEmpty() || configuredSitemap != result.name)) { - // update result - updateDefaultSitemap(result, connection) - } + if (result == null && configuredSitemap.isNotEmpty()) { + // clear old configuration + prefs.updateDefaultSitemap(connection, null) + } else if (result != null && (configuredSitemap.isEmpty() || configuredSitemap != result.name)) { + // update result + prefs.updateDefaultSitemap(connection, result) + updateSitemapDrawerEntries() } - updateSitemapDrawerEntries() return result } @@ -963,9 +961,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { .setItems(sitemapLabels) { _, which -> val sitemap = sitemaps[which] Log.d(TAG, "Selected sitemap $sitemap") - prefs.edit { - updateDefaultSitemap(sitemap, connection) - } + prefs.updateDefaultSitemap(connection, sitemap) controller.openSitemap(sitemap) updateSitemapDrawerEntries() } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 23ed038b6f..ccd241976a 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -33,6 +33,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.RequiresApi import androidx.annotation.StringRes import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDelegate import androidx.core.content.ContextCompat import androidx.core.content.edit @@ -68,15 +69,16 @@ import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.model.ServerPath import org.openhab.habdroid.model.ServerProperties import org.openhab.habdroid.ui.homescreenwidget.ItemUpdateWidget +import org.openhab.habdroid.ui.preference.BetaPreference import org.openhab.habdroid.ui.preference.CustomInputTypePreference import org.openhab.habdroid.ui.preference.ItemUpdatingPreference import org.openhab.habdroid.ui.preference.NotificationPollingPreference +import org.openhab.habdroid.ui.preference.SslClientCertificatePreference import org.openhab.habdroid.ui.preference.TileItemAndStatePreference import org.openhab.habdroid.util.CacheManager import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.ToastType import org.openhab.habdroid.util.Util -import org.openhab.habdroid.ui.preference.SslClientCertificatePreference import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getDayNightMode import org.openhab.habdroid.util.getNextAvailableServerId @@ -85,7 +87,6 @@ import org.openhab.habdroid.util.getPreference import org.openhab.habdroid.util.getPrefixForBgTasks import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getSecretPrefs -import org.openhab.habdroid.util.getStringOrEmpty import org.openhab.habdroid.util.getStringOrFallbackIfEmpty import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.hasPermissions @@ -227,6 +228,7 @@ class PreferencesActivity : AbstractBaseActivity() { private var notificationPollingPref: NotificationPollingPreference? = null private var notificationStatusHint: Preference? = null + private var addServerPref: BetaPreference? = null override fun onStart() { super.onStart() @@ -239,6 +241,11 @@ class PreferencesActivity : AbstractBaseActivity() { } } + override fun onResume() { + super.onResume() + addServerPref?.changeBetaTagVisibility(prefs.getConfiguredServerIds().isNotEmpty()) + } + override fun onStop() { super.onStop() ConnectionFactory.removeListener(this) @@ -247,7 +254,7 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences) - val addServerPref = getPreference("add_server") + addServerPref = getPreference("add_server") as BetaPreference val sendDeviceInfoPref = getPreference(PrefKeys.SUBSCREEN_SEND_DEVICE_INFO) notificationPollingPref = getPreference(PrefKeys.FOSS_NOTIFICATIONS_ENABLED) as NotificationPollingPreference @@ -255,7 +262,6 @@ class PreferencesActivity : AbstractBaseActivity() { val themePref = getPreference(PrefKeys.THEME) val accentColorPref = getPreference(PrefKeys.ACCENT_COLOR) as ColorPreferenceCompat val clearCachePref = getPreference(PrefKeys.CLEAR_CACHE) - val clearDefaultSitemapPref = getPreference(PrefKeys.CLEAR_DEFAULT_SITEMAP) val showSitemapInDrawerPref = getPreference(PrefKeys.SHOW_SITEMAPS_IN_DRAWER) val fullscreenPreference = getPreference(PrefKeys.FULLSCREEN) val iconFormatPreference = getPreference(PrefKeys.ICON_FORMAT) @@ -272,22 +278,14 @@ class PreferencesActivity : AbstractBaseActivity() { dataSaverPref.setSwitchTextOff(R.string.data_saver_off_pre_n) } - val currentDefaultSitemap = prefs.getStringOrNull(PrefKeys.SITEMAP_NAME) - val currentDefaultSitemapLabel = prefs.getStringOrEmpty(PrefKeys.SITEMAP_LABEL) - if (currentDefaultSitemap.isNullOrEmpty()) { - onNoDefaultSitemap(clearDefaultSitemapPref) - } else { - clearDefaultSitemapPref.summary = getString( - R.string.settings_current_default_sitemap, currentDefaultSitemapLabel) - } - updateRingtonePreferenceSummary(ringtonePref, prefs.getNotificationTone()) updateVibrationPreferenceIcon(vibrationPref, prefs.getStringOrNull(PrefKeys.NOTIFICATION_VIBRATION)) - addServerPref.setOnPreferenceClickListener { + addServerPref?.changeBetaTagVisibility(prefs.getConfiguredServerIds().isNotEmpty()) + addServerPref?.setOnPreferenceClickListener { val nextServerId = prefs.getNextAvailableServerId() - val f = ServerEditorFragment.newInstance(ServerConfiguration(nextServerId, "", null, null, null)) + val f = ServerEditorFragment.newInstance(ServerConfiguration(nextServerId, "", null, null, null, null)) parentActivity.openSubScreen(f) true } @@ -344,13 +342,6 @@ class PreferencesActivity : AbstractBaseActivity() { true } - clearDefaultSitemapPref.setOnPreferenceClickListener { preference -> - preference.sharedPreferences.edit { updateDefaultSitemap(null, null) } - onNoDefaultSitemap(preference) - parentActivity.resultIntent.putExtra(RESULT_EXTRA_SITEMAP_CLEARED, true) - true - } - if (!prefs.isTaskerPluginEnabled() && !isAutomationAppInstalled()) { preferenceScreen.removePreferenceRecursively(PrefKeys.TASKER_PLUGIN_ENABLED) } @@ -448,8 +439,7 @@ class PreferencesActivity : AbstractBaseActivity() { val config = ServerConfiguration.load(prefs, secretPrefs, serverId) if (config != null) { val pref = Preference(context) - pref.title = "Server $serverId" - pref.summary = config.name + pref.title = pref.context.getString(R.string.server_with_name, config.name) pref.key = "server_$serverId" pref.order = 10 * serverId pref.setOnPreferenceClickListener { @@ -487,11 +477,6 @@ class PreferencesActivity : AbstractBaseActivity() { } } - private fun onNoDefaultSitemap(pref: Preference) { - pref.isEnabled = false - pref.setSummary(R.string.settings_no_default_sitemap) - } - private fun updateNotificationStatusSummaries() { parentActivity.launch { notificationPollingPref?.updateSummary() @@ -587,44 +572,53 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.server_editor, menu) - val saveItem = menu.findItem(R.id.save) - saveItem.isEnabled = config.name.isNotEmpty() && (config.localPath != null || config.remotePath != null) val deleteItem = menu.findItem(R.id.delete) deleteItem.isVisible = prefs.getConfiguredServerIds().contains(config.id) } - override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) { - R.id.save -> { - config.saveToPrefs(prefs, secretPrefs) - val serverIdSet = prefs.getConfiguredServerIds() - if (!serverIdSet.contains(config.id)) { - serverIdSet.add(config.id) - prefs.edit { - putConfiguredServerIds(serverIdSet) - if (serverIdSet.size == 1) { - putInt(PrefKeys.ACTIVE_SERVER_ID, config.id) + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when(item.itemId) { + R.id.save -> { + if (config.name.isEmpty() || (config.localPath == null && config.remotePath == null)) { + context?.showToast(R.string.settings_server_at_least_name_and_connection) + return true + } + config.saveToPrefs(prefs, secretPrefs) + val serverIdSet = prefs.getConfiguredServerIds() + if (!serverIdSet.contains(config.id)) { + serverIdSet.add(config.id) + prefs.edit { + putConfiguredServerIds(serverIdSet) + if (serverIdSet.size == 1) { + putInt(PrefKeys.ACTIVE_SERVER_ID, config.id) + } } } + parentActivity.invalidateOptionsMenu() + parentFragmentManager.popBackStack() // close ourself + true } - parentActivity.invalidateOptionsMenu() - parentFragmentManager.popBackStack() // close ourself - true - } - R.id.delete -> { - // TODO: confirmation prompt - config.removeFromPrefs(prefs, secretPrefs) - val serverIdSet = prefs.getConfiguredServerIds() - serverIdSet.remove(config.id) - prefs.edit { - putConfiguredServerIds(serverIdSet) - if (prefs.getInt(PrefKeys.ACTIVE_SERVER_ID, 0) == config.id) { - putInt(PrefKeys.ACTIVE_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) - } + R.id.delete -> { + AlertDialog.Builder(preferenceManager.context) + .setMessage(R.string.settings_server_confirm_deletion) + .setPositiveButton(R.string.settings_menu_delete_server) { _, _ -> + config.removeFromPrefs(prefs, secretPrefs) + val serverIdSet = prefs.getConfiguredServerIds() + serverIdSet.remove(config.id) + prefs.edit { + putConfiguredServerIds(serverIdSet) + if (prefs.getInt(PrefKeys.ACTIVE_SERVER_ID, 0) == config.id) { + putInt(PrefKeys.ACTIVE_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) + } + } + parentFragmentManager.popBackStack() // close ourself + } + .setNegativeButton(android.R.string.cancel, null) + .show() + true } - parentFragmentManager.popBackStack() // close ourself - true + else -> super.onOptionsItemSelected(item) } - else -> super.onOptionsItemSelected(item) } override fun onStart() { @@ -640,7 +634,7 @@ class PreferencesActivity : AbstractBaseActivity() { serverNamePref.text = config.name serverNamePref.setOnPreferenceChangeListener { _, newValue -> config = ServerConfiguration(config.id, newValue as String, - config.localPath, config.remotePath, config.sslClientCert) + config.localPath, config.remotePath, config.sslClientCert, config.defaultSitemap) parentActivity.invalidateOptionsMenu() true } @@ -674,16 +668,35 @@ class PreferencesActivity : AbstractBaseActivity() { val clientCertPref = getPreference("clientcert") as SslClientCertificatePreference clientCertPref.setOnPreferenceChangeListener { _, newValue -> config = ServerConfiguration(config.id, config.name, - config.localPath, config.remotePath, newValue as String?) + config.localPath, config.remotePath, newValue as String?, config.defaultSitemap) true } + + val clearDefaultSitemapPref = getPreference(PrefKeys.CLEAR_DEFAULT_SITEMAP) + if (config.defaultSitemap?.name.isNullOrEmpty()) { + handleNoDefaultSitemap(clearDefaultSitemapPref) + } else { + clearDefaultSitemapPref.summary = getString( + R.string.settings_current_default_sitemap, config.defaultSitemap?.label.orEmpty()) + } + clearDefaultSitemapPref.setOnPreferenceClickListener { preference -> + preference.sharedPreferences.updateDefaultSitemap(null, null, config.id) + handleNoDefaultSitemap(preference) + parentActivity.resultIntent.putExtra(RESULT_EXTRA_SITEMAP_CLEARED, true) + true + } + } + + private fun handleNoDefaultSitemap(pref: Preference) { + pref.isEnabled = false + pref.setSummary(R.string.settings_no_default_sitemap) } fun onPathChanged(key: String, path: ServerPath) { - if (key == "local") { - config = ServerConfiguration(config.id, config.name, path, config.remotePath, config.sslClientCert) + config = if (key == "local") { + ServerConfiguration(config.id, config.name, path, config.remotePath, config.sslClientCert, config.defaultSitemap) } else { - config = ServerConfiguration(config.id, config.name, config.localPath, path, config.sslClientCert) + ServerConfiguration(config.id, config.name, config.localPath, path, config.sslClientCert, config.defaultSitemap) } parentActivity.invalidateOptionsMenu() } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/BetaPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/BetaPreference.kt new file mode 100644 index 0000000000..1c5eb6a53f --- /dev/null +++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/BetaPreference.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.habdroid.ui.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.TextView +import androidx.core.view.isGone +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import org.openhab.habdroid.R + +class BetaPreference constructor(context: Context, attrs: AttributeSet) : Preference(context, attrs) { + private var betaTag: TextView? = null + private var showBetaTag: Boolean = true + + init { + widgetLayoutResource = R.layout.pref_beta + } + + override fun onBindViewHolder(holder: PreferenceViewHolder?) { + super.onBindViewHolder(holder) + if (holder != null) { + betaTag = holder.itemView.findViewById(R.id.beta_tag) + betaTag?.isGone = !showBetaTag + } + } + + fun changeBetaTagVisibility(show: Boolean) { + showBetaTag = show + betaTag?.isGone = !showBetaTag + } +} diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt index 06ca27504c..32ebbc4a37 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt @@ -25,7 +25,9 @@ import androidx.preference.PreferenceFragmentCompat import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.Connection import org.openhab.habdroid.core.connection.DemoConnection +import org.openhab.habdroid.model.DefaultSitemap import org.openhab.habdroid.model.IconFormat +import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.model.ServerProperties import org.openhab.habdroid.model.Sitemap import org.openhab.habdroid.ui.preference.toItemUpdatePrefValue @@ -63,12 +65,11 @@ fun SharedPreferences.getRemoteUrl(): String { return getStringOrNull(PrefKeys.buildServerKey(getActiveServerId(), PrefKeys.REMOTE_URL_PREFIX)).orEmpty() } -fun SharedPreferences.getDefaultSitemap(connection: Connection?): String { - return if (connection is DemoConnection) { - "demo" - } else { - getStringOrEmpty(PrefKeys.SITEMAP_NAME) +fun SharedPreferences.getDefaultSitemap(connection: Connection?, id: Int = getActiveServerId()): DefaultSitemap? { + if (connection is DemoConnection) { + return DefaultSitemap("demo", "demo") } + return ServerConfiguration.getDefaultSitemap(this, id) } fun SharedPreferences.getIconFormat(): IconFormat { @@ -202,17 +203,13 @@ fun SharedPreferences.Editor.putConfiguredServerIds(ids: Set) { putStringSet(PrefKeys.SERVER_IDS, ids.map { id -> id.toString() }.toSet()) } -fun SharedPreferences.Editor.updateDefaultSitemap(sitemap: Sitemap?, connection: Connection?) { +fun SharedPreferences.updateDefaultSitemap(connection: Connection?, sitemap: Sitemap?, id: Int = getActiveServerId()) { if (connection is DemoConnection) { return } - if (sitemap == null) { - remove(PrefKeys.SITEMAP_NAME) - remove(PrefKeys.SITEMAP_LABEL) - } else { - putString(PrefKeys.SITEMAP_NAME, sitemap.name) - putString(PrefKeys.SITEMAP_LABEL, sitemap.label) - } + val defaultSitemap = sitemap?.let { DefaultSitemap(sitemap.name, sitemap.label) } + ServerConfiguration.saveDefaultSitemap(this, id, defaultSitemap) + } fun PreferenceFragmentCompat.getPreference(key: String): Preference { diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt index cdab0261e6..a4b5987b1a 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt @@ -28,11 +28,11 @@ object PrefKeys { const val REMOTE_USERNAME_PREFIX = "remote_username_" const val REMOTE_PASSWORD_PREFIX = "remote_password_" const val SSL_CLIENT_CERT_PREFIX = "sslclientcert_" + const val DEFAULT_SITEMAP_NAME_PREFIX = "default_sitemap_name_" + const val DEFAULT_SITEMAP_LABEL_PREFIX = "default_sitemap_label_" + const val CLEAR_DEFAULT_SITEMAP = "clear_default_sitemap" fun buildServerKey(id: Int, prefix: String) = "$prefix$id" - const val SITEMAP_NAME = "default_openhab_sitemap" - const val SITEMAP_LABEL = "default_openhab_sitemap_label" - const val CLEAR_DEFAULT_SITEMAP = "default_openhab_clear_default_sitemap" const val SHOW_SITEMAPS_IN_DRAWER = "show_sitemaps" const val ICON_FORMAT = "iconFormatType" const val CLEAR_CACHE = "default_openhab_cleacache" diff --git a/mobile/src/main/res/drawable/rounded_corner_box.xml b/mobile/src/main/res/drawable/rounded_corner_box.xml new file mode 100644 index 0000000000..87e69728ea --- /dev/null +++ b/mobile/src/main/res/drawable/rounded_corner_box.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/mobile/src/main/res/layout/pref_beta.xml b/mobile/src/main/res/layout/pref_beta.xml new file mode 100644 index 0000000000..6fd48668f5 --- /dev/null +++ b/mobile/src/main/res/layout/pref_beta.xml @@ -0,0 +1,10 @@ + + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 03c4e99e91..dbe8e16774 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -4,6 +4,7 @@ + openHAB _openhab-server-ssl._tcp.local. Settings Notifications @@ -653,4 +654,7 @@ lamp_floor lamp_bedside lamp_outdoor + At least server name and one connection is required + Server %s + Do you really want to remove this server? diff --git a/mobile/src/main/res/xml/preferences.xml b/mobile/src/main/res/xml/preferences.xml index 6376ea4ad7..8251a8bddb 100644 --- a/mobile/src/main/res/xml/preferences.xml +++ b/mobile/src/main/res/xml/preferences.xml @@ -12,17 +12,13 @@ android:summary="@string/settings_openhab_demomode_summary" android:title="@string/settings_openhab_demomode" android:order="0" /> - - + From bebbd49b009fea450ac74a1046637619c502df0a Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Fri, 24 Jul 2020 10:51:55 +0200 Subject: [PATCH 03/44] UI and docs for primary server Signed-off-by: mueller-ma --- docs/USAGE.md | 20 ++++++++ .../habdroid/core/CloudMessagingHelper.kt | 6 ++- .../habdroid/core/CloudMessagingHelper.kt | 7 +-- .../habdroid/core/UpdateBroadcastReceiver.kt | 1 + .../core/connection/ConnectionFactory.kt | 4 +- .../org/openhab/habdroid/ui/LogActivity.kt | 10 +++- .../habdroid/ui/PreferencesActivity.kt | 32 ++++++++++++- .../ui/preference/PrimaryServerPreference.kt | 46 +++++++++++++++++++ .../openhab/habdroid/util/PrefExtensions.kt | 16 +++++-- .../org/openhab/habdroid/util/PrefKeys.kt | 2 + mobile/src/main/res/values/strings.xml | 5 ++ .../src/main/res/xml/server_preferences.xml | 4 ++ 12 files changed, 139 insertions(+), 14 deletions(-) create mode 100644 mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt diff --git a/docs/USAGE.md b/docs/USAGE.md index ded1625222..95d645e287 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -278,6 +278,26 @@ In case of an error the plugin returns an error code. | 11 | The app couldn't establish a connection | | 1000+ | A connection was established, but an error occured. The error code is 1000 + the HTTP code | +## Multi server support + +Currently multi server support is still in development. + +Features, that support multiple servers: +* Display Sitemaps and HABPanel +* In-app voice commands +* Show a list of recent notifications + +Features, that don't support multiple servers: +* Sitemap shortcuts and Item widgets on the home screen +* Shortcuts for HABPanel, notifications and voice command +* Voice command widgets +* Quick tiles +* NFC tags +* Push notifications +* Send device information to openHAB +* Tasker plugin +* Setting the device identifier on a per server basis // Note: This is probably a "won't fix" + ## Help and Technical Details Please refer to the [openhab-android project on GitHub](https://github.com/openhab/openhab-android) for more details. diff --git a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt index e95e778e49..00125db58b 100644 --- a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt +++ b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt @@ -25,6 +25,7 @@ import org.openhab.habdroid.util.HttpClient import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.getHumanReadableErrorMessage import org.openhab.habdroid.util.getPrefs +import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getRemoteUrl object CloudMessagingHelper { @@ -53,12 +54,13 @@ object CloudMessagingHelper { e } + val prefs = context.getPrefs() return when { - !context.getPrefs().getBoolean(PrefKeys.FOSS_NOTIFICATIONS_ENABLED, false) -> PushNotificationStatus( + !prefs.getBoolean(PrefKeys.FOSS_NOTIFICATIONS_ENABLED, false) -> PushNotificationStatus( context.getString(R.string.push_notification_status_disabled), R.drawable.ic_bell_off_outline_grey_24dp ) - context.getPrefs().getRemoteUrl().isEmpty() -> PushNotificationStatus( + prefs.getRemoteUrl(prefs.getPrimaryServerId()).isEmpty() -> PushNotificationStatus( context.getString(R.string.push_notification_status_no_remote_configured), R.drawable.ic_bell_off_outline_grey_24dp ) diff --git a/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt b/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt index 7c8a01290e..bfce2565c5 100644 --- a/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt +++ b/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt @@ -23,10 +23,10 @@ import org.openhab.habdroid.core.connection.CloudConnection import org.openhab.habdroid.core.connection.ConnectionFactory import org.openhab.habdroid.ui.PushNotificationStatus import org.openhab.habdroid.util.HttpClient -import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.getHumanReadableErrorMessage import org.openhab.habdroid.util.getPrefs -import org.openhab.habdroid.util.getStringOrEmpty +import org.openhab.habdroid.util.getPrimaryServerId +import org.openhab.habdroid.util.getRemoteUrl object CloudMessagingHelper { internal var registrationDone: Boolean = false @@ -63,9 +63,10 @@ object CloudMessagingHelper { Log.d(TAG, "Got exception: $e") e } + val prefs = context.getPrefs() return when { // No remote server is configured - context.getPrefs().getStringOrEmpty(PrefKeys.REMOTE_URL).isEmpty() -> + prefs.getRemoteUrl(prefs.getPrimaryServerId()).isEmpty() -> PushNotificationStatus( context.getString(R.string.push_notification_status_no_remote_configured), R.drawable.ic_bell_off_outline_grey_24dp diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt index 601cbfeb58..4a1251e3fe 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt @@ -140,6 +140,7 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { prefs.edit { putConfiguredServerIds(setOf(config.id)) putInt(PrefKeys.ACTIVE_SERVER_ID, config.id) + putInt(PrefKeys.PRIMARY_SERVER_ID, config.id) remove("default_openhab_url") remove("default_openhab_alturl") remove("default_openhab_sslclientcert") diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt index 85304ae5aa..1daa09415f 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt @@ -449,8 +449,8 @@ class ConnectionFactory internal constructor( * Wait for initialization of the factory. * * This method blocks until all asynchronous work (that is, determination of - * available and cloud connection) is ready, so that [.getConnection] - * and [.getUsableConnection] can safely be used. + * available and cloud connection) is ready, so that {@link connection} + * and {@link usableConnection} can safely be used. */ suspend fun waitForInitialization() { instance.triggerConnectionUpdateIfNeededAndPending() diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/LogActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/LogActivity.kt index 772b498d43..06c03319b4 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/LogActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/LogActivity.kt @@ -33,11 +33,14 @@ import kotlinx.coroutines.withContext import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.openhab.habdroid.R import org.openhab.habdroid.core.OpenHabApplication +import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.util.ToastType import org.openhab.habdroid.util.determineDataUsagePolicy +import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getLocalUrl import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getRemoteUrl +import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.showToast class LogActivity : AbstractBaseActivity(), SwipeRefreshLayout.OnRefreshListener { @@ -193,8 +196,11 @@ class LogActivity : AbstractBaseActivity(), SwipeRefreshLayout.OnRefreshListener } var log = logBuilder.toString() - log = redactHost(log, getPrefs().getLocalUrl(), "") - log = redactHost(log, getPrefs().getRemoteUrl(), "") + getPrefs().getConfiguredServerIds().forEach { id -> + val serverName = ServerConfiguration.load(getPrefs(), getSecretPrefs(), id)?.name ?: id.toString() + log = redactHost(log, getPrefs().getLocalUrl(id), "") + log = redactHost(log, getPrefs().getRemoteUrl(id), "") + } log } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index ccd241976a..5b32b94882 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -79,6 +79,7 @@ import org.openhab.habdroid.util.CacheManager import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.ToastType import org.openhab.habdroid.util.Util +import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getDayNightMode import org.openhab.habdroid.util.getNextAvailableServerId @@ -86,12 +87,14 @@ import org.openhab.habdroid.util.getNotificationTone import org.openhab.habdroid.util.getPreference import org.openhab.habdroid.util.getPrefixForBgTasks import org.openhab.habdroid.util.getPrefs +import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrFallbackIfEmpty import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.hasPermissions import org.openhab.habdroid.util.isTaskerPluginEnabled import org.openhab.habdroid.util.putConfiguredServerIds +import org.openhab.habdroid.util.putPrimaryServerId import org.openhab.habdroid.util.showToast import org.openhab.habdroid.util.updateDefaultSitemap @@ -446,6 +449,11 @@ class PreferencesActivity : AbstractBaseActivity() { parentActivity.openSubScreen(ServerEditorFragment.newInstance(config)) true } + pref.icon = if (serverId == prefs.getPrimaryServerId()) { + ContextCompat.getDrawable(pref.context, R.drawable.ic_star_border_grey_24dp) + } else { + null + } connCategory.addPreference(pref) // The pref needs to be attached for doing this pref.dependency = PrefKeys.DEMO_MODE @@ -607,9 +615,12 @@ class PreferencesActivity : AbstractBaseActivity() { serverIdSet.remove(config.id) prefs.edit { putConfiguredServerIds(serverIdSet) - if (prefs.getInt(PrefKeys.ACTIVE_SERVER_ID, 0) == config.id) { + if (prefs.getActiveServerId() == config.id) { putInt(PrefKeys.ACTIVE_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) } + if (prefs.getPrimaryServerId() == config.id) { + putInt(PrefKeys.PRIMARY_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) + } } parentFragmentManager.popBackStack() // close ourself } @@ -685,6 +696,25 @@ class PreferencesActivity : AbstractBaseActivity() { parentActivity.resultIntent.putExtra(RESULT_EXTRA_SITEMAP_CLEARED, true) true } + + val primaryServerPref = getPreference(PrefKeys.PRIMARY_SERVER_PREF) + updatePrimaryServerPrefState(primaryServerPref, config.id == prefs.getPrimaryServerId()) + primaryServerPref.setOnPreferenceClickListener { + prefs.edit { + putPrimaryServerId(config.id) + } + updatePrimaryServerPrefState(primaryServerPref, true) + true + } + } + + private fun updatePrimaryServerPrefState(pref: Preference, isPrimary: Boolean) { + pref.summary = if (isPrimary) { + getString(R.string.settings_server_primary_summary_is_primary) + } else { + val nameOfPrimary = ServerConfiguration.load(prefs, secretPrefs, prefs.getPrimaryServerId())?.name + getString(R.string.settings_server_primary_summary_is_not_primary, nameOfPrimary) + } } private fun handleNoDefaultSitemap(pref: Preference) { diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt new file mode 100644 index 0000000000..95955fdcb8 --- /dev/null +++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.habdroid.ui.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageView +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import org.openhab.habdroid.R +import org.openhab.habdroid.ui.setupHelpIcon +import org.openhab.habdroid.ui.updateHelpIconAlpha + +class PrimaryServerPreference constructor(context: Context, attrs: AttributeSet) : Preference(context, attrs) { + private var helpIcon: ImageView? = null + + init { + widgetLayoutResource = R.layout.help_icon_pref + } + + override fun onBindViewHolder(holder: PreferenceViewHolder?) { + super.onBindViewHolder(holder) + if (holder != null) { + helpIcon = holder.itemView.findViewById(R.id.help_icon) + helpIcon?.setupHelpIcon(context.getString(R.string.settings_server_primary_url), + R.string.click_here_for_more_information) + helpIcon?.updateHelpIconAlpha(isEnabled) + } + } + + override fun onDependencyChanged(dependency: Preference, disableDependent: Boolean) { + super.onDependencyChanged(dependency, disableDependent) + helpIcon?.updateHelpIconAlpha(isEnabled) + } +} diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt index 32ebbc4a37..3bb2986fc8 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt @@ -43,6 +43,10 @@ fun SharedPreferences.getActiveServerId(): Int { return getInt(PrefKeys.ACTIVE_SERVER_ID, 0) } +fun SharedPreferences.getPrimaryServerId(): Int { + return getInt(PrefKeys.PRIMARY_SERVER_ID, 0) +} + fun SharedPreferences.getNextAvailableServerId(): Int { return getStringSet(PrefKeys.SERVER_IDS, null) ?.lastOrNull() @@ -57,12 +61,12 @@ fun SharedPreferences.getConfiguredServerIds(): MutableSet { ?: mutableSetOf() } -fun SharedPreferences.getLocalUrl(): String { - return getStringOrNull(PrefKeys.buildServerKey(getActiveServerId(), PrefKeys.LOCAL_URL_PREFIX)).orEmpty() +fun SharedPreferences.getLocalUrl(id: Int = getActiveServerId()): String { + return getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_URL_PREFIX)).orEmpty() } -fun SharedPreferences.getRemoteUrl(): String { - return getStringOrNull(PrefKeys.buildServerKey(getActiveServerId(), PrefKeys.REMOTE_URL_PREFIX)).orEmpty() +fun SharedPreferences.getRemoteUrl(id: Int = getActiveServerId()): String { + return getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_URL_PREFIX)).orEmpty() } fun SharedPreferences.getDefaultSitemap(connection: Connection?, id: Int = getActiveServerId()): DefaultSitemap? { @@ -203,6 +207,10 @@ fun SharedPreferences.Editor.putConfiguredServerIds(ids: Set) { putStringSet(PrefKeys.SERVER_IDS, ids.map { id -> id.toString() }.toSet()) } +fun SharedPreferences.Editor.putPrimaryServerId(id: Int) { + putInt(PrefKeys.PRIMARY_SERVER_ID, id) +} + fun SharedPreferences.updateDefaultSitemap(connection: Connection?, sitemap: Sitemap?, id: Int = getActiveServerId()) { if (connection is DemoConnection) { return diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt index a4b5987b1a..b8b57bbf8e 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefKeys.kt @@ -27,6 +27,8 @@ object PrefKeys { const val REMOTE_URL_PREFIX = "remote_url_" const val REMOTE_USERNAME_PREFIX = "remote_username_" const val REMOTE_PASSWORD_PREFIX = "remote_password_" + const val PRIMARY_SERVER_PREF = "primary_server_pref" + const val PRIMARY_SERVER_ID = "primary_server_id" const val SSL_CLIENT_CERT_PREFIX = "sslclientcert_" const val DEFAULT_SITEMAP_NAME_PREFIX = "default_sitemap_name_" const val DEFAULT_SITEMAP_LABEL_PREFIX = "default_sitemap_label_" diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index dbe8e16774..772da96e62 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -657,4 +657,9 @@ At least server name and one connection is required Server %s Do you really want to remove this server? + Primary server + This server is the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. + %s is the primary server. Click to make this server the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. + https://www.openhab.org/docs/apps/android.html + Click here for more information diff --git a/mobile/src/main/res/xml/server_preferences.xml b/mobile/src/main/res/xml/server_preferences.xml index 705ba0688e..a46eb22a88 100644 --- a/mobile/src/main/res/xml/server_preferences.xml +++ b/mobile/src/main/res/xml/server_preferences.xml @@ -39,4 +39,8 @@ android:clickable="true" android:key="clear_default_sitemap" android:title="@string/settings_clear_default_sitemap" /> + From 4d395b15b26dd95cbfc5a482562ab32f00d60081 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 27 Jul 2020 13:23:29 +0200 Subject: [PATCH 04/44] Extend connection framework for active vs. primary server. Signed-off-by: Danny Baumann --- .../habdroid/core/CloudMessagingHelper.kt | 12 +- .../habdroid/core/FcmRegistrationService.kt | 2 +- .../habdroid/background/ItemUpdateWorker.kt | 2 +- .../habdroid/core/NotificationHelper.kt | 4 +- .../core/connection/ConnectionFactory.kt | 276 +++++++++++------- .../org/openhab/habdroid/ui/AboutActivity.kt | 14 +- .../habdroid/ui/AbstractItemPickerActivity.kt | 2 +- .../org/openhab/habdroid/ui/ChartActivity.kt | 2 +- .../habdroid/ui/CloudNotificationAdapter.kt | 2 +- .../ui/CloudNotificationListFragment.kt | 10 +- .../openhab/habdroid/ui/ItemPickerAdapter.kt | 2 +- .../org/openhab/habdroid/ui/MainActivity.kt | 65 +++-- .../habdroid/ui/PreferencesActivity.kt | 12 +- .../openhab/habdroid/ui/WidgetListFragment.kt | 2 +- .../habdroid/ui/activity/ContentController.kt | 6 +- .../habdroid/ui/activity/WebViewFragment.kt | 14 +- .../ui/homescreenwidget/ItemUpdateWidget.kt | 2 +- 17 files changed, 259 insertions(+), 170 deletions(-) diff --git a/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt b/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt index bfce2565c5..7826f8337b 100644 --- a/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt +++ b/mobile/src/full/java/org/openhab/habdroid/core/CloudMessagingHelper.kt @@ -56,13 +56,6 @@ object CloudMessagingHelper { suspend fun getPushNotificationStatus(context: Context): PushNotificationStatus { ConnectionFactory.waitForInitialization() - val cloudFailure = try { - ConnectionFactory.cloudConnection - null - } catch (e: Exception) { - Log.d(TAG, "Got exception: $e") - e - } val prefs = context.getPrefs() return when { // No remote server is configured @@ -72,7 +65,8 @@ object CloudMessagingHelper { R.drawable.ic_bell_off_outline_grey_24dp ) // Cloud connection failed - ConnectionFactory.cloudConnectionOrNull == null && cloudFailure != null -> { + ConnectionFactory.primaryCloudConnection?.failureReason != null -> { + val cloudFailure = ConnectionFactory.primaryCloudConnection?.failureReason val message = context.getString(R.string.push_notification_status_http_error, context.getHumanReadableErrorMessage( if (cloudFailure is HttpClient.HttpException) cloudFailure.originalUrl else "", @@ -84,7 +78,7 @@ object CloudMessagingHelper { PushNotificationStatus(message, R.drawable.ic_bell_off_outline_grey_24dp) } // Remote server is configured, but it's not a cloud instance - ConnectionFactory.cloudConnectionOrNull == null && ConnectionFactory.remoteConnectionOrNull != null -> + ConnectionFactory.primaryCloudConnection?.connection == null && ConnectionFactory.primaryRemoteConnection != null -> PushNotificationStatus( context.getString(R.string.push_notification_status_remote_no_cloud), R.drawable.ic_bell_off_outline_grey_24dp diff --git a/mobile/src/full/java/org/openhab/habdroid/core/FcmRegistrationService.kt b/mobile/src/full/java/org/openhab/habdroid/core/FcmRegistrationService.kt index b241c2c6c2..3a3557d075 100644 --- a/mobile/src/full/java/org/openhab/habdroid/core/FcmRegistrationService.kt +++ b/mobile/src/full/java/org/openhab/habdroid/core/FcmRegistrationService.kt @@ -61,7 +61,7 @@ class FcmRegistrationService : JobIntentService() { runBlocking { ConnectionFactory.waitForInitialization() } - val connection = ConnectionFactory.cloudConnectionOrNull ?: return + val connection = ConnectionFactory.primaryCloudConnection?.connection ?: return when (intent.action) { ACTION_REGISTER -> { diff --git a/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt b/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt index 9edae553a9..269950c0e5 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt @@ -65,7 +65,7 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont } Log.d(TAG, "Trying to get connection") - val connection = ConnectionFactory.usableConnectionOrNull + val connection = ConnectionFactory.primaryUsableConnection?.connection val showToast = inputData.getBoolean(INPUT_DATA_SHOW_TOAST, false) val taskerIntent = inputData.getString(INPUT_DATA_TASKER_INTENT) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt b/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt index 13c26488fa..756773df47 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt @@ -123,8 +123,8 @@ class NotificationHelper constructor(private val context: Context) { var iconBitmap: Bitmap? = null if (message.icon != null) { - val connection = ConnectionFactory.cloudConnectionOrNull - if (connection != null && context.determineDataUsagePolicy().canDoLargeTransfers) { + val connection = ConnectionFactory.primaryCloudConnection?.connection + if (connection != null && !context.determineDataUsagePolicy().canDoLargeTransfers()) { try { val targetSize = context.resources.getDimensionPixelSize(R.dimen.notificationlist_icon_size) iconBitmap = connection.httpClient diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt index 1daa09415f..2f84244603 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt @@ -32,7 +32,6 @@ import okhttp3.internal.tls.OkHostnameVerifier import okhttp3.logging.HttpLoggingInterceptor import org.openhab.habdroid.core.CloudMessagingHelper import org.openhab.habdroid.core.connection.exception.ConnectionException -import org.openhab.habdroid.core.connection.exception.ConnectionNotInitializedException import org.openhab.habdroid.core.connection.exception.NetworkNotAvailableException import org.openhab.habdroid.core.connection.exception.NoUrlInformationException import org.openhab.habdroid.model.ServerConfiguration @@ -40,6 +39,7 @@ import org.openhab.habdroid.util.CacheManager import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getPrefs +import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.isDebugModeEnabled @@ -72,27 +72,42 @@ class ConnectionFactory internal constructor( private var httpClient: OkHttpClient private var lastClientCertAlias: String? = null - private var localConnection: Connection? = null - private var remoteConnection: AbstractConnection? = null + private var primaryConn: ServerConnections? = null + private var activeConn: ServerConnections? = null private val listeners = HashSet() private var needsUpdate: Boolean = false - private var availableCheck: Job? = null - private var cloudCheck: Job? = null + private var activeCheck: Job? = null + private var primaryCheck: Job? = null + private var activeCloudCheck: Job? = null + private var primaryCloudCheck: Job? = null + private data class ServerConnections constructor( + val local: Connection?, + val remote: AbstractConnection? + ) + data class ConnectionResult constructor( + val connection: Connection?, + val failureReason: ConnectionException? + ) + data class CloudConnectionResult constructor( + val connection: CloudConnection?, + val failureReason: Exception? + ) private data class StateHolder constructor( - val available: Connection?, - val availableFailureReason: ConnectionException?, - val cloudInitialized: Boolean, - val cloud: CloudConnection?, - val cloudFailureReason: Exception? + val primary: ConnectionResult?, + val active: ConnectionResult?, + val primaryCloud: CloudConnectionResult?, + val activeCloud: CloudConnectionResult? ) - private val stateChannel = ConflatedBroadcastChannel(StateHolder(null, null, false, null, null)) + private val stateChannel = ConflatedBroadcastChannel(StateHolder(null, null, null, null)) interface UpdateListener { - fun onAvailableConnectionChanged() - fun onCloudConnectionChanged(connection: CloudConnection?) + fun onActiveConnectionChanged() + fun onPrimaryConnectionChanged() + fun onActiveCloudConnectionChanged(connection: CloudConnection?) + fun onPrimaryCloudConnectionChanged(connection: CloudConnection?) } init { @@ -127,7 +142,7 @@ class ConnectionFactory internal constructor( if (listeners.isEmpty()) { // We're running in background. Clear current state and postpone update for next // listener registration. - updateState(false, available = null, availableFailureReason = null) + updateState(false, active = null, primary = null) needsUpdate = true } else { triggerConnectionUpdateIfNeeded() @@ -140,13 +155,13 @@ class ConnectionFactory internal constructor( if (l is Activity) { trustManager.bindDisplayActivity(l) } - if (!triggerConnectionUpdateIfNeededAndPending() && localConnection != null && listeners.size == 1) { + if (!triggerConnectionUpdateIfNeededAndPending() && activeConn?.local != null && listeners.size == 1) { // When coming back from background, re-do connectivity check for // local connections, as the reachability of the local server might have // changed since we went to background - val (available, reason, _, _) = stateChannel.value - val local = available === localConnection || - (reason is NoUrlInformationException && reason.wouldHaveUsedLocalConnection()) + val (_, active, _, _) = stateChannel.value + val local = active?.connection === activeConn?.local || + (active?.failureReason is NoUrlInformationException && active.failureReason.wouldHaveUsedLocalConnection()) if (local) { triggerConnectionUpdateIfNeeded() } @@ -176,26 +191,43 @@ class ConnectionFactory internal constructor( @VisibleForTesting fun updateConnections(callListenersImmediately: Boolean = false) { if (prefs.isDemoModeEnabled()) { - if (localConnection is DemoConnection) { + if (activeConn?.local is DemoConnection) { // demo mode already was enabled return } - remoteConnection = DemoConnection(httpClient) - localConnection = remoteConnection - updateState(true, available = localConnection, availableFailureReason = null, - cloudInitialized = true, cloud = null, cloudFailureReason = null) + val conn = DemoConnection(httpClient) + activeConn = ServerConnections(conn, conn) + primaryConn = activeConn + val connResult = ConnectionResult(conn, null) + updateState(true, connResult, connResult, CloudConnectionResult(null, null)) } else { - val config = ServerConfiguration.load(prefs, secretPrefs, prefs.getActiveServerId()) - localConnection = - config?.localPath?.let { path -> DefaultConnection(httpClient, Connection.TYPE_LOCAL, path) } - remoteConnection = - config?.remotePath?.let { path -> DefaultConnection(httpClient, Connection.TYPE_REMOTE, path) } + val activeServer = prefs.getActiveServerId() + activeConn = loadServerConnections(activeServer) + + val primaryServer = prefs.getPrimaryServerId() + if (primaryServer == activeServer) { + primaryConn = activeConn + } else { + primaryConn = loadServerConnections(primaryServer) + } - updateState(callListenersImmediately, null, null, false, null, null) + updateState(callListenersImmediately, null, null, null) triggerConnectionUpdateIfNeeded() } } + private fun loadServerConnections(serverId: Int): ServerConnections? { + val config = ServerConfiguration.load(prefs, secretPrefs, serverId) + if (config == null) { + return null + } + val local = + config.localPath?.let { path -> DefaultConnection(httpClient, Connection.TYPE_LOCAL, path) } + val remote = + config.remotePath?.let { path -> DefaultConnection(httpClient, Connection.TYPE_REMOTE, path) } + return ServerConnections(local, remote) + } + private fun updateHttpLoggerSettings() { with(httpLogger) { if (prefs.isDebugModeEnabled()) { @@ -244,22 +276,27 @@ class ConnectionFactory internal constructor( private fun updateState( callListenersOnChange: Boolean, - available: Connection? = stateChannel.value.available, - availableFailureReason: ConnectionException? = stateChannel.value.availableFailureReason, - cloudInitialized: Boolean = stateChannel.value.cloudInitialized, - cloud: CloudConnection? = stateChannel.value.cloud, - cloudFailureReason: Exception? = stateChannel.value.cloudFailureReason + primary: ConnectionResult? = stateChannel.value.primary, + active: ConnectionResult? = stateChannel.value.active, + primaryCloud: CloudConnectionResult? = stateChannel.value.primaryCloud, + activeCloud: CloudConnectionResult? = stateChannel.value.activeCloud ) { val prevState = stateChannel.value - val newState = StateHolder(available, availableFailureReason, cloudInitialized, cloud, cloudFailureReason) + val newState = StateHolder(primary, active, primaryCloud, activeCloud) stateChannel.offer(newState) if (callListenersOnChange) launch { - if (newState.availableFailureReason != null || prevState.available !== newState.available) { - listeners.forEach { l -> l.onAvailableConnectionChanged() } + if (newState.active?.failureReason != null || prevState.active?.connection !== newState.active?.connection) { + listeners.forEach { l -> l.onActiveConnectionChanged() } + } + if (newState.primary?.failureReason != null || prevState.primary?.connection !== newState.primary?.connection) { + listeners.forEach { l -> l.onPrimaryConnectionChanged() } } - if (prevState.cloud !== newState.cloud) { - CloudMessagingHelper.onConnectionUpdated(context, newState.cloud) - listeners.forEach { l -> l.onCloudConnectionChanged(newState.cloud) } + if (prevState.activeCloud !== newState.activeCloud) { + listeners.forEach { l -> l.onActiveCloudConnectionChanged(newState.activeCloud?.connection) } + } + if (prevState.primaryCloud !== newState.primaryCloud) { + CloudMessagingHelper.onConnectionUpdated(context, newState.primaryCloud?.connection) + listeners.forEach { l -> l.onPrimaryCloudConnectionChanged(newState.primaryCloud?.connection) } } } } @@ -274,37 +311,78 @@ class ConnectionFactory internal constructor( } private fun triggerConnectionUpdateIfNeeded() { - availableCheck?.cancel() - cloudCheck?.cancel() + activeCheck?.cancel() + primaryCheck?.cancel() + activeCloudCheck?.cancel() + primaryCloudCheck?.cancel() - if (localConnection is DemoConnection) { + if (activeConn?.local is DemoConnection) { return } - availableCheck = launch { + val active = activeConn + val primary = primaryConn + + val updateActive = { result: ConnectionResult -> + if (active === primary) { + updateState(true, active = result, primary = result) + } else { + updateState(true, active = result) + } + } + val updateActiveCloud = { result: CloudConnectionResult -> + if (active === primary) { + updateState(true, activeCloud = result, primaryCloud = result) + } else { + updateState(true, activeCloud = result) + } + } + + activeCheck = launch { try { - val result = withContext(Dispatchers.IO) { - checkAvailableConnection(localConnection, remoteConnection) - } - // Check whether the passed connection matches a known one. If not, the - // connections were updated while the thread was processing and we'll get - // a new callback. - if (result != localConnection && result != remoteConnection) { - return@launch + val usable = withContext(Dispatchers.IO) { + checkAvailableConnection(active?.local, active?.remote) } - updateState(true, available = result, availableFailureReason = null) + updateActive(ConnectionResult(usable, null)) } catch (e: ConnectionException) { - updateState(true, available = null, availableFailureReason = e) + updateActive(ConnectionResult(null, e)) } } - cloudCheck = launch { + + if (active !== primary) { + primaryCheck = launch { + try { + val usable = withContext(Dispatchers.IO) { + checkAvailableConnection(primary?.local, primary?.remote) + } + updateState(true, primary = ConnectionResult(usable, null)) + } catch (e: ConnectionException) { + updateState(true, primary = ConnectionResult(null, e)) + } + } + } + + activeCloudCheck = launch { try { val result = withContext(Dispatchers.IO) { - remoteConnection?.toCloudConnection() + active?.remote?.toCloudConnection() } - updateState(true, cloudInitialized = true, cloud = result, cloudFailureReason = null) + updateActiveCloud(CloudConnectionResult(result, null)) } catch (e: Exception) { - updateState(true, cloudInitialized = true, cloud = null, cloudFailureReason = e) + updateActiveCloud(CloudConnectionResult(null, e)) + } + } + + if (active !== primary) { + primaryCloudCheck = launch { + try { + val result = withContext(Dispatchers.IO) { + primary?.remote?.toCloudConnection() + } + updateState(true, primaryCloud = CloudConnectionResult(result, null)) + } catch (e: Exception) { + updateState(true, primaryCloud = CloudConnectionResult(null, e)) + } } } } @@ -456,8 +534,8 @@ class ConnectionFactory internal constructor( instance.triggerConnectionUpdateIfNeededAndPending() val sub = instance.stateChannel.openSubscription() do { - val (available, reason, cloudInitialized, _) = sub.receive() - } while ((available == null && reason == null) || !cloudInitialized) + val (primary, active, primaryCloud, activeCloud) = sub.receive() + } while (primary == null || active == null || primaryCloud == null || activeCloud == null) } fun addListener(l: UpdateListener) { @@ -475,69 +553,51 @@ class ConnectionFactory internal constructor( } /** - * Returns any openHAB connection that is most likely to work on the current network. The - * connections available will be tried in the following order: - * - TYPE_LOCAL - * - TYPE_REMOTE - * - TYPE_CLOUD - * - * If there's an issue in configuration or network connectivity, or the connection - * is not yet initialized, the respective exception is thrown. + * Returns any openHAB connection that is most likely to work for the active server on the current network. + * The returned object will contain either a working connection, or the initialization failure cause. + * If initialization did not finish yet, null is returned. */ - val usableConnection: Connection - @Throws(ConnectionException::class) - get() { - instance.triggerConnectionUpdateIfNeededAndPending() - val (available, reason, _, _) = instance.stateChannel.value - if (reason != null) { - throw reason - } - if (available == null) { - throw ConnectionNotInitializedException() - } - return available - } + val activeUsableConnection get() = instance.stateChannel.value.active /** - * Like {@link usableConnection}, but returns null instead of throwing in case - * a connection could not be determined + * Returns the configured local connection for the active server, or null if none is configured */ - val usableConnectionOrNull get() = instance.stateChannel.value.available + val activeLocalConnection get() = instance.activeConn?.local /** - * Returns the configured local connection, or null if none is configured + * Returns the configured remote connection for the active server, or null if none is configured */ - val localConnectionOrNull get() = instance.localConnection + val activeRemoteConnection get() = instance.activeConn?.remote /** - * Returns the configured remote connection, or null if none is configured + * Like {@link activeUsableConnection}, but for the primary instead of active server. */ - val remoteConnectionOrNull get() = instance.remoteConnection + val primaryUsableConnection get() = instance.stateChannel.value.primary /** - * Returns the resolved cloud connection. - * May throw an exception if no remote connection is configured - * or the remote connection is not usable as cloud connection. + * Returns the configured local connection for the primary server, or null if none is configured */ - val cloudConnection: CloudConnection - @Throws(Exception::class) - get() { - instance.triggerConnectionUpdateIfNeededAndPending() - val (_, _, _, cloud, cloudFailureReason) = instance.stateChannel.value - if (cloudFailureReason != null) { - throw cloudFailureReason - } - if (cloud == null) { - throw ConnectionNotInitializedException() - } - return cloud - } + val primaryLocalConnection get() = instance.primaryConn?.local + + /** + * Returns the configured remote connection for the primary server, or null if none is configured + */ + val primaryRemoteConnection get() = instance.primaryConn?.remote + + /** + * Returns the resolved cloud connection for the active server. + * The returned object will contain either + * - a working connection + * - the initialization failure cause or + * - null for both values + * (in case no remote server is configured or the remote server is not an openHAB cloud instance) + * If initialization did not finish yet, null is returned. + */ + val activeCloudConnection get() = instance.stateChannel.value.activeCloud /** - * Returns the resolved cloud connection. - * May return null if no remote connection is configured - * or the remote connection is not usable as cloud connection. + * Like {@link activeCloudConnection}, but for the primary instead of active server. */ - val cloudConnectionOrNull get() = instance.stateChannel.value.cloud + val primaryCloudConnection get() = instance.stateChannel.value.primaryCloud } } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/AboutActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/AboutActivity.kt index a0d917a70b..07a51d5390 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/AboutActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/AboutActivity.kt @@ -115,7 +115,7 @@ class AboutActivity : AbstractBaseActivity(), FragmentManager.OnBackStackChanged override fun getMaterialAboutList(context: Context): MaterialAboutList { val props: ServerProperties? = arguments?.getParcelable("serverProperties") - val connection = ConnectionFactory.usableConnectionOrNull + val connection = ConnectionFactory.activeUsableConnection?.connection val year = SimpleDateFormat("yyyy", Locale.US).format(Calendar.getInstance().time) val appCard = MaterialAboutCard.Builder() @@ -288,11 +288,19 @@ class AboutActivity : AbstractBaseActivity(), FragmentManager.OnBackStackChanged } } - override fun onAvailableConnectionChanged() { + override fun onActiveConnectionChanged() { updatePushStatusCard() } - override fun onCloudConnectionChanged(connection: CloudConnection?) { + override fun onPrimaryConnectionChanged() { + // no-op + } + + override fun onActiveCloudConnectionChanged(connection: CloudConnection?) { + // no-op + } + + override fun onPrimaryCloudConnectionChanged(connection: CloudConnection?) { updatePushStatusCard() } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt index 099aaa5000..21ecf03459 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt @@ -239,7 +239,7 @@ abstract class AbstractItemPickerActivity : AbstractBaseActivity(), SwipeRefresh requestJob = launch { ConnectionFactory.waitForInitialization() - val connection = ConnectionFactory.usableConnectionOrNull + val connection = ConnectionFactory.primaryUsableConnection?.connection // XXX: correct? if (connection == null) { updateViewVisibility(loading = false, loadError = true, showHint = false) return@launch diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/ChartActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/ChartActivity.kt index 4f82551e15..cb162f8265 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/ChartActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/ChartActivity.kt @@ -151,7 +151,7 @@ class ChartActivity : AbstractBaseActivity(), SwipeRefreshLayout.OnRefreshListen } private fun loadChartImage(force: Boolean) { - val connection = ConnectionFactory.usableConnectionOrNull + val connection = ConnectionFactory.activeUsableConnection?.connection if (connection == null) { finish() return diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationAdapter.kt b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationAdapter.kt index 5fbc27ecbb..2f16333686 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationAdapter.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationAdapter.kt @@ -107,7 +107,7 @@ class CloudNotificationAdapter(context: Context, private val loadMoreListener: ( DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0) messageView.text = notification.message - val conn = ConnectionFactory.cloudConnectionOrNull + val conn = ConnectionFactory.activeCloudConnection?.connection if (notification.icon != null && conn != null) { iconView.setImageUrl( conn, diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt index cefd751866..16efbdcdce 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt @@ -112,7 +112,11 @@ class CloudNotificationListFragment : Fragment(), View.OnClickListener, SwipeRef private fun loadNotifications(clearExisting: Boolean) { val activity = activity as AbstractBaseActivity? ?: return - val conn = ConnectionFactory.cloudConnectionOrNull + val conn = if (arguments?.getBoolean("primary") == true) { + ConnectionFactory.primaryCloudConnection?.connection + } else { + ConnectionFactory.activeCloudConnection?.connection + } if (conn == null) { updateViewVisibility(loading = false, loadError = true) return @@ -176,9 +180,9 @@ class CloudNotificationListFragment : Fragment(), View.OnClickListener, SwipeRef private const val PAGE_SIZE = 20 - fun newInstance(highlightedId: String?): CloudNotificationListFragment { + fun newInstance(highlightedId: String?, primaryServer: Boolean): CloudNotificationListFragment { val f = CloudNotificationListFragment() - f.arguments = bundleOf("highlightedId" to highlightedId) + f.arguments = bundleOf("highlightedId" to highlightedId, "primary" to primaryServer) return f } } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt index f5a8b7bd81..5602681547 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt @@ -118,7 +118,7 @@ class ItemPickerAdapter(context: Context, private val itemClickListener: ItemCli itemLabelView.text = item.label itemTypeView.text = item.type.toString() - val connection = ConnectionFactory.usableConnectionOrNull + val connection = ConnectionFactory.primaryUsableConnection?.connection // XXX: correct? val icon = item.category.toOH2IconResource() if (icon != null && connection != null) { iconView.setImageUrl( diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index 56d3b8855c..f4de37f034 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -83,7 +83,6 @@ import org.openhab.habdroid.core.connection.CloudConnection import org.openhab.habdroid.core.connection.Connection import org.openhab.habdroid.core.connection.ConnectionFactory import org.openhab.habdroid.core.connection.DemoConnection -import org.openhab.habdroid.core.connection.exception.ConnectionException import org.openhab.habdroid.core.connection.exception.ConnectionNotInitializedException import org.openhab.habdroid.core.connection.exception.NetworkNotAvailableException import org.openhab.habdroid.core.connection.exception.NoUrlInformationException @@ -201,7 +200,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { serverProperties = savedInstanceState.getParcelable(STATE_KEY_SERVER_PROPERTIES) val lastConnectionHash = savedInstanceState.getInt(STATE_KEY_CONNECTION_HASH) if (lastConnectionHash != -1) { - val c = ConnectionFactory.usableConnectionOrNull + val c = ConnectionFactory.activeUsableConnection?.connection if (c != null && c.hashCode() == lastConnectionHash) { connection = c } @@ -265,7 +264,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { ConnectionFactory.addListener(this) updateDrawerServerEntries() - onAvailableConnectionChanged() + onActiveConnectionChanged() if (connection != null && serverProperties == null) { controller.clearServerCommunicationFailure() @@ -415,20 +414,13 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } } - override fun onAvailableConnectionChanged() { - RemoteLog.d(TAG, "onAvailableConnectionChanged()") - var newConnection: Connection? - var failureReason: ConnectionException? + override fun onActiveConnectionChanged() { + RemoteLog.d(TAG, "onActiveConnectionChanged()") + val result = ConnectionFactory.activeUsableConnection + val newConnection = result?.connection + val failureReason = result?.failureReason - try { - newConnection = ConnectionFactory.usableConnection - failureReason = null - } catch (e: ConnectionException) { - newConnection = null - failureReason = e - } - - if (ConnectionFactory.cloudConnectionOrNull != null) { + if (ConnectionFactory.activeCloudConnection?.connection != null) { manageNotificationShortcut(true) } @@ -455,7 +447,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { failureReason is NoUrlInformationException -> { // Attempt resolving only if we're connected locally and // no local connection is configured yet - if (failureReason.wouldHaveUsedLocalConnection() && ConnectionFactory.localConnectionOrNull == null) { + if (failureReason.wouldHaveUsedLocalConnection() && ConnectionFactory.activeLocalConnection == null) { if (serviceResolveJob == null) { val resolver = AsyncServiceResolver(this, getString(R.string.openhab_service_type), this) @@ -504,12 +496,22 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } } - override fun onCloudConnectionChanged(connection: CloudConnection?) { - RemoteLog.d(TAG, "onCloudConnectionChanged()") + override fun onPrimaryConnectionChanged() { + // no-op + } + + override fun onActiveCloudConnectionChanged(connection: CloudConnection?) { + RemoteLog.d(TAG, "onActiveCloudConnectionChanged()") updateDrawerItemVisibility() handlePendingAction() } + override fun onPrimaryCloudConnectionChanged(connection: CloudConnection?) { + RemoteLog.d(TAG, "onPrimaryCloudConnectionChanged()") + handlePendingAction() + } + + private fun handleConnectionChange() { if (connection is DemoConnection) { showSnackbar(R.string.info_demo_mode_short, R.string.turn_off, TAG_SNACKBAR_DEMO_MODE_ACTIVE) { @@ -519,7 +521,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } } else { val hasLocalAndRemote = - ConnectionFactory.localConnectionOrNull != null && ConnectionFactory.remoteConnectionOrNull != null + ConnectionFactory.activeLocalConnection != null && ConnectionFactory.activeRemoteConnection != null val type = connection?.connectionType if (hasLocalAndRemote && type == Connection.TYPE_LOCAL) { showSnackbar(R.string.info_conn_url, tag = TAG_SNACKBAR_CONNECTION_ESTABLISHED, @@ -622,7 +624,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { ACTION_NOTIFICATION_SELECTED -> { CloudMessagingHelper.onNotificationSelected(this, intent) val id = intent.getStringExtra(EXTRA_PERSISTED_NOTIFICATION_ID).orEmpty() - executeActionIfPossible(PendingAction.OpenNotification(id)) + executeActionIfPossible(PendingAction.OpenNotification(id, true)) } ACTION_HABPANEL_SELECTED -> executeOrStoreAction(PendingAction.OpenHabPanel()) ACTION_VOICE_RECOGNITION_SELECTED -> executeOrStoreAction(PendingAction.LaunchVoiceRecognition()) @@ -687,7 +689,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { var handled = false when (item.itemId) { R.id.notifications -> { - openNotifications(null) + openNotifications(null, false) handled = true } R.id.nfc -> { @@ -830,7 +832,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { drawerMenu.setGroupVisible(R.id.options, true) val notificationsItem = drawerMenu.findItem(R.id.notifications) - notificationsItem.isVisible = ConnectionFactory.cloudConnectionOrNull != null + notificationsItem.isVisible = ConnectionFactory.activeCloudConnection?.connection != null val habPanelItem = drawerMenu.findItem(R.id.habpanel) habPanelItem.isVisible = serverProperties?.hasHabPanelInstalled() == true @@ -913,9 +915,14 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { launchVoiceRecognition() true } - action is PendingAction.OpenNotification && isStarted && ConnectionFactory.cloudConnectionOrNull != null -> { - openNotifications(action.notificationId) - true + action is PendingAction.OpenNotification && isStarted -> { + val conn = if (action.primary) ConnectionFactory.primaryCloudConnection else ConnectionFactory.activeCloudConnection + if (conn?.connection != null) { + openNotifications(action.notificationId, action.primary) + true + } else { + false + } } else -> false } @@ -968,8 +975,8 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { .show() } - private fun openNotifications(highlightedId: String?) { - controller.openNotifications(highlightedId) + private fun openNotifications(highlightedId: String?, primaryServer: Boolean) { + controller.openNotifications(highlightedId, primaryServer) drawerToggle.isDrawerIndicatorEnabled = false } @@ -1228,7 +1235,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { class OpenSitemapUrl constructor(val url: String) : PendingAction() class OpenHabPanel : PendingAction() class LaunchVoiceRecognition : PendingAction() - class OpenNotification constructor(val notificationId: String) : PendingAction() + class OpenNotification constructor(val notificationId: String, val primary: Boolean) : PendingAction() } companion object { diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 5b32b94882..6b89813395 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -553,11 +553,19 @@ class PreferencesActivity : AbstractBaseActivity() { } } - override fun onAvailableConnectionChanged() { + override fun onActiveConnectionChanged() { + // no-op + } + + override fun onPrimaryConnectionChanged() { updateNotificationStatusSummaries() } - override fun onCloudConnectionChanged(connection: CloudConnection?) { + override fun onActiveCloudConnectionChanged(connection: CloudConnection?) { + // no-op + } + + override fun onPrimaryCloudConnectionChanged(connection: CloudConnection?) { updateNotificationStatusSummaries() } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.kt index 9577cb928e..94fc18f8b8 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.kt @@ -485,7 +485,7 @@ class WidgetListFragment : Fragment(), WidgetAdapter.ItemClickListener, linkedPage: LinkedPage, whiteBackground: Boolean ) = GlobalScope.launch { - val connection = ConnectionFactory.usableConnectionOrNull ?: return@launch + val connection = ConnectionFactory.activeUsableConnection?.connection ?: return@launch /** * Icon size is defined in {@link AdaptiveIconDrawable}. Foreground size of * 46dp instead of 72dp adds enough border to the icon. diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt index 53d34d58ea..da98275d4c 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt @@ -89,7 +89,7 @@ abstract class ContentController protected constructor(private val activity: Mai */ val currentTitle get() = when { noConnectionFragment != null -> null - temporaryPage is CloudNotificationListFragment -> activity.getString(R.string.app_notifications) + temporaryPage is CloudNotificationListFragment -> activity.getString(R.string.app_notifications) // XXX: mark server type (active/primary) or index in title? temporaryPage is WebViewFragment -> activity.getString((temporaryPage as WebViewFragment).titleResId) temporaryPage != null -> null else -> fragmentForTitle?.title @@ -344,8 +344,8 @@ abstract class ContentController protected constructor(private val activity: Mai * * @param highlightedId ID of notification to be highlighted initially */ - fun openNotifications(highlightedId: String?) { - showTemporaryPage(CloudNotificationListFragment.newInstance(highlightedId)) + fun openNotifications(highlightedId: String?, primaryServer: Boolean) { + showTemporaryPage(CloudNotificationListFragment.newInstance(highlightedId, primaryServer)) } /** diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt index 95f956516b..8dc270967a 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt @@ -150,11 +150,19 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { } } - override fun onAvailableConnectionChanged() { + override fun onActiveConnectionChanged() { loadWebsite() } - override fun onCloudConnectionChanged(connection: CloudConnection?) { + override fun onPrimaryConnectionChanged() { + // no-op + } + + override fun onActiveCloudConnectionChanged(connection: CloudConnection?) { + // no-op + } + + override fun onPrimaryCloudConnectionChanged(connection: CloudConnection?) { // no-op } @@ -171,7 +179,7 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { } private fun loadWebsite(urlToLoad: String = this.urlToLoad) { - val conn = ConnectionFactory.usableConnectionOrNull + val conn = ConnectionFactory.activeUsableConnection?.connection if (conn == null) { updateViewVisibility(error = true, loading = false) return diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/ItemUpdateWidget.kt b/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/ItemUpdateWidget.kt index a98105a5e7..5e55ee73b6 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/ItemUpdateWidget.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/ItemUpdateWidget.kt @@ -225,7 +225,7 @@ open class ItemUpdateWidget : AppWidgetProvider() { } else { Log.d(TAG, "Download icon") ConnectionFactory.waitForInitialization() - val connection = ConnectionFactory.usableConnectionOrNull + val connection = ConnectionFactory.primaryUsableConnection?.connection if (connection == null) { Log.d(TAG, "Got no connection") return@launch From 8d71dec7fa07283618a6dc6d8a8f76a4df251426 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 27 Jul 2020 13:30:28 +0200 Subject: [PATCH 05/44] Keep server prefs update in one place. Makes sure all necessary keys are written and data stays consistent. Previously, primary server wasn't updated when adding the first server, and server ID set as well as active and primary servers weren't written at well when adding a discovered server. Signed-off-by: Danny Baumann --- .../habdroid/model/ServerConfiguration.kt | 24 +++++++++++++++++++ .../habdroid/ui/PreferencesActivity.kt | 23 ------------------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt index 6ae9d45ad9..e8175bd4cb 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt @@ -18,7 +18,11 @@ import android.os.Parcelable import androidx.core.content.edit import kotlinx.android.parcel.Parcelize import org.openhab.habdroid.util.PrefKeys +import org.openhab.habdroid.util.getActiveServerId +import org.openhab.habdroid.util.getConfiguredServerIds +import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getStringOrNull +import org.openhab.habdroid.util.putConfiguredServerIds import org.openhab.habdroid.util.toNormalizedUrl @Parcelize @@ -59,11 +63,21 @@ data class ServerConfiguration( val defaultSitemap: DefaultSitemap? ) : Parcelable { fun saveToPrefs(prefs: SharedPreferences, secretPrefs: SharedPreferences) { + val serverIdSet = prefs.getConfiguredServerIds() + prefs.edit { putString(PrefKeys.buildServerKey(id, PrefKeys.SERVER_NAME_PREFIX), name) putString(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_URL_PREFIX), localPath?.url) putString(PrefKeys.buildServerKey(id, PrefKeys.REMOTE_URL_PREFIX), remotePath?.url) putString(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX), sslClientCert) + if (!serverIdSet.contains(id)) { + serverIdSet.add(id) + putConfiguredServerIds(serverIdSet) + if (serverIdSet.size == 1) { + putInt(PrefKeys.ACTIVE_SERVER_ID, id) + putInt(PrefKeys.PRIMARY_SERVER_ID, id) + } + } } saveDefaultSitemap(prefs, id, defaultSitemap) secretPrefs.edit { @@ -74,6 +88,9 @@ data class ServerConfiguration( } } fun removeFromPrefs(prefs: SharedPreferences, secretPrefs: SharedPreferences) { + val serverIdSet = prefs.getConfiguredServerIds() + serverIdSet.remove(id) + prefs.edit { remove(PrefKeys.buildServerKey(id, PrefKeys.SERVER_NAME_PREFIX)) remove(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_URL_PREFIX)) @@ -81,6 +98,13 @@ data class ServerConfiguration( remove(PrefKeys.buildServerKey(id, PrefKeys.SSL_CLIENT_CERT_PREFIX)) remove(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_NAME_PREFIX)) remove(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_LABEL_PREFIX)) + putConfiguredServerIds(serverIdSet) + if (prefs.getActiveServerId() == id) { + putInt(PrefKeys.ACTIVE_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) + } + if (prefs.getPrimaryServerId() == id) { + putInt(PrefKeys.PRIMARY_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) + } } secretPrefs.edit { remove(PrefKeys.buildServerKey(id, PrefKeys.LOCAL_USERNAME_PREFIX)) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 6b89813395..2a85642f00 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -79,7 +79,6 @@ import org.openhab.habdroid.util.CacheManager import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.ToastType import org.openhab.habdroid.util.Util -import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getDayNightMode import org.openhab.habdroid.util.getNextAvailableServerId @@ -93,7 +92,6 @@ import org.openhab.habdroid.util.getStringOrFallbackIfEmpty import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.hasPermissions import org.openhab.habdroid.util.isTaskerPluginEnabled -import org.openhab.habdroid.util.putConfiguredServerIds import org.openhab.habdroid.util.putPrimaryServerId import org.openhab.habdroid.util.showToast import org.openhab.habdroid.util.updateDefaultSitemap @@ -600,16 +598,6 @@ class PreferencesActivity : AbstractBaseActivity() { return true } config.saveToPrefs(prefs, secretPrefs) - val serverIdSet = prefs.getConfiguredServerIds() - if (!serverIdSet.contains(config.id)) { - serverIdSet.add(config.id) - prefs.edit { - putConfiguredServerIds(serverIdSet) - if (serverIdSet.size == 1) { - putInt(PrefKeys.ACTIVE_SERVER_ID, config.id) - } - } - } parentActivity.invalidateOptionsMenu() parentFragmentManager.popBackStack() // close ourself true @@ -619,17 +607,6 @@ class PreferencesActivity : AbstractBaseActivity() { .setMessage(R.string.settings_server_confirm_deletion) .setPositiveButton(R.string.settings_menu_delete_server) { _, _ -> config.removeFromPrefs(prefs, secretPrefs) - val serverIdSet = prefs.getConfiguredServerIds() - serverIdSet.remove(config.id) - prefs.edit { - putConfiguredServerIds(serverIdSet) - if (prefs.getActiveServerId() == config.id) { - putInt(PrefKeys.ACTIVE_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) - } - if (prefs.getPrimaryServerId() == config.id) { - putInt(PrefKeys.PRIMARY_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) - } - } parentFragmentManager.popBackStack() // close ourself } .setNegativeButton(android.R.string.cancel, null) From b8edf7fe2648f02f22793d1102550628360291ee Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 27 Jul 2020 13:51:03 +0200 Subject: [PATCH 06/44] Improve primary server pref for new servers. Don't show the pref for the first server, and don't mark not-yet-created servers as primary. Signed-off-by: Danny Baumann --- .../habdroid/ui/PreferencesActivity.kt | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 2a85642f00..c94b23d6c7 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -574,6 +574,7 @@ class PreferencesActivity : AbstractBaseActivity() { class ServerEditorFragment : AbstractSettingsFragment() { private lateinit var config: ServerConfiguration + private var markAsPrimary = false override val titleResId: Int get() = R.string.settings_edit_server @@ -598,6 +599,9 @@ class PreferencesActivity : AbstractBaseActivity() { return true } config.saveToPrefs(prefs, secretPrefs) + if (markAsPrimary) { + prefs.edit().putPrimaryServerId(config.id) + } parentActivity.invalidateOptionsMenu() parentFragmentManager.popBackStack() // close ourself true @@ -682,14 +686,20 @@ class PreferencesActivity : AbstractBaseActivity() { true } - val primaryServerPref = getPreference(PrefKeys.PRIMARY_SERVER_PREF) - updatePrimaryServerPrefState(primaryServerPref, config.id == prefs.getPrimaryServerId()) - primaryServerPref.setOnPreferenceClickListener { - prefs.edit { - putPrimaryServerId(config.id) + if (prefs.getConfiguredServerIds().isEmpty()) { + preferenceScreen.removePreferenceRecursively(PrefKeys.PRIMARY_SERVER_PREF) + } else { + val primaryServerPref = getPreference(PrefKeys.PRIMARY_SERVER_PREF) + updatePrimaryServerPrefState(primaryServerPref, config.id == prefs.getPrimaryServerId()) + primaryServerPref.setOnPreferenceClickListener { + if (prefs.getConfiguredServerIds().contains(config.id)) { + prefs.edit().putPrimaryServerId(config.id) + } else { + markAsPrimary = true + } + updatePrimaryServerPrefState(primaryServerPref, true) + true } - updatePrimaryServerPrefState(primaryServerPref, true) - true } } From 14cad7ee7e180fc8c830fc6d3c15419ef2e65165 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 27 Jul 2020 15:07:49 +0200 Subject: [PATCH 07/44] Fix ConnectionFactory test. Signed-off-by: Danny Baumann --- .../core/connection/ConnectionFactoryTest.kt | 66 ++++++++++--------- .../core/connection/DefaultConnectionTest.kt | 15 +++-- .../habdroid/util/PreferencesUtilTest.kt | 2 +- 3 files changed, 45 insertions(+), 38 deletions(-) diff --git a/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt b/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt index fef0754ee7..589c9efeb6 100644 --- a/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt +++ b/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt @@ -41,7 +41,6 @@ import org.junit.BeforeClass import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder -import org.openhab.habdroid.core.connection.exception.ConnectionException import org.openhab.habdroid.core.connection.exception.NetworkNotAvailableException import org.openhab.habdroid.core.connection.exception.NoUrlInformationException import org.openhab.habdroid.util.PrefKeys @@ -82,7 +81,10 @@ class ConnectionFactoryTest { val cacheFolder = tempFolder.newFolder("cache") val appDir = tempFolder.newFolder() - mockPrefs = mock() + mockPrefs = mock() { + on { getStringSet(eq(PrefKeys.SERVER_IDS), anyOrNull()) } doReturn setOf("1") + on { getInt(eq(PrefKeys.ACTIVE_SERVER_ID), any()) } doReturn 1 + } mockContext = mock { on { cacheDir } doReturn cacheFolder on { getDir(any(), any()) } doAnswer { invocation -> @@ -102,10 +104,10 @@ class ConnectionFactoryTest { server.enqueue(MockResponse().setResponseCode(404)) server.start() - whenever(mockPrefs.getString(eq(PrefKeys.REMOTE_URL), anyOrNull())) doReturn server.url("/").toString() + fillInServers(remote = server.url("/").toString()) updateAndWaitForConnections() - val conn = ConnectionFactory.remoteConnectionOrNull + val conn = ConnectionFactory.activeRemoteConnection assertNotNull("Should return a remote connection if remote url is set.", conn) assertEquals("The connection type of a remote connection should be TYPE_REMOTE.", @@ -114,18 +116,19 @@ class ConnectionFactoryTest { @Test fun testGetConnectionRemoteWithoutUrl() { - whenever(mockPrefs.getString(eq(PrefKeys.REMOTE_URL), any())) doReturn "" + fillInServers(remote = "") updateAndWaitForConnections() - val conn = ConnectionFactory.remoteConnectionOrNull + val conn = ConnectionFactory.activeRemoteConnection assertNull("Should not return a remote connection if remote url isn't set.", conn) } @Test fun testGetConnectionLocalWithUrl() { - whenever(mockPrefs.getString(eq(PrefKeys.LOCAL_URL), anyOrNull())) doReturn "https://openhab.local:8080" + fillInServers(local = "https://openhab.local:8080") updateAndWaitForConnections() - val conn = ConnectionFactory.localConnectionOrNull + + val conn = ConnectionFactory.activeLocalConnection assertNotNull("Should return a local connection if local url is set.", conn) assertEquals("The connection type of a local connection should be TYPE_LOCAL.", @@ -134,9 +137,9 @@ class ConnectionFactoryTest { @Test fun testGetConnectionLocalWithoutUrl() { - whenever(mockPrefs.getString(eq(PrefKeys.LOCAL_URL), any())) doReturn "" + fillInServers(local = "") updateAndWaitForConnections() - val conn = ConnectionFactory.localConnectionOrNull + val conn = ConnectionFactory.activeLocalConnection assertNull("Should not return a local connection when local url isn't set.", conn) } @@ -148,9 +151,11 @@ class ConnectionFactoryTest { server.enqueue(MockResponse().setBody("{'gcm': { 'senderId': '12345'} }")) server.start() - whenever(mockPrefs.getString(eq(PrefKeys.REMOTE_URL), anyOrNull())) doReturn server.url("/").toString() + fillInServers(remote = server.url("/").toString()) updateAndWaitForConnections() - val conn = ConnectionFactory.cloudConnectionOrNull + val foo = ConnectionFactory.activeCloudConnection + //val conn = ConnectionFactory.activeCloudConnection?.connection + val conn = foo?.connection assertNotNull("Should return a cloud connection if remote url is set.", conn) assertEquals(CloudConnection::class.java, conn!!.javaClass) @@ -162,74 +167,69 @@ class ConnectionFactoryTest { server.shutdown() } - @Test(expected = NetworkNotAvailableException::class) - @Throws(ConnectionException::class) + @Test fun testGetAnyConnectionNoNetwork() { mockConnectionHelper.update(null) updateAndWaitForConnections() - ConnectionFactory.usableConnection + assertEquals(ConnectionFactory.activeUsableConnection?.failureReason?.javaClass, NetworkNotAvailableException::class.java) } @Test fun testGetConnectionUnknownNetwork() { - whenever(mockPrefs.getString(any(), anyOrNull())) doReturn "https://openhab.local:8080" + fillInServers("https://openhab.local:8080", "https://openhab.local:8080") mockConnectionHelper.update(ConnectionManagerHelper.ConnectionType.Unknown(null)) updateAndWaitForConnections() assertEquals( "Unknown transport types should be used for remote connections", - ConnectionFactory.usableConnection.connectionType, + ConnectionFactory.activeUsableConnection?.connection?.connectionType, Connection.TYPE_REMOTE ) } @Test - @Throws(ConnectionException::class, IOException::class) fun testGetAnyConnectionWifiRemoteOnly() { val server = MockWebServer() server.enqueue(MockResponse().setResponseCode(404)) server.start() - whenever(mockPrefs.getString(eq(PrefKeys.REMOTE_URL), anyOrNull())) doReturn server.url("/").toString() + fillInServers(remote = server.url("/").toString()) mockConnectionHelper.update(ConnectionManagerHelper.ConnectionType.Wifi(null)) updateAndWaitForConnections() - val conn = ConnectionFactory.usableConnection + val conn = ConnectionFactory.activeUsableConnection?.connection assertNotNull("Should return a connection in WIFI when only remote url is set.", conn) assertEquals("The connection type of the connection should be TYPE_REMOTE.", - Connection.TYPE_REMOTE, conn.connectionType) + Connection.TYPE_REMOTE, conn?.connectionType) server.shutdown() } @Test - @Throws(ConnectionException::class, IOException::class) fun testGetAnyConnectionWifiLocalRemote() { val server = MockWebServer() server.enqueue(MockResponse().setResponseCode(404)) server.start() - whenever(mockPrefs.getString(eq(PrefKeys.REMOTE_URL), anyOrNull())) doReturn server.url("/").toString() - whenever(mockPrefs.getString(eq(PrefKeys.LOCAL_URL), anyOrNull())) doReturn "https://myopenhab.org:443" + fillInServers(remote = server.url("/").toString(), local = "https://myopenhab.org:443") mockConnectionHelper.update(ConnectionManagerHelper.ConnectionType.Wifi(null)) updateAndWaitForConnections() - val conn = ConnectionFactory.usableConnection + val conn = ConnectionFactory.activeUsableConnection?.connection assertNotNull("Should return a connection in WIFI when a local url is set.", conn) assertEquals("The connection type of the connection should be TYPE_LOCAL.", - Connection.TYPE_LOCAL, conn.connectionType) + Connection.TYPE_LOCAL, conn?.connectionType) server.shutdown() } - @Test(expected = NoUrlInformationException::class) - @Throws(ConnectionException::class) + @Test fun testGetAnyConnectionWifiNoLocalNoRemote() { - whenever(mockPrefs.getString(any(), any())) doReturn null + fillInServers(null, null) mockConnectionHelper.update(ConnectionManagerHelper.ConnectionType.Wifi(null)) updateAndWaitForConnections() - ConnectionFactory.usableConnection + assertEquals(ConnectionFactory.activeUsableConnection?.failureReason?.javaClass, NoUrlInformationException::class.java) } private inner class MockConnectionHelper : ConnectionManagerHelper { @@ -244,6 +244,12 @@ class ConnectionFactoryTest { override fun shutdown() {} } + private fun fillInServers(local: String? = null, remote: String? = null) { + whenever(mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.LOCAL_URL_PREFIX)), anyOrNull())) doReturn local + whenever(mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.REMOTE_URL_PREFIX)), anyOrNull())) doReturn remote + whenever(mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.SERVER_NAME_PREFIX)), anyOrNull())) doReturn "Test Server" + } + private fun updateAndWaitForConnections() { runBlocking { launch(Dispatchers.Main) { diff --git a/mobile/src/test/java/org/openhab/habdroid/core/connection/DefaultConnectionTest.kt b/mobile/src/test/java/org/openhab/habdroid/core/connection/DefaultConnectionTest.kt index 2a61b44d0f..3d430a35ea 100644 --- a/mobile/src/test/java/org/openhab/habdroid/core/connection/DefaultConnectionTest.kt +++ b/mobile/src/test/java/org/openhab/habdroid/core/connection/DefaultConnectionTest.kt @@ -23,6 +23,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test +import org.openhab.habdroid.model.ServerPath import org.openhab.habdroid.util.HttpClient class DefaultConnectionTest { @@ -36,11 +37,11 @@ class DefaultConnectionTest { fun setup() { client = OkHttpClient.Builder().build() testConnection = DefaultConnection(client, Connection.TYPE_LOCAL, - TEST_BASE_URL, null, null) + ServerPath(TEST_BASE_URL, null, null)) testConnectionRemote = DefaultConnection(client, Connection.TYPE_REMOTE, - "", null, null) - testConnectionCloud = DefaultConnection(client, - Connection.TYPE_CLOUD, "", null, null) + ServerPath("", null, null)) + testConnectionCloud = DefaultConnection(client, Connection.TYPE_CLOUD, + ServerPath("", null, null)) } @Test @@ -71,14 +72,14 @@ class DefaultConnectionTest { @Test fun testGetUsernameSet() { val connection = DefaultConnection(client, Connection.TYPE_LOCAL, - TEST_BASE_URL, "Test-User", null) + ServerPath(TEST_BASE_URL, "Test-User", null)) assertEquals("Test-User", connection.username) } @Test fun testGetPasswordSet() { val connection = DefaultConnection(client, Connection.TYPE_LOCAL, - TEST_BASE_URL, null, "Test-Password") + ServerPath(TEST_BASE_URL, null, "Test-Password")) assertEquals("Test-Password", connection.password) } @@ -100,7 +101,7 @@ class DefaultConnectionTest { @Test fun testHasUsernamePassword() { val connection = DefaultConnection(client, Connection.TYPE_LOCAL, - TEST_BASE_URL, "Test-User", "Test-Password") + ServerPath(TEST_BASE_URL, "Test-User", "Test-Password")) val httpClient = connection.httpClient assertEquals(Credentials.basic("Test-User", "Test-Password"), diff --git a/mobile/src/test/java/org/openhab/habdroid/util/PreferencesUtilTest.kt b/mobile/src/test/java/org/openhab/habdroid/util/PreferencesUtilTest.kt index 7f9973f77f..ed0ca03e30 100644 --- a/mobile/src/test/java/org/openhab/habdroid/util/PreferencesUtilTest.kt +++ b/mobile/src/test/java/org/openhab/habdroid/util/PreferencesUtilTest.kt @@ -18,7 +18,7 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test import org.openhab.habdroid.ui.PreferencesActivity.AbstractSettingsFragment.Companion.isWeakPassword -import org.openhab.habdroid.ui.PreferencesActivity.MainSettingsFragment.Companion.beautifyUrl +import org.openhab.habdroid.ui.PreferencesActivity.ServerEditorFragment.Companion.beautifyUrl class PreferencesUtilTest { @Test From 4b74eb419a927b7f31b7172ab021d7d0909c7681 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Mon, 27 Jul 2020 15:38:07 +0200 Subject: [PATCH 08/44] Fix foss build Signed-off-by: Danny Baumann --- .../openhab/habdroid/core/CloudMessagingHelper.kt | 14 ++------------ .../openhab/habdroid/core/NotificationPoller.kt | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt index 00125db58b..948a3bd75f 100644 --- a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt +++ b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt @@ -15,7 +15,6 @@ package org.openhab.habdroid.core import android.content.Context import android.content.Intent -import android.util.Log import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.CloudConnection import org.openhab.habdroid.core.connection.ConnectionFactory @@ -29,8 +28,6 @@ import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getRemoteUrl object CloudMessagingHelper { - private val TAG = CloudMessagingHelper::class.java.simpleName - @Suppress("UNUSED_PARAMETER") fun onConnectionUpdated(context: Context, connection: CloudConnection?) {} @@ -46,14 +43,7 @@ object CloudMessagingHelper { suspend fun getPushNotificationStatus(context: Context): PushNotificationStatus { ConnectionFactory.waitForInitialization() - val cloudFailure = try { - ConnectionFactory.cloudConnection - null - } catch (e: Exception) { - Log.d(TAG, "Got exception: $e") - e - } - + val cloudFailure = ConnectionFactory.primaryCloudConnection?.failureReason val prefs = context.getPrefs() return when { !prefs.getBoolean(PrefKeys.FOSS_NOTIFICATIONS_ENABLED, false) -> PushNotificationStatus( @@ -64,7 +54,7 @@ object CloudMessagingHelper { context.getString(R.string.push_notification_status_no_remote_configured), R.drawable.ic_bell_off_outline_grey_24dp ) - ConnectionFactory.cloudConnectionOrNull != null -> PushNotificationStatus( + ConnectionFactory.primaryCloudConnection?.connection != null -> PushNotificationStatus( context.getString(R.string.push_notification_status_impaired), R.drawable.ic_bell_ring_outline_grey_24dp, AboutActivity.AboutMainFragment.makeClickRedirect( diff --git a/mobile/src/foss/java/org/openhab/habdroid/core/NotificationPoller.kt b/mobile/src/foss/java/org/openhab/habdroid/core/NotificationPoller.kt index b808a50e13..21685ecac6 100644 --- a/mobile/src/foss/java/org/openhab/habdroid/core/NotificationPoller.kt +++ b/mobile/src/foss/java/org/openhab/habdroid/core/NotificationPoller.kt @@ -30,7 +30,7 @@ object NotificationPoller { suspend fun checkForNewNotifications(context: Context) { ConnectionFactory.waitForInitialization() - val connection = ConnectionFactory.cloudConnectionOrNull + val connection = ConnectionFactory.primaryCloudConnection?.connection if (connection == null) { Log.d(TAG, "Got no connection") return From 302aafe6df4381d77f0ba02ded8477b8b8fe3bdb Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Mon, 27 Jul 2020 16:36:54 +0200 Subject: [PATCH 09/44] Quote server name in primary server pref Signed-off-by: mueller-ma --- mobile/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 772da96e62..63c46a37ea 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -659,7 +659,7 @@ Do you really want to remove this server? Primary server This server is the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. - %s is the primary server. Click to make this server the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. + "%s" is the primary server. Click to make this server the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. https://www.openhab.org/docs/apps/android.html Click here for more information From 57d9027b1cf6525aefb0ae944ba30a88c3c082b8 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Wed, 29 Jul 2020 09:26:26 +0200 Subject: [PATCH 10/44] Trigger connection factory update on primary server changes. Signed-off-by: Danny Baumann --- .../org/openhab/habdroid/core/connection/ConnectionFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt index 2f84244603..b5432fae64 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt @@ -495,7 +495,7 @@ class ConnectionFactory internal constructor( companion object { private val TAG = ConnectionFactory::class.java.simpleName private val UPDATE_TRIGGERING_KEYS = listOf( - PrefKeys.DEMO_MODE, PrefKeys.ACTIVE_SERVER_ID + PrefKeys.DEMO_MODE, PrefKeys.ACTIVE_SERVER_ID, PrefKeys.PRIMARY_SERVER_ID ) private val UPDATE_TRIGGERING_PREFIXES = listOf( PrefKeys.LOCAL_URL_PREFIX, PrefKeys.REMOTE_URL_PREFIX, From eb1f1b43d44f4a5e8055cfa6bba8b49bb6b6f60e Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 16:12:36 +0200 Subject: [PATCH 11/44] Fix primary server pref Signed-off-by: mueller-ma --- .../main/java/org/openhab/habdroid/ui/PreferencesActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index c94b23d6c7..ffffb6caf6 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -693,7 +693,9 @@ class PreferencesActivity : AbstractBaseActivity() { updatePrimaryServerPrefState(primaryServerPref, config.id == prefs.getPrimaryServerId()) primaryServerPref.setOnPreferenceClickListener { if (prefs.getConfiguredServerIds().contains(config.id)) { - prefs.edit().putPrimaryServerId(config.id) + prefs.edit { + putPrimaryServerId(config.id) + } } else { markAsPrimary = true } From 8e9eb855788415316be0974bcf685b8db407b514 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 16:13:41 +0200 Subject: [PATCH 12/44] Send dev info when primary server is changed Signed-off-by: mueller-ma --- .../org/openhab/habdroid/background/BackgroundTasksManager.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt index 376ee75a7b..51bd3acd53 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt @@ -212,7 +212,8 @@ class BackgroundTasksManager : BroadcastReceiver() { // Demo mode was disabled -> reschedule uploads (key == PrefKeys.DEMO_MODE && !prefs.isDemoModeEnabled()) || // Prefix has been changed -> reschedule uploads - key == PrefKeys.DEV_ID || key == PrefKeys.DEV_ID_PREFIX_BG_TASKS -> { + key == PrefKeys.DEV_ID || key == PrefKeys.DEV_ID_PREFIX_BG_TASKS || + key == PrefKeys.PRIMARY_SERVER_ID -> { KNOWN_KEYS.forEach { knowKey -> scheduleWorker(context, knowKey) } } key in KNOWN_KEYS -> scheduleWorker(context, key) From f2a657ffc1d43f5b014b08be26c467f645d04eaa Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Tue, 28 Jul 2020 17:45:40 +0200 Subject: [PATCH 13/44] Update About * Remove notification status, it's already shown in the prefs * Move api version to log Signed-off-by: mueller-ma --- .../habdroid/core/CloudMessagingHelper.kt | 7 +- .../habdroid/model/ServerProperties.kt | 2 + .../org/openhab/habdroid/ui/AboutActivity.kt | 117 +----------------- .../habdroid/ui/PreferencesActivity.kt | 6 +- .../NotificationPollingPreference.kt | 6 +- mobile/src/main/res/values/strings.xml | 5 - mobile/src/main/res/xml/preferences.xml | 3 +- 7 files changed, 14 insertions(+), 132 deletions(-) diff --git a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt index 948a3bd75f..bfad6ee09c 100644 --- a/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt +++ b/mobile/src/foss/java/org/openhab/habdroid/core/CloudMessagingHelper.kt @@ -18,7 +18,6 @@ import android.content.Intent import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.CloudConnection import org.openhab.habdroid.core.connection.ConnectionFactory -import org.openhab.habdroid.ui.AboutActivity import org.openhab.habdroid.ui.PushNotificationStatus import org.openhab.habdroid.util.HttpClient import org.openhab.habdroid.util.PrefKeys @@ -56,11 +55,7 @@ object CloudMessagingHelper { ) ConnectionFactory.primaryCloudConnection?.connection != null -> PushNotificationStatus( context.getString(R.string.push_notification_status_impaired), - R.drawable.ic_bell_ring_outline_grey_24dp, - AboutActivity.AboutMainFragment.makeClickRedirect( - context, - "https://www.openhab.org/docs/apps/android.html#notifications-in-foss-version" - ) + R.drawable.ic_bell_ring_outline_grey_24dp ) cloudFailure != null -> { val message = context.getString( diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerProperties.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerProperties.kt index c53a394a58..ab62b1a60d 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/ServerProperties.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerProperties.kt @@ -112,6 +112,7 @@ data class ServerProperties(val flags: Int, val sitemaps: List) : Parce or SERVER_FLAG_CHART_SCALING_SUPPORT) try { val version = resultJson.getString("version").toInt() + Log.i(TAG, "Server has rest api version $version") // all versions that return a number here have full SSE support flags = flags or SERVER_FLAG_SSE_SUPPORT if (version >= 2) { @@ -122,6 +123,7 @@ data class ServerProperties(val flags: Int, val sitemaps: List) : Parce } } catch (nfe: NumberFormatException) { // ignored: older versions without SSE support didn't return a number + Log.i(TAG, "Server has rest api version < 1") } val linksJsonArray = resultJson.optJSONArray("links") diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/AboutActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/AboutActivity.kt index 07a51d5390..0a3f2eb5d5 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/AboutActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/AboutActivity.kt @@ -15,10 +15,8 @@ package org.openhab.habdroid.ui import android.content.Context import android.os.Bundle -import android.util.Log import android.view.MenuItem import androidx.annotation.DrawableRes -import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.fragment.app.FragmentManager import androidx.fragment.app.commit @@ -30,16 +28,8 @@ import com.danielstone.materialaboutlibrary.model.MaterialAboutCard import com.danielstone.materialaboutlibrary.model.MaterialAboutList import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.ui.LibsSupportFragment -import kotlinx.coroutines.launch -import org.json.JSONException -import org.json.JSONObject import org.openhab.habdroid.BuildConfig import org.openhab.habdroid.R -import org.openhab.habdroid.core.CloudMessagingHelper -import org.openhab.habdroid.core.connection.CloudConnection -import org.openhab.habdroid.core.connection.ConnectionFactory -import org.openhab.habdroid.model.ServerProperties -import org.openhab.habdroid.util.HttpClient import org.openhab.habdroid.util.ScreenLockMode import org.openhab.habdroid.util.Util import org.openhab.habdroid.util.openInAppStore @@ -100,22 +90,8 @@ class AboutActivity : AbstractBaseActivity(), FragmentManager.OnBackStackChanged setTitle(titleResId) } - class AboutMainFragment : MaterialAboutFragment(), ConnectionFactory.UpdateListener { - private lateinit var pushStatusCard: MaterialAboutActionItem - - override fun onStart() { - super.onStart() - ConnectionFactory.addListener(this) - } - - override fun onStop() { - super.onStop() - ConnectionFactory.removeListener(this) - } - + class AboutMainFragment : MaterialAboutFragment() { override fun getMaterialAboutList(context: Context): MaterialAboutList { - val props: ServerProperties? = arguments?.getParcelable("serverProperties") - val connection = ConnectionFactory.activeUsableConnection?.connection val year = SimpleDateFormat("yyyy", Locale.US).format(Calendar.getInstance().time) val appCard = MaterialAboutCard.Builder() @@ -184,61 +160,6 @@ class AboutActivity : AbstractBaseActivity(), FragmentManager.OnBackStackChanged .setOnClickAction(makeClickRedirect(context, "https://www.openhabfoundation.org/privacy.html")) .build()) - val ohServerCard = MaterialAboutCard.Builder() - .title(R.string.about_server) - - if (connection == null || props == null) { - ohServerCard.addItem(MaterialAboutActionItem.Builder() - .text(R.string.error_about_no_conn) - .icon(R.drawable.ic_info_outline_grey_24dp) - .build()) - } else { - val scope = activity as AboutActivity - val httpClient = connection.httpClient - val apiVersionItem = MaterialAboutActionItem.Builder() - .text(R.string.info_openhab_apiversion_label) - .subText(R.string.list_loading_message) - .icon(R.drawable.ic_info_outline_grey_24dp) - .build() - ohServerCard.addItem(apiVersionItem) - val versionUrl = if (props.hasJsonApi()) "rest" else "static/version" - scope.launch { - try { - val response = httpClient.get(versionUrl).asText().response - var version = "" - if (!props.hasJsonApi()) { - version = response - } else { - try { - val pageJson = JSONObject(response) - version = pageJson.getString("version") - } catch (e: JSONException) { - Log.e(TAG, "Problem fetching version string", e) - } - } - - if (version.isEmpty()) { - version = getString(R.string.unknown) - } - - Log.d(TAG, "Got api version $version") - apiVersionItem.subText = version - } catch (e: HttpClient.HttpException) { - Log.e(TAG, "Could not rest API version $e") - apiVersionItem.subText = getString(R.string.error_about_no_conn) - } - refreshMaterialAboutList() - } - } - - pushStatusCard = MaterialAboutActionItem.Builder() - .text(R.string.info_openhab_push_notification_label) - .subText(R.string.list_loading_message) - .icon(R.drawable.ic_bell_outline_grey_24dp) - .build() - ohServerCard.addItem(pushStatusCard) - updatePushStatusCard() - val ohCommunityCard = MaterialAboutCard.Builder() .title(R.string.about_community) ohCommunityCard.addItem(MaterialAboutActionItem.Builder() @@ -264,7 +185,6 @@ class AboutActivity : AbstractBaseActivity(), FragmentManager.OnBackStackChanged return MaterialAboutList.Builder() .addCard(appCard.build()) - .addCard(ohServerCard.build()) .addCard(ohCommunityCard.build()) .build() } @@ -273,39 +193,7 @@ class AboutActivity : AbstractBaseActivity(), FragmentManager.OnBackStackChanged return Util.getActivityThemeId(requireContext()) } - private fun updatePushStatusCard() { - val scope = activity as AboutActivity - scope.launch { - Log.d(TAG, "Updating push notification status card") - val data = CloudMessagingHelper.getPushNotificationStatus(requireContext()) - pushStatusCard.apply { - subText = data.message - icon = ContextCompat.getDrawable(requireContext(), data.icon) - onClickAction = data.onClickAction - } - - refreshMaterialAboutList() - } - } - - override fun onActiveConnectionChanged() { - updatePushStatusCard() - } - - override fun onPrimaryConnectionChanged() { - // no-op - } - - override fun onActiveCloudConnectionChanged(connection: CloudConnection?) { - // no-op - } - - override fun onPrimaryCloudConnectionChanged(connection: CloudConnection?) { - updatePushStatusCard() - } - companion object { - private val TAG = AboutMainFragment::class.java.simpleName private const val URL_TO_GITHUB = "https://github.com/openhab/openhab-android" fun makeClickRedirect(context: Context, url: String) = MaterialAboutItemOnClickAction { @@ -317,6 +205,5 @@ class AboutActivity : AbstractBaseActivity(), FragmentManager.OnBackStackChanged data class PushNotificationStatus( val message: String, - @DrawableRes val icon: Int, - val onClickAction: MaterialAboutItemOnClickAction? = null + @DrawableRes val icon: Int ) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index ffffb6caf6..d250417502 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -485,9 +485,11 @@ class PreferencesActivity : AbstractBaseActivity() { private fun updateNotificationStatusSummaries() { parentActivity.launch { - notificationPollingPref?.updateSummary() + notificationPollingPref?.updateSummaryAndIcon() notificationStatusHint?.apply { - summary = CloudMessagingHelper.getPushNotificationStatus(this.context).message + val data = CloudMessagingHelper.getPushNotificationStatus(this.context) + summary = data.message + setIcon(data.icon) } } } diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/NotificationPollingPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/NotificationPollingPreference.kt index 0c34f764b6..21cedce91a 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/NotificationPollingPreference.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/NotificationPollingPreference.kt @@ -70,8 +70,10 @@ class NotificationPollingPreference constructor(context: Context, attrs: Attribu } } - suspend fun updateSummary() { - summary = CloudMessagingHelper.getPushNotificationStatus(context).message + suspend fun updateSummaryAndIcon() { + val status = CloudMessagingHelper.getPushNotificationStatus(context) + summary = status.message + setIcon(status.icon) } class PrefDialogFragment : PreferenceDialogFragmentCompat(), CompoundButton.OnCheckedChangeListener { diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 63c46a37ea..dc08787b6d 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -179,7 +179,6 @@ Gateway time-out (HTTP code %d). Not enough free space on server (HTTP code %d) Network authentication required (HTTP code %d) - Error while fetching openHAB server information Your openHAB server is offline while the cloud instance is running No browser found on your device No voice recognition app found @@ -194,8 +193,6 @@ Tag write error This device doesn\'t have NFC support NFC is disabled - openHAB Rest API version - Push notification status Settings Item updates can be written to NFC tags. You can select an Item here or you can long click a widget on the Sitemap. Not set @@ -396,7 +393,6 @@ Community forum openHAB Foundation openHAB community - openHAB server Help us translating openHAB Privacy policy Rate this app @@ -404,7 +400,6 @@ Extended error messages Troubleshooting Couldn\'t determine openHAB URL - Unknown Set Cancel Close diff --git a/mobile/src/main/res/xml/preferences.xml b/mobile/src/main/res/xml/preferences.xml index 8251a8bddb..74fc1081a0 100644 --- a/mobile/src/main/res/xml/preferences.xml +++ b/mobile/src/main/res/xml/preferences.xml @@ -84,8 +84,7 @@ + android:key="notification_status" /> Date: Sun, 16 Aug 2020 16:36:03 +0200 Subject: [PATCH 14/44] Add SharedPreferences.Editor.putActiveServerId() Signed-off-by: mueller-ma --- .../org/openhab/habdroid/core/UpdateBroadcastReceiver.kt | 3 ++- .../java/org/openhab/habdroid/model/ServerConfiguration.kt | 5 +++-- mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt | 3 ++- .../main/java/org/openhab/habdroid/util/PrefExtensions.kt | 4 ++++ 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt index 4a1251e3fe..b4f786305f 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt @@ -40,6 +40,7 @@ import org.openhab.habdroid.util.getDayNightMode import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrNull +import org.openhab.habdroid.util.putActiveServerId import org.openhab.habdroid.util.putConfiguredServerIds class UpdateBroadcastReceiver : BroadcastReceiver() { @@ -139,7 +140,7 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { config.saveToPrefs(prefs, secretPrefs) prefs.edit { putConfiguredServerIds(setOf(config.id)) - putInt(PrefKeys.ACTIVE_SERVER_ID, config.id) + putActiveServerId(config.id) putInt(PrefKeys.PRIMARY_SERVER_ID, config.id) remove("default_openhab_url") remove("default_openhab_alturl") diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt index e8175bd4cb..c5b6f1ecc5 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt @@ -22,6 +22,7 @@ import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getStringOrNull +import org.openhab.habdroid.util.putActiveServerId import org.openhab.habdroid.util.putConfiguredServerIds import org.openhab.habdroid.util.toNormalizedUrl @@ -74,7 +75,7 @@ data class ServerConfiguration( serverIdSet.add(id) putConfiguredServerIds(serverIdSet) if (serverIdSet.size == 1) { - putInt(PrefKeys.ACTIVE_SERVER_ID, id) + putActiveServerId(id) putInt(PrefKeys.PRIMARY_SERVER_ID, id) } } @@ -100,7 +101,7 @@ data class ServerConfiguration( remove(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_LABEL_PREFIX)) putConfiguredServerIds(serverIdSet) if (prefs.getActiveServerId() == id) { - putInt(PrefKeys.ACTIVE_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) + putActiveServerId(if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) } if (prefs.getPrimaryServerId() == id) { putInt(PrefKeys.PRIMARY_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index f4de37f034..8a86a8de54 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -122,6 +122,7 @@ import org.openhab.habdroid.util.isEventListenerEnabled import org.openhab.habdroid.util.isResolvable import org.openhab.habdroid.util.isScreenTimerDisabled import org.openhab.habdroid.util.openInAppStore +import org.openhab.habdroid.util.putActiveServerId import org.openhab.habdroid.util.updateDefaultSitemap class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { @@ -735,7 +736,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } if (item.groupId == R.id.servers) { prefs.edit { - putInt(PrefKeys.ACTIVE_SERVER_ID, item.itemId) + putActiveServerId(item.itemId) } updateServerNameInDrawer() // Menu views aren't updated in a click handler, so defer the menu update diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt index 3bb2986fc8..3606faa5d2 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt @@ -207,6 +207,10 @@ fun SharedPreferences.Editor.putConfiguredServerIds(ids: Set) { putStringSet(PrefKeys.SERVER_IDS, ids.map { id -> id.toString() }.toSet()) } +fun SharedPreferences.Editor.putActiveServerId(id: Int) { + putInt(PrefKeys.ACTIVE_SERVER_ID, id) +} + fun SharedPreferences.Editor.putPrimaryServerId(id: Int) { putInt(PrefKeys.PRIMARY_SERVER_ID, id) } From 4d2d305afe19dffa56c4f3cc09c4287d9e73b7e7 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 16:36:57 +0200 Subject: [PATCH 15/44] Use extension function putPrimaryServerId() Signed-off-by: mueller-ma --- .../org/openhab/habdroid/core/UpdateBroadcastReceiver.kt | 3 ++- .../java/org/openhab/habdroid/model/ServerConfiguration.kt | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt index b4f786305f..a1ac94e3ff 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt @@ -42,6 +42,7 @@ import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.putActiveServerId import org.openhab.habdroid.util.putConfiguredServerIds +import org.openhab.habdroid.util.putPrimaryServerId class UpdateBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -141,7 +142,7 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { prefs.edit { putConfiguredServerIds(setOf(config.id)) putActiveServerId(config.id) - putInt(PrefKeys.PRIMARY_SERVER_ID, config.id) + putPrimaryServerId(config.id) remove("default_openhab_url") remove("default_openhab_alturl") remove("default_openhab_sslclientcert") diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt index c5b6f1ecc5..2b64145dcf 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt @@ -24,6 +24,7 @@ import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.putActiveServerId import org.openhab.habdroid.util.putConfiguredServerIds +import org.openhab.habdroid.util.putPrimaryServerId import org.openhab.habdroid.util.toNormalizedUrl @Parcelize @@ -76,7 +77,7 @@ data class ServerConfiguration( putConfiguredServerIds(serverIdSet) if (serverIdSet.size == 1) { putActiveServerId(id) - putInt(PrefKeys.PRIMARY_SERVER_ID, id) + putPrimaryServerId(id) } } } @@ -104,7 +105,7 @@ data class ServerConfiguration( putActiveServerId(if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) } if (prefs.getPrimaryServerId() == id) { - putInt(PrefKeys.PRIMARY_SERVER_ID, if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) + putPrimaryServerId(if (serverIdSet.isNotEmpty()) serverIdSet.first() else 0) } } secretPrefs.edit { From a85654671b82d869fdc05f14bc56ac5bdc0a60e3 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 16:49:30 +0200 Subject: [PATCH 16/44] Support for voice commands Signed-off-by: mueller-ma --- docs/USAGE.md | 3 +- .../background/BackgroundTasksManager.kt | 33 +++++++++++++++---- .../habdroid/background/ItemUpdateWorker.kt | 13 ++++++-- .../background/NotificationUpdateObserver.kt | 4 ++- .../org/openhab/habdroid/ui/MainActivity.kt | 2 +- .../habdroid/ui/PreferencesActivity.kt | 6 ++++ .../ui/homescreenwidget/VoiceWidget.kt | 2 +- 7 files changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index 95d645e287..acb9f7d2da 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -284,13 +284,12 @@ Currently multi server support is still in development. Features, that support multiple servers: * Display Sitemaps and HABPanel -* In-app voice commands +* Voice commands launched from in-app and from widgets * Show a list of recent notifications Features, that don't support multiple servers: * Sitemap shortcuts and Item widgets on the home screen * Shortcuts for HABPanel, notifications and voice command -* Voice command widgets * Quick tiles * NFC tags * Push notifications diff --git a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt index 51bd3acd53..b433adcbbf 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt @@ -55,9 +55,11 @@ import org.openhab.habdroid.ui.preference.toItemUpdatePrefValue import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.TaskerIntent import org.openhab.habdroid.util.TaskerPlugin +import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getBackgroundTaskScheduleInMillis import org.openhab.habdroid.util.getPrefixForBgTasks import org.openhab.habdroid.util.getPrefs +import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getStringOrEmpty import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.hasPermissions @@ -165,7 +167,7 @@ class BackgroundTasksManager : BroadcastReceiver() { resultCode = TaskerPlugin.Setting.RESULT_CODE_PENDING } } - ACTION_VOICE_RESULT -> { + ACTION_VOICE_RESULT_APP, ACTION_VOICE_RESULT_WIDGET -> { val voiceCommand = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.elementAtOrNull(0) ?: return Log.i(TAG, "Recognized text: $voiceCommand") @@ -178,7 +180,8 @@ class BackgroundTasksManager : BroadcastReceiver() { ItemUpdateWorker.ValueWithInfo(voiceCommand, type = ItemUpdateWorker.ValueType.VoiceCommand), isImportant = true, showToast = true, - asCommand = true + asCommand = true, + primaryServer = intent.action == ACTION_VOICE_RESULT_WIDGET ) } } @@ -193,7 +196,8 @@ class BackgroundTasksManager : BroadcastReceiver() { val isImportant: Boolean, val showToast: Boolean, val taskerIntent: String?, - val asCommand: Boolean + val asCommand: Boolean, + val primaryServer: Boolean ) : Parcelable private class PrefsListener constructor(private val context: Context) : @@ -228,7 +232,8 @@ class BackgroundTasksManager : BroadcastReceiver() { internal const val ACTION_RETRY_UPLOAD = "org.openhab.habdroid.background.action.RETRY_UPLOAD" internal const val ACTION_CLEAR_UPLOAD = "org.openhab.habdroid.background.action.CLEAR_UPLOAD" - internal const val ACTION_VOICE_RESULT = "org.openhab.habdroid.background.action.VOICE_RESULT" + internal const val ACTION_VOICE_RESULT_APP = "org.openhab.habdroid.background.action.VOICE_RESULT_APP" + internal const val ACTION_VOICE_RESULT_WIDGET = "org.openhab.habdroid.background.action.VOICE_RESULT_WIDGET" internal const val EXTRA_RETRY_INFO_LIST = "retryInfoList" private const val WORKER_TAG_ITEM_UPLOADS = "itemUploads" @@ -240,6 +245,7 @@ class BackgroundTasksManager : BroadcastReceiver() { const val WORKER_TAG_PREFIX_WIDGET = "widget-" const val WORKER_TAG_PREFIX_TILE = "tile-" const val WORKER_TAG_VOICE_COMMAND = "voiceCommand" + fun buildWorkerTagForServer(id: Int) = "server-id-$id" internal val KNOWN_KEYS = listOf( PrefKeys.SEND_ALARM_CLOCK, @@ -483,19 +489,32 @@ class BackgroundTasksManager : BroadcastReceiver() { isImportant: Boolean, showToast: Boolean, taskerIntent: String? = null, - asCommand: Boolean + asCommand: Boolean, + primaryServer: Boolean = true ) { + val prefs = context.getPrefs() val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() - val inputData = - ItemUpdateWorker.buildData(itemName, label, value, showToast, taskerIntent, asCommand, isImportant) + val inputData = ItemUpdateWorker.buildData( + itemName, + label, + value, + showToast, + taskerIntent, + asCommand, + isImportant, + primaryServer + ) val workRequest = OneTimeWorkRequest.Builder(ItemUpdateWorker::class.java) .setConstraints(constraints) .setBackoffCriteria(if (isImportant) BackoffPolicy.LINEAR else BackoffPolicy.EXPONENTIAL, WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .addTag(tag) .addTag(WORKER_TAG_ITEM_UPLOADS) + .addTag(buildWorkerTagForServer( + if (primaryServer) prefs.getPrimaryServerId() else prefs.getActiveServerId() + )) .setInputData(inputData) .build() diff --git a/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt b/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt index 269950c0e5..4ceb7b3769 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt @@ -65,7 +65,11 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont } Log.d(TAG, "Trying to get connection") - val connection = ConnectionFactory.primaryUsableConnection?.connection + val connection = if (inputData.getBoolean(INPUT_DATA_PRIMARY_SERVER, false)) { + ConnectionFactory.primaryUsableConnection?.connection + } else { + ConnectionFactory.activeUsableConnection?.connection + } val showToast = inputData.getBoolean(INPUT_DATA_SHOW_TOAST, false) val taskerIntent = inputData.getString(INPUT_DATA_TASKER_INTENT) @@ -304,6 +308,7 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont .putString(OUTPUT_DATA_TASKER_INTENT, inputData.getString(INPUT_DATA_TASKER_INTENT)) .putBoolean(OUTPUT_DATA_AS_COMMAND, inputData.getBoolean(INPUT_DATA_AS_COMMAND, false)) .putBoolean(OUTPUT_DATA_IS_IMPORTANT, inputData.getBoolean(INPUT_DATA_IS_IMPORTANT, false)) + .putString(OUTPUT_DATA_PRIMARY_SERVER, inputData.getString(INPUT_DATA_PRIMARY_SERVER)) .putLong(OUTPUT_DATA_TIMESTAMP, System.currentTimeMillis()) .build() } @@ -345,6 +350,7 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont private const val INPUT_DATA_TASKER_INTENT = "taskerIntent" private const val INPUT_DATA_AS_COMMAND = "command" private const val INPUT_DATA_IS_IMPORTANT = "is_important" + private const val INPUT_DATA_PRIMARY_SERVER = "primary_server" const val OUTPUT_DATA_HAS_CONNECTION = "hasConnection" const val OUTPUT_DATA_HTTP_STATUS = "httpStatus" @@ -356,6 +362,7 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont const val OUTPUT_DATA_TASKER_INTENT = "taskerIntent" const val OUTPUT_DATA_AS_COMMAND = "command" const val OUTPUT_DATA_IS_IMPORTANT = "is_important" + const val OUTPUT_DATA_PRIMARY_SERVER = "primary_server" const val OUTPUT_DATA_TIMESTAMP = "timestamp" fun buildData( @@ -365,7 +372,8 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont showToast: Boolean, taskerIntent: String?, asCommand: Boolean, - isImportant: Boolean + isImportant: Boolean, + primaryServer: Boolean ): Data { return Data.Builder() .putString(INPUT_DATA_ITEM_NAME, itemName) @@ -375,6 +383,7 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont .putString(INPUT_DATA_TASKER_INTENT, taskerIntent) .putBoolean(INPUT_DATA_AS_COMMAND, asCommand) .putBoolean(INPUT_DATA_IS_IMPORTANT, isImportant) + .putBoolean(INPUT_DATA_PRIMARY_SERVER, primaryServer) .build() } } diff --git a/mobile/src/main/java/org/openhab/habdroid/background/NotificationUpdateObserver.kt b/mobile/src/main/java/org/openhab/habdroid/background/NotificationUpdateObserver.kt index 5a93c771e3..445240d48f 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/NotificationUpdateObserver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/NotificationUpdateObserver.kt @@ -94,6 +94,7 @@ internal class NotificationUpdateObserver(context: Context) : Observer config.removeFromPrefs(prefs, secretPrefs) + WorkManager.getInstance(preferenceManager.context).apply { + cancelAllWorkByTag(buildWorkerTagForServer(config.id)) + pruneWork() + } parentFragmentManager.popBackStack() // close ourself } .setNegativeButton(android.R.string.cancel, null) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/VoiceWidget.kt b/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/VoiceWidget.kt index adb961863b..7f93390e7d 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/VoiceWidget.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/VoiceWidget.kt @@ -39,7 +39,7 @@ open class VoiceWidget : AppWidgetProvider() { Log.d(TAG, "Build voice recognition intent") val callbackIntent = Intent(context, BackgroundTasksManager::class.java).apply { - action = BackgroundTasksManager.ACTION_VOICE_RESULT + action = BackgroundTasksManager.ACTION_VOICE_RESULT_WIDGET } val callbackPendingIntent = PendingIntent.getBroadcast(context, 9, callbackIntent, 0) From 511d2045b3b48be47a00f629c12e03508bfdd62f Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 16:52:38 +0200 Subject: [PATCH 17/44] Explain primary and active server in the docs Signed-off-by: mueller-ma --- docs/USAGE.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index acb9f7d2da..d87a3c1bb1 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -280,11 +280,13 @@ In case of an error the plugin returns an error code. ## Multi server support -Currently multi server support is still in development. +When adding multiple servers to the app, there's always a primary and an active one. +The active server is used for foreground operations, e.g. display the Sitemaps, and can be changed in the side menu. +The primary server is used for all features that don't support multiple servers and can be changed in the settings. Features, that support multiple servers: * Display Sitemaps and HABPanel -* Voice commands launched from in-app and from widgets +* Voice commands launched from in-app (send to active server) and from widgets (send to primary server) * Show a list of recent notifications Features, that don't support multiple servers: @@ -295,7 +297,6 @@ Features, that don't support multiple servers: * Push notifications * Send device information to openHAB * Tasker plugin -* Setting the device identifier on a per server basis // Note: This is probably a "won't fix" ## Help and Technical Details From 7d282dbdfec09794eeeaf1ae9c96592ba936974f Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 16:55:54 +0200 Subject: [PATCH 18/44] Fix build after rebase Signed-off-by: mueller-ma --- mobile/src/main/res/layout/drawer_header.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/res/layout/drawer_header.xml b/mobile/src/main/res/layout/drawer_header.xml index 197fc8b17d..f30724c738 100644 --- a/mobile/src/main/res/layout/drawer_header.xml +++ b/mobile/src/main/res/layout/drawer_header.xml @@ -30,7 +30,7 @@ android:layout_weight="1" android:layout_gravity="center_vertical" android:textAppearance="@style/TextAppearance.AppCompat.Small" - android:textColor="@color/white" + android:textColor="@android:color/white" android:textStyle="bold" tools:text="server" /> From 015d4a15b5893c083920371d4753d1c638510b2d Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 17:00:31 +0200 Subject: [PATCH 19/44] Hide NFC drawer entry for non-primary servers Signed-off-by: mueller-ma --- mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index cf14883971..0f3d7f2521 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -114,6 +114,7 @@ import org.openhab.habdroid.util.getHumanReadableErrorMessage import org.openhab.habdroid.util.getNextAvailableServerId import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getRemoteUrl +import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.hasPermissions @@ -840,7 +841,8 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { val nfcItem = drawerMenu.findItem(R.id.nfc) nfcItem.isVisible = serverProperties != null && - (NfcAdapter.getDefaultAdapter(this) != null || Util.isEmulator()) + (NfcAdapter.getDefaultAdapter(this) != null || Util.isEmulator()) && + prefs.getPrimaryServerId() == prefs.getActiveServerId() } } From 90e00cec8338164f57dc3e3fabbe00025604ec9d Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 17:10:28 +0200 Subject: [PATCH 20/44] HABPanel and sitemap shortcuts Signed-off-by: mueller-ma --- docs/USAGE.md | 5 +- .../habdroid/model/ServerConfiguration.kt | 3 + .../org/openhab/habdroid/ui/MainActivity.kt | 86 ++++++++++++++++--- .../openhab/habdroid/ui/WidgetListFragment.kt | 2 + .../habdroid/ui/activity/ContentController.kt | 19 +++- .../habdroid/ui/activity/WebViewFragment.kt | 21 +++-- mobile/src/main/res/values/strings.xml | 2 + 7 files changed, 115 insertions(+), 23 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index d87a3c1bb1..26f634dd93 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -288,10 +288,11 @@ Features, that support multiple servers: * Display Sitemaps and HABPanel * Voice commands launched from in-app (send to active server) and from widgets (send to primary server) * Show a list of recent notifications +* Sitemap shortcuts on the home screen +* Shortcuts for HABPanel, notifications and voice command Features, that don't support multiple servers: -* Sitemap shortcuts and Item widgets on the home screen -* Shortcuts for HABPanel, notifications and voice command +* Item widgets on the home screen * Quick tiles * NFC tags * Push notifications diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt index 2b64145dcf..5c61c5294e 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt @@ -117,6 +117,9 @@ data class ServerConfiguration( } companion object { + const val SERVER_ID_PRIMARY = 0 + const val SERVER_ID_CURRENT_ACTIVE = -1 + fun load(prefs: SharedPreferences, secretPrefs: SharedPreferences, id: Int): ServerConfiguration? { val localPath = ServerPath.load(prefs, secretPrefs, id, PrefKeys.LOCAL_URL_PREFIX, PrefKeys.LOCAL_USERNAME_PREFIX, PrefKeys.LOCAL_PASSWORD_PREFIX) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index 0f3d7f2521..fb7fd9bc30 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -103,6 +103,7 @@ import org.openhab.habdroid.util.ImageConversionPolicy import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.RemoteLog import org.openhab.habdroid.util.ScreenLockMode +import org.openhab.habdroid.util.ToastType import org.openhab.habdroid.util.Util import org.openhab.habdroid.util.areSitemapsShownInDrawer import org.openhab.habdroid.util.determineDataUsagePolicy @@ -124,6 +125,7 @@ import org.openhab.habdroid.util.isResolvable import org.openhab.habdroid.util.isScreenTimerDisabled import org.openhab.habdroid.util.openInAppStore import org.openhab.habdroid.util.putActiveServerId +import org.openhab.habdroid.util.showToast import org.openhab.habdroid.util.updateDefaultSitemap class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { @@ -620,19 +622,23 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { val sitemapUrl = tag?.sitemap if (!sitemapUrl.isNullOrEmpty()) { - executeOrStoreAction(PendingAction.OpenSitemapUrl(sitemapUrl)) + executeOrStoreAction(PendingAction.OpenSitemapUrl(sitemapUrl, 0)) } } ACTION_NOTIFICATION_SELECTED -> { CloudMessagingHelper.onNotificationSelected(this, intent) - val id = intent.getStringExtra(EXTRA_PERSISTED_NOTIFICATION_ID).orEmpty() - executeActionIfPossible(PendingAction.OpenNotification(id, true)) + val notificationId = intent.getStringExtra(EXTRA_PERSISTED_NOTIFICATION_ID).orEmpty() + executeActionIfPossible(PendingAction.OpenNotification(notificationId, true)) + } + ACTION_HABPANEL_SELECTED -> { + val serverId = intent.getIntExtra(EXTRA_SERVER_ID, ServerConfiguration.SERVER_ID_PRIMARY) + executeOrStoreAction(PendingAction.OpenHabPanel(serverId)) } - ACTION_HABPANEL_SELECTED -> executeOrStoreAction(PendingAction.OpenHabPanel()) ACTION_VOICE_RECOGNITION_SELECTED -> executeOrStoreAction(PendingAction.LaunchVoiceRecognition()) ACTION_SITEMAP_SELECTED -> { val sitemapUrl = intent.getStringExtra(EXTRA_SITEMAP_URL) ?: return - executeOrStoreAction(PendingAction.OpenSitemapUrl(sitemapUrl)) + val serverId = intent.getIntExtra(EXTRA_SERVER_ID, 0) + executeOrStoreAction(PendingAction.OpenSitemapUrl(sitemapUrl, serverId)) } } } @@ -838,6 +844,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { val habPanelItem = drawerMenu.findItem(R.id.habpanel) habPanelItem.isVisible = serverProperties?.hasHabPanelInstalled() == true + manageHabPanelShortcut(serverProperties?.hasHabPanelInstalled() == true) val nfcItem = drawerMenu.findItem(R.id.nfc) nfcItem.isVisible = serverProperties != null && @@ -907,12 +914,67 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { true } action is PendingAction.OpenSitemapUrl && isStarted && serverProperties != null -> { - buildUrlAndOpenSitemap(action.url) - true + when { + action.serverId == ServerConfiguration.SERVER_ID_PRIMARY && + prefs.getActiveServerId() != prefs.getPrimaryServerId() -> { + prefs.edit { + putActiveServerId(prefs.getPrimaryServerId()) + } + updateDrawerServerEntries() + false + } + action.serverId == ServerConfiguration.SERVER_ID_PRIMARY -> { + buildUrlAndOpenSitemap(action.url) + true + } + action.serverId !in prefs.getConfiguredServerIds() -> { + showToast(R.string.home_shortcut_server_has_been_deleted, ToastType.ERROR) + true + } + prefs.getActiveServerId() != action.serverId -> { + prefs.edit { + putActiveServerId(action.serverId) + } + updateDrawerServerEntries() + false + } + else -> { + buildUrlAndOpenSitemap(action.url) + true + } + } } action is PendingAction.OpenHabPanel && isStarted && serverProperties?.hasHabPanelInstalled() == true -> { - openHabPanel() - true + when { + action.serverId == ServerConfiguration.SERVER_ID_PRIMARY && + prefs.getActiveServerId() != prefs.getPrimaryServerId() -> { + prefs.edit { + putActiveServerId(prefs.getPrimaryServerId()) + } + updateDrawerServerEntries() + false + } + action.serverId == ServerConfiguration.SERVER_ID_PRIMARY || + action.serverId == ServerConfiguration.SERVER_ID_CURRENT_ACTIVE -> { + openHabPanel() + true + } + action.serverId !in prefs.getConfiguredServerIds() -> { + showToast(R.string.home_shortcut_server_has_been_deleted, ToastType.ERROR) + true + } + prefs.getActiveServerId() != action.serverId -> { + prefs.edit { + putActiveServerId(action.serverId) + } + updateDrawerServerEntries() + false + } + else -> { + openHabPanel() + true + } + } } action is PendingAction.LaunchVoiceRecognition && serverProperties != null -> { launchVoiceRecognition() @@ -1212,6 +1274,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } if (visible) { val intent = Intent(this, MainActivity::class.java) + .putExtra(EXTRA_SERVER_ID, ServerConfiguration.SERVER_ID_CURRENT_ACTIVE) .setAction(action) val shortcut = ShortcutInfo.Builder(this, id) .setShortLabel(getString(shortLabel)) @@ -1235,8 +1298,8 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { private sealed class PendingAction { class ChooseSitemap : PendingAction() - class OpenSitemapUrl constructor(val url: String) : PendingAction() - class OpenHabPanel : PendingAction() + class OpenSitemapUrl constructor(val url: String, val serverId: Int) : PendingAction() + class OpenHabPanel constructor(val serverId: Int) : PendingAction() class LaunchVoiceRecognition : PendingAction() class OpenNotification constructor(val notificationId: String, val primary: Boolean) : PendingAction() } @@ -1247,6 +1310,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { const val ACTION_VOICE_RECOGNITION_SELECTED = "org.openhab.habdroid.action.VOICE_SELECTED" const val ACTION_SITEMAP_SELECTED = "org.openhab.habdroid.action.SITEMAP_SELECTED" const val EXTRA_SITEMAP_URL = "sitemapUrl" + const val EXTRA_SERVER_ID = "serverId" const val EXTRA_PERSISTED_NOTIFICATION_ID = "persistedNotificationId" private const val TAG_SNACKBAR_PRESS_AGAIN_EXIT = "pressAgainToExit" private const val TAG_SNACKBAR_CONNECTION_ESTABLISHED = "connectionEstablished" diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.kt index 94fc18f8b8..71f966e623 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/WidgetListFragment.kt @@ -66,6 +66,7 @@ import org.openhab.habdroid.util.SuggestedCommandsFactory import org.openhab.habdroid.util.ToastType import org.openhab.habdroid.util.Util import org.openhab.habdroid.util.dpToPixel +import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getPrefs import org.openhab.habdroid.util.getStringOrEmpty import org.openhab.habdroid.util.openInBrowser @@ -528,6 +529,7 @@ class WidgetListFragment : Fragment(), WidgetAdapter.ItemClickListener, val startIntent = Intent(context, MainActivity::class.java).apply { action = MainActivity.ACTION_SITEMAP_SELECTED putExtra(MainActivity.EXTRA_SITEMAP_URL, shortSitemapUri) + putExtra(MainActivity.EXTRA_SERVER_ID, context.getPrefs().getActiveServerId()) } val name = if (linkedPage.title.isEmpty()) context.getString(R.string.app_name) else linkedPage.title diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt index da98275d4c..2ce1d102aa 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt @@ -46,6 +46,7 @@ import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.Connection import org.openhab.habdroid.core.connection.ConnectionFactory import org.openhab.habdroid.model.LinkedPage +import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.model.Sitemap import org.openhab.habdroid.model.Widget import org.openhab.habdroid.ui.CloudNotificationListFragment @@ -55,8 +56,11 @@ import org.openhab.habdroid.ui.WidgetListFragment import org.openhab.habdroid.util.HttpClient import org.openhab.habdroid.util.PrefKeys import org.openhab.habdroid.util.RemoteLog +import org.openhab.habdroid.util.getActiveServerId +import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getHumanReadableErrorMessage import org.openhab.habdroid.util.getPrefs +import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.isDebugModeEnabled import org.openhab.habdroid.util.openInBrowser @@ -90,7 +94,7 @@ abstract class ContentController protected constructor(private val activity: Mai val currentTitle get() = when { noConnectionFragment != null -> null temporaryPage is CloudNotificationListFragment -> activity.getString(R.string.app_notifications) // XXX: mark server type (active/primary) or index in title? - temporaryPage is WebViewFragment -> activity.getString((temporaryPage as WebViewFragment).titleResId) + temporaryPage is WebViewFragment -> (temporaryPage as WebViewFragment).title temporaryPage != null -> null else -> fragmentForTitle?.title } @@ -254,10 +258,19 @@ abstract class ContentController protected constructor(private val activity: Mai } fun showHabPanel() { - showTemporaryPage(WebViewFragment.newInstance(R.string.mainmenu_openhab_habpanel, + val prefs = activity.getPrefs() + val activeServerId = prefs.getActiveServerId() + val title = if (prefs.getConfiguredServerIds().size <= 1) { + activity.getString(R.string.mainmenu_openhab_habpanel) + } else { + val activeServerName = ServerConfiguration.load(prefs, activity.getSecretPrefs(), activeServerId)?.name + activity.getString(R.string.mainmenu_openhab_habpanel_on_server, activeServerName) + } + + showTemporaryPage(WebViewFragment.newInstance(title, R.string.habpanel_error, "/habpanel/index.html", "/rest/events", - MainActivity.ACTION_HABPANEL_SELECTED, R.string.mainmenu_openhab_habpanel, R.mipmap.ic_shortcut_habpanel)) + MainActivity.ACTION_HABPANEL_SELECTED, activeServerId, title, R.mipmap.ic_shortcut_habpanel)) } /** diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt index 8dc270967a..15416773f9 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt @@ -43,6 +43,7 @@ import kotlinx.coroutines.withContext import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.CloudConnection import org.openhab.habdroid.core.connection.ConnectionFactory +import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.ui.ConnectionWebViewClient import org.openhab.habdroid.ui.MainActivity import org.openhab.habdroid.ui.setUpForConnection @@ -55,8 +56,8 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { private lateinit var urlForError: String private var shortcutInfo: ShortcutInfoCompat? = null - val titleResId: Int - @StringRes get() = requireArguments().getInt(KEY_PAGE_TITLE) + val title: String + get() = requireArguments().getString(KEY_PAGE_TITLE)!! override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_webview, container, false) @@ -73,13 +74,16 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { urlToLoad = args.getString(KEY_URL_LOAD) as String urlForError = args.getString(KEY_URL_ERROR) as String val action = args.getString(KEY_SHORTCUT_ACTION) - @StringRes val label = args.getInt(KEY_SHORTCUT_LABEL) + val extraServerId = args.getInt(KEY_SHORTCUT_EXTRA_SERVER_ID, ServerConfiguration.SERVER_ID_PRIMARY) + val label = args.getString(KEY_SHORTCUT_LABEL) @DrawableRes val icon = args.getInt(KEY_SHORTCUT_ICON_RES) action?.let { + val context = view.context val intent = Intent(context, MainActivity::class.java) + .putExtra(MainActivity.EXTRA_SERVER_ID, extraServerId) .setAction(action) - shortcutInfo = ShortcutInfoCompat.Builder(view.context, action) - .setShortLabel(view.context.getString(label)) + shortcutInfo = ShortcutInfoCompat.Builder(context, action) + .setShortLabel(label!!) .setIcon(IconCompat.createWithResource(context, icon)) .setIntent(intent) .build() @@ -230,16 +234,18 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { private const val KEY_URL_LOAD = "url_load" private const val KEY_URL_ERROR = "url_error" private const val KEY_SHORTCUT_ACTION = "shortcut_action" + private const val KEY_SHORTCUT_EXTRA_SERVER_ID = "shortcut_extra_server_id" private const val KEY_SHORTCUT_LABEL = "shortcut_label" private const val KEY_SHORTCUT_ICON_RES = "shortcut_icon_res" fun newInstance( - @StringRes pageTitle: Int, + pageTitle: String, @StringRes errorMessage: Int, urlToLoad: String, urlForError: String, shortcutAction: String? = null, - shortcutLabel: Int = 0, + extraServerId: Int = ServerConfiguration.SERVER_ID_PRIMARY, + shortcutLabel: String? = null, shortcutIconRes: Int = 0 ): WebViewFragment { val f = WebViewFragment() @@ -249,6 +255,7 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { KEY_URL_LOAD to urlToLoad, KEY_URL_ERROR to urlForError, KEY_SHORTCUT_ACTION to shortcutAction, + KEY_SHORTCUT_EXTRA_SERVER_ID to extraServerId, KEY_SHORTCUT_LABEL to shortcutLabel, KEY_SHORTCUT_ICON_RES to shortcutIconRes) return f diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index dc08787b6d..e7637bf342 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Sitemaps Settings HABPanel + HABPanel (%s) An error occurred while loading HABPanel Select default Sitemap Clear images cache @@ -428,6 +429,7 @@ Pin to home Error pinning to home Pinned to home + The server for this shortcut has been deleted Open roller shutter From 7f7ec2651424e6da9c640868430587c01dee7e4c Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 21:04:42 +0200 Subject: [PATCH 21/44] Implement review comments Signed-off-by: mueller-ma --- .../java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt | 2 ++ .../main/java/org/openhab/habdroid/ui/PreferencesActivity.kt | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt index a1ac94e3ff..37de39491a 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt @@ -146,6 +146,8 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { remove("default_openhab_url") remove("default_openhab_alturl") remove("default_openhab_sslclientcert") + remove("default_openhab_sitemap") + remove("default_openhab_sitemap_label") } secretPrefs.edit { remove("default_openhab_username") diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index a50c7e920c..e661b06eb7 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -57,8 +57,8 @@ import kotlinx.coroutines.launch import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.openhab.habdroid.R import org.openhab.habdroid.background.BackgroundTasksManager -import org.openhab.habdroid.background.EventListenerService import org.openhab.habdroid.background.BackgroundTasksManager.Companion.buildWorkerTagForServer +import org.openhab.habdroid.background.EventListenerService import org.openhab.habdroid.background.tiles.AbstractTileService import org.openhab.habdroid.background.tiles.TileData import org.openhab.habdroid.background.tiles.getTileData @@ -285,7 +285,6 @@ class PreferencesActivity : AbstractBaseActivity() { updateVibrationPreferenceIcon(vibrationPref, prefs.getStringOrNull(PrefKeys.NOTIFICATION_VIBRATION)) - addServerPref?.changeBetaTagVisibility(prefs.getConfiguredServerIds().isNotEmpty()) addServerPref?.setOnPreferenceClickListener { val nextServerId = prefs.getNextAvailableServerId() val f = ServerEditorFragment.newInstance(ServerConfiguration(nextServerId, "", null, null, null, null)) From 1f638ad4484dee21105c9088107ac03a72e10b82 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 21:27:05 +0200 Subject: [PATCH 22/44] Unsaved changes dialog Signed-off-by: mueller-ma --- .../habdroid/ui/PreferencesActivity.kt | 53 ++++++++++++++----- mobile/src/main/res/menu/server_editor.xml | 4 +- mobile/src/main/res/values/strings.xml | 8 +-- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index e661b06eb7..fb4144ffd2 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -150,7 +150,9 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onBackPressed() { with(supportFragmentManager) { if (backStackEntryCount > 0) { - popBackStack() + if ((fragments.last() as? AbstractSettingsFragment)?.onBackPressed() == false) { + popBackStack() + } } else { super.onBackPressed() } @@ -198,6 +200,8 @@ class PreferencesActivity : AbstractBaseActivity() { } } + open fun onBackPressed() = false + companion object { /** * Password is considered strong when it is at least 8 chars long and contains 3 from those @@ -597,22 +601,13 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when(item.itemId) { R.id.save -> { - if (config.name.isEmpty() || (config.localPath == null && config.remotePath == null)) { - context?.showToast(R.string.settings_server_at_least_name_and_connection) - return true - } - config.saveToPrefs(prefs, secretPrefs) - if (markAsPrimary) { - prefs.edit().putPrimaryServerId(config.id) - } - parentActivity.invalidateOptionsMenu() - parentFragmentManager.popBackStack() // close ourself + saveAndQuit() true } R.id.delete -> { AlertDialog.Builder(preferenceManager.context) .setMessage(R.string.settings_server_confirm_deletion) - .setPositiveButton(R.string.settings_menu_delete_server) { _, _ -> + .setPositiveButton(R.string.delete) { _, _ -> config.removeFromPrefs(prefs, secretPrefs) WorkManager.getInstance(preferenceManager.context).apply { cancelAllWorkByTag(buildWorkerTagForServer(config.id)) @@ -628,6 +623,40 @@ class PreferencesActivity : AbstractBaseActivity() { } } + private fun saveAndQuit() { + if (config.name.isEmpty() || (config.localPath == null && config.remotePath == null)) { + context?.showToast(R.string.settings_server_at_least_name_and_connection) + return + } + config.saveToPrefs(prefs, secretPrefs) + if (markAsPrimary) { + prefs.edit { + putPrimaryServerId(config.id) + } + } + parentActivity.invalidateOptionsMenu() + parentFragmentManager.popBackStack() // close ourself + } + + override fun onBackPressed() : Boolean { + if (ServerConfiguration.load(prefs, secretPrefs, config.id) != config) { + AlertDialog.Builder(preferenceManager.context) + .setTitle(R.string.settings_server_confirm_leave_title) + .setMessage(R.string.settings_server_confirm_leave_message) + .setPositiveButton(R.string.save) { _, _ -> + saveAndQuit() + } + .setNegativeButton(R.string.discard) { _, _ -> + parentActivity.invalidateOptionsMenu() + parentFragmentManager.popBackStack() // close ourself + } + .setNeutralButton(android.R.string.cancel, null) + .show() + return true + } + return false + } + override fun onStart() { super.onStart() updateConnectionSummary("local", config.localPath) diff --git a/mobile/src/main/res/menu/server_editor.xml b/mobile/src/main/res/menu/server_editor.xml index ab6116deb0..3598dc1a3d 100644 --- a/mobile/src/main/res/menu/server_editor.xml +++ b/mobile/src/main/res/menu/server_editor.xml @@ -17,11 +17,11 @@ diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index e7637bf342..ffb42934fc 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -9,6 +9,9 @@ Settings Notifications + Save + Discard + Delete Sitemaps @@ -98,8 +101,6 @@ Server name Add server Edit server - Save - Delete Servers @@ -535,7 +536,6 @@ Empty tile Select an Item This tile can be configured in the openHAB app settings - Save Please fill out every setting Turned off Turn on @@ -654,6 +654,8 @@ At least server name and one connection is required Server %s Do you really want to remove this server? + Unsaved changes + You made some changes. Do you want to save or discard them? Primary server This server is the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. "%s" is the primary server. Click to make this server the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. From 77acf1dfe6718680f616bff819d546520ca1d4c7 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 21:40:29 +0200 Subject: [PATCH 23/44] Code style Signed-off-by: mueller-ma --- .../background/BackgroundTasksManager.kt | 8 +- .../habdroid/core/UpdateBroadcastReceiver.kt | 44 ++++++--- .../core/connection/ConnectionFactory.kt | 9 +- .../habdroid/model/ServerConfiguration.kt | 26 +++-- .../habdroid/ui/AbstractItemPickerActivity.kt | 2 +- .../openhab/habdroid/ui/ItemPickerAdapter.kt | 2 +- .../org/openhab/habdroid/ui/MainActivity.kt | 18 +++- .../habdroid/ui/PreferencesActivity.kt | 95 +++++++++++++------ .../habdroid/ui/activity/ContentController.kt | 18 +++- .../ui/preference/PrimaryServerPreference.kt | 6 +- .../openhab/habdroid/util/PrefExtensions.kt | 1 - mobile/src/main/res/values/strings.xml | 20 ++-- .../core/connection/ConnectionFactoryTest.kt | 43 ++++++--- .../core/connection/DefaultConnectionTest.kt | 45 ++++++--- 14 files changed, 231 insertions(+), 106 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt index b433adcbbf..f5f12ca269 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt @@ -512,9 +512,11 @@ class BackgroundTasksManager : BroadcastReceiver() { WorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .addTag(tag) .addTag(WORKER_TAG_ITEM_UPLOADS) - .addTag(buildWorkerTagForServer( - if (primaryServer) prefs.getPrimaryServerId() else prefs.getActiveServerId() - )) + .addTag( + buildWorkerTagForServer( + if (primaryServer) prefs.getPrimaryServerId() else prefs.getActiveServerId() + ) + ) .setInputData(inputData) .build() diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt index 37de39491a..715ad388d4 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt @@ -66,10 +66,14 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { context.getSecretPrefs().edit { putString("default_openhab_username", prefs.getStringOrNull("default_openhab_username")) putString("default_openhab_password", prefs.getStringOrNull("default_openhab_password")) - putString("default_openhab_remote_username", - prefs.getStringOrNull("default_openhab_remote_username")) - putString("default_openhab_remote_password", - prefs.getStringOrNull("default_openhab_remote_password")) + putString( + "default_openhab_remote_username", + prefs.getStringOrNull("default_openhab_remote_username") + ) + putString( + "default_openhab_remote_password", + prefs.getStringOrNull("default_openhab_remote_password") + ) } // Clear from unencrypted prefs remove("default_openhab_username") @@ -121,14 +125,20 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { val remoteUrl = prefs.getStringOrNull("default_openhab_alturl") if (localUrl != null || remoteUrl != null) { val secretPrefs = context.getSecretPrefs() - val localPath = localUrl?.let { url -> ServerPath(url, - secretPrefs.getStringOrNull("default_openhab_username"), - secretPrefs.getStringOrNull("default_openhab_password") - ) } - val remotePath = remoteUrl?.let { url -> ServerPath(url, - secretPrefs.getStringOrNull("default_openhab_remote_username"), - secretPrefs.getStringOrNull("default_openhab_remote_password") - ) } + val localPath = localUrl?.let { url -> + ServerPath( + url, + secretPrefs.getStringOrNull("default_openhab_username"), + secretPrefs.getStringOrNull("default_openhab_password") + ) + } + val remotePath = remoteUrl?.let { url -> + ServerPath( + url, + secretPrefs.getStringOrNull("default_openhab_remote_username"), + secretPrefs.getStringOrNull("default_openhab_remote_password") + ) + } val defaultSitemapName = prefs.getStringOrNull("default_openhab_sitemap") val defaultSitemapLabel = prefs.getStringOrNull("default_openhab_sitemap_label") val defaultSitemap = if (defaultSitemapName.isNullOrEmpty() || defaultSitemapLabel == null) { @@ -136,8 +146,14 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { } else { DefaultSitemap(defaultSitemapName, defaultSitemapLabel) } - val config = ServerConfiguration(1, "openHAB", localPath, remotePath, - prefs.getStringOrNull("default_openhab_sslclientcert"), defaultSitemap) + val config = ServerConfiguration( + 1, + "openHAB", + localPath, + remotePath, + prefs.getStringOrNull("default_openhab_sslclientcert"), + defaultSitemap + ) config.saveToPrefs(prefs, secretPrefs) prefs.edit { putConfiguredServerIds(setOf(config.id)) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt index b5432fae64..faf594e334 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt @@ -161,7 +161,8 @@ class ConnectionFactory internal constructor( // changed since we went to background val (_, active, _, _) = stateChannel.value val local = active?.connection === activeConn?.local || - (active?.failureReason is NoUrlInformationException && active.failureReason.wouldHaveUsedLocalConnection()) + (active?.failureReason is NoUrlInformationException && + active.failureReason.wouldHaveUsedLocalConnection()) if (local) { triggerConnectionUpdateIfNeeded() } @@ -285,10 +286,12 @@ class ConnectionFactory internal constructor( val newState = StateHolder(primary, active, primaryCloud, activeCloud) stateChannel.offer(newState) if (callListenersOnChange) launch { - if (newState.active?.failureReason != null || prevState.active?.connection !== newState.active?.connection) { + if (newState.active?.failureReason != null || + prevState.active?.connection !== newState.active?.connection) { listeners.forEach { l -> l.onActiveConnectionChanged() } } - if (newState.primary?.failureReason != null || prevState.primary?.connection !== newState.primary?.connection) { + if (newState.primary?.failureReason != null || + prevState.primary?.connection !== newState.primary?.connection) { listeners.forEach { l -> l.onPrimaryConnectionChanged() } } if (prevState.activeCloud !== newState.activeCloud) { diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt index 5c61c5294e..5a59d04b6f 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt @@ -121,10 +121,22 @@ data class ServerConfiguration( const val SERVER_ID_CURRENT_ACTIVE = -1 fun load(prefs: SharedPreferences, secretPrefs: SharedPreferences, id: Int): ServerConfiguration? { - val localPath = ServerPath.load(prefs, secretPrefs, id, - PrefKeys.LOCAL_URL_PREFIX, PrefKeys.LOCAL_USERNAME_PREFIX, PrefKeys.LOCAL_PASSWORD_PREFIX) - val remotePath = ServerPath.load(prefs, secretPrefs, id, - PrefKeys.REMOTE_URL_PREFIX, PrefKeys.REMOTE_USERNAME_PREFIX, PrefKeys.REMOTE_PASSWORD_PREFIX) + val localPath = ServerPath.load( + prefs, + secretPrefs, + id, + PrefKeys.LOCAL_URL_PREFIX, + PrefKeys.LOCAL_USERNAME_PREFIX, + PrefKeys.LOCAL_PASSWORD_PREFIX + ) + val remotePath = ServerPath.load( + prefs, + secretPrefs, + id, + PrefKeys.REMOTE_URL_PREFIX, + PrefKeys.REMOTE_USERNAME_PREFIX, + PrefKeys.REMOTE_PASSWORD_PREFIX + ) val serverName = prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.SERVER_NAME_PREFIX)) if ((localPath == null && remotePath == null) || serverName.isNullOrEmpty()) { return null @@ -141,8 +153,10 @@ data class ServerConfiguration( } fun getDefaultSitemap(prefs: SharedPreferences, id: Int): DefaultSitemap? { - val defaultSitemapName = prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_NAME_PREFIX)) - val defaultSitemapLabel = prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_LABEL_PREFIX)) + val defaultSitemapName = + prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_NAME_PREFIX)) + val defaultSitemapLabel = + prefs.getStringOrNull(PrefKeys.buildServerKey(id, PrefKeys.DEFAULT_SITEMAP_LABEL_PREFIX)) return if (defaultSitemapName != null && defaultSitemapLabel != null) { DefaultSitemap(defaultSitemapName, defaultSitemapLabel) } else { diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt index 21ecf03459..51da7f37c9 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt @@ -239,7 +239,7 @@ abstract class AbstractItemPickerActivity : AbstractBaseActivity(), SwipeRefresh requestJob = launch { ConnectionFactory.waitForInitialization() - val connection = ConnectionFactory.primaryUsableConnection?.connection // XXX: correct? + val connection = ConnectionFactory.primaryUsableConnection?.connection // TODO: correct? if (connection == null) { updateViewVisibility(loading = false, loadError = true, showHint = false) return@launch diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt index 5602681547..f4b2fd57e3 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt @@ -118,7 +118,7 @@ class ItemPickerAdapter(context: Context, private val itemClickListener: ItemCli itemLabelView.text = item.label itemTypeView.text = item.type.toString() - val connection = ConnectionFactory.primaryUsableConnection?.connection // XXX: correct? + val connection = ConnectionFactory.primaryUsableConnection?.connection // TODO: correct? val icon = item.category.toOH2IconResource() if (icon != null && connection != null) { iconView.setImageUrl( diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index fb7fd9bc30..1b1ceaf218 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -515,7 +515,6 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { handlePendingAction() } - private fun handleConnectionChange() { if (connection is DemoConnection) { showSnackbar(R.string.info_demo_mode_short, R.string.turn_off, TAG_SNACKBAR_DEMO_MODE_ACTIVE) { @@ -602,9 +601,13 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { val port = info.port.toString() Log.d(TAG, "Service resolved: $address port: $port") - val config = ServerConfiguration(prefs.getNextAvailableServerId(), getString(R.string.openhab), + val config = ServerConfiguration( + prefs.getNextAvailableServerId(), + getString(R.string.openhab), ServerPath("https://$address:$port", null, null), - null, null, null + null, + null, + null ) config.saveToPrefs(prefs, getSecretPrefs()) } else { @@ -784,7 +787,8 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { private fun updateSitemapDrawerEntries() { val defaultSitemapItem = drawerMenu.findItem(R.id.default_sitemap) - val sitemaps = serverProperties?.sitemaps?.sortedWithDefaultName(prefs.getDefaultSitemap(connection)?.name.orEmpty()) + val sitemaps = serverProperties?.sitemaps + ?.sortedWithDefaultName(prefs.getDefaultSitemap(connection)?.name.orEmpty()) drawerMenu.getGroupItems(R.id.sitemaps) .filter { item -> item !== defaultSitemapItem } @@ -981,7 +985,11 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { true } action is PendingAction.OpenNotification && isStarted -> { - val conn = if (action.primary) ConnectionFactory.primaryCloudConnection else ConnectionFactory.activeCloudConnection + val conn = if (action.primary) { + ConnectionFactory.primaryCloudConnection + } else { + ConnectionFactory.activeCloudConnection + } if (conn?.connection != null) { openNotifications(action.notificationId, action.primary) true diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index fb4144ffd2..013ad5c2d8 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -599,7 +599,7 @@ class PreferencesActivity : AbstractBaseActivity() { } override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when(item.itemId) { + return when (item.itemId) { R.id.save -> { saveAndQuit() true @@ -638,7 +638,7 @@ class PreferencesActivity : AbstractBaseActivity() { parentFragmentManager.popBackStack() // close ourself } - override fun onBackPressed() : Boolean { + override fun onBackPressed(): Boolean { if (ServerConfiguration.load(prefs, secretPrefs, config.id) != config) { AlertDialog.Builder(preferenceManager.context) .setTitle(R.string.settings_server_confirm_leave_title) @@ -669,42 +669,58 @@ class PreferencesActivity : AbstractBaseActivity() { val serverNamePref = getPreference("name") as EditTextPreference serverNamePref.text = config.name serverNamePref.setOnPreferenceChangeListener { _, newValue -> - config = ServerConfiguration(config.id, newValue as String, - config.localPath, config.remotePath, config.sslClientCert, config.defaultSitemap) + config = ServerConfiguration( + config.id, + newValue as String, + config.localPath, + config.remotePath, + config.sslClientCert, + config.defaultSitemap + ) parentActivity.invalidateOptionsMenu() true } val localConnPref = getPreference("local") localConnPref.setOnPreferenceClickListener { - parentActivity.openSubScreen(ConnectionSettingsFragment.newInstance( - localConnPref.key, - config.localPath, - R.xml.local_connection_preferences, - R.string.settings_openhab_connection, - R.string.settings_openhab_url_summary, - this - )) + parentActivity.openSubScreen( + ConnectionSettingsFragment.newInstance( + localConnPref.key, + config.localPath, + R.xml.local_connection_preferences, + R.string.settings_openhab_connection, + R.string.settings_openhab_url_summary, + this + ) + ) false } val remoteConnPref = getPreference("remote") remoteConnPref.setOnPreferenceClickListener { - parentActivity.openSubScreen(ConnectionSettingsFragment.newInstance( - remoteConnPref.key, - config.remotePath, - R.xml.remote_connection_preferences, - R.string.settings_openhab_alt_connection, - R.string.settings_openhab_alturl_summary, - this - )) + parentActivity.openSubScreen( + ConnectionSettingsFragment.newInstance( + remoteConnPref.key, + config.remotePath, + R.xml.remote_connection_preferences, + R.string.settings_openhab_alt_connection, + R.string.settings_openhab_alturl_summary, + this + ) + ) false } val clientCertPref = getPreference("clientcert") as SslClientCertificatePreference clientCertPref.setOnPreferenceChangeListener { _, newValue -> - config = ServerConfiguration(config.id, config.name, - config.localPath, config.remotePath, newValue as String?, config.defaultSitemap) + config = ServerConfiguration( + config.id, + config.name, + config.localPath, + config.remotePath, + newValue as String?, + config.defaultSitemap + ) true } @@ -713,7 +729,8 @@ class PreferencesActivity : AbstractBaseActivity() { handleNoDefaultSitemap(clearDefaultSitemapPref) } else { clearDefaultSitemapPref.summary = getString( - R.string.settings_current_default_sitemap, config.defaultSitemap?.label.orEmpty()) + R.string.settings_current_default_sitemap, config.defaultSitemap?.label.orEmpty() + ) } clearDefaultSitemapPref.setOnPreferenceClickListener { preference -> preference.sharedPreferences.updateDefaultSitemap(null, null, config.id) @@ -757,9 +774,23 @@ class PreferencesActivity : AbstractBaseActivity() { fun onPathChanged(key: String, path: ServerPath) { config = if (key == "local") { - ServerConfiguration(config.id, config.name, path, config.remotePath, config.sslClientCert, config.defaultSitemap) + ServerConfiguration( + config.id, + config.name, + path, + config.remotePath, + config.sslClientCert, + config.defaultSitemap + ) } else { - ServerConfiguration(config.id, config.name, config.localPath, path, config.sslClientCert, config.defaultSitemap) + ServerConfiguration( + config.id, + config.name, + config.localPath, + path, + config.sslClientCert, + config.defaultSitemap + ) } parentActivity.invalidateOptionsMenu() } @@ -816,12 +847,18 @@ class PreferencesActivity : AbstractBaseActivity() { getString(requireArguments().getInt("urlsummary"), actualValue) } - userNamePreference = initEditor("username", path.userName, - R.drawable.ic_person_outline_grey_24dp) { value -> + userNamePreference = initEditor( + "username", + path.userName, + R.drawable.ic_person_outline_grey_24dp + ) { value -> if (!value.isNullOrEmpty()) value else getString(R.string.info_not_set) } - passwordPreference = initEditor("password", path.password, - R.drawable.ic_shield_key_outline_grey_24dp) { value -> + passwordPreference = initEditor( + "password", + path.password, + R.drawable.ic_shield_key_outline_grey_24dp + ) { value -> getString(when { value.isNullOrEmpty() -> R.string.info_not_set isWeakPassword(value) -> R.string.settings_openhab_password_summary_weak diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt index 2ce1d102aa..d45565b149 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt @@ -93,7 +93,7 @@ abstract class ContentController protected constructor(private val activity: Mai */ val currentTitle get() = when { noConnectionFragment != null -> null - temporaryPage is CloudNotificationListFragment -> activity.getString(R.string.app_notifications) // XXX: mark server type (active/primary) or index in title? + temporaryPage is CloudNotificationListFragment -> activity.getString(R.string.app_notifications) // TODO: mark server type (active/primary) or index in title? temporaryPage is WebViewFragment -> (temporaryPage as WebViewFragment).title temporaryPage != null -> null else -> fragmentForTitle?.title @@ -267,10 +267,18 @@ abstract class ContentController protected constructor(private val activity: Mai activity.getString(R.string.mainmenu_openhab_habpanel_on_server, activeServerName) } - showTemporaryPage(WebViewFragment.newInstance(title, - R.string.habpanel_error, - "/habpanel/index.html", "/rest/events", - MainActivity.ACTION_HABPANEL_SELECTED, activeServerId, title, R.mipmap.ic_shortcut_habpanel)) + showTemporaryPage( + WebViewFragment.newInstance( + title, + R.string.habpanel_error, + "/habpanel/index.html", + "/rest/events", + MainActivity.ACTION_HABPANEL_SELECTED, + activeServerId, + title, + R.mipmap.ic_shortcut_habpanel + ) + ) } /** diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt index 95955fdcb8..25f21164b2 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/PrimaryServerPreference.kt @@ -33,8 +33,10 @@ class PrimaryServerPreference constructor(context: Context, attrs: AttributeSet) super.onBindViewHolder(holder) if (holder != null) { helpIcon = holder.itemView.findViewById(R.id.help_icon) - helpIcon?.setupHelpIcon(context.getString(R.string.settings_server_primary_url), - R.string.click_here_for_more_information) + helpIcon?.setupHelpIcon( + context.getString(R.string.settings_server_primary_url), + R.string.click_here_for_more_information + ) helpIcon?.updateHelpIconAlpha(isEnabled) } } diff --git a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt index 3606faa5d2..94520ea8d2 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt +++ b/mobile/src/main/java/org/openhab/habdroid/util/PrefExtensions.kt @@ -221,7 +221,6 @@ fun SharedPreferences.updateDefaultSitemap(connection: Connection?, sitemap: Sit } val defaultSitemap = sitemap?.let { DefaultSitemap(sitemap.name, sitemap.label) } ServerConfiguration.saveDefaultSitemap(this, id, defaultSitemap) - } fun PreferenceFragmentCompat.getPreference(key: String): Preference { diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index ffb42934fc..4c108a25af 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -102,6 +102,16 @@ Add server Edit server Servers + Server %s + At least server name and one connection is required + Do you really want to remove this server? + Unsaved changes + You made some changes. Do you want to save or discard them? + Primary server + This server is the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. + "%s" is the primary server. Click to make this server the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. + https://www.openhab.org/docs/apps/android.html + Click here for more information Voice commands @@ -651,14 +661,4 @@ lamp_floor lamp_bedside lamp_outdoor - At least server name and one connection is required - Server %s - Do you really want to remove this server? - Unsaved changes - You made some changes. Do you want to save or discard them? - Primary server - This server is the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. - "%s" is the primary server. Click to make this server the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. - https://www.openhab.org/docs/apps/android.html - Click here for more information diff --git a/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt b/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt index 589c9efeb6..a2bad8bfc1 100644 --- a/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt +++ b/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt @@ -153,9 +153,7 @@ class ConnectionFactoryTest { fillInServers(remote = server.url("/").toString()) updateAndWaitForConnections() - val foo = ConnectionFactory.activeCloudConnection - //val conn = ConnectionFactory.activeCloudConnection?.connection - val conn = foo?.connection + val conn = ConnectionFactory.activeCloudConnection?.connection assertNotNull("Should return a cloud connection if remote url is set.", conn) assertEquals(CloudConnection::class.java, conn!!.javaClass) @@ -171,7 +169,10 @@ class ConnectionFactoryTest { fun testGetAnyConnectionNoNetwork() { mockConnectionHelper.update(null) updateAndWaitForConnections() - assertEquals(ConnectionFactory.activeUsableConnection?.failureReason?.javaClass, NetworkNotAvailableException::class.java) + assertEquals( + ConnectionFactory.activeUsableConnection?.failureReason?.javaClass, + NetworkNotAvailableException::class.java + ) } @Test @@ -199,8 +200,11 @@ class ConnectionFactoryTest { val conn = ConnectionFactory.activeUsableConnection?.connection assertNotNull("Should return a connection in WIFI when only remote url is set.", conn) - assertEquals("The connection type of the connection should be TYPE_REMOTE.", - Connection.TYPE_REMOTE, conn?.connectionType) + assertEquals( + "The connection type of the connection should be TYPE_REMOTE.", + Connection.TYPE_REMOTE, + conn?.connectionType + ) server.shutdown() } @@ -218,8 +222,11 @@ class ConnectionFactoryTest { val conn = ConnectionFactory.activeUsableConnection?.connection assertNotNull("Should return a connection in WIFI when a local url is set.", conn) - assertEquals("The connection type of the connection should be TYPE_LOCAL.", - Connection.TYPE_LOCAL, conn?.connectionType) + assertEquals( + "The connection type of the connection should be TYPE_LOCAL.", + Connection.TYPE_LOCAL, + conn?.connectionType + ) server.shutdown() } @@ -229,7 +236,10 @@ class ConnectionFactoryTest { fillInServers(null, null) mockConnectionHelper.update(ConnectionManagerHelper.ConnectionType.Wifi(null)) updateAndWaitForConnections() - assertEquals(ConnectionFactory.activeUsableConnection?.failureReason?.javaClass, NoUrlInformationException::class.java) + assertEquals( + ConnectionFactory.activeUsableConnection?.failureReason?.javaClass, + NoUrlInformationException::class.java + ) } private inner class MockConnectionHelper : ConnectionManagerHelper { @@ -245,9 +255,18 @@ class ConnectionFactoryTest { } private fun fillInServers(local: String? = null, remote: String? = null) { - whenever(mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.LOCAL_URL_PREFIX)), anyOrNull())) doReturn local - whenever(mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.REMOTE_URL_PREFIX)), anyOrNull())) doReturn remote - whenever(mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.SERVER_NAME_PREFIX)), anyOrNull())) doReturn "Test Server" + whenever( + mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.LOCAL_URL_PREFIX)), + anyOrNull()) + ) doReturn local + whenever( + mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.REMOTE_URL_PREFIX)), + anyOrNull()) + ) doReturn remote + whenever( + mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.SERVER_NAME_PREFIX)), + anyOrNull()) + ) doReturn "Test Server" } private fun updateAndWaitForConnections() { diff --git a/mobile/src/test/java/org/openhab/habdroid/core/connection/DefaultConnectionTest.kt b/mobile/src/test/java/org/openhab/habdroid/core/connection/DefaultConnectionTest.kt index 3d430a35ea..93eb7e1e19 100644 --- a/mobile/src/test/java/org/openhab/habdroid/core/connection/DefaultConnectionTest.kt +++ b/mobile/src/test/java/org/openhab/habdroid/core/connection/DefaultConnectionTest.kt @@ -36,12 +36,21 @@ class DefaultConnectionTest { @Before fun setup() { client = OkHttpClient.Builder().build() - testConnection = DefaultConnection(client, Connection.TYPE_LOCAL, - ServerPath(TEST_BASE_URL, null, null)) - testConnectionRemote = DefaultConnection(client, Connection.TYPE_REMOTE, - ServerPath("", null, null)) - testConnectionCloud = DefaultConnection(client, Connection.TYPE_CLOUD, - ServerPath("", null, null)) + testConnection = DefaultConnection( + client, + Connection.TYPE_LOCAL, + ServerPath(TEST_BASE_URL, null, null) + ) + testConnectionRemote = DefaultConnection( + client, + Connection.TYPE_REMOTE, + ServerPath("", null, null) + ) + testConnectionCloud = DefaultConnection( + client, + Connection.TYPE_CLOUD, + ServerPath("", null, null) + ) } @Test @@ -71,15 +80,21 @@ class DefaultConnectionTest { @Test fun testGetUsernameSet() { - val connection = DefaultConnection(client, Connection.TYPE_LOCAL, - ServerPath(TEST_BASE_URL, "Test-User", null)) + val connection = DefaultConnection( + client, + Connection.TYPE_LOCAL, + ServerPath(TEST_BASE_URL, "Test-User", null) + ) assertEquals("Test-User", connection.username) } @Test fun testGetPasswordSet() { - val connection = DefaultConnection(client, Connection.TYPE_LOCAL, - ServerPath(TEST_BASE_URL, null, "Test-Password")) + val connection = DefaultConnection( + client, + Connection.TYPE_LOCAL, + ServerPath(TEST_BASE_URL, null, "Test-Password") + ) assertEquals("Test-Password", connection.password) } @@ -100,12 +115,14 @@ class DefaultConnectionTest { @Test fun testHasUsernamePassword() { - val connection = DefaultConnection(client, Connection.TYPE_LOCAL, - ServerPath(TEST_BASE_URL, "Test-User", "Test-Password")) + val connection = DefaultConnection( + client, + Connection.TYPE_LOCAL, + ServerPath(TEST_BASE_URL, "Test-User", "Test-Password") + ) val httpClient = connection.httpClient - assertEquals(Credentials.basic("Test-User", "Test-Password"), - httpClient.authHeader) + assertEquals(Credentials.basic("Test-User", "Test-Password"), httpClient.authHeader) } @Test From 26e7f1aafce892a2eb02dd918c591460d552d493 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 21:50:07 +0200 Subject: [PATCH 24/44] Remove 2 TODOs Signed-off-by: mueller-ma --- .../java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt | 2 +- .../src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt index 51da7f37c9..161221f45e 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/AbstractItemPickerActivity.kt @@ -239,7 +239,7 @@ abstract class AbstractItemPickerActivity : AbstractBaseActivity(), SwipeRefresh requestJob = launch { ConnectionFactory.waitForInitialization() - val connection = ConnectionFactory.primaryUsableConnection?.connection // TODO: correct? + val connection = ConnectionFactory.primaryUsableConnection?.connection if (connection == null) { updateViewVisibility(loading = false, loadError = true, showHint = false) return@launch diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt index f4b2fd57e3..105b4b6ee5 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/ItemPickerAdapter.kt @@ -118,7 +118,7 @@ class ItemPickerAdapter(context: Context, private val itemClickListener: ItemCli itemLabelView.text = item.label itemTypeView.text = item.type.toString() - val connection = ConnectionFactory.primaryUsableConnection?.connection // TODO: correct? + val connection = ConnectionFactory.primaryUsableConnection?.connection val icon = item.category.toOH2IconResource() if (icon != null && connection != null) { iconView.setImageUrl( From 723698b0ab59960c43fd5f6ed397a38dff933a06 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 21:51:27 +0200 Subject: [PATCH 25/44] Rename xml preference files Signed-off-by: mueller-ma --- .../java/org/openhab/habdroid/ui/PreferencesActivity.kt | 6 +++--- ...ion_preferences.xml => preferences_local_connection.xml} | 0 ...on_preferences.xml => preferences_remote_connection.xml} | 0 .../xml/{server_preferences.xml => preferences_server.xml} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename mobile/src/main/res/xml/{local_connection_preferences.xml => preferences_local_connection.xml} (100%) rename mobile/src/main/res/xml/{remote_connection_preferences.xml => preferences_remote_connection.xml} (100%) rename mobile/src/main/res/xml/{server_preferences.xml => preferences_server.xml} (100%) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 013ad5c2d8..a7aef6e1b9 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -664,7 +664,7 @@ class PreferencesActivity : AbstractBaseActivity() { } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - addPreferencesFromResource(R.xml.server_preferences) + addPreferencesFromResource(R.xml.preferences_server) val serverNamePref = getPreference("name") as EditTextPreference serverNamePref.text = config.name @@ -687,7 +687,7 @@ class PreferencesActivity : AbstractBaseActivity() { ConnectionSettingsFragment.newInstance( localConnPref.key, config.localPath, - R.xml.local_connection_preferences, + R.xml.preferences_local_connection, R.string.settings_openhab_connection, R.string.settings_openhab_url_summary, this @@ -702,7 +702,7 @@ class PreferencesActivity : AbstractBaseActivity() { ConnectionSettingsFragment.newInstance( remoteConnPref.key, config.remotePath, - R.xml.remote_connection_preferences, + R.xml.preferences_remote_connection, R.string.settings_openhab_alt_connection, R.string.settings_openhab_alturl_summary, this diff --git a/mobile/src/main/res/xml/local_connection_preferences.xml b/mobile/src/main/res/xml/preferences_local_connection.xml similarity index 100% rename from mobile/src/main/res/xml/local_connection_preferences.xml rename to mobile/src/main/res/xml/preferences_local_connection.xml diff --git a/mobile/src/main/res/xml/remote_connection_preferences.xml b/mobile/src/main/res/xml/preferences_remote_connection.xml similarity index 100% rename from mobile/src/main/res/xml/remote_connection_preferences.xml rename to mobile/src/main/res/xml/preferences_remote_connection.xml diff --git a/mobile/src/main/res/xml/server_preferences.xml b/mobile/src/main/res/xml/preferences_server.xml similarity index 100% rename from mobile/src/main/res/xml/server_preferences.xml rename to mobile/src/main/res/xml/preferences_server.xml From 3f2dfefbff5b57167da00d116dd8ee7c34d7015f Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 22:00:09 +0200 Subject: [PATCH 26/44] Add default server name Signed-off-by: mueller-ma --- .../java/org/openhab/habdroid/ui/PreferencesActivity.kt | 9 ++++++++- mobile/src/main/res/values/strings.xml | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index a7aef6e1b9..ba14b91cfd 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -291,7 +291,14 @@ class PreferencesActivity : AbstractBaseActivity() { addServerPref?.setOnPreferenceClickListener { val nextServerId = prefs.getNextAvailableServerId() - val f = ServerEditorFragment.newInstance(ServerConfiguration(nextServerId, "", null, null, null, null)) + val nextName = if (prefs.getConfiguredServerIds().isEmpty()) { + getString(R.string.openhab) + } else { + getString(R.string.settings_server_default_name, nextServerId) + } + val f = ServerEditorFragment.newInstance( + ServerConfiguration(nextServerId, nextName, null, null, null, null) + ) parentActivity.openSubScreen(f) true } diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 4c108a25af..9d868b4026 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -111,6 +111,7 @@ This server is the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. "%s" is the primary server. Click to make this server the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. https://www.openhab.org/docs/apps/android.html + openHAB %d Click here for more information From 0681b3c9a130c56518cc65a26afc029634232c36 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 16 Aug 2020 22:08:58 +0200 Subject: [PATCH 27/44] Show server name in notifications fragment title Signed-off-by: mueller-ma --- .../ui/CloudNotificationListFragment.kt | 17 +++++++++++++++++ .../habdroid/ui/activity/ContentController.kt | 3 ++- mobile/src/main/res/values/strings.xml | 3 ++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt index 16efbdcdce..60402b3896 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt @@ -13,6 +13,7 @@ package org.openhab.habdroid.ui +import android.content.Context import android.os.Bundle import android.util.Log import android.view.LayoutInflater @@ -33,9 +34,14 @@ import org.json.JSONArray import org.json.JSONException import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.ConnectionFactory +import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.model.toCloudNotification import org.openhab.habdroid.ui.widget.DividerItemDecoration import org.openhab.habdroid.util.HttpClient +import org.openhab.habdroid.util.getActiveServerId +import org.openhab.habdroid.util.getConfiguredServerIds +import org.openhab.habdroid.util.getPrefs +import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.map /** @@ -175,6 +181,17 @@ class CloudNotificationListFragment : Fragment(), View.OnClickListener, SwipeRef retryButton.isVisible = loadError } + fun getTitle(context: Context): String { + val prefs = context.getPrefs() + return if (prefs.getConfiguredServerIds().size <= 1) { + context.getString(R.string.app_notifications) + } else { + val activeServerId = prefs.getActiveServerId() + val activeServerName = ServerConfiguration.load(prefs, context.getSecretPrefs(), activeServerId)?.name + context.getString(R.string.app_notifications_on_server, activeServerName) + } + } + companion object { private val TAG = CloudNotificationListFragment::class.java.simpleName diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt index d45565b149..2203f98bfc 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt @@ -93,7 +93,8 @@ abstract class ContentController protected constructor(private val activity: Mai */ val currentTitle get() = when { noConnectionFragment != null -> null - temporaryPage is CloudNotificationListFragment -> activity.getString(R.string.app_notifications) // TODO: mark server type (active/primary) or index in title? + temporaryPage is CloudNotificationListFragment -> + (temporaryPage as CloudNotificationListFragment).getTitle(activity) temporaryPage is WebViewFragment -> (temporaryPage as WebViewFragment).title temporaryPage != null -> null else -> fragmentForTitle?.title diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 9d868b4026..07c0f56fbb 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -7,7 +7,6 @@ openHAB _openhab-server-ssl._tcp.local. Settings - Notifications Save Discard @@ -18,6 +17,8 @@ Settings HABPanel HABPanel (%s) + Notifications + Notifications (%s) An error occurred while loading HABPanel Select default Sitemap Clear images cache From 621fb9029d9194912729dd6d01dfbbeadae35b73 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Thu, 20 Aug 2020 11:05:52 +0200 Subject: [PATCH 28/44] Cleanup. Signed-off-by: Danny Baumann --- .../background/BackgroundTasksManager.kt | 26 ++++++++++++++++--- .../core/connection/ConnectionFactory.kt | 6 +++-- .../org/openhab/habdroid/ui/MainActivity.kt | 15 +---------- .../ui/homescreenwidget/VoiceWidget.kt | 17 +----------- 4 files changed, 28 insertions(+), 36 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt index f5f12ca269..dd38fa5a95 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt @@ -17,6 +17,7 @@ import android.Manifest import android.annotation.SuppressLint import android.app.AlarmManager import android.app.NotificationManager +import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Context.NOTIFICATION_SERVICE @@ -167,7 +168,7 @@ class BackgroundTasksManager : BroadcastReceiver() { resultCode = TaskerPlugin.Setting.RESULT_CODE_PENDING } } - ACTION_VOICE_RESULT_APP, ACTION_VOICE_RESULT_WIDGET -> { + ACTION_VOICE_RESULT -> { val voiceCommand = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.elementAtOrNull(0) ?: return Log.i(TAG, "Recognized text: $voiceCommand") @@ -181,7 +182,7 @@ class BackgroundTasksManager : BroadcastReceiver() { isImportant = true, showToast = true, asCommand = true, - primaryServer = intent.action == ACTION_VOICE_RESULT_WIDGET + primaryServer = intent.getBooleanExtra(EXTRA_FROM_BACKGROUND, false) ) } } @@ -232,9 +233,9 @@ class BackgroundTasksManager : BroadcastReceiver() { internal const val ACTION_RETRY_UPLOAD = "org.openhab.habdroid.background.action.RETRY_UPLOAD" internal const val ACTION_CLEAR_UPLOAD = "org.openhab.habdroid.background.action.CLEAR_UPLOAD" - internal const val ACTION_VOICE_RESULT_APP = "org.openhab.habdroid.background.action.VOICE_RESULT_APP" - internal const val ACTION_VOICE_RESULT_WIDGET = "org.openhab.habdroid.background.action.VOICE_RESULT_WIDGET" + private const val ACTION_VOICE_RESULT = "org.openhab.habdroid.background.action.VOICE_RESULT" internal const val EXTRA_RETRY_INFO_LIST = "retryInfoList" + private const val EXTRA_FROM_BACKGROUND = "fromBackground" private const val WORKER_TAG_ITEM_UPLOADS = "itemUploads" private const val WORKER_TAG_PERIODIC_TRIGGER = "periodicTrigger" @@ -364,6 +365,23 @@ class BackgroundTasksManager : BroadcastReceiver() { ) } + fun buildVoiceRecognitionIntent(context: Context, fromBackground: Boolean): Intent { + val callbackIntent = Intent(context, BackgroundTasksManager::class.java).apply { + action = ACTION_VOICE_RESULT + putExtra(EXTRA_FROM_BACKGROUND, fromBackground) + } + val callbackPendingIntent = PendingIntent.getBroadcast(context, + if (fromBackground) 1 else 0, callbackIntent, 0) + + return Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + // Display an hint to the user about what he should say. + putExtra(RecognizerIntent.EXTRA_PROMPT, context.getString(R.string.info_voice_input)) + putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) + putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1) + putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, callbackPendingIntent) + } + } + fun triggerPeriodicWork(context: Context) { Log.d(TAG, "triggerPeriodicWork()") KNOWN_PERIODIC_KEYS.forEach { key -> scheduleWorker(context, key) } diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt index faf594e334..0fb0d5fe23 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt @@ -287,11 +287,13 @@ class ConnectionFactory internal constructor( stateChannel.offer(newState) if (callListenersOnChange) launch { if (newState.active?.failureReason != null || - prevState.active?.connection !== newState.active?.connection) { + prevState.active?.connection !== newState.active?.connection + ) { listeners.forEach { l -> l.onActiveConnectionChanged() } } if (newState.primary?.failureReason != null || - prevState.primary?.connection !== newState.primary?.connection) { + prevState.primary?.connection !== newState.primary?.connection + ) { listeners.forEach { l -> l.onPrimaryConnectionChanged() } } if (prevState.activeCloud !== newState.activeCloud) { diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index 1b1ceaf218..086cba56ff 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -30,7 +30,6 @@ import android.nfc.NfcAdapter import android.os.Build import android.os.Bundle import android.provider.Settings -import android.speech.RecognizerIntent import android.speech.SpeechRecognizer import android.text.SpannableStringBuilder import android.text.style.RelativeSizeSpan @@ -1082,19 +1081,7 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } private fun launchVoiceRecognition() { - val callbackIntent = Intent(this, BackgroundTasksManager::class.java).apply { - action = BackgroundTasksManager.ACTION_VOICE_RESULT_APP - } - val openhabPendingIntent = PendingIntent.getBroadcast(this, 0, callbackIntent, 0) - - val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { - // Display an hint to the user about what he should say. - putExtra(RecognizerIntent.EXTRA_PROMPT, getString(R.string.info_voice_input)) - putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) - putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1) - putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, openhabPendingIntent) - } - + val speechIntent = BackgroundTasksManager.buildVoiceRecognitionIntent(this, false) if (speechIntent.isResolvable(this)) { startActivity(speechIntent) } else { diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/VoiceWidget.kt b/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/VoiceWidget.kt index 7f93390e7d..0f48b66130 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/VoiceWidget.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/homescreenwidget/VoiceWidget.kt @@ -17,8 +17,6 @@ import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context -import android.content.Intent -import android.speech.RecognizerIntent import android.util.Log import android.widget.RemoteViews import androidx.annotation.LayoutRes @@ -38,20 +36,7 @@ open class VoiceWidget : AppWidgetProvider() { val views = RemoteViews(context.packageName, layoutRes) Log.d(TAG, "Build voice recognition intent") - val callbackIntent = Intent(context, BackgroundTasksManager::class.java).apply { - action = BackgroundTasksManager.ACTION_VOICE_RESULT_WIDGET - } - val callbackPendingIntent = PendingIntent.getBroadcast(context, 9, callbackIntent, 0) - - val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { - // Display an hint to the user about what he should say. - putExtra(RecognizerIntent.EXTRA_PROMPT, context.getString(R.string.info_voice_input)) - putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) - putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1) - putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, callbackPendingIntent) - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - + val intent = BackgroundTasksManager.buildVoiceRecognitionIntent(context, true) val pendingIntent = PendingIntent.getActivity(context, 6, intent, 0) views.setOnClickPendingIntent(R.id.outer_layout, pendingIntent) From f673a60df50a790d112a92fd062e1eb5cb5bd5ca Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Thu, 20 Aug 2020 11:12:10 +0200 Subject: [PATCH 29/44] Correctly handle notification list title. Signed-off-by: Danny Baumann --- .../openhab/habdroid/ui/CloudNotificationListFragment.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt index 60402b3896..8aa1c83f52 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt @@ -41,6 +41,7 @@ import org.openhab.habdroid.util.HttpClient import org.openhab.habdroid.util.getActiveServerId import org.openhab.habdroid.util.getConfiguredServerIds import org.openhab.habdroid.util.getPrefs +import org.openhab.habdroid.util.getPrimaryServerId import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.map @@ -118,7 +119,7 @@ class CloudNotificationListFragment : Fragment(), View.OnClickListener, SwipeRef private fun loadNotifications(clearExisting: Boolean) { val activity = activity as AbstractBaseActivity? ?: return - val conn = if (arguments?.getBoolean("primary") == true) { + val conn = if (usePrimaryServer()) { ConnectionFactory.primaryCloudConnection?.connection } else { ConnectionFactory.activeCloudConnection?.connection @@ -181,13 +182,15 @@ class CloudNotificationListFragment : Fragment(), View.OnClickListener, SwipeRef retryButton.isVisible = loadError } + private fun usePrimaryServer() = arguments?.getBoolean("primary") == true + fun getTitle(context: Context): String { val prefs = context.getPrefs() return if (prefs.getConfiguredServerIds().size <= 1) { context.getString(R.string.app_notifications) } else { - val activeServerId = prefs.getActiveServerId() - val activeServerName = ServerConfiguration.load(prefs, context.getSecretPrefs(), activeServerId)?.name + val serverId = if (usePrimaryServer()) prefs.getPrimaryServerId() else prefs.getActiveServerId() + val activeServerName = ServerConfiguration.load(prefs, context.getSecretPrefs(), serverId)?.name context.getString(R.string.app_notifications_on_server, activeServerName) } } From 6dabf2b6609d7145854bebe698b6eb923ba5e933 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 23 Aug 2020 23:09:58 +0200 Subject: [PATCH 30/44] Update docs Signed-off-by: mueller-ma --- docs/USAGE.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index 26f634dd93..ac7d376588 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -282,16 +282,16 @@ In case of an error the plugin returns an error code. When adding multiple servers to the app, there's always a primary and an active one. The active server is used for foreground operations, e.g. display the Sitemaps, and can be changed in the side menu. -The primary server is used for all features that don't support multiple servers and can be changed in the settings. +The primary server is used for all background operations and can be changed in the settings. Features, that support multiple servers: * Display Sitemaps and HABPanel -* Voice commands launched from in-app (send to active server) and from widgets (send to primary server) +* Voice commands launched from in-app (sent to active server) and from widgets (sent to primary server) * Show a list of recent notifications * Sitemap shortcuts on the home screen * Shortcuts for HABPanel, notifications and voice command -Features, that don't support multiple servers: +Features, that don't support multiple servers, i.e. use the primary server: * Item widgets on the home screen * Quick tiles * NFC tags From a9bdfd27297461f8950cdb5dca7b4f76a4e3429e Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 23 Aug 2020 23:10:12 +0200 Subject: [PATCH 31/44] Rename changeBetaTagVisibility() Signed-off-by: mueller-ma --- .../main/java/org/openhab/habdroid/ui/PreferencesActivity.kt | 2 +- .../java/org/openhab/habdroid/ui/preference/BetaPreference.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index ba14b91cfd..284e0bf92b 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -250,7 +250,7 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onResume() { super.onResume() - addServerPref?.changeBetaTagVisibility(prefs.getConfiguredServerIds().isNotEmpty()) + addServerPref?.setBetaTagVisibility(prefs.getConfiguredServerIds().isNotEmpty()) } override fun onStop() { diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/preference/BetaPreference.kt b/mobile/src/main/java/org/openhab/habdroid/ui/preference/BetaPreference.kt index 1c5eb6a53f..f1e24067ae 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/preference/BetaPreference.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/preference/BetaPreference.kt @@ -37,7 +37,7 @@ class BetaPreference constructor(context: Context, attrs: AttributeSet) : Prefer } } - fun changeBetaTagVisibility(show: Boolean) { + fun setBetaTagVisibility(show: Boolean) { showBetaTag = show betaTag?.isGone = !showBetaTag } From 8300bc4ac45f7181a7c379ed75a35940771e5cf1 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Thu, 20 Aug 2020 11:34:20 +0200 Subject: [PATCH 32/44] Remove placeholder server IDs and always use actual IDs instead. Signed-off-by: Danny Baumann --- .../habdroid/model/ServerConfiguration.kt | 3 -- .../org/openhab/habdroid/ui/MainActivity.kt | 36 +++---------------- .../habdroid/ui/activity/ContentController.kt | 2 +- .../habdroid/ui/activity/WebViewFragment.kt | 6 ++-- 4 files changed, 9 insertions(+), 38 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt index 5a59d04b6f..4315134027 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt +++ b/mobile/src/main/java/org/openhab/habdroid/model/ServerConfiguration.kt @@ -117,9 +117,6 @@ data class ServerConfiguration( } companion object { - const val SERVER_ID_PRIMARY = 0 - const val SERVER_ID_CURRENT_ACTIVE = -1 - fun load(prefs: SharedPreferences, secretPrefs: SharedPreferences, id: Int): ServerConfiguration? { val localPath = ServerPath.load( prefs, diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index 086cba56ff..98e4703e39 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -633,13 +633,13 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { executeActionIfPossible(PendingAction.OpenNotification(notificationId, true)) } ACTION_HABPANEL_SELECTED -> { - val serverId = intent.getIntExtra(EXTRA_SERVER_ID, ServerConfiguration.SERVER_ID_PRIMARY) + val serverId = intent.getIntExtra(EXTRA_SERVER_ID, prefs.getActiveServerId()) executeOrStoreAction(PendingAction.OpenHabPanel(serverId)) } ACTION_VOICE_RECOGNITION_SELECTED -> executeOrStoreAction(PendingAction.LaunchVoiceRecognition()) ACTION_SITEMAP_SELECTED -> { val sitemapUrl = intent.getStringExtra(EXTRA_SITEMAP_URL) ?: return - val serverId = intent.getIntExtra(EXTRA_SERVER_ID, 0) + val serverId = intent.getIntExtra(EXTRA_SERVER_ID, prefs.getActiveServerId()) executeOrStoreAction(PendingAction.OpenSitemapUrl(sitemapUrl, serverId)) } } @@ -918,25 +918,13 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } action is PendingAction.OpenSitemapUrl && isStarted && serverProperties != null -> { when { - action.serverId == ServerConfiguration.SERVER_ID_PRIMARY && - prefs.getActiveServerId() != prefs.getPrimaryServerId() -> { - prefs.edit { - putActiveServerId(prefs.getPrimaryServerId()) - } - updateDrawerServerEntries() - false - } - action.serverId == ServerConfiguration.SERVER_ID_PRIMARY -> { - buildUrlAndOpenSitemap(action.url) - true - } action.serverId !in prefs.getConfiguredServerIds() -> { showToast(R.string.home_shortcut_server_has_been_deleted, ToastType.ERROR) true } - prefs.getActiveServerId() != action.serverId -> { + action.serverId != prefs.getActiveServerId() -> { prefs.edit { - putActiveServerId(action.serverId) + putActiveServerId(prefs.getPrimaryServerId()) } updateDrawerServerEntries() false @@ -949,24 +937,11 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } action is PendingAction.OpenHabPanel && isStarted && serverProperties?.hasHabPanelInstalled() == true -> { when { - action.serverId == ServerConfiguration.SERVER_ID_PRIMARY && - prefs.getActiveServerId() != prefs.getPrimaryServerId() -> { - prefs.edit { - putActiveServerId(prefs.getPrimaryServerId()) - } - updateDrawerServerEntries() - false - } - action.serverId == ServerConfiguration.SERVER_ID_PRIMARY || - action.serverId == ServerConfiguration.SERVER_ID_CURRENT_ACTIVE -> { - openHabPanel() - true - } action.serverId !in prefs.getConfiguredServerIds() -> { showToast(R.string.home_shortcut_server_has_been_deleted, ToastType.ERROR) true } - prefs.getActiveServerId() != action.serverId -> { + action.serverId != prefs.getActiveServerId() -> { prefs.edit { putActiveServerId(action.serverId) } @@ -1269,7 +1244,6 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { } if (visible) { val intent = Intent(this, MainActivity::class.java) - .putExtra(EXTRA_SERVER_ID, ServerConfiguration.SERVER_ID_CURRENT_ACTIVE) .setAction(action) val shortcut = ShortcutInfo.Builder(this, id) .setShortLabel(getString(shortLabel)) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt index 2203f98bfc..8ec2368d80 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.kt @@ -274,8 +274,8 @@ abstract class ContentController protected constructor(private val activity: Mai R.string.habpanel_error, "/habpanel/index.html", "/rest/events", - MainActivity.ACTION_HABPANEL_SELECTED, activeServerId, + MainActivity.ACTION_HABPANEL_SELECTED, title, R.mipmap.ic_shortcut_habpanel ) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt index 15416773f9..5683fecea1 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt @@ -74,7 +74,7 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { urlToLoad = args.getString(KEY_URL_LOAD) as String urlForError = args.getString(KEY_URL_ERROR) as String val action = args.getString(KEY_SHORTCUT_ACTION) - val extraServerId = args.getInt(KEY_SHORTCUT_EXTRA_SERVER_ID, ServerConfiguration.SERVER_ID_PRIMARY) + val extraServerId = args.getInt(KEY_SHORTCUT_EXTRA_SERVER_ID) val label = args.getString(KEY_SHORTCUT_LABEL) @DrawableRes val icon = args.getInt(KEY_SHORTCUT_ICON_RES) action?.let { @@ -243,8 +243,8 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { @StringRes errorMessage: Int, urlToLoad: String, urlForError: String, + serverId: Int, shortcutAction: String? = null, - extraServerId: Int = ServerConfiguration.SERVER_ID_PRIMARY, shortcutLabel: String? = null, shortcutIconRes: Int = 0 ): WebViewFragment { @@ -255,7 +255,7 @@ class WebViewFragment : Fragment(), ConnectionFactory.UpdateListener { KEY_URL_LOAD to urlToLoad, KEY_URL_ERROR to urlForError, KEY_SHORTCUT_ACTION to shortcutAction, - KEY_SHORTCUT_EXTRA_SERVER_ID to extraServerId, + KEY_SHORTCUT_EXTRA_SERVER_ID to serverId, KEY_SHORTCUT_LABEL to shortcutLabel, KEY_SHORTCUT_ICON_RES to shortcutIconRes) return f From 627fc0afdf7d2a325a31e4727c34034a60b7b55b Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Mon, 24 Aug 2020 21:49:38 +0200 Subject: [PATCH 33/44] ItemUpdateWorker: Extract correct class from bundle Signed-off-by: mueller-ma --- .../java/org/openhab/habdroid/background/ItemUpdateWorker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt b/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt index 4ceb7b3769..62217c2072 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/ItemUpdateWorker.kt @@ -308,7 +308,7 @@ class ItemUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont .putString(OUTPUT_DATA_TASKER_INTENT, inputData.getString(INPUT_DATA_TASKER_INTENT)) .putBoolean(OUTPUT_DATA_AS_COMMAND, inputData.getBoolean(INPUT_DATA_AS_COMMAND, false)) .putBoolean(OUTPUT_DATA_IS_IMPORTANT, inputData.getBoolean(INPUT_DATA_IS_IMPORTANT, false)) - .putString(OUTPUT_DATA_PRIMARY_SERVER, inputData.getString(INPUT_DATA_PRIMARY_SERVER)) + .putBoolean(OUTPUT_DATA_PRIMARY_SERVER, inputData.getBoolean(INPUT_DATA_PRIMARY_SERVER, false)) .putLong(OUTPUT_DATA_TIMESTAMP, System.currentTimeMillis()) .build() } From 3c118175328da6c6535726419f3d32220c3d0a61 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Mon, 24 Aug 2020 21:54:22 +0200 Subject: [PATCH 34/44] Code style Signed-off-by: mueller-ma --- .../habdroid/background/BackgroundTasksManager.kt | 8 ++++++-- .../org/openhab/habdroid/ui/activity/WebViewFragment.kt | 1 - .../habdroid/core/connection/ConnectionFactoryTest.kt | 9 +++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt index dd38fa5a95..4a73fc6f24 100644 --- a/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt +++ b/mobile/src/main/java/org/openhab/habdroid/background/BackgroundTasksManager.kt @@ -370,8 +370,12 @@ class BackgroundTasksManager : BroadcastReceiver() { action = ACTION_VOICE_RESULT putExtra(EXTRA_FROM_BACKGROUND, fromBackground) } - val callbackPendingIntent = PendingIntent.getBroadcast(context, - if (fromBackground) 1 else 0, callbackIntent, 0) + val callbackPendingIntent = PendingIntent.getBroadcast( + context, + if (fromBackground) 1 else 0, + callbackIntent, + 0 + ) return Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { // Display an hint to the user about what he should say. diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt index 5683fecea1..1a507d4bbd 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/WebViewFragment.kt @@ -43,7 +43,6 @@ import kotlinx.coroutines.withContext import org.openhab.habdroid.R import org.openhab.habdroid.core.connection.CloudConnection import org.openhab.habdroid.core.connection.ConnectionFactory -import org.openhab.habdroid.model.ServerConfiguration import org.openhab.habdroid.ui.ConnectionWebViewClient import org.openhab.habdroid.ui.MainActivity import org.openhab.habdroid.ui.setUpForConnection diff --git a/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt b/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt index a2bad8bfc1..03e9df089c 100644 --- a/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt +++ b/mobile/src/test/java/org/openhab/habdroid/core/connection/ConnectionFactoryTest.kt @@ -256,16 +256,13 @@ class ConnectionFactoryTest { private fun fillInServers(local: String? = null, remote: String? = null) { whenever( - mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.LOCAL_URL_PREFIX)), - anyOrNull()) + mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.LOCAL_URL_PREFIX)), anyOrNull()) ) doReturn local whenever( - mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.REMOTE_URL_PREFIX)), - anyOrNull()) + mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.REMOTE_URL_PREFIX)), anyOrNull()) ) doReturn remote whenever( - mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.SERVER_NAME_PREFIX)), - anyOrNull()) + mockPrefs.getString(eq(PrefKeys.buildServerKey(1, PrefKeys.SERVER_NAME_PREFIX)), anyOrNull()) ) doReturn "Test Server" } From 6736b0914b069ec50299fc69a8400d777207d12d Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 30 Aug 2020 18:44:32 +0200 Subject: [PATCH 35/44] Use dialog fragments for server editor dialogs. Signed-off-by: mueller-ma Signed-off-by: Danny Baumann --- .../habdroid/ui/PreferencesActivity.kt | 116 ++++++++++++++---- 1 file changed, 91 insertions(+), 25 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 284e0bf92b..917bbcba2c 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -14,6 +14,7 @@ package org.openhab.habdroid.ui import android.app.Activity +import android.app.Dialog import android.app.KeyguardManager import android.content.Context import android.content.Intent @@ -40,6 +41,7 @@ import androidx.core.content.edit import androidx.core.graphics.drawable.DrawableCompat import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager import androidx.fragment.app.commit import androidx.preference.EditTextPreference import androidx.preference.ListPreference @@ -51,6 +53,7 @@ import androidx.preference.SwitchPreferenceCompat import androidx.preference.forEachIndexed import androidx.work.WorkManager import com.jaredrummler.android.colorpicker.ColorPreferenceCompat +import java.lang.IllegalArgumentException import java.util.BitSet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -586,7 +589,66 @@ class PreferencesActivity : AbstractBaseActivity() { } } - class ServerEditorFragment : AbstractSettingsFragment() { + class ConfirmationDialogFragment : DialogFragment() { + interface Callback { + fun onConfirmed(tag: String?) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val args = requireArguments() + return AlertDialog.Builder(requireContext()) + .setMessage(args.getInt("message")) + .setPositiveButton(args.getInt("buttontext")) { _, _ -> + val callback = parentFragment as Callback? ?: throw IllegalArgumentException() + callback.onConfirmed(args.getString("tag")) + } + .setNegativeButton(android.R.string.cancel, null) + .create() + } + + companion object { + fun show(fm: FragmentManager, messageResId: Int, actionButtonTextResId: Int, tag: String) { + val f = ConfirmationDialogFragment() + f.arguments = bundleOf( + "message" to messageResId, + "buttontext" to actionButtonTextResId, + "tag" to tag + ) + f.show(fm, tag) + } + } + } + + class ServerEditorConfirmLeaveDialogFragment : DialogFragment() { + interface Callback { + fun onLeaveAndSave() + fun onLeaveAndDiscard() + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return AlertDialog.Builder(requireContext()) + .setTitle(R.string.settings_server_confirm_leave_title) + .setMessage(R.string.settings_server_confirm_leave_message) + .setPositiveButton(R.string.save) { _, _ -> handleDone(true) } + .setNegativeButton(R.string.discard) { _, _ -> handleDone(false) } + .setNeutralButton(android.R.string.cancel, null) + .create() + } + + private fun handleDone(confirmed: Boolean) { + val callback = parentFragment as Callback? ?: throw IllegalArgumentException() + if (confirmed) { + callback.onLeaveAndSave() + } else { + callback.onLeaveAndDiscard() + } + } + } + + class ServerEditorFragment : + AbstractSettingsFragment(), + ConfirmationDialogFragment.Callback, + ServerEditorConfirmLeaveDialogFragment.Callback { private lateinit var config: ServerConfiguration private var markAsPrimary = false @@ -612,18 +674,12 @@ class PreferencesActivity : AbstractBaseActivity() { true } R.id.delete -> { - AlertDialog.Builder(preferenceManager.context) - .setMessage(R.string.settings_server_confirm_deletion) - .setPositiveButton(R.string.delete) { _, _ -> - config.removeFromPrefs(prefs, secretPrefs) - WorkManager.getInstance(preferenceManager.context).apply { - cancelAllWorkByTag(buildWorkerTagForServer(config.id)) - pruneWork() - } - parentFragmentManager.popBackStack() // close ourself - } - .setNegativeButton(android.R.string.cancel, null) - .show() + ConfirmationDialogFragment.show( + childFragmentManager, + R.string.settings_server_confirm_deletion, + R.string.delete, + "delete_server_confirmation" + ) true } else -> super.onOptionsItemSelected(item) @@ -647,23 +703,33 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onBackPressed(): Boolean { if (ServerConfiguration.load(prefs, secretPrefs, config.id) != config) { - AlertDialog.Builder(preferenceManager.context) - .setTitle(R.string.settings_server_confirm_leave_title) - .setMessage(R.string.settings_server_confirm_leave_message) - .setPositiveButton(R.string.save) { _, _ -> - saveAndQuit() - } - .setNegativeButton(R.string.discard) { _, _ -> - parentActivity.invalidateOptionsMenu() - parentFragmentManager.popBackStack() // close ourself - } - .setNeutralButton(android.R.string.cancel, null) - .show() + ServerEditorConfirmLeaveDialogFragment().show(childFragmentManager, "dialog_confirm_leave") return true } return false } + override fun onConfirmed(tag: String?) = when (tag) { + "delete_server_confirmation" -> { + config.removeFromPrefs(prefs, secretPrefs) + WorkManager.getInstance(preferenceManager.context).apply { + cancelAllWorkByTag(buildWorkerTagForServer(config.id)) + pruneWork() + } + parentFragmentManager.popBackStack() // close ourself + } + else -> {} + } + + override fun onLeaveAndSave() { + saveAndQuit() + } + + override fun onLeaveAndDiscard() { + parentActivity.invalidateOptionsMenu() + parentFragmentManager.popBackStack() // close ourself + } + override fun onStart() { super.onStart() updateConnectionSummary("local", config.localPath) From 47688546a0b2ad78ae5f15b063df0cacf4124889 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 6 Sep 2020 17:21:41 +0200 Subject: [PATCH 36/44] Fix link for multi server docs Signed-off-by: mueller-ma --- mobile/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 07c0f56fbb..683a2f721f 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -111,7 +111,7 @@ Primary server This server is the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. "%s" is the primary server. Click to make this server the primary one.\nCurrently only the primary server is supported by all features of this app. Click on the help button for more information. - https://www.openhab.org/docs/apps/android.html + https://www.openhab.org/docs/apps/android.html#multi-server-support openHAB %d Click here for more information From 3f337cc7808bb5a6b6deacdbaf87b2d79c331ec0 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 6 Sep 2020 17:23:12 +0200 Subject: [PATCH 37/44] Code style Signed-off-by: mueller-ma --- .../core/connection/ConnectionFactory.kt | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt index 0fb0d5fe23..0b10b87751 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/connection/ConnectionFactory.kt @@ -21,6 +21,16 @@ import android.security.KeyChainException import android.util.Log import androidx.annotation.VisibleForTesting import de.duenndns.ssl.MemorizingTrustManager +import java.net.Socket +import java.security.Principal +import java.security.PrivateKey +import java.security.cert.X509Certificate +import java.util.HashSet +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.KeyManager +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManager +import javax.net.ssl.X509KeyManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -44,16 +54,6 @@ import org.openhab.habdroid.util.getSecretPrefs import org.openhab.habdroid.util.getStringOrNull import org.openhab.habdroid.util.isDebugModeEnabled import org.openhab.habdroid.util.isDemoModeEnabled -import java.net.Socket -import java.security.Principal -import java.security.PrivateKey -import java.security.cert.X509Certificate -import java.util.HashSet -import javax.net.ssl.HttpsURLConnection -import javax.net.ssl.KeyManager -import javax.net.ssl.SSLContext -import javax.net.ssl.TrustManager -import javax.net.ssl.X509KeyManager /** * A factory class, which is the main entry point to get a Connection to a specific openHAB @@ -161,8 +161,10 @@ class ConnectionFactory internal constructor( // changed since we went to background val (_, active, _, _) = stateChannel.value val local = active?.connection === activeConn?.local || - (active?.failureReason is NoUrlInformationException && - active.failureReason.wouldHaveUsedLocalConnection()) + ( + active?.failureReason is NoUrlInformationException && + active.failureReason.wouldHaveUsedLocalConnection() + ) if (local) { triggerConnectionUpdateIfNeeded() } From 0255beebaed8912aa58aa6a11ff6a7405677fd0f Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sun, 6 Sep 2020 17:25:36 +0200 Subject: [PATCH 38/44] Only show primary server icon if more than one server is configured Signed-off-by: mueller-ma --- .../main/java/org/openhab/habdroid/ui/PreferencesActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 917bbcba2c..66e64f8a96 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -53,7 +53,6 @@ import androidx.preference.SwitchPreferenceCompat import androidx.preference.forEachIndexed import androidx.work.WorkManager import com.jaredrummler.android.colorpicker.ColorPreferenceCompat -import java.lang.IllegalArgumentException import java.util.BitSet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -462,7 +461,7 @@ class PreferencesActivity : AbstractBaseActivity() { parentActivity.openSubScreen(ServerEditorFragment.newInstance(config)) true } - pref.icon = if (serverId == prefs.getPrimaryServerId()) { + pref.icon = if (serverId == prefs.getPrimaryServerId() && prefs.getConfiguredServerIds().size > 1) { ContextCompat.getDrawable(pref.context, R.drawable.ic_star_border_grey_24dp) } else { null From 1ae598cce14ee5b4ae8d39016d23a2dea2b339e9 Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Thu, 17 Sep 2020 18:10:53 +0200 Subject: [PATCH 39/44] Fix rebase mistake Signed-off-by: mueller-ma --- .../main/java/org/openhab/habdroid/core/NotificationHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt b/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt index 756773df47..218d264266 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt @@ -124,7 +124,7 @@ class NotificationHelper constructor(private val context: Context) { if (message.icon != null) { val connection = ConnectionFactory.primaryCloudConnection?.connection - if (connection != null && !context.determineDataUsagePolicy().canDoLargeTransfers()) { + if (connection != null && !context.determineDataUsagePolicy().canDoLargeTransfers) { try { val targetSize = context.resources.getDimensionPixelSize(R.dimen.notificationlist_icon_size) iconBitmap = connection.httpClient From 2c84823fb7ebe9155f11a2d0f09bc2fd1207cf2f Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Thu, 17 Sep 2020 18:12:36 +0200 Subject: [PATCH 40/44] Set multiserver version in UpdateBroadcastReceiver Signed-off-by: mueller-ma --- mobile/build.gradle | 2 +- .../java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/build.gradle b/mobile/build.gradle index cae291dd69..4aa2d03487 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -47,7 +47,7 @@ android { applicationId "org.openhab.habdroid" minSdkVersion 21 targetSdkVersion 29 - versionCode 326 + versionCode 330 versionName "2.15.3-beta" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt index 715ad388d4..407c5a38cf 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/UpdateBroadcastReceiver.kt @@ -192,7 +192,7 @@ class UpdateBroadcastReceiver : BroadcastReceiver() { private const val SECURE_CREDENTIALS = 190 private const val DARK_MODE = 200 private const val WIDGET_ICON = 250 - private const val MULTI_SERVER_SUPPORT = 274 + private const val MULTI_SERVER_SUPPORT = 330 fun updateComparableVersion(editor: SharedPreferences.Editor) { editor.putInt(PrefKeys.COMPARABLE_VERSION, BuildConfig.VERSION_CODE).apply() From b3673be1de1f4d7a88442edf22a5a61cf1f14b08 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Fri, 18 Sep 2020 09:03:08 +0200 Subject: [PATCH 41/44] Grammar fixes in docs. Signed-off-by: Danny Baumann --- docs/USAGE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/USAGE.md b/docs/USAGE.md index ac7d376588..e8adeb7fc4 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -284,14 +284,14 @@ When adding multiple servers to the app, there's always a primary and an active The active server is used for foreground operations, e.g. display the Sitemaps, and can be changed in the side menu. The primary server is used for all background operations and can be changed in the settings. -Features, that support multiple servers: +Features that support multiple servers: * Display Sitemaps and HABPanel * Voice commands launched from in-app (sent to active server) and from widgets (sent to primary server) * Show a list of recent notifications * Sitemap shortcuts on the home screen * Shortcuts for HABPanel, notifications and voice command -Features, that don't support multiple servers, i.e. use the primary server: +Features that don't support multiple servers, i.e. use the primary server: * Item widgets on the home screen * Quick tiles * NFC tags From a941297120f7415013d80dda2230a0f71eb0d471 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Fri, 18 Sep 2020 09:18:41 +0200 Subject: [PATCH 42/44] Cleanup Signed-off-by: Danny Baumann --- .../ui/CloudNotificationListFragment.kt | 2 +- .../org/openhab/habdroid/ui/MainActivity.kt | 54 +++++++------------ 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt index 8aa1c83f52..c4b290d34c 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/CloudNotificationListFragment.kt @@ -182,7 +182,7 @@ class CloudNotificationListFragment : Fragment(), View.OnClickListener, SwipeRef retryButton.isVisible = loadError } - private fun usePrimaryServer() = arguments?.getBoolean("primary") == true + private fun usePrimaryServer() = requireArguments().getBoolean("primary") fun getTitle(context: Context): String { val prefs = context.getPrefs() diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt index 98e4703e39..1c31bd0406 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/MainActivity.kt @@ -917,42 +917,10 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { true } action is PendingAction.OpenSitemapUrl && isStarted && serverProperties != null -> { - when { - action.serverId !in prefs.getConfiguredServerIds() -> { - showToast(R.string.home_shortcut_server_has_been_deleted, ToastType.ERROR) - true - } - action.serverId != prefs.getActiveServerId() -> { - prefs.edit { - putActiveServerId(prefs.getPrimaryServerId()) - } - updateDrawerServerEntries() - false - } - else -> { - buildUrlAndOpenSitemap(action.url) - true - } - } + executeActionForServer(action.serverId) { buildUrlAndOpenSitemap(action.url) } } action is PendingAction.OpenHabPanel && isStarted && serverProperties?.hasHabPanelInstalled() == true -> { - when { - action.serverId !in prefs.getConfiguredServerIds() -> { - showToast(R.string.home_shortcut_server_has_been_deleted, ToastType.ERROR) - true - } - action.serverId != prefs.getActiveServerId() -> { - prefs.edit { - putActiveServerId(action.serverId) - } - updateDrawerServerEntries() - false - } - else -> { - openHabPanel() - true - } - } + executeActionForServer(action.serverId) { openHabPanel() } } action is PendingAction.LaunchVoiceRecognition && serverProperties != null -> { launchVoiceRecognition() @@ -974,6 +942,24 @@ class MainActivity : AbstractBaseActivity(), ConnectionFactory.UpdateListener { else -> false } + private fun executeActionForServer(serverId: Int, action: () -> Unit): Boolean = when { + serverId !in prefs.getConfiguredServerIds() -> { + showToast(R.string.home_shortcut_server_has_been_deleted, ToastType.ERROR) + true + } + serverId != prefs.getActiveServerId() -> { + prefs.edit { + putActiveServerId(serverId) + } + updateDrawerServerEntries() + false + } + else -> { + action() + true + } + } + private fun selectConfiguredSitemapFromList(): Sitemap? { val configuredSitemap = prefs.getDefaultSitemap(connection)?.name.orEmpty() val sitemaps = serverProperties?.sitemaps From 158c247a1a958e5148853ffa7160d0e813f7db08 Mon Sep 17 00:00:00 2001 From: Danny Baumann Date: Fri, 18 Sep 2020 09:32:00 +0200 Subject: [PATCH 43/44] Pop back stack also if last fragment isn't an AbstractSettingsFragment Signed-off-by: Danny Baumann --- .../main/java/org/openhab/habdroid/ui/PreferencesActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt index 66e64f8a96..c5b782b973 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt +++ b/mobile/src/main/java/org/openhab/habdroid/ui/PreferencesActivity.kt @@ -152,7 +152,7 @@ class PreferencesActivity : AbstractBaseActivity() { override fun onBackPressed() { with(supportFragmentManager) { if (backStackEntryCount > 0) { - if ((fragments.last() as? AbstractSettingsFragment)?.onBackPressed() == false) { + if ((fragments.last() as? AbstractSettingsFragment)?.onBackPressed() != true) { popBackStack() } } else { @@ -202,6 +202,7 @@ class PreferencesActivity : AbstractBaseActivity() { } } + // Returns true if back key press was consumed open fun onBackPressed() = false companion object { From b8def8404f9430fe00b8b11dc04dd2478eff341f Mon Sep 17 00:00:00 2001 From: mueller-ma Date: Sat, 19 Sep 2020 12:16:52 +0200 Subject: [PATCH 44/44] Fix datasaver check Signed-off-by: mueller-ma --- .../main/java/org/openhab/habdroid/core/NotificationHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt b/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt index 218d264266..8418d711a3 100644 --- a/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt +++ b/mobile/src/main/java/org/openhab/habdroid/core/NotificationHelper.kt @@ -124,7 +124,7 @@ class NotificationHelper constructor(private val context: Context) { if (message.icon != null) { val connection = ConnectionFactory.primaryCloudConnection?.connection - if (connection != null && !context.determineDataUsagePolicy().canDoLargeTransfers) { + if (connection != null && context.determineDataUsagePolicy().canDoLargeTransfers) { try { val targetSize = context.resources.getDimensionPixelSize(R.dimen.notificationlist_icon_size) iconBitmap = connection.httpClient