From 823fecdb7ce2b96d7d71e0699d005d44bd461863 Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski <janne@kuschku.de> Date: Wed, 26 May 2021 18:26:19 +0200 Subject: [PATCH] Implement deceptive network warning dialog --- .../quasseldroid/ui/chat/ChatActivity.kt | 96 +++++++++++-------- .../DeceptiveNetworkDialog.kt | 91 ++++++++++++++++++ .../res/layout/dialog_deceptive_network.xml | 45 +++++++++ .../raw/untrustworthy_network_freenode.html | 21 ++++ app/src/main/res/values/strings.xml | 2 + .../util/safety/DeceptiveNetworkManager.kt | 43 +++++++++ .../viewmodel/helper/ChatViewModelHelper.kt | 8 ++ .../res/values/untrustworthy_networks.xml | 24 +++++ 8 files changed, 289 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/deceptive_networks/DeceptiveNetworkDialog.kt create mode 100644 app/src/main/res/layout/dialog_deceptive_network.xml create mode 100644 app/src/main/res/raw/untrustworthy_network_freenode.html create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/util/safety/DeceptiveNetworkManager.kt create mode 100644 viewmodel/src/main/res/values/untrustworthy_networks.xml diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt index 2e117103f..ad058aedc 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt @@ -94,6 +94,7 @@ import de.kuschku.quasseldroid.util.ui.DragInterceptBottomSheetBehavior import de.kuschku.quasseldroid.util.ui.drawable.DrawerToggleActivityDrawable import de.kuschku.quasseldroid.util.ui.drawable.NickCountDrawable import de.kuschku.quasseldroid.util.ui.view.WarningBarView +import de.kuschku.quasseldroid.util.deceptive_networks.DeceptiveNetworkDialog import de.kuschku.quasseldroid.viewmodel.ChatViewModel import de.kuschku.quasseldroid.viewmodel.data.BufferData import de.kuschku.quasseldroid.viewmodel.helper.ChatViewModelHelper @@ -765,54 +766,67 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc }) binding.layoutMain.connectionStatus.setOnClickListener { - modelHelper.sessionManager.value?.orNull()?.apply { - log(INFO, "ChatActivity", "Reconnect triggered: User action") - backend.safeValue.orNull()?.autoConnect(ignoreErrors = true, ignoreSetting = true) + if (modelHelper.connectionProgress.value?.first == ConnectionState.CONNECTED + && modelHelper.deceptiveNetwork.value == true) { + DeceptiveNetworkDialog.Builder(this) + .message(R.raw.untrustworthy_network_freenode) + .show() + } else { + modelHelper.sessionManager.value?.orNull()?.apply { + log(INFO, "ChatActivity", "Reconnect triggered: User action") + backend.safeValue.orNull()?.autoConnect(ignoreErrors = true, ignoreSetting = true) + } } } // Show Connection Progress Bar - modelHelper.connectionProgress.toLiveData().observe(this, Observer { - val (state, progress, max) = it ?: Triple(ConnectionState.DISCONNECTED, 0, 0) - when (state) { - ConnectionState.DISCONNECTED, - ConnectionState.CLOSED -> { - binding.layoutMain.layoutToolbar.progressBar.visibility = View.INVISIBLE - - binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_ICON) - binding.layoutMain.connectionStatus.setText(getString(R.string.label_status_disconnected)) - } - ConnectionState.CONNECTING -> { - binding.layoutMain.layoutToolbar.progressBar.visibility = View.VISIBLE - binding.layoutMain.layoutToolbar.progressBar.isIndeterminate = true - - binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_PROGRESS) - binding.layoutMain.connectionStatus.setText(getString(R.string.label_status_connecting)) - } - ConnectionState.HANDSHAKE -> { - binding.layoutMain.layoutToolbar.progressBar.visibility = View.VISIBLE - binding.layoutMain.layoutToolbar.progressBar.isIndeterminate = true + combineLatest(modelHelper.connectionProgress, modelHelper.deceptiveNetwork) + .toLiveData().observe(this, Observer { + val (connection, deceptive) = it ?: Pair(Triple(ConnectionState.DISCONNECTED, 0, 0), false) + val (state, progress, max) = connection + when (state) { + ConnectionState.DISCONNECTED, + ConnectionState.CLOSED -> { + binding.layoutMain.layoutToolbar.progressBar.visibility = View.INVISIBLE + + binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_ICON) + binding.layoutMain.connectionStatus.setText(getString(R.string.label_status_disconnected)) + } + ConnectionState.CONNECTING -> { + binding.layoutMain.layoutToolbar.progressBar.visibility = View.VISIBLE + binding.layoutMain.layoutToolbar.progressBar.isIndeterminate = true - binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_PROGRESS) - binding.layoutMain.connectionStatus.setText(getString(R.string.label_status_handshake)) - } - ConnectionState.INIT -> { - binding.layoutMain.layoutToolbar.progressBar.visibility = View.VISIBLE - // Show indeterminate when no progress has been made yet - binding.layoutMain.layoutToolbar.progressBar.isIndeterminate = progress == 0 || max == 0 - binding.layoutMain.layoutToolbar.progressBar.progress = progress - binding.layoutMain.layoutToolbar.progressBar.max = max - - binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_PROGRESS) - binding.layoutMain.connectionStatus.setText(getString(R.string.label_status_init)) - } - ConnectionState.CONNECTED -> { - binding.layoutMain.layoutToolbar.progressBar.visibility = View.INVISIBLE + binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_PROGRESS) + binding.layoutMain.connectionStatus.setText(getString(R.string.label_status_connecting)) + } + ConnectionState.HANDSHAKE -> { + binding.layoutMain.layoutToolbar.progressBar.visibility = View.VISIBLE + binding.layoutMain.layoutToolbar.progressBar.isIndeterminate = true - binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_NONE) + binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_PROGRESS) + binding.layoutMain.connectionStatus.setText(getString(R.string.label_status_handshake)) + } + ConnectionState.INIT -> { + binding.layoutMain.layoutToolbar.progressBar.visibility = View.VISIBLE + // Show indeterminate when no progress has been made yet + binding.layoutMain.layoutToolbar.progressBar.isIndeterminate = progress == 0 || max == 0 + binding.layoutMain.layoutToolbar.progressBar.progress = progress + binding.layoutMain.layoutToolbar.progressBar.max = max + + binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_PROGRESS) + binding.layoutMain.connectionStatus.setText(getString(R.string.label_status_init)) + } + ConnectionState.CONNECTED -> { + binding.layoutMain.layoutToolbar.progressBar.visibility = View.INVISIBLE + if (deceptive) { + binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_ICON) + binding.layoutMain.connectionStatus.setText(R.string.deceptive_network) + } else { + binding.layoutMain.connectionStatus.setMode(WarningBarView.MODE_NONE) + } + } } - } - }) + }) // Only show nick list when we’re in a channel bufferId modelHelper.bufferDataThrottled.toLiveData().observe(this, Observer { diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/deceptive_networks/DeceptiveNetworkDialog.kt b/app/src/main/java/de/kuschku/quasseldroid/util/deceptive_networks/DeceptiveNetworkDialog.kt new file mode 100644 index 000000000..2acf8d935 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/util/deceptive_networks/DeceptiveNetworkDialog.kt @@ -0,0 +1,91 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2020 Janne Mareike Koschinski + * Copyright (c) 2020 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid.util.deceptive_networks + +import android.annotation.SuppressLint +import android.app.Dialog +import android.os.Bundle +import android.text.Html +import android.widget.TextView +import androidx.annotation.RawRes +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import butterknife.BindView +import butterknife.ButterKnife +import com.afollestad.materialdialogs.MaterialDialog +import de.kuschku.quasseldroid.R + +class DeceptiveNetworkDialog : DialogFragment() { + private var builder: Builder? = null + + @BindView(R.id.message) + lateinit var message: TextView + + @SuppressLint("StringFormatInvalid") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = MaterialDialog.Builder(requireContext()) + .customView(R.layout.dialog_deceptive_network, true) + .title(R.string.deceptive_network) + .build() + ButterKnife.bind(this, dialog.customView!!) + builder?.message?.let { + message.text = Html.fromHtml( + resources.openRawResource(it).bufferedReader(Charsets.UTF_8).readText() + ) + } + return dialog + } + + fun show(context: FragmentActivity) = show(context.supportFragmentManager) + fun show(context: FragmentManager) { + dismissIfNecessary(context) + show(context, TAG) + } + + private fun dismissIfNecessary(fragmentManager: FragmentManager) { + fragmentManager.findFragmentByTag(tag)?.let { frag -> + (frag as? DialogFragment)?.dismiss() + fragmentManager.beginTransaction().remove(frag).commit() + } + } + + class Builder(private val fragmentManager: FragmentManager) { + constructor(context: FragmentActivity) : this(context.supportFragmentManager) + + @RawRes + var message: Int? = null + + fun message(@RawRes message: Int?): Builder { + this.message = message + return this + } + + fun build() = DeceptiveNetworkDialog().apply { + builder = this@Builder + } + + fun show() = build().show(fragmentManager) + } + + companion object { + const val TAG = "[DECEPTIVE_NETWORK]" + } +} diff --git a/app/src/main/res/layout/dialog_deceptive_network.xml b/app/src/main/res/layout/dialog_deceptive_network.xml new file mode 100644 index 000000000..b3328bed9 --- /dev/null +++ b/app/src/main/res/layout/dialog_deceptive_network.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Quasseldroid - Quassel client for Android + + Copyright (c) 2020 Janne Mareike Koschinski + Copyright (c) 2020 The Quassel Project + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License version 3 as published + by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:descendantFocusability="blocksDescendants" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingBottom="8dp"> + + <TextView + android:id="@+id/message" + style="@style/Widget.RtlConformTextView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="?listPreferredItemPaddingLeft" + android:paddingTop="8dp" + android:paddingEnd="?listPreferredItemPaddingRight" + android:paddingBottom="8dp" + android:textColor="?colorTextPrimary" + android:textSize="16sp" /> + </LinearLayout> +</androidx.core.widget.NestedScrollView> diff --git a/app/src/main/res/raw/untrustworthy_network_freenode.html b/app/src/main/res/raw/untrustworthy_network_freenode.html new file mode 100644 index 000000000..a565c563b --- /dev/null +++ b/app/src/main/res/raw/untrustworthy_network_freenode.html @@ -0,0 +1,21 @@ +<!-- + Quasseldroid - Quassel client for Android + + Copyright (c) 2021 Janne Mareike Koschinski + Copyright (c) 2021 The Quassel Project + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License version 3 as published + by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see <http://www.gnu.org/licenses/>. + --> +<p>The staff of this network has taken over several official project channels against the wishes of the channel owners.<br/> +Those projects have mostly moved to other networks such as libera.chat.</p> +<p>Before joining or chatting in any project channel make sure to verify that the channel you are in hasn’t moved or been taken over.</p> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cbde82b7e..e1fae9264 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -187,6 +187,8 @@ <string name="notification_channel_highlight_title">Highlights</string> <string name="notification_channel_old_highlight_title">Old Highlights</string> + <string name="deceptive_network">Deceptive Network detected</string> + <string name="label_missing_features">Missing Features</string> <string name="info_missing_features" tools:ignore="StringFormatCount">Your core is missing features that are required for Quasseldroid to work correctly. You should <a href="https://quassel-irc.org>upgrade</a> your Quassel core to %1$s or newer.</string> diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/safety/DeceptiveNetworkManager.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/safety/DeceptiveNetworkManager.kt new file mode 100644 index 000000000..7e86ed9a7 --- /dev/null +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/safety/DeceptiveNetworkManager.kt @@ -0,0 +1,43 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2021 Janne Mareike Koschinski + * Copyright (c) 2021 The Quassel Project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.kuschku.quasseldroid.util.safety + +import android.content.Context +import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork +import de.kuschku.libquassel.util.ExpressionMatch +import de.kuschku.quasseldroid.viewmodel.R +import javax.inject.Inject + +class DeceptiveNetworkManager @Inject constructor(context: Context) { + private val untrustworthyNetworks: List<String> = + context.resources.getStringArray(R.array.deceptive_networks).toList() + + private val matcher = ExpressionMatch( + untrustworthyNetworks.joinToString("\n"), + ExpressionMatch.MatchMode.MatchMultiWildcard, + false + ) + + fun isDeceptive(info: INetwork.NetworkInfo): Boolean { + return info.serverList.any { + matcher.match(it.host ?: return false, false) + } + } +} diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt index 1458f0e23..d9120a21a 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt @@ -25,9 +25,11 @@ import de.kuschku.libquassel.quassel.BufferInfo import de.kuschku.libquassel.quassel.syncables.BufferViewConfig import de.kuschku.libquassel.quassel.syncables.IrcChannel import de.kuschku.libquassel.quassel.syncables.IrcUser +import de.kuschku.libquassel.quassel.syncables.Network import de.kuschku.libquassel.util.Optional import de.kuschku.libquassel.util.flag.hasFlag import de.kuschku.libquassel.util.helper.* +import de.kuschku.quasseldroid.util.safety.DeceptiveNetworkManager import de.kuschku.quasseldroid.viewmodel.ChatViewModel import de.kuschku.quasseldroid.viewmodel.QuasselViewModel import de.kuschku.quasseldroid.viewmodel.data.BufferData @@ -38,6 +40,7 @@ import javax.inject.Inject open class ChatViewModelHelper @Inject constructor( val chat: ChatViewModel, + val deceptiveNetworkManager: DeceptiveNetworkManager, quassel: QuasselViewModel ) : QuasselViewModelHelper(quassel) { val bufferViewConfig = bufferViewManager.flatMapSwitchMap { manager -> @@ -172,6 +175,11 @@ open class ChatViewModelHelper @Inject constructor( val selectedBuffer = processSelectedBuffer(bufferViewConfig, chat.selectedBufferId) + val deceptiveNetwork = + network.mapSwitchMap(Network::liveNetworkInfo) + .mapMap(deceptiveNetworkManager::isDeceptive) + .mapOrElse(false) + fun processChatBufferList( filtered: Observable<Pair<Map<BufferId, Int>, Int>> ) = filterBufferList( diff --git a/viewmodel/src/main/res/values/untrustworthy_networks.xml b/viewmodel/src/main/res/values/untrustworthy_networks.xml new file mode 100644 index 000000000..d31a6f194 --- /dev/null +++ b/viewmodel/src/main/res/values/untrustworthy_networks.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Quasseldroid - Quassel client for Android + + Copyright (c) 2021 Janne Mareike Koschinski + Copyright (c) 2021 The Quassel Project + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License version 3 as published + by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see <http://www.gnu.org/licenses/>. + --> +<resources> + <string-array name="deceptive_networks" translatable="false"> + <item>*.freenode.net</item> + </string-array> +</resources> -- GitLab