Merge pull request #353 from meshtastic/perms

update permissions for Android 12 (API 31+)
pull/355/head
Andre Kirchhoff 2022-01-25 18:49:07 -03:00 zatwierdzone przez GitHub
commit fb48f379c7
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
4 zmienionych plików z 107 dodań i 64 usunięć

Wyświetl plik

@ -12,15 +12,22 @@
android:name="android.hardware.location.gps"
android:required="false" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Request legacy Bluetooth permissions on older devices -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- only useful if this phone can do BTLE -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- needed to access bluetooth when app is background -->
<!-- API 31+ Bluetooth permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<!--
This permission is required for analytics - and soon the MQTT gateway
-->
<!-- Permissions required for providing location (from phone GPS) to mesh -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- This permission is required for analytics - and soon the MQTT gateway -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
@ -41,9 +48,6 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- For android >=26 we can use the new BLE scanning API, which allows auto launching our service when our device is seen -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND" />
@ -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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -162,7 +167,8 @@
android:resource="@xml/device_filter" />
</activity>
<receiver android:name="com.geeksville.mesh.service.BootCompleteReceiver">
<receiver android:name="com.geeksville.mesh.service.BootCompleteReceiver"
android:exported="false">
<!-- handle boot events -->
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />

Wyświetl plik

@ -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
@ -251,12 +252,9 @@ class MainActivity : AppCompatActivity(), Logging,
val requiredPerms: MutableList<String> = 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 +273,6 @@ class MainActivity : AppCompatActivity(), Logging,
*/
private fun getMinimumPermissions(): List<String> {
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 +280,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
@ -300,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)
@ -312,16 +309,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<String> = 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
@ -342,7 +342,7 @@ class MainActivity : AppCompatActivity(), Logging,
*
* @return true if we already have the needed permissions
*/
fun requestPermission(
private fun requestPermission(
missingPerms: List<String> = getMinimumPermissions(),
shouldShowDialog: Boolean = true
): Boolean =
@ -369,7 +369,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 +550,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() {
@ -814,7 +817,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 {

Wyświetl plik

@ -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<String>) = perms.filter {
) != PackageManager.PERMISSION_GRANTED
}
/**
* Bluetooth scan/discovery permissions (or empty if we already have what we need)
*/
fun Context.getScanPermissions(): List<String> {
val perms = mutableListOf<String>()
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)
*/
@ -41,7 +61,7 @@ fun Context.getCameraPermissions(): List<String> {
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<String> {
val perms = mutableListOf(Manifest.permission.ACCESS_FINE_LOCATION)
@ -49,7 +69,7 @@ fun Context.getLocationPermissions(): List<String> {
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()
/**

Wyświetl plik

@ -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
@ -447,7 +448,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 +477,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
}
@ -561,7 +561,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 ->
@ -615,6 +615,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)
})
@ -637,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()
@ -655,34 +661,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()
}
}
@ -730,8 +733,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
b.isChecked =
scanModel.onSelected(myActivity, device)
if (!b.isSelected)
if (!b.isSelected) {
binding.scanStatusText.text = getString(R.string.please_pair)
}
}
}
@ -757,7 +761,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
@ -798,9 +802,13 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
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()
}
}
}
@ -828,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()
}
}
}