kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Merge pull request #353 from meshtastic/perms
update permissions for Android 12 (API 31+)pull/355/head
commit
fb48f379c7
|
@ -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" />
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue