Android permission

안드로이드는 privilege-separated OS이다. 각각의 어플리케이션들은 서로 다른 system ID(Linux의 User ID 또는 Group ID)로 구분된다.

안드로이드 시스템의 보안 기능은 세분화 되어있으며 Permission Mechanism에 의해 동작한다.

안드로이드 시스템은 시스템 자체의 무결성(Integrity)개인 정보 보호를 위해 각 어플리케이션이 sandbox(보호된 영역) 내에서만 동작하도록 제한해 두었다.

어플리케이션이 자신의 sandbox 밖의 리소스, 파일 등의 사용을 원할 때에는 명시적으로 해당 permission에 대해 요청하고 획득해야 한다.

Permission은 특정 프로세스가 특정 기능을 수행하기 위해 필요한 권한을 의미하며, 사용자가 허가를 해야 사용할 수 있다.

안드로이드의 System permission은 두 가지 종류로 나눌 수 있다. (Normal and Dangerous Permissions 참조)

  • Normal permissions
    • 다른 프로세스, 데이터, 사용자에게 악영향을 주지 못하는 권한들.
  • Dangerous permissions
    • 파일 저장 및 운용, 주소록 접근 등 다른 요소에 악영향을 줄 수도 있는 권한들.

안드로이드 시스템은 어플리케이션이 Normal permissions을 요청하면 자동으로 권한을 부여하고, Dangerous permissions를 요청하면 사용자에게 Dialog를 통해 확인(Dangerous permissions)을 받은 후 허가를 해준다.

사용자에게 확인을 받는 동작은 안드로이드 버전에 따라 다르게 동작한다.

  • Android 5.1 (Lollipop) 이하
    • 어플리케이션 설치 시 사용할 permission들에 대해 사용자의 허가를 받음.
  • Android 6.0 (Marshmallow) 이상
    • Runtime에 permission을 부여할지 묻는다.

System Permissions

Security architecture

안드로이드는 기본적으로 Permission이 없으면 하나의 어플리케이션이 다른 어플리케이션, OS, user에게 위해를 가할 수 없다.

예를 들어, permission 없이는 주소록이나 이메일 등의 접근할 수 없고 다른 어플리케이션에 속한 파일에 접근할 수 없다는 뜻이다.

어플리케이션은 필요로 하는 permission에 대해 Application Manifest 파일에 static하게 선언해야 한다.

<uses-permission> 태그를 이용하며, 아래의 예시는 SMS의 발신 권한을 획득하기 위한 선언이다.

1
2
3
4
5
6
7
8
9
10
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
<uses-permission android:name="android.permission.SEND_SMS"/>
<application ...>
...
</application>
</manifest>

어플리케이션 Sandbox는 어플리케이션이 어떻게 빌드 되었던 간에 독립적으로 동작하며 어플리케이션의 형태(Java, native, hybrid)에 관련 없이 같은 방식, 같은 강도의 보안을 가지고 있다.

Permission의 요청이 실패할 경우 SecurityException을 return받게 된다. 하지만 요청이 실패한다고 해서 반드시 이 exception이 발생하는 것은 아니다.

예를 들어, sendBroadcast(Intent) 메소드는 각각의 receiver로 전달되는 데이터에 대해 permission check를 진행하지만 값이 return될 때 permission failure가 발생했었는지 여부는 알 수 없게 된다.

그래도 대부분 permission failure에 대한 정보는 system log에 남기 때문에 이상 여부를 확인할 수 있다.

사용 가능한 Permission의 종류는 Manifest.permission에서 확인할 수 있다.

아래와 같은 특정한 상황에서는 어플리케이션 동작 중에 별도의 permission을 요구하게 된다.

  • 시스템에 대한 호출 시
    • 어플리케이션이 특정 기능을 무단으로 사용하는 것을 방지하기 위해
  • Activity가 시작될 때
    • 다른 어플리케이션의 activity가 임의로 시작되는 것을 막기 위해
  • Broadcast의 전송/수신 시
    • 내가 보낸 Broadcast를 누가 받았는지, 누가 나에게 Broadcast를 보냈는지 제어하기 위해
  • Content provider에 접근 시
  • Service를 시작하거나 Binding 할 때

Application Signing

모든 APK들은 개발자의 private key을 이용한 인증과정을 통해 서명된 상태여야 한다. 서명은 특별히 인증기관 등의 허가를 받는 것은 아니며, 개발자 스스로 생성한 key로 서명할 수 있다.

서명의 목적은 제작자가 누구인지 구분하는 것이다. (복제 방지, 악용 방지)

서명을 통해 시스템은 어플리케이션이 signature-level permissions (normal, dangerous, signature, signatureOrSystem)에 접근할 수 있는 권한을 부여/거부한다.

어플리케이션이 다른 어플리케이션과 동일한 Linux ID를 사용하려 할 때에도 서명 정보를 필요로 한다.

User ID와 File access

어플리케이션 설치 시 안드로이드는 각각의 package에 각각 다른 Linux user ID를 부여한다. 이는 상수값이며 해당 package가 죽을 때까지 가지고 있는다.

보안 권한은 Process level에 존재하고, 일반적으로 서로 다른 두 개의 package가 하나의 process에서 수행되는 일은 없다. 보통 서로 다른 Linux user로 동작한다.

하지만 AndroidManifest.xml의 manifest tag 내에 sharedUserId 속성을 사용하게 되면 서로 다른 package들이 같은 user ID를 사용할 수도 있다.

이 방법을 통해, 보안을 목적으로 하는 두 개의 package가 하나의 어플리케이션인척 행동하게 된다. 이 때 user ID 및 file에 대한 권한을 동일하게 갖는다.

보안을 유지하기 위해 동일한 서명을 사용한 어플리케이션 중 단 두 개의 어플리케이션만 동일한 user ID를 사용할 수 있다.

어플리케이션이 저장한 data들은 해당 어플리케이션의 user ID에 할당되며, 기본적으로 다른 package에서는 접근이 불가하다.

어플리케이션이 새로 만들거나 저장하는 파일들에 대한 메소드는 다음과 같다.

  • getSharedPreferences(String, int)
  • openFileOutput(String, int)
  • openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)

만약 다른 package들이 read/write를 할 수 있게 하려면 MODE_WORLD_READABLE, MODE_WORLD_WRITEABLE flag를 사용해야 한다. 이 flag들을 사용하여도 소유권은 변하지 않는다.

Automatic permission adjustments

시간이 지나면서(새로운 sdk가 나오면) 이전에는 없던 permission이 만들어지기도 한다. 해당 permission이 만들어지기 전에 생성된 어플리케이션은 당연히 권한 없이도 이를 사용할 수 있다는 가정 하에 개발되었기 때문에 관련 기능을 사용하려면 에러가 날 수도 있다.

에러를 방지하기 위해 안드로이드는 Manifest에 새 permission에 대한 코드를 추가한다. 추가할지 말지 결정하는 근거가 되는 것이 targetSdkVersion 이며, 이 값이 permission이 생성된 버전보다 낮다면 안드로이드는 permission을 추가한다.

예를 들어, WRITE_EXTERNAL_STORAGE permission은 공유된 저장소에 접근하는 것을 제한하기 위해 API level 4에 만들어졌다. 만약 targetSdkVersion이 3보다 낮다면 이 permission은 자동으로 추가된다.

만약 이 경우처럼 자동으로 permission이 추가되는 경우, Google Play에서 이러한 권한들에 대해 보여주게 된다. (실제로 그 어플리케이션에서 사용하지 않더라도 보여준다.)

사용하지 않는 permission을 제거하기 위해서는 어플리케이션의 targetSdkVersion을 항상 최신화하여 유지하는 것이 좋다. Build.VERSION_CODES를 통해 각 버전 별로 업데이트 된 permission 목록을 확인할 수 있다.

Permission groups

모든 dangerous Android system permissionpermission group에 속해있다.

Permission이 같은 group에 속한 경우 사용자에게 한 번만 사용 허가를 받으면 같은 group에 속한 다른 permission들도 사용할 수 있다.

예를 들어, READ_CONTACTS에 대한 권한을 사용자로부터 허가받아 부여받았다면, WRITE_CONTACTS 권한은 자동으로 부여받는다.

[표 1. Dangerous permissions and permission groups]

|————-+——————————|
| CALENDAR | READ_CALENDAR |
| | WRITE_CALENDAR |
| CAMERA | CAMERA |
| CONTACTS | READ_CONTACTS |
| | WRITE_CONTACTS |
| | GET_ACCOUNTS |
| LOCATION | ACCESS_FINE_LOCATION |
| | ACCESS_COARSE_LOCATION |
| MICROPHONE | RECORD_AUDIO |
| PHONE | READ_PHONE_STATE |
| | CALL_PHONE |
| | READ_CALL_LOG |
| | WRITE_CALL_LOG |
| | ADD_VOICEMAIL |
| | USE_SIP |
| | PROCESS_OUTGOING_CALLS |
| SENSORS | BODY_SENSORS |
| SMS | SEND_SMS |
| | RECEIVE_SMS |
| | READ_SMS |
| | RECEIVE_WAP_PUSH |
| | RECEIVE_MMS |
| STORAGE | READ_EXTERNAL_STORAGE |
| | WRITE_EXTERNAL_STORAGE |

Permission의 정의 및 제한

Permission을 적용하기 위해서는 AndroidManifest.xml<permission> 태그를 사용하여 정의해야 한다.

아래의 예시는 어플리케이션이 자신이 가진 activity 중 하나를 시작하는 권한을 다른 어플리케이션에게 부여하기 위해 permission을 선언한 것이다.

1
2
3
4
5
6
7
8
9
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp" >
<permission android:name="com.example.myapp.permission.DEADLY_ACTIVITY"
android:label="@string/permlab_deadlyActivity"
android:description="@string/permdesc_deadlyActivity"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous" />
...
</manifest>

참고로 모든 package가 동일한 인증서로 서명하지 않은 한, 여러 package가 동일한 이름의 permission을 선언할 수는 없다.

하나의 Package가 permission을 선언한 경우 시스템은 같은 permission 이름을 가진 다른 package가 시스템에 설치되는 것을 막는다. 물론 이미 설치되어 있는 package와 동일한 서명을 가지고 있다면 설치를 허가한다.

이런 충돌을 피하기 위해 안드로이드에서는 reverse-domain-style naming을 추천한다. ex. com.example.myapp.ENGAGE_HYPERSPACE

위의 예시에서 하나하나 살펴보면;

  • protectionLevel
    • Mandatory 항목이다.
    • 시스템이 어플리케이션이 이 권한을 필요로 하는 이유 또는 이 권한을 가지고 있는 것들이 무엇인지 사용자에게 알려주기 위한 속성이다.
    • 아래 AndroidManifestPermission_protectionLevel 을 살펴보자.
  • android:permissionGroup
    • Optional 항목이다.
    • 시스템이 권한에 대해 사용자에게 보여줄 때에만 사용된다. 대부분의 경우, standard system group(android.Manifest.permission_group)으로 설정하게 된다.
      • CALENDAR
      • CAMERA
      • CONTACTS
      • LOCATION
      • MICROPHONE
      • PHONE
      • SENSORS
      • SMS
      • STORAGE
    • 개발자가 직접 정의할 수도 있다. 하지만 왠만하면 기존에 있는 것으로 사용하자.
    • 안드로이드 폰에서 설정 > 앱 > 앱 설정 > 앱 권한 으로 가면 permissionGroup으로 묶여있는 것을 확인할 수 있다.
  • android:label
  • android:description
    • label 및 description은 사용자가 permission list 확인 시 보여줄 제목 및 상세 내용이다. 새로 추가해야 한다면 가능한 짧게 작성하라.

아래는 CALL_PHONE permission에 대한 label 및 description 예시이다.

$ adb shell pm list permissions 명령어를 통해 현지 시스템에서 사용하고 있는 권한들을 확인할 수 있다.

1
2
3
4
5
<string name="permlab_callPhone">directly call phone numbers</string>
<string name="permdesc_callPhone">Allows the application to call
phone numbers without your intervention. Malicious applications may
cause unexpected calls on your phone bill. Note that this does not
allow the application to call emergency numbers.</string>

AndroidManifestPermission_protectionLevel

|———————–+——————-+———————|
| Constant | Value | Description |
| normal | 0 | 낮은 위험도를 가진 permission. 어플리케이션이 다른 어플리케이션에 별다른 영향을 주지 않는 부분으로 접근을 시도한다. 시스템은 이 권한을 요청하는 어플리케이션에게 권한을 자동적으로 부여한다. |
| dangerous | 1 | 높은 위험도를 가진 permission. 어플리케이션이 개인 정보 및 디바이스를 나쁜 목적으로 사용이 가능하다. 시스템은 이 권한을 자동으로 부여하지 않고 사용자에게 허가를 요구하는 dialog 를 띄워서 허가를 받는다. |
| signature | 2 | 같은 인증서로 서명된 어플리케이션에게만 이 권한을 허가한다. 인증서가 동일한 것이 판명되면 시스템은 자동적으로 권한을 부여한다. |
| signatureOrSystem | 3 | 안드로이드 시스템 이미지 내의 package 또는 같은 인증서로 서명된 어플리케이션에게만 권한을 허가한다. 이 옵션은 피해야 하며, Signature protection level은 대부분의 요구를 충족시켜야 하고 어플리케이션이 어디에 설치되었건 정확하게 동작해야 한다. |
| privileged | 0x10 | base permission type으로부터 파생된 추가적인 flag로 이 permission은 시스템 이미지에 설치된 privileged app들에 부여될 수 있다. 이 옵션은 피해야 하며, Signature protection level은 대부분의 요구를 충족시켜야 하고 어플리케이션이 어디에 설치되었건 정확하게 동작해야 한다. |
| system | 0x10 | “privileged”와 동일하다. 이전에 사용하던 용어. |
| development | 0x20 | base permission type으로부터 파생된 추가적인 flag로 개발용 어플리케이션에 선택적으로 부여될 수 있다. |
| appop | 0x40 | base permission type으로부터 파생된 추가적인 flag로 제어 권한을 위한 app op과 밀접한 관련을 가지고 있음. |
| pre23 | 0x80 | base permission type으로부터 파생된 추가적인 flag로 Marshmallow 이전 버전의 API level을 갖는 어플리케이션들에게 자동으로 부여된다. (runtime permission 부여를 지원하지 않는 버전들) |
| installer | 0x100 | base permission type으로부터 파생된 추가적인 flag로 시스템 어플리케이션 권한을 가진 어플리케이션들에게 자동으로 부여된다. |
| verifier | 0x200 | base permission type으로부터 파생된 추가적인 flag로 시스템 어플리케이션 권한을 가진 어플리케이션들에게 자동으로 부여된다. |
| preinstalled | 0x400 | base permission type으로부터 파생된 추가적인 flag로 시스템 이미지 위에 미리 설치되는 어플리케이션들(privileged 어플리케이션을 포함한)에게 자동으로 부여된다. |
| setup | 0x800 | base permission type으로부터 파생된 추가적인 flag로 setup wizard 어플리케이션에게 자동으로 부여된다. |

Custom permission, 이렇게 사용하라.

  • 여러 어플리케이션을 묶어서 제품군을 설계할 때, 각각의 permission이 단 한번만 정의될 수 있도록 설계해야 한다.
  • 서로 다른 어플리케이션이 같은 인증서로 서명한 경우 signature check를 통해 중복으로 permission을 정의하는 것을 막을 수 있다.
  • 하나의 제품군을 개발하는 경우 이 모든 제품군의 permission을 관리하는 하나의 pacakage를 개발하는 것이 좋다. 이 package는 특별히 서비스를 제공하진 않고 permission을 관리할 뿐이며, 제품군에 속한 다른 어플리케이션들은 만 사용하게끔 한다.

AndroidManifest.xml의 permission 제한

개발자는 AndroidManifest.xml을 통해 시스템 또는 어플리케이션의 모든 컴포넌트에 접근 제한을 걸 수 있다. 이를 위해 컴포넌트 별로 android:permission 속성을 선언하고 해줘야 한다.

  • Activity permissions ()
    • 관련된 activity를 실행할 수 있는 권한을 나타낸다. 이 permission은 Context.startActivity()Activity.startActivityForResult() 실행 시 체크된다. 만약 호출한 측이 permission을 가지지 않았다면 SecurityException이 발생한다.
  • Service permissions ()
    • 관련된 service에 bind할 수 있는 권한을 나타낸다. 이 permission은 Context.startService(), Context.stopService(), Context.bindService() 실행 시 체크된다. 만약 호출한 측이 permission을 가지지 않았다면 SecurityException이 발생한다.
  • BroadcastReceiver permissions ()
    • 관련된 receiver로 broadcast를 보낼 수 있는 권한을 나타낸다. Context.sendBroadcast()가 return될 때 permission 체크가 이루어지며, 시스템은 broadcast를 정해진 receiver로 전달하려 한다. Permission이 없는 경우 호출한 측으로 result가 제대로 전달되지 않고 exception이 전달된다.
    • 이 Permission은 Context.registerReceiver를 통해 동적으로 등록할 수도 있다.
    • 다른 방법으로 Context.sendBroadast() 호출 시 permission을 명시하여 broadcast를 수신할 수 있는 BroadcastReceiver를 제한할 수 있다.
  • ContentProvider permissions ()
    • ContentProvider 내의 data에 접근하는 것을 제한할 수 있다(ContentProvider는 URI permission이라 불리는 중요한 추가 보안 기능을 가지고 있다.).
    • 다른 컴포넌트들과는 다르게 두 개의 permission attribute를 설정할 수 있다. write 권한을 가지고 있다고 해도 read할 수 없다.
      • android:readPermission
      • android:writePermission
    • 최초 provider를 찾을 때(retrieve) permission에 대해 체크하며 두 개의 permission 모두 없다면 SecurityException이 발생한다.
    • ContentResolver.query()read permission을 요구하며, ContentResolver.insert(), ContentResolver.update(), ContentResolver.delete()write permission을 요구한다.

Sending Broadcast 시의 Permission 제한

BroadcastReceiver를 regist하는 것(Context.registerReceiver())에 더하여 broadcast를 보낼 때 필요한 permission을 지정할 수도 있다. Context.sendBroadcast() 호출 시 permission string을 포함하여 보내면 해당 permission을 가지고 있는 receiver만 수신할 수 있다.

receiverbroadcaster 모두 permission을 가지고 있어야 한다.

Permission을 제한하는 다른 방법

다른 프로세스가 현재 프로세스의 Service 등을 호출했을 때 Context.checkCallingPermission()을 사용할 수 있으며 return값인 PERMISSION_GRANTED, PERMISSION_DENIED 로 호출한 프로세스가 권한을 가지고 있는지 확인할 수 있다.

Permission 체크를 위한 두 가지 방법이 더 있다.

  • 다른 프로세스의 PID(Process Identifier)를 알고있다면 Context.checkPermission(String, int, int)를 사용할 수 있다.
  • 다른 어플리케이션의 Package name을 알고있다면 PackageManager.checkPermission(String, String)을 사용할 수 있다.

URI Permission

Content provider 사용 시 standard permission system에 기술된 내용은 충분하지 않다.

