Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow treating ethernet and VPN as home network/for internal URL #4872

Merged
merged 8 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package io.homeassistant.companion.android.settings.server

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.InputType
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
Expand All @@ -26,8 +23,6 @@ import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.R
import io.homeassistant.companion.android.authenticator.Authenticator
import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.util.DisabledLocationHandler
import io.homeassistant.companion.android.common.util.LocationPermissionInfoHandler
import io.homeassistant.companion.android.launch.LaunchActivity
import io.homeassistant.companion.android.settings.SettingsActivity
import io.homeassistant.companion.android.settings.ssid.SsidFragment
Expand All @@ -50,10 +45,6 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
@Inject
lateinit var presenter: ServerSettingsPresenter

private val permissionsRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
onPermissionsResult(it)
}

private var serverId = -1

private var serverDeleteDialog: AlertDialog? = null
Expand Down Expand Up @@ -155,7 +146,14 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {

findPreference<Preference>("connection_internal_ssids")?.let {
it.setOnPreferenceClickListener {
onDisplaySsidScreen()
parentFragmentManager.commit {
replace(
R.id.content,
SsidFragment::class.java,
Bundle().apply { putInt(SsidFragment.EXTRA_SERVER, serverId) }
)
addToBackStack(getString(commonR.string.pref_connection_homenetwork))
}
return@setOnPreferenceClickListener true
}
it.isVisible = presenter.hasWifi()
Expand Down Expand Up @@ -208,10 +206,9 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {

override fun enableInternalConnection(isEnabled: Boolean) {
val iconTint = if (isEnabled) ContextCompat.getColor(requireContext(), commonR.color.colorAccent) else Color.DKGRAY
val doEnable = isEnabled && hasLocationPermission()

findPreference<EditTextPreference>("connection_internal")?.let {
it.isEnabled = doEnable
it.isEnabled = isEnabled
try {
val unwrappedDrawable =
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_computer)
Expand All @@ -223,7 +220,7 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
}

findPreference<SwitchPreference>("app_lock_home_bypass")?.let {
it.isEnabled = doEnable
it.isEnabled = isEnabled
try {
val unwrappedDrawable =
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_wifi)
Expand All @@ -246,68 +243,18 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
}
}

override fun updateSsids(ssids: List<String>) {
override fun updateHomeNetwork(ssids: List<String>, ethernet: Boolean?, vpn: Boolean?) {
findPreference<Preference>("connection_internal_ssids")?.let {
it.summary =
if (ssids.isEmpty()) {
getString(commonR.string.pref_connection_ssids_empty)
if (ssids.isEmpty() && ethernet != true && vpn != true) {
getString(commonR.string.not_set)
} else {
ssids.joinToString()
}
}
}
val options = ssids.toMutableList()
if (ethernet == true) options += getString(commonR.string.manage_ssids_ethernet)
if (vpn == true) options += getString(commonR.string.manage_ssids_vpn)

private fun onDisplaySsidScreen() {
val permissionsToCheck: Array<String> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
}

if (DisabledLocationHandler.isLocationEnabled(requireContext())) {
if (!checkPermission(permissionsToCheck)) {
LocationPermissionInfoHandler.showLocationPermInfoDialogIfNeeded(
requireContext(),
permissionsToCheck,
continueYesCallback = {
requestLocationPermission()
// showSsidSettings() will be called if permission is granted
}
)
} else {
showSsidSettings()
}
} else {
if (presenter.isSsidUsed()) {
DisabledLocationHandler.showLocationDisabledWarnDialog(
requireActivity(),
arrayOf(
getString(commonR.string.pref_connection_wifi)
),
showAsNotification = false,
withDisableOption = true
) {
presenter.clearSsids()
options.joinToString()
}
} else {
DisabledLocationHandler.showLocationDisabledWarnDialog(
requireActivity(),
arrayOf(
getString(commonR.string.pref_connection_wifi)
)
)
}
}
}

private fun showSsidSettings() {
parentFragmentManager.commit {
replace(
R.id.content,
SsidFragment::class.java,
Bundle().apply { putInt(SsidFragment.EXTRA_SERVER, serverId) }
)
addToBackStack(getString(commonR.string.manage_ssids))
}
}

Expand All @@ -324,37 +271,6 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
return (result == Authenticator.SUCCESS || result == Authenticator.CANCELED)
}

private fun hasLocationPermission(): Boolean {
val permissionsToCheck: Array<String> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
}
return checkPermission(permissionsToCheck)
}

private fun requestLocationPermission() {
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) // Background location will be requested later
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
}
permissionsRequest.launch(permissions)
}

private fun checkPermission(permissions: Array<String>?): Boolean {
if (!permissions.isNullOrEmpty()) {
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(requireContext(), permission) == PackageManager.PERMISSION_DENIED) {
return false
}
}
}
return true
}

override fun onRemovedServer(success: Boolean, hasAnyRemaining: Boolean) {
serverDeleteHandler.removeCallbacksAndMessages(null)
serverDeleteDialog?.cancel()
Expand All @@ -368,24 +284,6 @@ class ServerSettingsFragment : ServerSettingsView, PreferenceFragmentCompat() {
}
}

