feat: make sure app is allowed to scan for bluetooth devices

This commit is contained in:
Fabian Christoffel
2023-06-14 16:14:13 +02:00
parent e704139a07
commit 6752e79515
3 changed files with 171 additions and 8 deletions

17
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="a4566948" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-06-14T13:19:30.001338Z" />
</component>
</project>

View File

@@ -3,27 +3,45 @@ package com.example.sensortestingapp
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import com.google.android.material.snackbar.Snackbar
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import android.view.Menu
import android.view.MenuItem
import androidx.core.content.ContextCompat
import com.example.sensortestingapp.databinding.ActivityMainBinding
private const val ENABLE_BLUETOOTH_REQUEST_CODE = 1
private const val RUNTIME_PERMISSION_REQUEST_CODE = 2
fun Context.hasPermission(permissionType: String): Boolean {
return ContextCompat.checkSelfPermission(this, permissionType) ==
PackageManager.PERMISSION_GRANTED
}
fun Context.hasRequiredRuntimePermissions(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
hasPermission(Manifest.permission.BLUETOOTH_SCAN) &&
hasPermission(Manifest.permission.BLUETOOTH_CONNECT)
} else {
hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)
}
}
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
@@ -48,9 +66,10 @@ class MainActivity : AppCompatActivity() {
setupActionBarWithNavController(navController, appBarConfiguration)
binding.fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAnchorView(R.id.fab)
.setAction("Action", null).show()
startBleScan()
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
// .setAnchorView(R.id.fab)
// .setAction("Action", null).show()
}
}
@@ -94,6 +113,44 @@ class MainActivity : AppCompatActivity() {
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
RUNTIME_PERMISSION_REQUEST_CODE -> {
val containsPermanentDenial = permissions.zip(grantResults.toTypedArray()).any {
it.second == PackageManager.PERMISSION_DENIED &&
!ActivityCompat.shouldShowRequestPermissionRationale(this, it.first)
}
val containsDenial = grantResults.any { it == PackageManager.PERMISSION_DENIED }
val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
when {
containsPermanentDenial -> {
// TODO: Handle permanent denial (e.g., show AlertDialog with justification)
// Note: The user will need to navigate to App Settings and manually grant
// permissions that were permanently denied
}
containsDenial -> {
requestRelevantRuntimePermissions()
}
allGranted && hasRequiredRuntimePermissions() -> {
startBleScan()
}
else -> {
// Unexpected scenario encountered when handling permissions
recreate()
}
}
}
}
}
@SuppressLint("MissingPermission")
private fun promptEnableBluetooth() {
if (!bluetoothAdapter.isEnabled) {
@@ -102,4 +159,93 @@ class MainActivity : AppCompatActivity() {
}
}
private fun startBleScan() {
if (!hasRequiredRuntimePermissions()) {
requestRelevantRuntimePermissions()
} else { /* TODO: Actually perform scan */
}
}
private fun Activity.requestRelevantRuntimePermissions() {
if (hasRequiredRuntimePermissions()) {
return
}
when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> {
requestLocationPermission()
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
requestBluetoothPermissions()
}
}
}
private fun requestLocationPermission() {
val onClick = { dialog: DialogInterface, which: Int ->
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
RUNTIME_PERMISSION_REQUEST_CODE
)
}
runOnUiThread {
val builder = AlertDialog.Builder(this)
with(builder)
{
setTitle("Location permission required")
setMessage(
"Starting from Android M (6.0), the system requires apps to be granted " +
"location access in order to scan for BLE devices."
)
setCancelable(false)
setPositiveButton(
android.R.string.ok,
DialogInterface.OnClickListener(function = onClick)
)
show()
}
}
}
private fun requestBluetoothPermissions() {
val onClick = { dialog: DialogInterface, which: Int ->
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_CONNECT
),
RUNTIME_PERMISSION_REQUEST_CODE
)
}
runOnUiThread {
val builder = AlertDialog.Builder(this)
with(builder)
{
setTitle("Bluetooth permission required")
setMessage(
"Starting from Android 12, the system requires apps to be granted " +
"Bluetooth access in order to scan for and connect to BLE devices."
)
setCancelable(false)
setPositiveButton(
android.R.string.ok,
DialogInterface.OnClickListener(function = onClick)
)
show()
}
}
}
}

View File

@@ -28,6 +28,6 @@
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_dialog_email" />
app:srcCompat="@android:drawable/stat_sys_data_bluetooth" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>