sforkowany z mirror/meshtastic-android
location warnings are now much cleaner
rodzic
d32f8ad99e
commit
7efaf56f4f
|
@ -118,7 +118,7 @@ dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||||
implementation 'androidx.core:core-ktx:1.5.0'
|
implementation 'androidx.core:core-ktx:1.5.0'
|
||||||
implementation 'androidx.fragment:fragment-ktx:1.3.4'
|
implementation 'androidx.fragment:fragment-ktx:1.3.5'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
|
|
|
@ -25,7 +25,6 @@ import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
@ -249,38 +248,78 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** Ask the user to grant background location permission */
|
/** Ask the user to grant background location permission */
|
||||||
fun requestBackgroundPermission() = requestPermission(getBackgroundPermissions())
|
fun requestBackgroundPermission() = requestPermission(getBackgroundPermissions())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a localized string warning user about missing permissions. Or null if everything is find
|
||||||
|
*/
|
||||||
|
fun getMissingMessage(): 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)
|
||||||
|
)
|
||||||
|
|
||||||
|
val deniedPermissions = getMinimumPermissions().mapNotNull {
|
||||||
|
if(renamedPermissions.containsKey(it))
|
||||||
|
renamedPermissions[it]
|
||||||
|
else // No localization found - just show the nasty android string
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
|
return if(deniedPermissions.isEmpty())
|
||||||
|
null
|
||||||
|
else {
|
||||||
|
val asEnglish = deniedPermissions.joinToString(" & ")
|
||||||
|
|
||||||
|
getString(R.string.permission_missing).format(asEnglish)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Possibly prompt user to grant permissions
|
/** Possibly prompt user to grant permissions
|
||||||
*
|
*
|
||||||
* @return true if we already have the needed permissions
|
* @return true if we already have the needed permissions
|
||||||
*/
|
*/
|
||||||
private fun requestPermission(missingPerms: List<String> = getMinimumPermissions()): Boolean =
|
private fun requestPermission(missingPerms: List<String> = getMinimumPermissions()): Boolean =
|
||||||
if (missingPerms.isNotEmpty()) {
|
if (missingPerms.isNotEmpty()) {
|
||||||
missingPerms.forEach {
|
val shouldShow = missingPerms.filter {
|
||||||
// Permission is not granted
|
ActivityCompat.shouldShowRequestPermissionRationale(this, it)
|
||||||
// Should we show an explanation?
|
|
||||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this, it)) {
|
|
||||||
// FIXME
|
|
||||||
// Show an explanation to the user *asynchronously* -- don't block
|
|
||||||
// this thread waiting for the user's response! After the user
|
|
||||||
// sees the explanation, try again to request the permission.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask for all the missing perms
|
fun doRequest() {
|
||||||
ActivityCompat.requestPermissions(
|
info("requesting permissions")
|
||||||
this,
|
// Ask for all the missing perms
|
||||||
missingPerms.toTypedArray(),
|
ActivityCompat.requestPermissions(
|
||||||
DID_REQUEST_PERM
|
this,
|
||||||
)
|
missingPerms.toTypedArray(),
|
||||||
|
DID_REQUEST_PERM
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldShow.isNotEmpty()) {
|
||||||
|
// DID_REQUEST_PERM is an
|
||||||
|
// app-defined int constant. The callback method gets the
|
||||||
|
// result of the request.
|
||||||
|
warn("Permissions $shouldShow missing, we should show dialog")
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(getString(R.string.required_permissions))
|
||||||
|
.setMessage(getMissingMessage())
|
||||||
|
.setNeutralButton("Cancel (no radio access)") { _, _ ->
|
||||||
|
error("User bailed due to permissions")
|
||||||
|
}
|
||||||
|
.setPositiveButton("Allow (will show dialog)") { _, _ ->
|
||||||
|
doRequest()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
info("Permissions $missingPerms missing, no need to show dialog, just asking OS")
|
||||||
|
doRequest()
|
||||||
|
}
|
||||||
|
|
||||||
// DID_REQUEST_PERM is an
|
|
||||||
// app-defined int constant. The callback method gets the
|
|
||||||
// result of the request.
|
|
||||||
error("Permissions missing, asked user to grant")
|
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
// Permission has already been granted
|
// Permission has already been granted
|
||||||
|
@ -295,20 +334,11 @@ class MainActivity : AppCompatActivity(), Logging,
|
||||||
*/
|
*/
|
||||||
@SuppressLint("InlinedApi") // This function is careful to work with old APIs correctly
|
@SuppressLint("InlinedApi") // This function is careful to work with old APIs correctly
|
||||||
fun warnMissingPermissions(): Boolean {
|
fun warnMissingPermissions(): Boolean {
|
||||||
// Older versions of android don't know about these permissions - ignore failure to grant
|
val message = getMissingMessage()
|
||||||
val ignoredPermissions = setOf(
|
|
||||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
|
||||||
Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND,
|
|
||||||
Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND
|
|
||||||
)
|
|
||||||
|
|
||||||
val deniedPermissions = getMinimumPermissions().filter { name ->
|
return if (message != null) {
|
||||||
!ignoredPermissions.contains(name)
|
errormsg("Denied permissions: $message")
|
||||||
}
|
showToast(message)
|
||||||
|
|
||||||
return if (deniedPermissions.isNotEmpty()) {
|
|
||||||
errormsg("Denied permissions: ${deniedPermissions.joinToString(",")}")
|
|
||||||
showToast(R.string.permission_missing)
|
|
||||||
true
|
true
|
||||||
} else
|
} else
|
||||||
false
|
false
|
||||||
|
|
|
@ -35,6 +35,7 @@ import com.geeksville.mesh.MainActivity
|
||||||
import com.geeksville.mesh.R
|
import com.geeksville.mesh.R
|
||||||
import com.geeksville.mesh.RadioConfigProtos
|
import com.geeksville.mesh.RadioConfigProtos
|
||||||
import com.geeksville.mesh.android.bluetoothManager
|
import com.geeksville.mesh.android.bluetoothManager
|
||||||
|
import com.geeksville.mesh.android.hasBackgroundPermission
|
||||||
import com.geeksville.mesh.android.usbManager
|
import com.geeksville.mesh.android.usbManager
|
||||||
import com.geeksville.mesh.databinding.SettingsFragmentBinding
|
import com.geeksville.mesh.databinding.SettingsFragmentBinding
|
||||||
import com.geeksville.mesh.model.UIViewModel
|
import com.geeksville.mesh.model.UIViewModel
|
||||||
|
@ -460,6 +461,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
requireContext().getSystemService(CompanionDeviceManager::class.java)
|
requireContext().getSystemService(CompanionDeviceManager::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val myActivity get() = requireActivity() as MainActivity
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
guiJob.cancel()
|
guiJob.cancel()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
@ -564,7 +567,11 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
// Update the status string (highest priority messages first)
|
// Update the status string (highest priority messages first)
|
||||||
val info = model.myNodeInfo.value
|
val info = model.myNodeInfo.value
|
||||||
val statusText = binding.scanStatusText
|
val statusText = binding.scanStatusText
|
||||||
|
val permissionsWarning = myActivity.getMissingMessage()
|
||||||
when {
|
when {
|
||||||
|
permissionsWarning != null ->
|
||||||
|
statusText.text = permissionsWarning
|
||||||
|
|
||||||
region == RadioConfigProtos.RegionCode.Unset ->
|
region == RadioConfigProtos.RegionCode.Unset ->
|
||||||
statusText.text = getString(R.string.must_set_region)
|
statusText.text = getString(R.string.must_set_region)
|
||||||
|
|
||||||
|
@ -643,6 +650,24 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
requireActivity().hideKeyboard()
|
requireActivity().hideKeyboard()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.provideLocationCheckbox.setOnCheckedChangeListener { view, isChecked ->
|
||||||
|
if (view.isPressed && isChecked) { // We want to ignore changes caused by code (as opposed to the user)
|
||||||
|
debug("User changed location tracking to $isChecked")
|
||||||
|
view.isChecked =
|
||||||
|
myActivity.hasBackgroundPermission() // Don't check the box until the system setting changes
|
||||||
|
if (!view.isChecked)
|
||||||
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
.setTitle(R.string.background_required)
|
||||||
|
.setMessage(R.string.why_background_required)
|
||||||
|
.setNeutralButton(R.string.cancel) { _, _ ->
|
||||||
|
debug("Decided not to report a bug")
|
||||||
|
}
|
||||||
|
.setPositiveButton(getString(R.string.show_system_settings)) { _, _ ->
|
||||||
|
myActivity.requestBackgroundPermission()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val app = (requireContext().applicationContext as GeeksvilleApplication)
|
val app = (requireContext().applicationContext as GeeksvilleApplication)
|
||||||
|
|
||||||
|
@ -689,7 +714,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
binding.scanStatusText.setText(R.string.starting_pairing)
|
binding.scanStatusText.setText(R.string.starting_pairing)
|
||||||
|
|
||||||
b.isChecked =
|
b.isChecked =
|
||||||
scanModel.onSelected(requireActivity() as MainActivity, device)
|
scanModel.onSelected(myActivity, device)
|
||||||
|
|
||||||
if (!b.isSelected)
|
if (!b.isSelected)
|
||||||
binding.scanStatusText.text = getString(R.string.please_pair)
|
binding.scanStatusText.text = getString(R.string.please_pair)
|
||||||
|
@ -812,8 +837,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
.setSingleDevice(false)
|
.setSingleDevice(false)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
//val mainActivity = requireActivity() as MainActivity
|
|
||||||
|
|
||||||
// When the app tries to pair with the Bluetooth device, show the
|
// When the app tries to pair with the Bluetooth device, show the
|
||||||
// appropriate pairing request dialog to the user.
|
// appropriate pairing request dialog to the user.
|
||||||
deviceManager.associate(
|
deviceManager.associate(
|
||||||
|
@ -901,7 +924,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
}
|
}
|
||||||
|
|
||||||
locationSettingsResponse.addOnSuccessListener {
|
locationSettingsResponse.addOnSuccessListener {
|
||||||
if(!it.locationSettingsStates.isBleUsable || !it.locationSettingsStates.isLocationUsable)
|
if (!it.locationSettingsStates.isBleUsable || !it.locationSettingsStates.isLocationUsable)
|
||||||
weNeedAccess()
|
weNeedAccess()
|
||||||
else
|
else
|
||||||
debug("We have location access")
|
debug("We have location access")
|
||||||
|
@ -953,15 +976,16 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
|
||||||
if (!hasCompanionDeviceApi)
|
if (!hasCompanionDeviceApi)
|
||||||
scanModel.startScan()
|
scanModel.startScan()
|
||||||
|
|
||||||
val activity = requireActivity() as MainActivity
|
// system permissions might have changed while we were away
|
||||||
|
binding.provideLocationCheckbox.isChecked = myActivity.hasBackgroundPermission()
|
||||||
|
|
||||||
activity.registerReceiver(updateProgressReceiver, updateProgressFilter)
|
myActivity.registerReceiver(updateProgressReceiver, updateProgressFilter)
|
||||||
|
|
||||||
// Keep reminding user BLE is still off
|
// Keep reminding user BLE is still off
|
||||||
val hasUSB = activity?.let { SerialInterface.findDrivers(it).isNotEmpty() } ?: true
|
val hasUSB = SerialInterface.findDrivers(myActivity).isNotEmpty()
|
||||||
if (!hasUSB) {
|
if (!hasUSB) {
|
||||||
// First warn about permissions, and then if needed warn abotu settings
|
// First warn about permissions, and then if needed warn about settings
|
||||||
if(!activity.warnMissingPermissions()) {
|
if (!myActivity.warnMissingPermissions()) {
|
||||||
// Warn user if BLE is disabled
|
// Warn user if BLE is disabled
|
||||||
if (scanModel.bluetoothAdapter?.isEnabled != true) {
|
if (scanModel.bluetoothAdapter?.isEnabled != true) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
<string name="map_not_allowed">You have analytics disabled. Unfortunately our map provider (mapbox) requires analytics to be allowed for their \'free\' plan. So we have turned off the map view.\n\n
|
<string name="map_not_allowed">You have analytics disabled. Unfortunately our map provider (mapbox) requires analytics to be allowed for their \'free\' plan. So we have turned off the map view.\n\n
|
||||||
If you would like to see the map, you\'ll need to turn on analytics in the Settings pane (also, for the time being you might need to force restart the application).\n\n
|
If you would like to see the map, you\'ll need to turn on analytics in the Settings pane (also, for the time being you might need to force restart the application).\n\n
|
||||||
If you are interested in us paying for mapbox (or switching to a different map provider), please post in meshtastic.discourse.group</string>
|
If you are interested in us paying for mapbox (or switching to a different map provider), please post in meshtastic.discourse.group</string>
|
||||||
<string name="permission_missing">A required permission is missing, Meshtastic won\'t be able to work properly. Please enable in Android application settings.</string>
|
<string name="permission_missing">Meshtastic needs %s permission granted. Without this Android will not allow connecting to the LoRa bluetooth radios.</string>
|
||||||
<string name="radio_sleeping">Radio was sleeping, could not change channel</string>
|
<string name="radio_sleeping">Radio was sleeping, could not change channel</string>
|
||||||
<string name="report_bug">Report Bug</string>
|
<string name="report_bug">Report Bug</string>
|
||||||
<string name="report_a_bug">Report a bug</string>
|
<string name="report_a_bug">Report a bug</string>
|
||||||
|
@ -108,4 +108,9 @@
|
||||||
<string name="theme_dark">Dark</string>
|
<string name="theme_dark">Dark</string>
|
||||||
<string name="theme_system">System default</string>
|
<string name="theme_system">System default</string>
|
||||||
<string name="choose_theme_title">Choose theme</string>
|
<string name="choose_theme_title">Choose theme</string>
|
||||||
|
<string name="background_required">Background location access required</string>
|
||||||
|
<string name="show_system_settings">Show system settings</string>
|
||||||
|
<string name="why_background_required">In order to enable this feature, you must grant this application "allow location access all the time" permission. This allows meshtastic to read your location while the application is in the background, so that it can send your location to other members of your mesh.</string>
|
||||||
|
<string name="required_permissions">Required permissions</string>
|
||||||
|
<string name="location">location</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Ładowanie…
Reference in New Issue