[Android] Android 12 이상에서 블루투스 권한 요청하기


개요

실리콘밸리 인턴십에서 진행하는 프로젝트에서 뇌파를 받아오는 블루투스 헤드셋과 통신하는 기능 개발이 필요하여 블루투스 권한에 대해 알아보던 도중, Android 12 이상부터 새로운 블루투스 런타임 권한이 추가된 것을 알게되어 이에 대해 정리해보겠습니다.


기존과의 변경점

Android 12 미만의 버전에서는 manifest에 블루투스 권한을 명시만 해준다면 별도의 과정 없이 블루투스를 사용할 수 있었습니다. 하지만 Android 12 이상부터는 블루투스에 대한 권한을 manifest에 명시함과 동시에 런타임 권한으로써 앱 사용 중에 사용자로부터 승인을 받아야 사용할 수 있게 되었습니다. 또한 manifest에 명시해야 하는 권한 또한 블루투스의 사용 범위에 따라 더욱 상세하게 분류되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<manifest ... >

    <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.BLUETOOTH_ADVERTISE" />

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

</manifest>

BLUETOOTH 권한과 BLUETOOTH_ADMIN 권한은 Android 12 미만, 즉 SDK 30 버전까지만 사용되는 권한으로써, maxSdkVersion 속성을 통해 SDK 30 이하에서만 사용하도록 합니다. 그 외에 추가된 3가지 권한은 Android 12 이상에서 새로 등장한 블루투스 권한으로써, 각각 다음과 같은 사용 범위를 나타냅니다.

  • BLUETOOTH_SCAN: 주변 블루투스 디바이스를 검색
  • BLUETOOTH_CONNECT: 페어링된 블루투스 디바이스와 통신
  • BLUETOOTH_ADVERTISE: 본 디바이스의 블루투스 검색 허용

일반적으로 블루투스 권한을 사용하는 앱이라면 일반적으로 SCANCONNECT 권한은 거의 고정적으로 사용하게 됩니다.

이때 앱이 블루투스 기기 검색을 통해 해당 기기의 실제 위치를 파싱한다면, 추가적으로 ACCESS_FINE_LOCATION 권한이 필요합니다. 반대로 해당 앱이 블루트스 기기의 실제 위치를 절대로 파싱하지 않다는 일종의 assertion을 추가할 수도 있는데, SCAN 권한에 다음과 같은 속성을 부여할 수 있습니다.

1
2
3
<uses-permission
    android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />

위와 같이 neverForLocation을 선언할 경우, ACCESS_FINE_LOCATION 권한을 추가 시에 컴파일 오류를 발생시켜줍니다.


권한에 대한 사용자 승인 요청

블루투스 권한에 대한 승인 요청은 블루투스 기능을 사용하게 되는 시점에 발생시킬 것을 권장하고 있으며, 다음과 같은 코드를 해당 기능이 동작하는 Activity에서 실행함으로써 요청할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class BluetoothActivity : BaseActivity() {

    private val permissionsRequestActivityLauncher =
        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
            val granted = permissions.entries.all { it.value }
            if (granted) {
                // 권한 승인
            } else {
                // 권한 거부
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            . . .
        }
    }

    private fun requestPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            val permissions = BLUETOOTH_PERMISSIONS
            if (!hasPermissions(permissions)) {
                permissionsRequestActivityLauncher.launch(permissions.toTypedArray())
            } else {
                // 이미 권한을 승인받음
            }
        }
    }

    private fun hasPermissions(permissions: List<String>): Boolean = permissions.all {
        ActivityCompat.checkSelfPermission(
            this,
            it
        ) == PackageManager.PERMISSION_GRANTED
    }

    companion object {
        private val BLUETOOTH_PERMISSIONS = listOf(
            Manifest.permission.BLUETOOTH_SCAN,
            Manifest.permission.BLUETOOTH_CONNECT,
            Manifest.permission.BLUETOOTH_ADVERTISE
        )
    }
}


권한 요청 플로우

image

안드로이드 디벨로퍼에서는 위와 같은 앱 권한 요청에 대한 올바른 플로우를 제공하고 있습니다.

또한 런타임 권한의 경우, 사용자가 특정 권한에 대한 승인 요청을 2회 거부하게 되면 그 이후에는 앱에서 해당 권한에 대한 사용자 승인 요청을 보낼 수 없게 됩니다. 따라서 이러한 플로우까지 대비하기 위해선 2회 거부 이후부터는 사용자가 직접 해당 앱 권한 설정으로 이동하도록 유도하는 기능 구현이 별도로 필요합니다.


참고

0%