만족

[Android] 권한 (Permission) 본문

[Android] 권한 (Permission)

FrontEnd/Android Satisfaction 2021. 4. 15. 21:34

developer.android.com/training/permissions/requesting?hl=ko

 

앱 권한 요청  |  Android 개발자  |  Android Developers

모든 Android 앱은 액세스가 제한된 샌드박스에서 실행됩니다. 앱이 자체 샌드박스 밖에 있는 리소스나 정보를 사용해야 하는 경우 권한을 선언하고 이 액세스를 제공하는 권한 요청을 설정할 수

developer.android.com

안드로이드에는 각 동작마다 권한이 존재한다.

 

예를 들어, 앱에서 카메라를 사용하는 것도 '카메라 사용 권한'이 필요하고,

내장 메모리를 사용하는 것도 '스토리지 접근 권한'이 필요하다.

 

단순 인터넷 사용 권한 같은 "사용자의 개인정보에 영향을 미치지 않는 권한들은 일반권한"으로 분류되어

manifest에 명시만 하면 별도 허락 없이도 사용할 수 있다.


그러나 특정 권한은 위험 권한으로 분류되어, 사용자에게 반드시 허락을 받아야만 사용할 수 있다.

 

가령 내장 메모리 접근 권한은 사용자에게 허락을 받지 않는 이상,

그 앱은 임의로 저장공간에 접근할 수 없게 된다.

 

옛날에는 이런 위험 권한이 사용자의 허락을 받지 않고도 사용할 수 있어서

손전등 앱에 저장공간 접근, 연락처 접근, 카메라 접근 등 모든 권한을 마음대로 쓰곤 했다.

(아마 개인정보도 상당히 유출되었을 것이다... 꽤 유명한 앱이였다)

 

모든 종류에 대한 권한은 아래 링크에서 확인할 수 있다

 

developer.android.com/reference/android/Manifest.permission?hl=ko

 

Manifest.permission  |  Android 개발자  |  Android Developers

 

developer.android.com

순서

위험 권한을 사용하기 위해서는 두 단계가 필요하다.

 

1. 매니페스트에 어떤 종류의 권한을 사용하는지 명시

2. 실제 그 권한을 사용하는 Activity에서 허용 여부를 판단하고, 권한 허용 요청

 

딱 봐도 귀찮지만 어쩔 수 없다.

 

매니페스트에 명시

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="kr.ac.konkuk.cse">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        ....
        />
        
</manifest>

AndroidManifest.xml의 manifest레벨에

<use-permission android:name="권한 이름"/>

을 추가해 어떤 종류의 권한을 사용할 것인지 명시한다.

 

예시에서는 CALL_PHONE (전화 걸기) 권한을 사용한다고 가정하자.

실제 그 권한을 사용하는 Activity에서 허용 여부를 판단하고 권한 허용 요청

class IntentActivity : AppCompatActivity() {
    lateinit var binding: ActivityIntentBinding
    val CALL_REQUEST = 100

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

        binding = ActivityIntentBinding.inflate(layoutInflater)
        setContentView(binding.root)

        init()
    }

    fun callAction() {
        val number = Uri.parse("tel:010-1234-1234")
        val callIntent = Intent(Intent.ACTION_CALL, number)

        if (
        
            android.os.Build.VERSION.SDK_INT >= 23 
            && 
            ActivityCompat.checkSelfPermission(
                this,
                android.Manifest.permission.CALL_PHONE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            Toast.makeToast(this, "CALL_PHONE권한이 필요합니다", Toast.SHORT).show()
            
             ActivityCompat.requestPermissions(
                    this,
                    arrayOf(android.Manifest.permission.CALL_PHONE),
                    CALL_REQUEST
                )
            return
        }
        startActivity(callIntent)
    }

    fun init() {
        with(binding) {
            callbtn.setOnClickListener {
                callAction()
            }
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            CALL_REQUEST -> {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "권한 승인", Toast.LENGTH_LONG).show()
                    callAction()
                } else {
                    Toast.makeText(this, "권한 거절", Toast.LENGTH_LONG).show()
                }
            }
        }
    }
}

 

어질어질하다...그죠?

 

callAction()과 onRequestPermissionResult(...) 만 집중해서 보면 된다.

 

