From 71e8e9ff6f6095f61a4d86c32d4405a119b88d6d Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 25 Jan 2022 01:20:31 -0300 Subject: [PATCH 1/5] fix provideLocationCheckbox --- .../java/com/geeksville/mesh/MainActivity.kt | 2 +- .../geeksville/mesh/ui/SettingsFragment.kt | 47 +++++++++---------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 5d7e432e1..be39e0713 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -814,7 +814,7 @@ class MainActivity : AppCompatActivity(), Logging, model.isConnected.value = oldConnection } // if provideLocation enabled: Start providing location (from phone GPS) to mesh - if (model.provideLocation.value == true && (oldConnection != connected)) + if (model.provideLocation.value == true) service.setupProvideLocation() } } else { diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index 8567c653c..f8e4bbba8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -655,34 +655,31 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { binding.provideLocationCheckbox.isEnabled = isGooglePlayAvailable(requireContext()) binding.provideLocationCheckbox.setOnCheckedChangeListener { view, isChecked -> - model.provideLocation.value = isChecked + if (view.isPressed && isChecked) { // We want to ignore changes caused by code (as opposed to the user) + // Don't check the box until the system setting changes + view.isChecked = myActivity.hasLocationPermission() && myActivity.hasBackgroundPermission() - if (view.isChecked) { - debug("User changed location tracking to $isChecked") - if (view.isPressed) { // We want to ignore changes caused by code (as opposed to the user) - // Don't check the box until the system setting changes - view.isChecked = myActivity.hasLocationPermission() && myActivity.hasBackgroundPermission() - - if (!myActivity.hasLocationPermission()) // Make sure we have location permission (prerequisite) - myActivity.requestLocationPermission() - else if (!myActivity.hasBackgroundPermission()) - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.background_required) - .setMessage(R.string.why_background_required) - .setNeutralButton(R.string.cancel) { _, _ -> - debug("User denied background permission") - } - .setPositiveButton(getString(R.string.accept)) { _, _ -> - myActivity.requestBackgroundPermission() - } - .show() - - if (view.isChecked) { - checkLocationEnabled(getString(R.string.location_disabled)) - model.meshService?.setupProvideLocation() - } + if (!myActivity.hasLocationPermission()) // Make sure we have location permission (prerequisite) + myActivity.requestLocationPermission() + else if (!myActivity.hasBackgroundPermission()) + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.background_required) + .setMessage(R.string.why_background_required) + .setNeutralButton(R.string.cancel) { _, _ -> + debug("User denied background permission") + } + .setPositiveButton(getString(R.string.accept)) { _, _ -> + myActivity.requestBackgroundPermission() + } + .show() + if (view.isChecked) { + debug("User changed location tracking to $isChecked") + model.provideLocation.value = isChecked + checkLocationEnabled(getString(R.string.location_disabled)) + model.meshService?.setupProvideLocation() } } else { + model.provideLocation.value = isChecked model.meshService?.stopProvideLocation() } } From d9e1f17418cba98e93180ea6b509fa5d8cd642d3 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 25 Jan 2022 01:34:46 -0300 Subject: [PATCH 2/5] handle bluetooth disabled --- .../geeksville/mesh/ui/SettingsFragment.kt | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index f8e4bbba8..c5fd8e484 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -447,7 +447,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private val guiJob = Job() private val mainScope = CoroutineScope(Dispatchers.Main + guiJob) - private val hasCompanionDeviceApi: Boolean by lazy { BluetoothInterface.hasCompanionDeviceApi(requireContext()) } @@ -477,7 +476,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { _binding = SettingsFragmentBinding.inflate(inflater, container, false) return binding.root } @@ -615,6 +614,12 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { regionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinner.adapter = regionAdapter + model.bluetoothEnabled.observe( + viewLifecycleOwner, { + if (it) binding.changeRadioButton.show() + else binding.changeRadioButton.hide() + }) + model.ownerName.observe(viewLifecycleOwner, { name -> binding.usernameEditText.setText(name) }) @@ -727,8 +732,11 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { b.isChecked = scanModel.onSelected(myActivity, device) - if (!b.isSelected) - binding.scanStatusText.text = getString(R.string.please_pair) + if (!b.isSelected) { + binding.warningNotPaired.visibility = View.VISIBLE + binding.scanStatusText.text = getString(R.string.not_paired_yet) + // binding.scanStatusText.text = getString(R.string.please_pair) + } } } @@ -754,7 +762,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // and before use val bleAddr = scanModel.selectedBluetooth - if (bleAddr != null && adapter != null && adapter.isEnabled) { + if (bleAddr != null && adapter != null) { val bDevice = adapter.getRemoteDevice(bleAddr) if (bDevice.name != null) { // ignore nodes that node have a name, that means we've lost them since they appeared @@ -786,9 +794,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { if (curRadio != null && !MockInterface.addressValid(requireContext(), "")) { binding.warningNotPaired.visibility = View.GONE // binding.scanStatusText.text = getString(R.string.current_pair).format(curRadio) - } else { - binding.warningNotPaired.visibility = View.VISIBLE - binding.scanStatusText.text = getString(R.string.not_paired_yet) } } From 8aa2f546b03d21ffd417738fcc9cb60df93aae52 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 25 Jan 2022 15:59:45 -0300 Subject: [PATCH 3/5] update permissions --- .../java/com/geeksville/mesh/MainActivity.kt | 21 +++++++++---------- .../mesh/android/ContextServices.kt | 2 +- .../geeksville/mesh/ui/SettingsFragment.kt | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index be39e0713..aaa758981 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -251,12 +251,9 @@ class MainActivity : AppCompatActivity(), Logging, val requiredPerms: MutableList = mutableListOf() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - requiredPerms.add(Manifest.permission.BLUETOOTH_SCAN) requiredPerms.add(Manifest.permission.BLUETOOTH_CONNECT) } else { - requiredPerms.add(Manifest.permission.ACCESS_FINE_LOCATION) requiredPerms.add(Manifest.permission.BLUETOOTH) - requiredPerms.add(Manifest.permission.BLUETOOTH_ADMIN) } if (getMissingPermissions(requiredPerms).isEmpty()) { @@ -275,8 +272,6 @@ class MainActivity : AppCompatActivity(), Logging, */ private fun getMinimumPermissions(): List { val perms = mutableListOf( - Manifest.permission.ACCESS_COARSE_LOCATION, - Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WAKE_LOCK // We only need this for logging to capture files for the simulator - turn off for most users @@ -284,11 +279,9 @@ class MainActivity : AppCompatActivity(), Logging, ) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - perms.add(Manifest.permission.BLUETOOTH_SCAN) perms.add(Manifest.permission.BLUETOOTH_CONNECT) } else { perms.add(Manifest.permission.BLUETOOTH) - perms.add(Manifest.permission.BLUETOOTH_ADMIN) } // Some old phones complain about requesting perms they don't understand @@ -312,16 +305,19 @@ class MainActivity : AppCompatActivity(), Logging, /** * @return a localized string warning user about missing permissions. Or null if everything is find */ - fun getMissingMessage(): String? { + fun getMissingMessage( + missingPerms: List = getMinimumPermissions() + ): String? { val renamedPermissions = mapOf( // Older versions of android don't know about these permissions - ignore failure to grant Manifest.permission.ACCESS_COARSE_LOCATION to null, Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND to null, Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND to null, - Manifest.permission.ACCESS_FINE_LOCATION to getString(R.string.location) + Manifest.permission.ACCESS_FINE_LOCATION to getString(R.string.location), + Manifest.permission.BLUETOOTH_CONNECT to "Bluetooth" ) - val deniedPermissions = getMinimumPermissions().mapNotNull { + val deniedPermissions = missingPerms.mapNotNull { if (renamedPermissions.containsKey(it)) renamedPermissions[it] else // No localization found - just show the nasty android string @@ -369,7 +365,7 @@ class MainActivity : AppCompatActivity(), Logging, MaterialAlertDialogBuilder(this) .setTitle(getString(R.string.required_permissions)) - .setMessage(getMissingMessage()) + .setMessage(getMissingMessage(missingPerms)) .setNeutralButton(R.string.cancel) { _, _ -> warn("User bailed due to permissions") } @@ -550,6 +546,9 @@ class MainActivity : AppCompatActivity(), Logging, handleIntent(intent) askToRate() + + // if (!isInTestLab) - very important - even in test lab we must request permissions because we need location perms for some of our tests to pass + requestPermission() } private fun initToolbar() { diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt index 093d58f69..6e8eed03d 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt @@ -41,7 +41,7 @@ fun Context.getCameraPermissions(): List { fun Context.hasCameraPermission() = getCameraPermissions().isEmpty() /** - * Camera permission (or empty if we already have what we need) + * Location permission (or empty if we already have what we need) */ fun Context.getLocationPermissions(): List { val perms = mutableListOf(Manifest.permission.ACCESS_FINE_LOCATION) diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index c5fd8e484..f5dbbbcea 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -560,7 +560,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { val statusText = binding.scanStatusText val permissionsWarning = myActivity.getMissingMessage() when { - (!hasCompanionDeviceApi && permissionsWarning != null) -> + (permissionsWarning != null) -> statusText.text = permissionsWarning region == RadioConfigProtos.RegionCode.Unset -> From 3a1c87d26f41bbe8a4364ab7e140f3c88eecdbc6 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 25 Jan 2022 17:53:44 -0300 Subject: [PATCH 4/5] update API 31+ bluetooth manifest --- app/src/main/AndroidManifest.xml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e2a26a86..94c6aab71 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,15 +12,22 @@ android:name="android.hardware.location.gps" android:required="false" /> - - + + + - - + + + - + + + + + @@ -41,9 +48,6 @@ - - @@ -124,7 +128,8 @@ android:label="@string/app_name" android:screenOrientation="portrait" android:windowSoftInputMode="stateAlwaysHidden" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:exported="true"> @@ -162,7 +167,8 @@ android:resource="@xml/device_filter" /> - + From f5c9363d0657362e8d1bbb55c1f69e566cadd93f Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 25 Jan 2022 18:14:10 -0300 Subject: [PATCH 5/5] add bluetooth scan permission --- .../java/com/geeksville/mesh/MainActivity.kt | 6 +++- .../mesh/android/ContextServices.kt | 22 ++++++++++++- .../geeksville/mesh/ui/SettingsFragment.kt | 32 +++++++++++++------ 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index aaa758981..b3509b158 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -47,6 +47,7 @@ import com.geeksville.mesh.android.getLocationPermissions import com.geeksville.mesh.android.getBackgroundPermissions import com.geeksville.mesh.android.getCameraPermissions import com.geeksville.mesh.android.getMissingPermissions +import com.geeksville.mesh.android.getScanPermissions import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.databinding.ActivityMainBinding import com.geeksville.mesh.model.ChannelSet @@ -293,6 +294,9 @@ class MainActivity : AppCompatActivity(), Logging, return getMissingPermissions(perms) } + /** Ask the user to grant Bluetooth scan/discovery permission */ + fun requestScanPermission() = requestPermission(getScanPermissions(), true) + /** Ask the user to grant camera permission */ fun requestCameraPermission() = requestPermission(getCameraPermissions(), false) @@ -338,7 +342,7 @@ class MainActivity : AppCompatActivity(), Logging, * * @return true if we already have the needed permissions */ - fun requestPermission( + private fun requestPermission( missingPerms: List = getMinimumPermissions(), shouldShowDialog: Boolean = true ): Boolean = diff --git a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt index 6e8eed03d..71e22b0b3 100644 --- a/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt +++ b/app/src/main/java/com/geeksville/mesh/android/ContextServices.kt @@ -8,6 +8,7 @@ import android.content.pm.PackageManager import android.hardware.usb.UsbManager import android.os.Build import androidx.core.content.ContextCompat +import com.geeksville.mesh.service.BluetoothInterface /** * @return null on platforms without a BlueTooth driver (i.e. the emulator) @@ -28,6 +29,25 @@ fun Context.getMissingPermissions(perms: List) = perms.filter { ) != PackageManager.PERMISSION_GRANTED } +/** + * Bluetooth scan/discovery permissions (or empty if we already have what we need) + */ +fun Context.getScanPermissions(): List { + val perms = mutableListOf() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + perms.add(Manifest.permission.BLUETOOTH_SCAN) + } else if (!BluetoothInterface.hasCompanionDeviceApi(this)) { + perms.add(Manifest.permission.ACCESS_FINE_LOCATION) + perms.add(Manifest.permission.BLUETOOTH_ADMIN) + } + + return getMissingPermissions(perms) +} + +/** @return true if the user already has Bluetooth scan/discovery permission */ +fun Context.hasScanPermission() = getScanPermissions().isEmpty() + /** * Camera permission (or empty if we already have what we need) */ @@ -49,7 +69,7 @@ fun Context.getLocationPermissions(): List { return getMissingPermissions(perms) } -/** @return true if the user already has camera permission */ +/** @return true if the user already has location permission */ fun Context.hasLocationPermission() = getLocationPermissions().isEmpty() /** diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index f5dbbbcea..b19290a2e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -34,6 +34,7 @@ import com.geeksville.mesh.MainActivity import com.geeksville.mesh.R import com.geeksville.mesh.RadioConfigProtos import com.geeksville.mesh.android.bluetoothManager +import com.geeksville.mesh.android.hasScanPermission import com.geeksville.mesh.android.hasLocationPermission import com.geeksville.mesh.android.hasBackgroundPermission import com.geeksville.mesh.android.usbManager @@ -642,9 +643,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { } }) - scanModel.devices.observe( - viewLifecycleOwner, - { devices -> updateDevicesButtons(devices) }) + scanModel.devices.observe(viewLifecycleOwner, { devices -> + updateDevicesButtons(devices) + }) binding.updateFirmwareButton.setOnClickListener { doFirmwareUpdate() @@ -733,9 +734,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { scanModel.onSelected(myActivity, device) if (!b.isSelected) { - binding.warningNotPaired.visibility = View.VISIBLE - binding.scanStatusText.text = getString(R.string.not_paired_yet) - // binding.scanStatusText.text = getString(R.string.please_pair) + binding.scanStatusText.text = getString(R.string.please_pair) } } } @@ -794,15 +793,22 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { if (curRadio != null && !MockInterface.addressValid(requireContext(), "")) { binding.warningNotPaired.visibility = View.GONE // binding.scanStatusText.text = getString(R.string.current_pair).format(curRadio) + } else { + binding.warningNotPaired.visibility = View.VISIBLE + binding.scanStatusText.text = getString(R.string.not_paired_yet) } } private fun initClassicScan() { binding.changeRadioButton.setOnClickListener { - if (myActivity.warnMissingPermissions()) { - myActivity.requestPermission() - } else scanLeDevice() + debug("User clicked changeRadioButton") + if (!myActivity.hasScanPermission()) { + myActivity.requestScanPermission() + } else { + checkLocationEnabled() + scanLeDevice() + } } } @@ -830,7 +836,13 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { private fun initModernScan() { binding.changeRadioButton.setOnClickListener { - myActivity.startCompanionScan() + debug("User clicked changeRadioButton") + if (!myActivity.hasScanPermission()) { + myActivity.requestScanPermission() + } else { + // checkLocationEnabled() // ? some phones still need location turned on + myActivity.startCompanionScan() + } } }