본문 바로가기
깡샘 코틀린

10-2 퍼미션 설정하기

by 농농씨 2023. 6. 24.

퍼미션(permission)이란 앱의 특정 기능에 부여하는 접근 권한을 말한다. 내가 개발하는 앱이 다른 앱이나 안드로이드 시스템에서 보호하는 특정 기능을 이용할 때 퍼미션 사용을 설정해야 한다. 마찬가지로 내가 만든 기능을 다른 앱에서 사용할 수 없도록 보호하고 권한을 얻은 앱에서만 허용하고 싶을 때 퍼미션을 설정한다. 

 

 

퍼미션 설정과 사용 설정

퍼미션을 이해하고자 두 앱을 연동하는 상황을 가정해보자.

A 앱B 앱이 있고 A 앱의 컴포넌트를 B앱에서 사용하는 상황을 예로 들겠다.

B 앱에서 A 앱의 컴포넌트와 연동하는 코드만 잘 구현했다면 A앱의 컴포넌트를 B앱에서 얼마든지 사용할 수 있다. 그런데 만약 A앱의 컴포넌트에 퍼미션을 설정하면 B앱에서 연동할 때 문제가 발생한다.(B가 A에 접근할 때 문제가 생긴다)

A앱의 개발자가 매니페스트 파일에 <permission> 태그로 퍼미션을 설정하면 이를 이용하는 B앱의 코드를 아무리 잘 구현하더라도 실행되지 않는다. 이때는 B 앱의 매니페스트 파일에 <uses-permission> 태그로 해당 퍼미션을 이용하겠다고 설정해줘야 한다.

 

  • <permission> : 기능을 보호하려는 앱의 매니페스트 파일에 설정한다.
  • <uses-permission>: 퍼미션으로 보호된 기능을 사용하려는 앱의 매니페스트 파일에 설정한다.

이처럼 앱의 컴포넌트를 보호하고 싶을 때 매니페스트 파일에서 퍼미션을 설정할 수 있으며, 이렇게 퍼미션으로 보호받는 앱을 이용하는 외부 앱은 매니페스트 파일에 해당 퍼미션을 사용하겠다고 설정해야 한다. 

매니페스트 파일에 퍼미션을 설정할 때는 <permission> 태그와 다음 속성을 이용한다.

  • name: 퍼미션의 이름
  • label, description: 퍼미션을 설명함
  • protectionLevel: 보호 수준
// 퍼미션 설정
<permission android:name="com.example.permission.TEST_PERMISSION"
	 android:label="Test Permission"
     android:desciption="@string/permission_desc"
     android:protectionLevel="dangerous" />

name 속성값은 개발자가 정하는 이름으로, 퍼미션을 구별하는 식별자 역할을 한다. label과 description 속성값은 이 퍼미션을 이용하는 외부 앱에서 권한 인증 화면에 출력할 퍼미션의 정보이다. 

그리고 protectionLevel 속성값은 보호 수준을 의미하며 다음과 같은 값을 지정할 수 있다.

  • normal: 낮은 수준의 보호이다. 사용자에게 권한 허용을 요청하지 않아도 됨.
  • dangerous: 높은 수준의 보호이다. 사용자에게 권한 허용을 요청해야 함.
  • signature: 같은 키로 인증한 앱만 실행한다.
  • signatureOrSystem: 안드로이드 시스템 앱이거나 같은 키로 인증한 앱만 실행한다.

protectionLevel의 속성값은 보통 normal, dangerous, signature를 사용한다. normal로 설정하면 이 기능을 사용하는 앱이 퍼미션 사용 설정을 해야 하지만 사용자에게 권한 허용을 요청하지 않아도 되므로 부담스럽지 않다. 반면에 dangerous로 설정하면 퍼미션 사용 설정은 물론 사용자에게도 권한 허용을 요청해야 한다.

예를 들어 어떤 앱의 매니페스트 파일에 다음처럼 <uses-permission>을 2개 설정했다고 가정하자.

// 퍼미션 사용 설정
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

ACCESS_NETWORK_STATE와 ACCESS_FINE_LOCATION은 시스템에 선언된 퍼미션으로 각각 네트워크에 접근하고 사용자 위치를 추적하는 데 필요하다. ACCESS_NETWORK_STATE는 protectionLevel이 normal이고 ACCESS_FINE_LOCATION은 dangerous로 설정되어 있다.