callAction()

    fun callAction() {
        val number = Uri.parse("tel:010-1234-1234")
        val callIntent = Intent(Intent.ACTION_CALL, number)

        if (
            android.os.Build.VERSION.SDK_INT >= 23 
            && 
            ActivityCompat.checkSelfPermission(
                this,
                android.Manifest.permission.CALL_PHONE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            Toast.makeToast(this, "CALL_PHONE권한이 필요합니다", Toast.SHORT).show()
            
             ActivityCompat.requestPermissions(
                    this,
                    arrayOf(android.Manifest.permission.CALL_PHONE),
                    CALL_REQUEST
                )
            return
        }
        startActivity(callIntent)
    }

 

CALL_PHONE 권한이 있는지 확인하고,

권한이 없다면 사용자에게 해당 권한이 필요하다는 메시지를 출력하고

권한이 있다면 010-1234-1234로 전화를 거는 함수다.

 

가장 먼저, 맨 처음에 위험 권한의 요청은 안드로이드 6.0(API 23) 부터 사용한다고 했으므로

android.os.Build.VERSION.SDK_INT >= 23 

해당 비교 구문을 적용해 23 미만일 경우 권한 체크/승인 절차를 무시하게 작성한다.

ActivityCompat.checkSelfPermission(this, android.Manifest.permission.CALL_PHONE) 
	!= PackageManager.PERMISSION_GRANTED

ActivityCompat.checkSelfPermission은 전달된 권한이 허용되어있는지 아닌지를 리턴한다.

만약 권한이 허용되어 있지 않다면 

 

그런데 리턴값은 boolean 타입이 아닌 Int타입의 상수이기 때문에

== PackageManager.PERMISSION_GRANTED //허용됨

== PackageManager.PERMISSION_DENIED //허용되지 않음

처럼 비교해주면 된다.

 

if (
   android.os.Build.VERSION.SDK_INT >= 23 
   && 
   ActivityCompat.checkSelfPermission(
         this,
         android.Manifest.permission.CALL_PHONE
   ) != PackageManager.PERMISSION_GRANTED
) {
   Toast.makeToast(this, "CALL_PHONE권한이 필요합니다", Toast.SHORT).show()
            
   ActivityCompat.requestPermissions(
          this,
          arrayOf(android.Manifest.permission.CALL_PHONE),
          CALL_REQUEST
          )
   return
}

다시 코드로 돌아오면 if문에서

ActivityCompat.checkSelfPermission(...) != PackageManager.PERMISSION_GRANTED 로 비교하므로,

if문 블록은 CALL_PHONE 권한이 없을 때 실행된다.

 

권한이 허용되지 않았을 때 "CALL_PHONE권한이 필요합니다"가 표시되고

ActivityCompat.requestPermissions에 의해 권한 요청 다이얼로그가 표시된다.

 

requestPermssions의 세 번째 매개변수로 request Code를 요청하는데,

이는 한 화면에서 여러 종류의 권한 세트를 요청할 수 있으므로,

어떤 권한 세트를 지정했는지를 확인하기 위해 사용하는 임의의 상수라고 보면 된다.

onRequestPermissionResult(...)

onRequestPermissionResult은 상위 클래스에서 상속받은 메소드를 오버라이드한다.

 

onRequestPermissionResult는 어떤 권한을 요청하고 나서

사용자가 그 권한을 거절하거나 승인했을 때 콜백으로 불려지는 메소드다.

 

override fun onRequestPermissionsResult(
  requestCode: Int,
  permissions: Array<out String>,
  grantResults: IntArray
)

우선 매개변수로 전달되는 값부터 살펴보자.

 

requestCode는 아까 ActivityCompat.requestPermissions()에서 세 번째 매개변수로 전달한 값이 전달된다.

 

permission는 요청한 권한 세트가 전달된다만,

우리는 CALL_PHONE만 전달했으니 길이가 1인 String Array가 전달된다.

 

grantResults는 요청한 권한 세트의 허용/거부 결과 세트이다.

역시 CALL_PHONE만 전달했으니, 이 권한의 허용/거부 결과가 담긴 길이가 1인 Int Array가 전달된다.

 

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            CALL_REQUEST -> {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "권한 승인", Toast.LENGTH_LONG).show()
                    callAction()
                } else {
                    Toast.makeText(this, "권한 거절", Toast.LENGTH_LONG).show()
                }
            }
        }
    }

한 페이지에서 요청하는 권한 세트가 많으면

내부는 when(switch)로 구현되고,

적으면 if를 사용하거나 아예 조건문을 사용하지 않기도 한다.

 

아무튼 requestCode를 이용해 내가 요청한 권한 세트가 무엇인지를 알아내고,

그 권한 세트의 요청 결과(허용/거부)를 불려온다.

 

grantResults[0]에는 CALL_PHONE 권한의 요청 결과가 저장되어 있다.

== PackageManager.PERMISSION_GRANTED //허용됨

== PackageManager.PERMISSION_DENIED //허용되지 않음

마찬가지로 PackageManager의 PERMISSION_GRANTED, PERMISSION_DENIED를 이용해 판정한다.

 

만약 필요한 권한이 모두 허용되었다면, 다시 callAction()을 실행한다.

 

    fun callAction() {
        val number = Uri.parse("tel:010-1234-1234")
        val callIntent = Intent(Intent.ACTION_CALL, number)

        if (ActivityCompat.checkSelfPermission(
                this,
                android.Manifest.permission.CALL_PHONE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            Toast.makeToast(this, "CALL_PHONE권한이 필요합니다", Toast.SHORT).show()
            
             ActivityCompat.requestPermissions(
                    this,
                    arrayOf(android.Manifest.permission.CALL_PHONE),
                    CALL_REQUEST
                )
            return
        }
        startActivity(callIntent)
    }

직전에 CALL_PHONE 권한이 허용되었으므로,

위 코드는 아래와 같이 동작한다고 봐도 무방하다.

    fun callAction() {
        val number = Uri.parse("tel:010-1234-1234")
        val callIntent = Intent(Intent.ACTION_CALL, number)

        startActivity(callIntent)
    }

한번 허용받았다고 끝이 아니다

번거로움이 있기 때문에 귀찮아서 매번 권한 허용여부를 검사하지 않는다면 앱이 다운될 수 있다.

 

설정>애플리케이션에서 이미 부여한 권한을 취소할 수도 있기 때문에 반드시 위험 권한을 사용하는 코드 실행 직전에 권한 허용 여부를 체크해야 한다.

 

만약 허가받지 않은 권한을 사용하려고 하면 SecurityException이 발생하면서 앱이 다운된다.

 

라이브러리를 사용해 번거로움을 줄여보자

꽤나 유명한 안드로이드 권한 관련 라이브러리가 있다.

 

관심이 있다면 한번쯤 사용해봐도 좋을듯.

 

github.com/ParkSangGwon/TedPermission

 

ParkSangGwon/TedPermission

Easy check permission library for Android Marshmallow - ParkSangGwon/TedPermission

github.com

 

 

 



Comments