Skip to content

Commit 3306e30

Browse files
committed
Add BlockInsecureScreen
1 parent 4b280e1 commit 3306e30

File tree

5 files changed

+623
-16
lines changed

5 files changed

+623
-16
lines changed

app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import io.homeassistant.companion.android.common.util.toJsonObject
104104
import io.homeassistant.companion.android.common.util.toJsonObjectOrNull
105105
import io.homeassistant.companion.android.database.authentication.Authentication
106106
import io.homeassistant.companion.android.database.authentication.AuthenticationDao
107+
import io.homeassistant.companion.android.database.server.SecurityStatus
107108
import io.homeassistant.companion.android.databinding.DialogAuthenticationBinding
108109
import io.homeassistant.companion.android.improv.ui.ImprovPermissionDialog
109110
import io.homeassistant.companion.android.improv.ui.ImprovSetupDialog
@@ -131,6 +132,7 @@ import io.homeassistant.companion.android.webview.externalbus.ExternalConfigResp
131132
import io.homeassistant.companion.android.webview.externalbus.ExternalEntityAddToAction
132133
import io.homeassistant.companion.android.webview.externalbus.NavigateTo
133134
import io.homeassistant.companion.android.webview.externalbus.ShowSidebar
135+
import io.homeassistant.companion.android.webview.insecure.BlockInsecureFragment
134136
import javax.inject.Inject
135137
import kotlinx.coroutines.CoroutineScope
136138
import kotlinx.coroutines.Dispatchers
@@ -272,6 +274,11 @@ class WebViewActivity :
272274

273275
private val snackbarHostState = SnackbarHostState()
274276

