will come true

[Android/Kotlin] 퍼미션(Permission) 권한 확인 및 설정하기 본문

Android

[Android/Kotlin] 퍼미션(Permission) 권한 확인 및 설정하기

haehyun 2022. 5. 3. 18:06

퍼미션(Permission)

  • 안드로이드 앱의 특정 기능에 부여하는 접근 권한 (ex: 카메라 촬영 퍼미션, 전화 발신 퍼미션 등)
  • 내가 개발하는 앱이 다른 앱이나 안드로이드 시스템에서 보호하는 특정 기능을 이용할 때 퍼미션 사용을 설정해야 한다. (다른 앱의 보호된 기능을 사용할 때)
  • 또는 내가 만든 앱 기능을 다른 앱에서 사용할 수 있도록 보호하고 권한을 얻은 앱에서만 허용하고 싶을 때 퍼미션을 설정한다. (내 앱의 기능을 다른앱으로부터 보호할 때)
  • 즉, 앱끼리 서로 연동하고 상호작용할 때 필요한 절차이다.
  • 예를 들어, 카X카오톡과 같은 메신저 앱을 만들고 친구 프로필을 누르면 전화번호가 표시되면 내가 만든 앱에서도 [전화걸기] 버튼만 누르면 바로 친구에게 전화가 걸리게 하고 싶다면 안드로이드 시스템으로부터 '전화 발신' 기능에 대한 퍼미션을 얻어야 한다.(앱에서 퍼미션을 요청하는 순간 아래 사진처럼 '{앱 이름}에서 ~기능을 이용하도록 허용하시겠습니까?' 메시지가 뜨게되고, 여기서 '허용'을 클릭하면 퍼미션을 허용하는 게 된다.) 물론 퍼미션은 '이 기기에서 해당 앱이 특정 기능을 사용하는 걸 허가한다'는 허용 행위일 뿐이기 때문에, 실제로 전화걸기 기능을 구현하기 위해서는 전화 인텐트(ACTION_CALL)를 실행하는 코드를 따로 작성해야 '전화걸기' 라는 기능을 구현할 수 있다.

  • 안드로이드 기기에서 [설정 > 앱 정보 > 앱 권한]에 접속하면 각 앱에 설정된 권한을 확인할 수 있다. 여기서 [앱 권한]에 표시되는 퍼미션들이 개발자가 Manifest.xml 파일에 <uses-permission> 태그로 기재해둔 퍼미션 목록이다.

 

1. 퍼미션 사용 설정

이 앱이 어떤 퍼미션을 요청·사용할지 그 목록을 미리 매니페스트(Manifest)에 설정한다. <uses-permission> 태그의 name 속성에 퍼미션 종류를 기재한다. 요청할 퍼미션 개수대로 <uses-permission> 태그를 추가한다.

*퍼미션 종류 : https://developer.android.com/reference/android/Manifest.permission

 

API 레벨 23 이전에는 이와같이 개발자가 매니페스트 파일에 <uses-permission>으로 선언만 하면 바로 보호받는 기능을 앱에서 이용할 수 있었지만(=이러한 기능을 사용하겠다는 통보식), API 레벨 23 버전부터는 퍼미션이 '허가제'로 바뀌었기 때문에 사용자로부터 직접 '허가'를 받아야만 특정 기능을 이용할 수 있다. 즉, API 레벨이 22(롤리팝)이던 2014년만 해도 "이 앱이 기기의 위치 정보에 접근하는 걸 허용하시겠습니까? 거부 / 허용" 과 같은 메시지 창이 안떴으나 어느 순간부터 위와같은 퍼미션 요청창이 일상적으로 뜨는 걸 볼 수 있다.

 

2. 퍼미션 허용 확인

사용하려는 권한을 이미 부여받았는지 확인한다. 사용자가 이전에 한번이라도 퍼미션 요청을 허가했으면 이후로는 퍼미션 요청 메시지가 뜨지 않도록 해야한다. 참고로 앱을 설치한 후 초기 퍼미션은 모두 거부 상태이기 때문에 필요한 권한을 허용해 달라고 사용자에게 요청해야 한다.

  • 이전에 퍼미션을 허용했을 경우 → 앱 기능 실행
  • 이전에 퍼미션을 거부했을 경우 → 퍼미션 허용 재요청 + 필요 이유 설명
  • 최초로 퍼미션 요청을 받았을 경우 → 퍼미션 허용 요청

 

