Multiserver app lock (#3376)

* WebView check app lock on server change

* Settings check app lock on server details

 - If the currently active server has a lock, show it
 - If the currently visible server has a lock, also show it
This commit is contained in:
Joris Pelgröm 2023-02-28 05:29:15 +01:00 committed by GitHub
parent c066e7c3cf
commit 0c4c32e512
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 98 additions and 48 deletions

View file

@ -24,6 +24,7 @@ import io.homeassistant.companion.android.common.data.servers.ServerManager
import io.homeassistant.companion.android.settings.notification.NotificationHistoryFragment
import io.homeassistant.companion.android.settings.qs.ManageTilesFragment
import io.homeassistant.companion.android.settings.sensor.SensorDetailFragment
import io.homeassistant.companion.android.settings.server.ServerSettingsFragment
import io.homeassistant.companion.android.settings.websocket.WebsocketSettingFragment
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
@ -113,48 +114,28 @@ class SettingsActivity : BaseActivity() {
override fun onUserLeaveHint() {
super.onUserLeaveHint()
runBlocking {
if (serverManager.isRegistered()) serverManager.integrationRepository().setAppActive(false)
}
setAppActive(false)
}
override fun onPause() {
super.onPause()
runBlocking {
if (serverManager.isRegistered()) serverManager.integrationRepository().setAppActive(false)
}
setAppActive(false)
}
override fun onResume() {
super.onResume()
val appLocked = runBlocking {
if (serverManager.isRegistered()) serverManager.integrationRepository().isAppLocked()
else false
}
blurView.setBlurEnabled(appLocked)
blurView.setBlurEnabled(isAppLocked())
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus && !isFinishing) {
val appLocked = runBlocking {
if (serverManager.isRegistered()) {
try {
serverManager.integrationRepository().isAppLocked()
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Cannot determine app locked state")
false
}
} else false
}
if (appLocked) {
if (isAppLocked()) {
authenticating = true
authenticator.authenticate(getString(commonR.string.biometric_title))
blurView.setBlurEnabled(true)
} else {
setAppActive(true)
blurView.setBlurEnabled(false)
}
}
@ -176,9 +157,7 @@ class SettingsActivity : BaseActivity() {
Authenticator.SUCCESS -> {
Log.d(TAG, "Authentication successful, unlocking app")
blurView.setBlurEnabled(false)
runBlocking {
if (serverManager.isRegistered()) serverManager.integrationRepository().setAppActive(true)
}
setAppActive(true)
}
Authenticator.CANCELED -> {
Log.d(TAG, "Authentication canceled by user, closing activity")
@ -189,6 +168,42 @@ class SettingsActivity : BaseActivity() {
}
}
/**
* @return `true` if the app is locked for the active server or the currently visible server
*/
private fun isAppLocked(): Boolean {
val serverFragment = supportFragmentManager.findFragmentByTag(ServerSettingsFragment.TAG)
val serverLocked = serverFragment?.let { isAppLocked((it as ServerSettingsFragment).getServerId()) } ?: false
return serverLocked || isAppLocked(ServerManager.SERVER_ID_ACTIVE)
}
fun isAppLocked(serverId: Int?): Boolean = runBlocking {
serverManager.getServer(serverId ?: ServerManager.SERVER_ID_ACTIVE)?.let {
try {
serverManager.integrationRepository(it.id).isAppLocked()
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Cannot determine app locked state")
false
}
} ?: false
}
/**
* Set the app active for the currently active server, and the currently visible server if
* different
*/
private fun setAppActive(active: Boolean) {
val serverFragment = supportFragmentManager.findFragmentByTag(ServerSettingsFragment.TAG)
serverFragment?.let { setAppActive((it as ServerSettingsFragment).getServerId(), active) }
setAppActive(ServerManager.SERVER_ID_ACTIVE, active)
}
fun setAppActive(serverId: Int?, active: Boolean) = runBlocking {
serverManager.getServer(serverId ?: ServerManager.SERVER_ID_ACTIVE)?.let {
serverManager.integrationRepository(it.id).setAppActive(active)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {

View file

@ -27,6 +27,7 @@ import androidx.preference.SwitchPreference
import com.google.android.material.snackbar.Snackbar
import io.homeassistant.companion.android.BuildConfig
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.authenticator.Authenticator
import io.homeassistant.companion.android.database.server.Server
import io.homeassistant.companion.android.nfc.NfcSetupActivity
import io.homeassistant.companion.android.onboarding.OnboardApp
@ -73,6 +74,7 @@ class SettingsFragment(
private val requestOnboardingResult = registerForActivityResult(OnboardApp(), this::onOnboardingComplete)
private var serverAuth: Int? = null
private val serverMutex = Mutex()
private var snackbar: Snackbar? = null
@ -366,13 +368,16 @@ class SettingsFragment(
Log.e(TAG, "Unable to set the server icon", e)
}
serverPreference.setOnPreferenceClickListener {
parentFragmentManager.commit {
replace(
R.id.content,
ServerSettingsFragment::class.java,
Bundle().apply { putInt(ServerSettingsFragment.EXTRA_SERVER, server.id) }
)
addToBackStack(getString(commonR.string.server_settings))
serverAuth = server.id
val settingsActivity = requireActivity() as SettingsActivity
val needsAuth = settingsActivity.isAppLocked(server.id)
if (!needsAuth) {
onServerLockResult(Authenticator.SUCCESS)
} else {
val canAuth = settingsActivity.requestAuthentication(getString(commonR.string.biometric_set_title), ::onServerLockResult)
if (!canAuth) {
onServerLockResult(Authenticator.SUCCESS)
}
}
return@setOnPreferenceClickListener true
}
@ -397,6 +402,22 @@ class SettingsFragment(
}
}
private fun onServerLockResult(result: Int): Boolean {
if (result == Authenticator.SUCCESS && serverAuth != null) {
(activity as? SettingsActivity)?.setAppActive(serverAuth, true)
parentFragmentManager.commit {
replace(
R.id.content,
ServerSettingsFragment::class.java,
Bundle().apply { putInt(ServerSettingsFragment.EXTRA_SERVER, serverAuth!!) },
ServerSettingsFragment.TAG
)
addToBackStack(getString(commonR.string.server_settings))
}
}
return true
}
private fun onOnboardingComplete(result: OnboardApp.Output?) {
lifecycleScope.launch {
presenter.addServer(result)

View file

@ -42,7 +42,7 @@ import io.homeassistant.companion.android.common.R as commonR
class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
companion object {
private const val TAG = "ServerSettingsFragment"
const val TAG = "ServerSettingsFragment"
const val EXTRA_SERVER = "server"
}
@ -305,7 +305,7 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
switchLock?.isChecked = success
// Prevent requesting authentication after just enabling the app lock
presenter.setAppActive()
presenter.setAppActive(true)
findPreference<SwitchPreference>("app_lock_home_bypass")?.isVisible = success
findPreference<EditTextPreference>("session_timeout")?.isVisible = success
@ -385,4 +385,6 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
presenter.onFinish()
super.onDestroy()
}
fun getServerId(): Int = serverId
}

View file

@ -14,5 +14,5 @@ interface ServerSettingsPresenter {
fun isSsidUsed(): Boolean
fun clearSsids()
fun setAppActive()
fun setAppActive(active: Boolean)
}

View file

@ -119,6 +119,9 @@ class ServerSettingsPresenterImpl @Inject constructor(
}
override fun onFinish() {
if (serverManager.getServer()?.id != serverId) {
setAppActive(false)
}
mainScope.cancel()
}
@ -174,7 +177,7 @@ class ServerSettingsPresenterImpl @Inject constructor(
}
}
override fun setAppActive() = runBlocking {
serverManager.integrationRepository(serverId).setAppActive(true)
override fun setAppActive(active: Boolean) = runBlocking {
serverManager.integrationRepository(serverId).setAppActive(active)
}
}

View file

@ -18,5 +18,7 @@ interface WebView {
fun relaunchApp()
fun unlockAppIfNeeded()
fun showError(errorType: ErrorType = ErrorType.TIMEOUT, error: SslError? = null, description: String? = null)
}

View file

@ -1022,14 +1022,7 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
if (hasFocus && !isFinishing) {
appLocked = presenter.isAppLocked()
if (appLocked) {
binding.blurView.setBlurEnabled(true)
authenticator.authenticate(getString(commonR.string.biometric_title))
} else {
binding.blurView.setBlurEnabled(false)
}
unlockAppIfNeeded()
val path = intent.getStringExtra(EXTRA_PATH)
presenter.onViewReady(path)
if (path?.startsWith("entityId:") == true)
@ -1043,6 +1036,16 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
}
}
override fun unlockAppIfNeeded() {
appLocked = presenter.isAppLocked()
if (appLocked) {
binding.blurView.setBlurEnabled(true)
authenticator.authenticate(getString(commonR.string.biometric_title))
} else {
binding.blurView.setBlurEnabled(false)
}
}
private fun hideSystemUI() {
windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())

View file

@ -131,8 +131,12 @@ class WebViewPresenterImpl @Inject constructor(
}
override fun switchActiveServer(id: Int) {
if (serverId != id && serverId != ServerManager.SERVER_ID_ACTIVE) {
setAppActive(false) // 'Lock' old server
}
setActiveServer(id)
onViewReady(null)
view.unlockAppIfNeeded()
}
override fun nextServer() = moveToServer(next = true)