277+
/**
278+
* Indicates if the current state is insecure. If so we should not load the URL.
279+
*/
280+
private var isInsecureState: Boolean = false
281+
275282
@SuppressLint("SetJavaScriptEnabled")
276283
override fun onCreate(savedInstanceState: Bundle?) {
277284
if (
@@ -1392,8 +1399,7 @@ class WebViewActivity :
13921399
clearHistory = !keepHistory
13931400
lifecycleScope.launch {
13941401
if (!presenter.shouldSetSecurityLevel()) {
1395-
webView.loadUrl(url)
1396-
waitForConnection()
1402+
secureLoadUrl(url)
13971403
} else {
13981404
val serverId = presenter.getActiveServer()
13991405
Timber.d("Security level not set for server $serverId, showing ConnectionSecurityLevelFragment")
@@ -1410,6 +1416,46 @@ class WebViewActivity :
14101416
}
14111417
}
14121418

1419+
/**
1420+
* Make sure we only load the url if the securityLevel and current states allow it.
1421+
*/
1422+
private suspend fun secureLoadUrl(url: String) {
1423+
fun loadAndWait() {
1424+
webView.loadUrl(url)
1425+
isInsecureState = false
1426+
waitForConnection()
1427+
}
1428+
1429+
if (url.startsWith("https")) {
1430+
loadAndWait()
1431+
return
1432+
}
1433+
1434+
val allowInsecureConnection = serverManager.integrationRepository(
1435+
presenter.getActiveServer(),
1436+
).getAllowInsecureConnection()
1437+
1438+
when (allowInsecureConnection) {
1439+
null, true -> loadAndWait()
1440+
false -> {
1441+
val status = serverManager.getServer(
1442+
presenter.getActiveServer(),
1443+
)?.connection?.currentSecurityStatusForUrl(this, url)
1444+
when (status) {
1445+
is SecurityStatus.Insecure, null -> {
1446+
isInsecureState = true
1447+
showBlockInsecureFragment(
1448+
serverId = presenter.getActiveServer(),
1449+
missingHomeSetup = status?.missingHomeSetup ?: false,
1450+
missingLocation = status?.missingLocation ?: false,
1451+
)
1452+
}
1453+
SecurityStatus.Secure -> loadAndWait()
1454+
}
1455+
}
1456+
}
1457+
}
1458+
14131459
private fun showConnectionSecurityLevelFragment(serverId: Int) {
14141460
supportFragmentManager.setFragmentResultListener(
14151461
ConnectionSecurityLevelFragment.RESULT_KEY,
@@ -1419,8 +1465,9 @@ class WebViewActivity :
14191465
supportFragmentManager.clearFragmentResultListener(ConnectionSecurityLevelFragment.RESULT_KEY)
14201466

14211467
if (::loadedUrl.isInitialized) {
1422-
webView.loadUrl(loadedUrl)
1423-
waitForConnection()
1468+
lifecycleScope.launch {
1469+
secureLoadUrl(loadedUrl)
1470+
}
14241471
}
14251472
}
14261473

@@ -1430,6 +1477,33 @@ class WebViewActivity :
14301477
.commit()
14311478
}
14321479

1480+
private fun showBlockInsecureFragment(serverId: Int, missingHomeSetup: Boolean, missingLocation: Boolean) {
1481+
supportFragmentManager.setFragmentResultListener(
1482+
BlockInsecureFragment.RESULT_KEY,
1483+
this,
1484+
) { _, _ ->
1485+
Timber.d("Block insecure screen exited by user, retrying URL loading")
1486+
supportFragmentManager.clearFragmentResultListener(BlockInsecureFragment.RESULT_KEY)
1487+
1488+
if (::loadedUrl.isInitialized) {
1489+
lifecycleScope.launch {
1490+
secureLoadUrl(loadedUrl)
1491+
}
1492+
}
1493+
}
1494+
supportFragmentManager.beginTransaction()
1495+
.replace(
1496+
android.R.id.content,
1497+
BlockInsecureFragment.newInstance(
1498+
serverId = serverId,
1499+
missingHomeSetup = missingHomeSetup,
1500+
missingLocation = missingLocation,
1501+
),
1502+
)
1503+
.addToBackStack(null)
1504+
.commit()
1505+
}
1506+
14331507
override fun setStatusBarAndBackgroundColor(statusBarColor: Int, backgroundColor: Int) {
14341508
// Set background colors
14351509
if (statusBarColor != 0) {
@@ -1705,18 +1779,22 @@ class WebViewActivity :
17051779
}
17061780

17071781
private fun waitForConnection() {
1708-
Handler(Looper.getMainLooper()).postDelayed(
1709-
{
1710-
if (
1711-
!isConnected &&
1712-
!loadedUrl.toHttpUrl().pathSegments.first().contains("api") &&
1713-
!loadedUrl.toHttpUrl().pathSegments.first().contains("local")
1714-
) {
1715-
showError(errorType = ErrorType.TIMEOUT_EXTERNAL_BUS)
1716-
}
1717-
},
1718-
CONNECTION_DELAY,
1719-
)
1782+
if (!isInsecureState) {
1783+
Handler(Looper.getMainLooper()).postDelayed(
1784+
{
1785+
if (
1786+
!isConnected &&
1787+
!loadedUrl.toHttpUrl().pathSegments.first().contains("api") &&
1788+
!loadedUrl.toHttpUrl().pathSegments.first().contains("local")
1789+
) {
1790+
showError(errorType = ErrorType.TIMEOUT_EXTERNAL_BUS)
1791+
}
1792+
},
1793+
CONNECTION_DELAY,
1794+
)
1795+
} else {
1796+
Timber.i("Not waiting for connection since currently being block because insecure")
1797+
}
17201798
}
17211799

17221800
override fun sendExternalBusMessage(message: ExternalBusMessage) {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package io.homeassistant.companion.android.webview.insecure
2+
3+
import android.content.Intent
4+
import android.os.Bundle
5+
import android.provider.Settings
6+
import android.view.LayoutInflater
7+
import android.view.View
8+
import android.view.ViewGroup
9+
import androidx.compose.ui.platform.ComposeView
10+
import androidx.compose.ui.platform.LocalUriHandler
11+
import androidx.fragment.app.Fragment
12+
import androidx.fragment.app.setFragmentResult
13+
import dagger.hilt.android.AndroidEntryPoint
14+
import io.homeassistant.companion.android.common.compose.theme.HATheme
15+
import io.homeassistant.companion.android.common.util.DisabledLocationHandler
16+
import io.homeassistant.companion.android.settings.ConnectionSecurityLevelFragment
17+
import io.homeassistant.companion.android.settings.SettingsActivity
18+
19+
/**
20+
* Fragment explaining why the current connection is blocked.
21+
*/
22+
@AndroidEntryPoint
23+
class BlockInsecureFragment private constructor() : Fragment() {
24+
25+
companion object {
26+
const val RESULT_KEY = "block_insecure_result"
27+
private const val EXTRA_SERVER = "server_id"
28+
private const val EXTRA_MISSING_HOME_SETUP = "missing_home_setup"
29+
private const val EXTRA_MISSING_LOCATION = "missing_location"
30+
31+
fun newInstance(serverId: Int, missingHomeSetup: Boolean, missingLocation: Boolean): BlockInsecureFragment {
32+
return BlockInsecureFragment().apply {
33+
arguments = Bundle().apply {
34+
putInt(EXTRA_SERVER, serverId)
35+
putBoolean(EXTRA_MISSING_HOME_SETUP, missingHomeSetup)
36+
putBoolean(EXTRA_MISSING_LOCATION, missingLocation)
37+
}
38+
}
39+
}
40+
}
41+
42+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
43+
val serverId = arguments?.getInt(EXTRA_SERVER, -1) ?: -1
44+
val missingHomeSetup = arguments?.getBoolean(EXTRA_MISSING_HOME_SETUP, false) ?: false
45+
val missingLocation = arguments?.getBoolean(EXTRA_MISSING_LOCATION, false) ?: false
46+
47+
return ComposeView(requireContext()).apply {
48+
setContent {
49+
val uriHandler = LocalUriHandler.current
50+
HATheme {
51+
BlockInsecureScreen(
52+
missingHomeSetup = missingHomeSetup,
53+
missingLocation = missingLocation,
54+
onRetry = ::retryAndClose,
55+
onHelpClick = {
56+
uriHandler.openUri(
57+
"https://companion.home-assistant.io/docs/getting_started/connection-security-level/",
58+
)
59+
},
60+
onOpenSettings = {
61+
startActivity(SettingsActivity.newInstance(requireContext()))
62+
},
63+
onChangeSecurityLevel = {
64+
showConnectionSecurityLevelFragment(serverId)
65+
},
66+
onOpenLocationSettings = ::openLocationSettings,
67+
onConfigureHomeNetwork = {
68+
startActivity(
69+
SettingsActivity.newInstance(
70+
context = requireContext(),
71+
screen = SettingsActivity.Deeplink.HomeNetwork(serverId),
72+
),
73+
)
74+
},
75+
)
76+
}
77+
}
78+
}
79+
}
80+
81+
private fun openLocationSettings() {
82+
if (DisabledLocationHandler.isLocationEnabled(requireContext())) {
83+
retryAndClose()
84+
return
85+
}
86+
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).apply {
87+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
88+
addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
89+
addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
90+
}
91+
if (intent.resolveActivity(requireContext().packageManager) == null) {
92+
intent.action = Settings.ACTION_SETTINGS
93+
}
94+
startActivity(intent)
95+
}
96+
97+
private fun retryAndClose() {
98+
setFragmentResult(RESULT_KEY, Bundle())
99+
parentFragmentManager.popBackStack()
100+
}
101+
102+
private fun showConnectionSecurityLevelFragment(serverId: Int) {
103+
val fragment = ConnectionSecurityLevelFragment.newInstance(
104+
serverId = serverId,
105+
handleAllInsets = true,
106+
)
107+
parentFragmentManager.beginTransaction()
108+
.replace(android.R.id.content, fragment)
109+
.addToBackStack(null)
110+
.commit()
111+
}
112+
}

0 commit comments

Comments
 (0)