Created
March 10, 2022 03:02
-
-
Save frestoinc/73c12469ec372df13b1dbb143a9eed57 to your computer and use it in GitHub Desktop.
BLE Permission helper supporting Android 12
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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" /> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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