Content provider는 직접적인 client는 물론 다른 어플리케이션이 동작하기 위한 특정한 URI를 다룰 때 read/write permission을 통해 스스로를 보호하기를 원한다.

대표적인 예로, Mail 어플리케이션에서의 파일 첨부 기능을 보자. Mail로의 접근은 user-data의 보호를 위해 permission으로 막혀있다. Image 첨부 기능의 URI가 Image viewer에 제공되어 있다 하더라도, Image viewer는 permission이 없기 때문에 첨부 파일을 열 수 없다.

이 문제의 해결책이 per-URI permission이다. Activity가 시작되거나 result가 activity로 return될 때 Caller는 Intent.FLAG_GRANT_READ_URI_PERMISSION 또는 Intent.FLAG_GRANT_WRITE_URI_PERMISSION을 설정할 수 있다.

이는 별도로 Content provider의 data 접근을 위한 permission을 가질 필요 없이 Intent 내의 특정 data URI를 통해 activity permission을 받겠다는 의미이다.

이 동작 방식은 파일 열기, 주소록 선택 등의 사용자와의 상호 작용 발생 시 임시로 권한을 허가하는, 일반적인 capability-style 모델을 가능케 한다. 또한, 어플리케이션이 쓸데없는 permission을 갖게지 않게하는데 핵심적인 역할을 한다.

URI permission의 권한 부여을 위해 해당 URI들을 가지고 있는 Content provider는 자신이 이 기능을 구현하고 있다는 것을 알리기 위해 android:grantUriPermissions 속성을 선언하거나 <grant-uri-permissions> tag를 선언해야 한다.

아래와 같은 방식으로 Flag를 세팅할 수 있다.

1
2
3
4
5
6
startActivity(
new Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, type)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
);

Permission 사용하기

Permission 확인

어플리케이션에서 Dangerous permission의 사용을 요청할 경우 사용할 때마다 permission을 얻은 상태인지 체크 해야하며,

ContextCompat.checkSelfPermission() 메소드를 사용하면 된다.

아래는 캘린더 작성 permission에 대한 Android의 예시이다.

Permission을 획득했다면 PackageManager.PERMISSION_GRANTED가 return될 것이며, permission이 없다면 PERMISSION_DENIED가 return될 것이다.

1
2
3
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);

Permission 요청하기

사용자가 한 번 거부한 permission에 대해서는 설명을 추가(ex. Don’t ask again 체크박스)해야 하는데 한 번 거부한 permission인지 알 수 있는 방법은 shouldShowRequestPremissionRationale() 메소드를 사용하는 것이다. 만약 이전에 사용자가 DENY처리한 적이 있다면 true가 return된다.

어플리케이션이 permission을 아직 안 가지고 있을 경우 requestPremissions()를 호출해야 한다.

아래 코드는 READ_CONTACTS 기능에 대해 permission을 가지고 있는지 확인하며, permission이 없다면 요청한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}

Permission request response

Permission을 사용자에게 요청 시 dialog box를 보여주게 된다. 사용자가 allow/deny를 입력하면 시스템은 어플리케이션의 onRequestPermissionsResult() 메소드를 호출한다.

어플리케이션에서 보낸 requestPermission()에 대해 동일한 request code가 도착하는지 확인하기 위해 callback 함수인 onRequestPermissionResult()를 override 해야한다.

아래 예시는 READ_CONTACTS 권한을 요청하고 callback 받는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}

Permission 잘 사용하기

Intent의 사용을 고려하라

App에서 task를 수행하기 위한 방법은 크게 두 가지가 있다.

  • App이 task를 스스로 수행하게 하는 것
  • Intent를 던져서 다른 어플리케이션이 task를 수행하게 하는 것

사진을 찍고 싶으면 CAMERA permission을 요청하여 카메라 제어와 관련된 모든 permission을 가지고 올 수 있다. 하지만 ACTION_IMAGE_CAPTURE intent를 날려서 사진을 찍을 수도 있다.

이 intent를 던지면 카메라 app을 설정하라는 dialog가 발생한다. 만약 default app으로 설정된게 있으면 안 뜬다. 사용자가 사진을 찍고 나면 onActivityResult()를 통해 사진을 callback받을 수 있다.

이 두 가지 방법은 이 세상 이치가 그렇듯 각각의 장단점이 있었으니…

  • Permission 사용 시
    • App이 모든 제어권을 갖지만 그에 따른 모든 UI를 설계해야 하기 때문에 App이 복잡해진다.
    • 한 번 permission을 받으면 계속 사용할 수 있지만 사용자가 permission 획득을 거부한다면 어플리케이션은 무용지물이 된다.
  • Intent 사용 시
    • 수행 동작들에 대한 UI를 구성할 필요가 없으며 intent를 처리하는 어플리케이션이 준비한 UI를 사용한다. 하지만 이는 곧 UX를 제공할 수 없다는 말이 된다.
    • 사용자가 default app을 지정하지 않았다면 system은 app을 고르는 화면을 보여줄 것이다. 사용자가 default handler를 지정하지 않으면 동작이 수행될 때마다 dialog를 띄워줄 것이다.

꼭 필요한 permission만 요청하라

Android 6.0 이상의 버전에서는 permission이 필요한 기능을 새로 수행할 때마다 사용자에게 수행할 것인지 묻는다. 사용자 입장에서 여러가지 기능에 대해 자꾸 물어보면 짜증나니까 꼭 필요한 permission만 사용하고 요청하도록 한다.

어플리케이션의 core가 되는 기능이 아니라면 왠만하면 intent를 던져서 사용하자.

쓸데없이 permission을 많이 가져가지 말아라

한 번에 너무 많은 permission을 요청할 경우, 어플리케이션이 종료될 수도 있다.

어플리케이션 구동에 있어서 꼭 필요한 permission은 어플리케이션이 처음 실행될 때 획득할 수 있게 처리하는 것이 좋다.

예를 들어, 사진 어플리케이션이 처음 구동될 때 카메라 제어에 대한 permission을 받는 것이 좋지만 READ_CONTACTS등의 permission은 주소록을 통한 공유 등 그 기능이 처음 수행될 때 받는 것이 좋다는 이야기이다.

그 Permission이 왜 필요한지 사용자에게 설명하라

requestPermissions()를 호출하면 permission dialog가 뜬다. 하지만 왜 이 permission을 획득해야 하는지는 알려주지 않는다. Permission을 요청하면 사용자는 왜 이 permission이 필요한지 궁금해진다.

예를 들어, 사진 어플리케이션이 geotag를 위해 location service를 필요로 할 때, 무턱대고 location permission을 요청하면 사용자들은 의아하게 생각한다.

따라서 requestPermissions()를 호출하기 전에 사용자에게 설명해줄 필요가 있다.

사용자에게 알려주는 방법 중 하나는 app tutorial을 넣는 것이다. Demo를 보여줌으로써 사용자가 이 permission이 왜 필요한지 느낄 수 있다.

Permission에 대해 test하는 방법

API level 23 이상의 단말에서는 아래 방법으로 permission과 관련된 문제 있는 코드를 확인할 수 있다.

  • 어플리케이션의 현재 permission 및 관련 경로를 확인한다.
  • Permission이 필요한 service를 수행하거나 data에 접근을 시도한다.
  • 여러가지 permission을 얻었다 잃었다 하면서 테스트한다. 예를 들어, 카메라 어플리케이션은 CAMERA, READ_CONTACTS, ACCESS_FINE_LOCATION이 필요하다고 할 때, 이 세 가지를 껐다 켰다 하면서 테스트를 진행한다.

ADB를 통해 다음 항목들을 시험해볼 수 있다.

  • Permission list를 확인하는 방법
    1
    2
    $ adb shell pm list permissions -d -g
  • permission을 획득/제거하는 방법
    1
    2
    $ adb shell pm [grant|revoke] <permission-name> ...
Share