이렇게 퍼미션을 2개 사용하는 것으로 설정했지만 이 앱을 설치한 후 앱의 권한 화면*을 보면 보호 수준이 dangerous로 설정된 퍼미션만 나온다.

*앱의 권한화면:설정->애플리케이션(또는 '앱 정보')에서 확인

 

매니페스트 파일에 <permission>을 설정했다고 해서 컴포넌트가 보호되지는 않는다. <permission>을 설정한 다음 이 퍼미션으로 보호하려는 컴포넌트에 적용해야 한다. 퍼미션을 컴포넌트에 적용할 때는 android:permission 속성을 이용해야 한다. 

// 컴포넌트에 퍼미션 적용
<activity android:name=".OneActivity" android:permission="com.example.TEST_PERMISSION"
					 // android:permission 속성 사용됨
	<intetn-filter>
    	<>action android:name="android.intent.action.PICK" />
    </intent-filter>
</activity>

이제 이 컴포넌트는 com.example.TEST_PERMISSION에 의해 보호되며 이 컴포넌트를 이용하는 곳에서는 매니페스트 파일에 <uses-permission>을 선언해 줘야 정상으로 실행된다.

// 퍼미션 사용 설정
<uses-permission android:name="com.example.permission.TEST_PERMISSION" />

 

이처럼 외부 앱과 연동할 때 퍼미션 사용을 설정해야 하지만 안드로이드 시스템에서 보호하는 기능을 사용할 때도 매니페스트 파일에 퍼미션 사용 설정을 해야 한다. 시스템이 보호하는 기능은 대표적으로 다음과 같다.

ACCESS_FINE_LOCATION: 위치 정보 접근

ACCESS_NETWORK_STATE: 네트워크 정보 접근

ACCESS_WIFI_STATE: 와이파이 네트워크 정보 접근

BATTERY_STATS: 배터리 정보 접근

BLUETOOTH: 블루투스 장치에 연결

BLUETOOTH_ADMIN: 블루투스 장치를 검색하고 페어링

CAMERA: 카메라 장치에 접근

INTERNET: 네트워크 연결

READ_EXTERNAL_STORAGE: 외부 저장소에서 파일 읽기

WRITE_EXTERNAL_STORAGE: 외부 저장소에서 파일 쓰기

READ_PHONE_STATE: 전화기 정보 접근

SEND_SMS: 문자 메시지 발신

RECEIVE_SMS: 문자 메시지 수신

RECEIVE_BOOT_COMPLETED: 부팅 완료 시 실행

VIBRATE: 진동 울리기

 

 

퍼미션 허용 확인

퍼미션은 API 레벨 1 버전부터 있었던 내용이지만 API 레벨 23(android 6) 버부터 정책이 바뀌었다. API 레벨 23 이전에는 앞에서 살펴본 바와 같이 개발자가 매니페스트 파일에 <uses-permission>으로 선언만 하면 보호받는 기능을 앱에서 이용하는 데 문제가 없었다. 왜냐하면 사용자는 앱의 권한 화면에서 이 앱이 어떤 기능을 이용하는지 확인만 할 수 있었기 때문이다. 결국 개발자가 "이 퍼미션을 이용한다."라고 선언만 하면 되는 일종의 신고제였다. 

그런데 API 레벨 23 버전부터 허가제로 바뀌었다. 즉, 개발자가 <uses-permission>으로 선언했더라도 사용자가 권한 화면에서 이를 거부할 수 있게 되었다

만약 사용자가 앱의 권한 설정에서 특정 퍼미션을 거부하면 <uses-permission>을 선언하지 않은 것과 같으며 앱에서는 해당 기능을 이용할 수 없다. 결국 API 레벨 23 버전부터는 1️⃣매니페스트 파일에 <uses-permission>을 선언하는 것뿐만 아니라 2️⃣앱을 실행할 때 사용자가 퍼미션을 거부했는지 확인하고 3️⃣만약 거부했으면 다시 퍼미션을 허용해달라고 요청해야 한다.

❓사용자가 권한 허용을 거부했는데 다시 확인하고 또 요청하는 이유는?

❗️사용자가 퍼미션을 거부한 것을 까먹었을 수도 있기 때문. 그리고 사용자가 앱을 처음 설치하면 기본으로 거부상태라서 처음 실행할 때 퍼미션을 확인해서 앱을 이용하는 데 필요한 퍼미션을 허용해달라고 요청해야 함.

 

사용자가 퍼미션을 허용했는지 확인하려면 checkSelfPermission() 함수를 이용한다.

// 퍼미션 허용 확인 함수
open static fun checkSelfPermission(
	@NonNull context: Context,
    @NonNull permission: String
): Int

두번째 매개변수가 퍼미션을 구분하는 이름이며 결괏값은 다음 중 하나의 상수로 전달된다. 

  • PackageManager.PERMISSION_GRANTED: 권한을 허용한 경우
  • PackageManager.PERMISSION_DENIED: 권한을 거부한 경우
// 퍼미션 허용 확인 예
val status = ContextCompat.checkSelfPermission(this, // checkSelfPermission() 함수 사용하여 퍼미션 허용 확인
	"android.permission.ACCESS_FINE_LOCATION")
if (status == PackageManager.PERMISSION_GRANTED) {
	Log.d("kkang", "permission granted")
} else {
	Log.d("kkang", "permission denied")
}

만약 퍼미션을 거부한 상태라면 사용자에게 해당 퍼미션을 허용해 달라고 요청해야 한다. 사용자에게 퍼미션 허용을 요청할 때는 ActivityResultLauncher를 이용한다. 이 클래스는 액티비티에서 결과를 돌려받아야 할 때 사용하며 대표적으로 퍼미션 허용 요청과 다른 액티비티를 실행하고 결과를 돌려받을 때이다.

ActivityResultLauncher 객체는 registerForActivityResult() 함수를 호출해서 만든다.

// registerForActivityResult() 함수
public final <I, 0> ActivityResultLauncher<I> registerForActivityResult(
	@NonNull ActivityResultContract<I, 0> contract,
    @NonNull ActivityResultCallback<0> callback)

registerForActivityResult() 함수는 매개변수가 2개이다. 첫 번째는 어떤 요청인지를 나타내는 ActivityResultContract 타입 객체이며 다양한 요청에 대응하는 서브 클래스들이 있다. 대표적으로 다른 액티비티를 실행하고 결과를 돌려받을 때는 StartActivityForResult, 퍼미션 허용을 요청할 때는 RequestPermission을 사용한다. 

그리고 registerForActivityResult() 함수의 두 번째 매개변수는 결과를 받았을 때 호출되는 콜백이다.(? 흐음... 콜백?)

// 퍼미션 허용 요청 확인
val requestPermissionLauncher = registerForActivityResult(
    // 객체 만듦				// 함수를 통해서!
	ActivityResultContracts.RequestPermission() // 첫번째 매개변수, 무슨 요청인지 나타냄
    									// 여기서는 퍼미션 허용을 요청함.
) { isGranted ->
	if (isGranted) { // 허용하면
    	Log.d("kkang", "callback, granted..")
    } else { // 거부하면
    	Log.d("kkang", "callback, denied..")
    }
}

registerForActivityResult() 함수로 ActivityResultLauncher 객체를 만들었다면 필요한 곳에서 ActivityResultLauncher 객체의 launch() 함수를 호출하여 요청을 실행한다.

// 퍼미션 허용 요청 실행
requestPermissionLauncher.launch("android.permission.ACCESS_FINE_LOCATION")

요청 결과는 registerForActivityResult() 함수의 두번째 매개변수로 등록한 콜백으로 전달된다. 위 예제는 퍼미션 허용 요청과 결과를 받는 방법이었고 액티비티를 실행하고 결과를 돌려받는 방법은 이후에 다룰 것이다.

 

콜백이란?

 

'깡샘 코틀린' 카테고리의 다른 글

10-4 소리와 진동 알림  (0) 2023.06.25
10-3 다양한 다이얼로그  (0) 2023.06.24
10-1 API 레벨 호환성 고려하기  (2) 2023.06.24
09-3 폰 크기의 호환성  (0) 2023.06.23
09-2 리소스 조건 설정  (0) 2023.06.23