Android에서 Special Permission 대응 방법

Hamcoding·2023년 2월 26일

서론

오늘 포스팅의 주제는 안드로이드 권한 중 Special Permission에 대응하는 방법입니다.

사용자의 스마트폰 사용 기록을 보여주는 앱을 개발하면서, 사용자의 앱 사용 기록을 보기 위해 PACKAGE_USAGE_STATS 권한이 필요하다는 것을 알게 되었습니다. 이 권한은 Special Permission으로 Runtime Permission과 Install-time Permission과는 다르게 대응해야 합니다. Special Permission은 인터넷에 정보가 많이 없어 포스팅을 작성하였습니다.

포스팅을 시작하기에 앞서 저는 안드로이드를 배우고 있는 상황이므로 틀린 정보가 있을 수 있습니다. 만약 틀린 정보가 있는 경우 정정 댓글을 달아주시면 수정하겠습니다.

권한의 3가지 종류

Special Permission에 대해 이야기하기 전에, 안드로이드 문서에서 소개하는 안드로이드 권한에는 3가지 종류가 있습니다.

Install-time Permissions: 다른 앱이나 시스템에 거의 영향을 주지 않는 제한적인 권한으로, 사용자가 앱을 설치할 때 시스템에서 자동으로 권한을 승인해줍니다.

Runtime Permission: Dangerous 권한으로, 사용자가 해당 권한이 필요한 기능을 사용할 때 런타임으로 요청해야 합니다.

Speical Permission: 시스템 설정 앱을 통해 부여해야 하는 권한입니다.

필요한 권한의 Protection Level을 확인하려면 권한 API 참조 페이지를 참고하시면 됩니다.

설정>특별한 접근에 들어가면 Special Permission 목록을 확인할 수 있으며 여기서 Spcial Permission을 허용하는 등의 설정을 할 수 있습니다.

Special Permission 대응 방법

앞에서 이야기하였듯이 Special Permission은 런타임으로 요청이 불가하고 설정 앱을 통해 권한을 요청할 수 있습니다. 사용자의 앱 사용 기록에 접근할 수 있는 권한 PACKAGE_USAGE_STATS를 예시로 Special Permission의 work flow를 살펴보겠습니다. 자세한 work flow는 안드로이드 개발자 문서의 특별 권한 요청에 있습니다.

1. 앱의 메니페스트 파일에 Special Permission 선언

<uses-permission-sdk-23 android:name="android.permission.PACKAGE_USAGE_STATS" />

PACKAGE_USAGE_STATS는 API 23에 추가된 권한이므로 <user-permission-sdk-23>을 사용합니다.

2. Special Permission이 필요한 작업을 사용자가 호출할 때 Special Permission을 요청합니다.

제가 기획한 서비스는 Special Permission을 허용하지 않으면 사용 불가하므로 Launcher Activity에 Speical Permission을 요청하는 코드를 포함했습니다.

override fun onCreate(savedInstanceState: Bundle?) {
	// ...
    if (hasUsageStatsPermission()) {
        moveToFirstScreen()
    } else {
        requirePermission()
    }
}

3. 사용자가 이미 Spcial Permission 권한을 부여했는지 확인합니다.

Special Permission은 각 권한마다 커스텀 검사 함수를 가지고 있습니다. 권한 API 문서를 참조하여 필요한 Special Permission의 검사 함수를 활용해야 합니다.
PACKAGE_USAGE_STATS 권한은 AppOpManager를 활용해서 권한이 허용되었는지 확인해야 합니다. 코드는 다음과 같습니다.

private fun hasUsageStatsPermission(): Boolean {
    val appOps = getSystemService(APP_OPS_SERVICE) as AppOpsManager
    val mode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        appOps.unsafeCheckOpNoThrow(
            AppOpsManager.OPSTR_GET_USAGE_STATS,
            android.os.Process.myUid(), packageName
        )
    } else {
        appOps.checkOpNoThrow(
            AppOpsManager.OPSTR_GET_USAGE_STATS,
            android.os.Process.myUid(), packageName
        )
    }
    return mode == AppOpsManager.MODE_ALLOWED
}

checkOpNoThrow는 API29부터 deprecated되었고 API29 이상부터는 unsafeCheckOpNoThrow를 사용해야 합니다.

권한을 검사해서 이미 허용되었다면, 다음 작업으로 넘어가는 처리를 해줍니다.

4. 사용자에게 설명합니다.

UI로 사용자에게 1) 해당 권한으로 접근할 수 있는 데이터 2) 앱이 사용자에게 주는 이점 3) 사용자가 권한을 부여하는 방법을 간단하게 안내해야 합니다. 저는 다음과 같이 UI를 구성했습니다.

5. Special Permission을 요청합니다.

Runtime-Permission과 달리 Dialog를 사용하지 않습니다. Intent를 사용하여 Special Permission을 요청하며 저는 권한 요청 버튼을 눌렀을 때 허용 화면으로 이동하도록 했습니다.

private fun requirePermission() {
    binding.btnPermission.setOnClickListener {
        startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
    }
}


필요한 권한 요청에 해당하는 Action은 안드로이드 Setting 문서를 확인하시길 바랍니다.

6. 사용자의 응답 처리

사용자가 권한 설정 후 앱으로 돌아왔을 때 onResume() 함수에서 권한이 부여되었는지 확인하고 대응해야 합니다. 제가 기획한 앱은 Special Permission을 허용하지 않으면 서비스를 이용할 수 없어 권한을 허용하지 않으면 다음 페이지로 넘어갈 수 없도록 처리하였습니다.

override fun onResume() {
    super.onResume()
    if (hasUsageStatsPermission()) {
        moveToFirstScreen()
    }
}

결과물

앱을 처음 시작 시 권한 화면으로 이동하며 권한 허용 시 로그인 화면으로 이동합니다.

전체 코드가 궁금하면 Github를 참고하시길 바랍니다.

마치며

안드로이드 개발을 하면서 Special Permission을 요청해야 하는 일은 드물다고 생각합니다. 그러나 저처럼 특별한 목적의 앱을 만드는 경우, Special Permission을 요청해야 할 수도 있습니다. 이 글이 누군가에게 도움이 되길 바라며 마칩니다. 감사합니다.

참고 문헌

Android Developers-Request special permissions
Android Developers-Declare app permissions
Android Developers-Settings
Android Developers-Permissions on Android
https://stackoverflow.com/questions/13801984/permission-is-only-granted-to-system-app

0개의 댓글