이번 예제에서는 메인화면에 버튼을 하나 배치시키고, 이 버튼을 누르면 '카메라(CAMERA)' 권한을 요청하도록 한다. 

package com.example.myapplication

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val btnCameraPermission: Button = findViewById(R.id.btnCameraPermission)
        // 2. 퍼미션 허용 확인
        btnCameraPermission.setOnClickListener {
            val status = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            if (status == PackageManager.PERMISSION_GRANTED) {
                // 퍼미션이 허용되어 있는 경우
                Toast.makeText(this, "Permission granted for camera.", Toast.LENGTH_LONG).show()
            } else {
                // 퍼미션이 거부되어 있는 경우 (default)
                Toast.makeText(this, "Permission denied for camera.", Toast.LENGTH_LONG).show()
            }
        }
    }
}

 

※ 이때 카메라 퍼미션 문자열 상수를 가지고 있는 Manifest 클래스를 참조하게 되는데, 'android.Manifest'를 Import해야 한다. 같은 이름의 'java.util.jar.Manifest'가 Import 되지 않도록 주의하자.

 

ContextCompat.checkSelfPermission(context: Context, permission: String)

앱이 이미 특정 기능에 대한 권한을 부여받았는지 확인한다.

Parameters
permission 앱에서 요청하길 원하는 퍼미션 종류
Return
int PackageManager.PERMISSION_GRANTED - 권한을 허용한 경우
PackageManager.PERMISSION_DENIED - 권한을 거부한 경우

 

shouldShowRequestPermissionRationale(activity: Activity, permission: String)

사용자가 이전에 퍼미션 요청을 거부한 경우 true를 반환한다. 이 메서드의 반환값으로 사용자가 이전에 퍼미션 요청을 받았으나 거부한 경우 / 퍼미션 요청을 최초로 받은 경우를 구분하여 처리할 수 있다. (ex: 각 경우에 따라 다른 내용의 다이얼로그 표시하기)

Parameters
activity 대상 액티비티
permission 앱에서 허용을 요청하는 퍼미션 이름
*Manifest.permission 에 정의된 문자열 상수를 사용한다.
Return
boolean 퍼미션 요청 이유를 표시해야하는지 여부

 

3. 퍼미션 허용 요청

앱을 최초 실행했거나, 이전에 퍼미션을 거부한 경우 퍼미션 허용을 요청하는 메시지창을 표시한다. String 배열을 사용해 한번에 여러 개의 퍼미션을 요청할 수 있다. requestPermissions() 메서드를 이용한 퍼미션 요청 결과(=사용자 응답)는 onRequestPermissionResult() 에서 받아 확인할 수 있다. 

앞에서 호출한 checkSelfPermission() 메서드 결과값에 따라 퍼미션이 거부되어 있을 경우에만 권한을 요청한다.

package com.example.myapplication

import ...

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val btnCameraPermission: Button = findViewById(R.id.btnCameraPermission)
        // 2. 퍼미션 허용 확인
        btnCameraPermission.setOnClickListener {
            val status = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            if (status == PackageManager.PERMISSION_GRANTED) {
                // 퍼미션이 허용되어 있는 경우
                Toast.makeText(this, "Permission granted for camera.", Toast.LENGTH_LONG).show()
            } else {
                // 퍼미션이 거부되어 있는 경우 (default)
                Toast.makeText(this, "Permission denied for camera.", Toast.LENGTH_LONG).show()
                // 3. 퍼미션 허용 요청
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf<String>(Manifest.permission.CAMERA),
                    100
                )
            }
        }
    }
}

 

requestPermissions(activity: Activity, permissions: Array<String>, requestCode: int)

사용자에게 퍼미션 허용을 요청한다. 이 함수가 호출되면 퍼미션을 요청하는 다이얼로그가 화면에 표시된다. 함수의 반환값은 따로 없고, 퍼미션 요청 결과는 onRequestPermissionResult() 함수에 전달된다.

Parameters
activity 대상 액티비티
permission 앱에서 허용을 요청하는 퍼미션 목록 (문자열 배열로 한번에 여러 개 요청 가능)
requestCode onRequestPermissionResult() 에서 구분용으로 사용할 요청 코드

 

