Skip to content

Instantly share code, notes, and snippets.

@frestoinc
Created March 10, 2022 03:02
Show Gist options
  • Save frestoinc/73c12469ec372df13b1dbb143a9eed57 to your computer and use it in GitHub Desktop.
Save frestoinc/73c12469ec372df13b1dbb143a9eed57 to your computer and use it in GitHub Desktop.
BLE Permission helper supporting Android 12
<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.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
import android.Manifest.permission.*
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.LocationManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
enum class BleOperationErrorState {
STATE_PERMISSION_DENIED,
STATE_PERMISSION_PERMANENTLY_DENIED,
STATE_MISSING_BLE,
STATE_BLE_ADAPTER_DISABLED,
STATE_LOCATION_ADAPTER_DISABLED
}
class BlePermissionManager(private val fragment: Fragment) {
private var _permanentDenied = false
private var _permissionLambda: Pair<() -> Unit, ((BleOperationErrorState) -> Unit)>? = null
private val _resultLauncher =
fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
_permissionLambda?.let {
checkPermissionGrantedAndEnabled(it.first, it.second)
}
}
private val _permissionLauncher =
fragment.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
result.filter { !it.value }.map { it.key }.let { list ->
if (list.isNotEmpty()) {
val permissions = list.groupBy { permission ->
if (fragment.shouldShowRequestPermissionRationale(permission)) 0 else 1
}
permissions[0]?.let {
_permanentDenied = false
}
permissions[1]?.let {
if (_permanentDenied) {
launchAppSettings()
} else {
_permanentDenied = true
}
}
}
_permissionLambda?.let {
checkPermissionGrantedAndEnabled(it.first, it.second)
}
}
}
fun requestForBleOperationPermission() {
when {
_permanentDenied -> launchAppSettings()
else -> _permissionLauncher.launch(getArrayPermissions())
}
}
fun enableBleAdapter() {
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE).also {
_resultLauncher.launch(it)
}
}
fun enableLocationAdapter() {
Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS).also {
_resultLauncher.launch(it)
}
}
private fun launchAppSettings() {
Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", fragment.requireContext().packageName, null)
}.also {
_resultLauncher.launch(it)
}
}
private fun getArrayPermissions(): Array<String> =
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> arrayOf(
BLUETOOTH_SCAN,
BLUETOOTH_CONNECT
)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> if (fragment.requireActivity()
.checkSelfPermission(
ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
arrayOf(ACCESS_BACKGROUND_LOCATION)
} else {
arrayOf(ACCESS_FINE_LOCATION)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> arrayOf(
ACCESS_FINE_LOCATION,
ACCESS_BACKGROUND_LOCATION
)
else -> arrayOf(ACCESS_COARSE_LOCATION)
}
fun checkPermissionGrantedAndEnabled(
onAllFeaturesEnabled: () -> Unit,
onSomeFeaturesDisabled: (BleOperationErrorState) -> Unit
) {
_permissionLambda = Pair(onAllFeaturesEnabled, onSomeFeaturesDisabled)
if (!isBleSupported()) {
onSomeFeaturesDisabled(BleOperationErrorState.STATE_MISSING_BLE)
return
}
if (!permissionForBleOperationGranted()) {
onSomeFeaturesDisabled(
if (_permanentDenied) {
BleOperationErrorState.STATE_PERMISSION_PERMANENTLY_DENIED
} else {
BleOperationErrorState.STATE_PERMISSION_DENIED
}
)
return
}
if (!isBleAdapterEnabled()) {
onSomeFeaturesDisabled(BleOperationErrorState.STATE_BLE_ADAPTER_DISABLED)
return
}
if (!isLocationAdapterEnabled()) {
onSomeFeaturesDisabled(BleOperationErrorState.STATE_LOCATION_ADAPTER_DISABLED)
return
}
onAllFeaturesEnabled()
}
@SuppressLint("InlinedApi")
private fun permissionForBleOperationGranted(): Boolean =
when (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
true -> {
if (fragment.requireActivity()
.checkSelfPermission(ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
) {
fragment.requireActivity()
.checkSelfPermission(ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED
} else {
fragment.requireActivity()
.checkSelfPermission(ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
}
}
else -> getArrayPermissions().all { permission ->
fragment.requireActivity()
.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
}
}
private fun isBleSupported(): Boolean =
fragment.context?.let {
it.packageManager?.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
} ?: false
private fun isBleAdapterEnabled(): Boolean =
fragment.context?.let {
(it.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter.isEnabled
} ?: false
private fun isLocationAdapterEnabled(): Boolean = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> true
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P -> fragment.context?.let {
(it.getSystemService(Context.LOCATION_SERVICE) as LocationManager).isLocationEnabled
} ?: false
else -> Settings.Secure.getInt(
fragment.requireContext().contentResolver,
Settings.Secure.LOCATION_MODE
) != Settings.Secure.LOCATION_MODE_OFF
}
}
val manager = BlePermissionManager(this)
manager.checkPermissionGrantedAndEnabled({
Timber.e("GOOD TO GO)"
}, { state ->
when(state) {
BleOperationErrorState.STATE_MISSING_BLE -> //missing ble
BleOperationErrorState.STATE_PERMISSION_DENIED -> {
//show permission denied option dialog
//if ok button ->
manager.requestForBleOperationPermission()
}
BleOperationErrorState.STATE_PERMISSION_PERMANENTLY_DENIED -> {
//show permission permanently denied option dialog
//if ok button ->
manager.requestForBleOperationPermission()
}
BleOperationErrorState.STATE_BLE_ADAPTER_DISABLED -> {
//show bluetooth disable option dialog
//if ok button ->
manager.enableBleAdapter()
}
BleOperationErrorState.STATE_LOCATION_ADAPTER_DISABLED -> {
//show location disable option dialog
//if ok button ->
manager.enableLocationAdapter()
}
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment