깡쌤의 안드로이드 프로그래밍 책을 보며 작성하였습니다.
퍼미션(Permission)이란, 개발자 코드의 알고리즘이 아니라 AndroidManifest.xml에 들어가는 설정입니다. 안드로이드 컴포넌트를 이용한 앱과 앱 사이의 연동이 빈번한데, 이러한 연동에서 어떤 앱이 <permission>
을 부여했다면 그 앱을 이용하는 앱은 <uses-permission>
을 선언해야 합니다. 이는 앱과 앱 간의 문제일 수도 있고, 시스템에서 특정 기능에 퍼미션을 부여하는 경우도 있습니다.
<permission>
이용<permission>
은 자신의 앱을 외부에서 이용할 때 권한을 부여하여 해당 권한을 가지고 들어올 때만 실행되게 하기 위한 설정입니다. 즉, <permission>
으로 선언된 앱을 이용하는 앱이 <uses-permission>
을 선언하지 않으면 실행 시 에러가 발생합니다.
TestApp에서 TargetApp의 컴포넌트를 실행한다고 가정하겠습니다. 이 때, TargetApp에서 해당 컴포넌트에 <permission>
을 부여하여 보호하고 있는데 TestApp에서 오류가 발생하지 않고 해당 컴포넌트를 실행하기 위해서는 꼭 <uses-permission>
을 선언해서 사용해야 합니다.
정리하자면 앱의 컴포넌트를 보호하고 싶을 때 <permission>
을 선언하고, 그렇게 선언된 앱을 이용하려면 <uses-permission>
을 선언하는 구조입니다.
<!-- TargetApp의 Permission 생성 코드 -->
<!-- name : 퍼미션 레벨 -->
<!-- label, description : 퍼미션에 대한 설명 -->
<!-- protectionLevel : 보호 수준 -->
<permission
android:name="kr.co.lee.permission.TargetPermission"
android:description="Permission 예제입니다"
android:label="Target Permission"
android:protectionLevel="normal" />
<!-- 컴포넌트에 정의한 퍼미션 적용 -->
<activity
android:name=".TargetActivity"
android:permission="kr.co.lee.permission.TargetPermission">
<intent-filter>
<action android:name="Target" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- TestApp에서 TargetApp의 컴포넌트를 실행하기 위한 uses-permisson 선언 -->
<!-- permission의 이름으로 설정 -->
<uses-permission android:name="kr.co.lee.permission.TargetPermission" />
우선 컴포넌트를 퍼미션으로 보호하고 싶으면 <permission>
태그를 추가합니다.
퍼미션 태그안의 속성은 다음과 같습니다.
protectionLevel의 보호 수준은 다음과 같습니다
퍼미션을 선언할 때 protectionLevel 속성을 선언하는데, 보통 "normal", "dangerous", "signature" 중 하나의 값으로 설정합니다. 어떤 값으로 선언하는지에 따라 동작이 달라집니다.
normal로 선언하면 <uses-permission>
은 요구하지만 사용자에게 퍼미션 부여를 요구하지는 않습니다.
"dangerous"로 선언하면 <uses-permission>
도 요구하고 사용자에게 퍼미션 부여도 요구합니다.
"signature"는 외부 앱이 같은 키로 서명되어 있어야 실행됩니다.
위의 링크를 클릭하면 시스템 퍼미션관련 정보가 나오는데 퍼미션 정보와 protectionLevel에 대한 설명이 나와있습니다.
퍼미션이 앱과 앱 사이의 문제일 수도 있지만, 어떤 퍼미션은 시스템에서 선언한 것도 있습니다. 다른 앱을 연동하는 것도 아닌데 특정 기능을 시스템에서 보호하고 있어서 앱에서 그 기능을 이용할 때 <uses-permission>
을 선언하지 않으면 에러가 발생합니다.
대표적인 예가 사용자 위치 정보를 사용할 때 입니다. 사용자 위치 정보는 외부 앱 연동이 아님에도 시스템 자체에서 퍼미션으로 보호하고 있어서 <uses-permission>
을 선언해 주어야 합니다.
시스템의 퍼미션 중 이용빈도가 높은 퍼미션으로는 다음과 같습니다.
퍼미션 제목 | 내용 |
---|---|
ACCESS_FINE_LOCATION | 정확한 위치 정보 액세스 |
ACCESS_NETWORK_STATE | 네트워크에 대한 정보 액세스 |
ACCESS_WIFI_STATE | 와이파이 네트워크에 대한 정보 액세스 |
BATTERY_STATS | 배터리 통계 수집 |
BLUETOOTH | 연결된 블루투스 장치에 연결 |
BLUETOOTH_ADMIN | 블루투스 장치를 검색하고 페어링 |
CALL_PHONE | 다이얼 UI를 거치치 않고 전화를 시작 |
CAMERA | 카메라 장치에 액세스 |
INTERNET | 네트워크 연결 |
READ_CONTACTS | 사용자의 연락처 데이터 읽기 |
READ_EXTERNAL_STORAGE | 외부 저장소에서 파일 읽기 |
READ_PHONE_STATE | 장치의 전화번호, 네트워크 정보, 진행 중인 통화 상태 등 전화 상태에 대한 읽기 |
READ_SMS | SMS 메시지 읽기 |
RECEIVE_BOOT_COMPLETED | 부팅 완료 시 수행 |
RECORD_AUDIO | 오디오 녹음 |
SEND_SMS | 메시지 발신 |
VIBRATE | 진동 울리기 |
WRITE_CONTACTS | 사용자의 연락처 데이터 읽기 |
WRITE_EXTERNAL_STORAGE | 외부 저장소에 파일 쓰기 |
만약 시스템 퍼미션 중 protectionLevel이 dangerous(위험)인 것들은 <uses-permission>
을 AndroidManifest.xml에 정의한다고 하더라도 끝나는 것이 아닙니다. 코드에서 권한을 확인하고 거부상태이면 다이얼로그를 통한 권한 요청 등을 수행하는 식으로 알고리즘을 작성해야 합니다. 그 상황에서 사용하는 함수들 입니다.
class TargetActivity : AppCompatActivity() {
// 위치 권한 확인용 변수
var locationFlag: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_target)
// 권한 확인 용 메소드
// 매개변수 첫 번째로는 Context, 두 번째는 확인 할 권한
// 반환값으로 PackageMananger.PERMISSION_GRANTED(권한 허용) or PackageManager.PERMISSION_DENIED(권한 거부)가 들어온다
if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// 다이얼로그를 통한 권한 요청하기
// 첫 번째 매개변수는 Activity, 두 번째 매개변수는 권한목록 배열(들어온 권한의 순서대로 다이얼로그가 차례대로 뜬다
// 세 번째 매개변수는 다이얼로그가 닫히면 자동으로 호출되는 onRequestPermissionResult에서 퍼미션을 구분하기 위한 값
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 100)
} else { // 권한이 허용일 경우
locationFlag = true
}
}
// ActivityCompat.requestPermissions이 끝나면 자동으로 호출되는 메소드
// 첫 번째 매개변수는 requestPermissions에서 지정한 상태코드 값
// 두 번째 매개변수는 요청한 퍼미션 이름
// 세 번째 매개변수는 사용자가 조정한 결과 값
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
// 요청코드가 100번이고 결과가 있는 경우
if(requestCode == 100 && grantResults.isNotEmpty()) {
if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission 허용시 코드
locationFlag = true
} else if(grantResults[0] == PackageManager.PERMISSION_DENIED) {
// Permission 거부시 코드
locationFlag = false
}
}
return super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}