4. 퍼미션 요청 결과 확인, 처리

퍼미션 허용 요청 메시지창에 대한 사용자의 반응인 퍼미션 허용 요청 결과를 확인한다.

  • 퍼미션을 허용할 경우  앱 기능 실행
  • 퍼미션을 허용하지 않을 경우 앱 기능 실행 안함 (해당 기능을 실행하기 위해선 반드시 퍼미션이 필요함)
package com.example.myapplication

import ...

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val btnCameraPermission: Button = findViewById(R.id.btnCameraPermission)
        // 2. 퍼미션 허용 확인
        btnCameraPermission.setOnClickListener {
            val status = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            if (status == PackageManager.PERMISSION_GRANTED) {
                // 퍼미션이 허용되어 있는 경우
                Toast.makeText(this, "Permission granted for camera.", Toast.LENGTH_LONG).show()
            } else {
                // 퍼미션이 거부되어 있는 경우 (default)
                Toast.makeText(this, "Permission denied for camera.", Toast.LENGTH_LONG).show()
                // 3. 퍼미션 허용 요청
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf<String>(Manifest.permission.CAMERA),
                    100
                )
            }
        }
    }

    // 4. 퍼미션 허용 요청 결과 확인
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 사용자가 퍼미션을 허용함
            Toast.makeText(this, "Permission granted for camera.", Toast.LENGTH_LONG).show()
        } else {
            // 사용자가 퍼미션을 거부함
            Toast.makeText(this, "Permission denied for camera.", Toast.LENGTH_LONG).show()
        }
    }
}

onRequestPermissionsResult(String[], int[])

이 함수를 선언해 놓으면 퍼미션 요청 다이얼로그가 닫힐 때 자동으로 호출되며, 사용자가 다이얼로그에서 퍼미션을 허용했는지 요청 결과를 grantResults 매개변수로 받는다.

Parameters
requestCode requestPermissions() 메서드로 퍼미션 허용을 요청했을 때 사용자가 설정해둔 요청 코드
permissions 앱에서 허용을 요청한 퍼미션 목록
grantResults 다이얼로그에서 요청한 퍼미션의 결과값

 

+) ActivityResultLauncher를 사용한 퍼미션 요청 방법

[MainActivity.kt] *Manifest, main_activity 코드는 위 예제와 동일

package com.example.permissionsdemo

import android.Manifest
import android.app.AlertDialog
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts

class MainActivity : AppCompatActivity() {

    // 카메라 퍼미션 요청
    private val cameraResultLauncher : ActivityResultLauncher<String> =
        registerForActivityResult(ActivityResultContracts.RequestPermission()){ isGranted ->
            if (isGranted) {
                Toast.makeText(this, "Permission granted for camera.", Toast.LENGTH_LONG).show()
            } else {
                Toast.makeText(this, "Permission denied for camera.", Toast.LENGTH_LONG).show()
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val btnCameraPermission: Button = findViewById(R.id.btnCameraPermission)
        btnCameraPermission.setOnClickListener {
            // 카메라 퍼미션 확인
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
                // API 23 이상이고, 이전에 퍼미션을 거부했을 경우
                showRationaleDialog("Permission Demo requires camera access", "Camera cannot be used because Camera access is denied")

            } else {
                // API 23 미만이거나 최초로 퍼미션 요청을 받았을 경우
                cameraResultLauncher.launch(Manifest.permission.CAMERA)
            }
        }
    }

    // 특정 퍼미션이 필요한 이유를 설명하는 다이얼로그, 사용자가 이전에 퍼미션 요청을 거부했을 경우에 표시
    private fun showRationaleDialog(title: String, message:String) {
        val builder: AlertDialog.Builder = AlertDialog.Builder(this)
        // 타이틀, 메시지, 버튼 1개 설정
        builder.setTitle(title)
            .setMessage(message)
            .setPositiveButton("Cancel"){ dialog, _ ->
                dialog.dismiss()
            }
        builder.create().show()
    }
}

 

(좌) 최초로 퍼미션 허용 요청을 받았을 경우 / (우) 이전에 퍼미션 허용 요청을 거부했던 경우

 


[참고자료]

 

Comments