private fun onPermissionsResult(results: Map<String, Boolean>) {
if (results.keys.contains(Manifest.permission.ACCESS_FINE_LOCATION) &&
results[Manifest.permission.ACCESS_FINE_LOCATION] == true &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
) {
// For Android 11+ we MUST NOT request Background Location permission with fine or coarse
// permissions as for Android 11 the background location request needs to be done separately
// See here: https://developer.android.com/about/versions/11/privacy/location#request-background-location-separately
// The separate request of background location is done here
permissionsRequest.launch(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
return
}

if (results.entries.all { it.value }) {
showSsidSettings()
}
}

override fun onResume() {
super.onResume()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,21 +147,10 @@ class ServerSettingsPresenterImpl @Inject constructor(
}
}
mainScope.launch {
val ssids = serverManager.getServer(serverId)?.connection?.internalSsids.orEmpty()
if (ssids.isEmpty()) {
serverManager.getServer(serverId)?.let {
serverManager.updateServer(
it.copy(
connection = it.connection.copy(
internalUrl = null
)
)
)
}
}

view.enableInternalConnection(ssids.isNotEmpty())
view.updateSsids(ssids)
val connection = serverManager.getServer(serverId)?.connection
val ssids = connection?.internalSsids.orEmpty()
view.enableInternalConnection(ssids.isNotEmpty() || connection?.internalEthernet == true || connection?.internalVpn == true)
view.updateHomeNetwork(ssids, connection?.internalEthernet, connection?.internalVpn)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ interface ServerSettingsView {
fun updateServerName(name: String)
fun enableInternalConnection(isEnabled: Boolean)
fun updateExternalUrl(url: String, useCloud: Boolean)
fun updateSsids(ssids: List<String>)
fun updateHomeNetwork(ssids: List<String>, ethernet: Boolean?, vpn: Boolean?)
fun onRemovedServer(success: Boolean, hasAnyRemaining: Boolean)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
package io.homeassistant.companion.android.settings.ssid

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
import io.homeassistant.companion.android.common.R as commonR
import io.homeassistant.companion.android.common.util.DisabledLocationHandler
import io.homeassistant.companion.android.common.util.LocationPermissionInfoHandler
import io.homeassistant.companion.android.settings.addHelpMenuProvider
import io.homeassistant.companion.android.settings.ssid.views.SsidView
import io.homeassistant.companion.android.util.compose.HomeAssistantAppTheme
Expand All @@ -22,6 +32,12 @@ class SsidFragment : Fragment() {

val viewModel: SsidViewModel by viewModels()

private val permissionsRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
onPermissionsResult(it)
}

private var canReadWifi by mutableStateOf(false)

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -32,12 +48,18 @@ class SsidFragment : Fragment() {
HomeAssistantAppTheme {
SsidView(
wifiSsids = viewModel.wifiSsids,
canReadWifi = canReadWifi,
ethernet = viewModel.ethernet,
vpn = viewModel.vpn,
prioritizeInternal = viewModel.prioritizeInternal,
usingWifi = viewModel.usingWifi,
activeSsid = viewModel.activeSsid,
activeBssid = viewModel.activeBssid,
onAddWifiSsid = viewModel::addHomeWifiSsid,
onRemoveWifiSsid = viewModel::removeHomeWifiSsid,
onRequestPermission = { onRequestLocationPermission() },
onSetEthernet = viewModel::setInternalWithEthernet,
onSetVpn = viewModel::setInternalWithVpn,
onSetPrioritize = viewModel::setPrioritize
)
}
Expand All @@ -51,6 +73,86 @@ class SsidFragment : Fragment() {

override fun onResume() {
super.onResume()
activity?.title = getString(commonR.string.pref_connection_wifi)
activity?.title = getString(commonR.string.pref_connection_homenetwork)
updateLocationStatus()
}

private fun updateLocationStatus() {
val locationEnabled = DisabledLocationHandler.isLocationEnabled(requireContext())
if (!locationEnabled) {
canReadWifi = false
return
}

val permissionsToCheck = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
}
canReadWifi = checkPermission(permissionsToCheck)
}

private fun onRequestLocationPermission() {
val permissionsToCheck: Array<String> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
}

if (DisabledLocationHandler.isLocationEnabled(requireContext())) {
LocationPermissionInfoHandler.showLocationPermInfoDialogIfNeeded(
requireContext(),
permissionsToCheck,
continueYesCallback = {
requestLocationPermission()
}
)
} else {
DisabledLocationHandler.showLocationDisabledWarnDialog(
requireActivity(),
arrayOf(
getString(commonR.string.manage_ssids_wifi)
),
showAsNotification = false
)
}
}

private fun checkPermission(permissions: Array<String>?): Boolean {
if (!permissions.isNullOrEmpty()) {
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(requireContext(), permission) == PackageManager.PERMISSION_DENIED) {
return false
}
}
}
return true
}

private fun requestLocationPermission() {
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) // Background location will be requested later
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION)
}
permissionsRequest.launch(permissions)
}

private fun onPermissionsResult(results: Map<String, Boolean>) {
if (results.keys.contains(Manifest.permission.ACCESS_FINE_LOCATION) &&
results[Manifest.permission.ACCESS_FINE_LOCATION] == true &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
) {
// For Android 11+ we MUST NOT request Background Location permission with fine or coarse
// permissions as for Android 11 the background location request needs to be done separately
// See here: https://developer.android.com/about/versions/11/privacy/location#request-background-location-separately
// The separate request of background location is done here
permissionsRequest.launch(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
return
}
updateLocationStatus()
viewModel.updateWifiState()
}
}
Loading
Loading