［Android］ パーミッション の Normal と Dangerous の違い 

# はじめに

Android ではセキュリティの都合上 Android で動作するアプリから個人情報の取得、端末の操作など
セキュリティ的に重要な制御をする場合はパーミッションで何をするか明示的に示す必要があります。

そのパーミッションですが Normal や Signature や Dangerous といった3つの保護レベルがあるらしく、
その Normal や Signature や Dangerous でパーミッション許可の方法が異なるようです。

# パーミッションとは?

Android では 次のような個人情報の取得、端末の操作などの
制御をする場合はアプリにパーミッションを設定する必要があります。

| パーミッション | 内容 |
| --- | --- |
| ACCESS_WIFI_STATE | Wi-Fiネットワークに関する情報にアクセスできるようにする。|
| ACCESS_NETWORK_STATE | ネットワークに関する情報にアクセスできるようにする。|
| CAMERA | カメラデバイスにアクセスできるようにする。 |
| READ_CONTACTS | ユーザーの連絡先データの読み取りをアプリケーションに許可します。 |
| READ_CALENDAR | ユーザーのカレンダーデータを読み取ることを許可します。|
| SYSTEM_ALERT_WINDOW | すべてのアプリの上に表示されるtypeを使用してウィンドウを作成することをアプリに許可します。 |
| WRITE_SETTINGS | システム設定の読み取りまたは書き込みを許可します。 |

# パーミッションの保護レベルとは？

Android ではパーミッションに保護レベルが定義されています。
次のようにセキュリティレベルに応じて 
Normal や Signature や Dangerous などの保護レベルをパーミッションに設定しています。

| パーミッション | 保護レベル | 内容 |
| --- | --- | --- |
| ACCESS_WIFI_STATE | Normal | Wi-Fiネットワークに関する情報にアクセスできるようにする。|
| ACCESS_NETWORK_STATE | Normal | ネットワークに関する情報にアクセスできるようにする。|
| CAMERA | Dangerous |カメラデバイスにアクセスできるようにする。 |
| READ_CONTACTS | Dangerous |ユーザーの連絡先データの読み取りをアプリケーションに許可します。 |
| READ_CALENDAR | Dangerous |ユーザーのカレンダーデータを読み取ることを許可します。|
| SYSTEM_ALERT_WINDOW | Signature | すべてのアプリの上に表示されるtypeを使用してウィンドウを作成することをアプリに許可します。 |
| WRITE_SETTINGS | Signature | システム設定の読み取りまたは書き込みを許可します。 |

## Normal パーミッションとは?

Normal に該当するパーミッションは次のようなものと定義されています。
簡単に言うと Noramal はセキュリティリスクが小さいものに設定されます。

```
アプリがサンドボックス外のデータやリソースにアクセスする必要があるものの、
ユーザーの個人情報や他のアプリの操作に影響を及ぼすリスクがほとんどないもの
```

## Dangerous パーミッションとは?

Dangerous に該当するパーミッションは次のようなものと定義されていあす。
簡単に言うと Dangerous はセキュリティリスクが大きいものに設定されます。

```
アプリがユーザーの個人情報を含むデータやリソースを必要とする、
あるいはユーザーが保存したデータや他のアプリ操作に影響を及ぼす可能性があるもの
```

## Signature パーミッションとは？

Signature に該当するパーミッションは次のようなものと定義されています。
Signature はサードパーティのアプリからは利用できません。

```
パーミッションを定義したアプリケーションと
同じ署名を持つアプリケーションのみに与えられるもの
```

# Normal と Dangerous ではパーミッションの許可方法が違う

Android ではこの保護レベルごとにパーミッションの許可方法を変えており、
Dangerous であるものはユーザーの許可なしに実行できないような仕組みにしています。

## Normal パーミッションを許可する方法は？

例えば Normal だと AndroidManifest.xml に許可を記載するだけで利用できるようになります。
特にユーザーがダイアログを操作して許可する必要ないです。

### サンプル

そのため次のように Wifi 状態を取得するパーミッションを追加していれば
 アプリ起動してすぐに Wifi 接続状態を取得して表示できます。

#### 1. AndroidManifest.xml でパーミッションを設定する
```xml:AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="kaleidot725.permissonsample">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
```

#### 2. Wifi 接続状態を表示するテキストビューを定義する
```xml:activity_main.xml
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/message_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="Hello World!"/>

</FrameLayout>
```

#### 3. Wifi 接続状態を取得しテキストビューに設定する

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

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

        val textView = findViewById<TextView>(R.id.message_textview)
        textView.text = if (wifiIsConnected())  "Wifi接続中" else "Wifi切断中"
    }

    private fun wifiIsConnected() : Boolean {
        val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val network = cm.activeNetwork
        if (network != null) {
            val capa = cm.getNetworkCapabilities(network)
            return capa.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
        }

        return false
    }
}
```

#### 4. 起動すると、特にパーミッション許可することなく Wifi 情報を取得、表示できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/b4548934-7137-6525-85a4-977997ed9059.png)

## Dangerous パーミッションを許可する方法は？

Dangerous だと AndroidManifest.xml に記述するだけでなく、
アプリ起動時にダイアログを表示してユーザーの許可が必要になっています。

### サンプル

次のように GPS 情報を取得するために Dangerous パーミッションを有効にするには、
AndroidManifest.xml に記載するだけでなく、ダイアログを表示しユーザーに許可を取る必要があります。
このダイアログは開発者側が必要に応じて表示する必要があります。

#### 1. AndroidManifest.xml でパーミッションを設定する
```xml:AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="kaleidot725.permissonsample">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
```

#### 2. GPS 情報を表示するテキストビューを定義する
```xml:activity_main.xml
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/message_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="Hello World!"/>

</FrameLayout>
```

#### 3. Dangerous パーミッションを許可するためのダイアログを表示する

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

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

        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 0)
            recreate()
        }
    }
}
```

#### 4. GPS 情報を取得してテキストビューに表示する

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var textView : TextView

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

        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 0)
            recreate()
            return
        }

        textView = findViewById(R.id.message_textview)
        requestLocationUpdates()
    }

    private fun requestLocationUpdates() {
        if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            val locationListener = object : LocationListener {
                override fun onLocationChanged(location: Location) {
                    textView.text = location.toString()
                }

                override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {}
                override fun onProviderEnabled(provider: String) {}
                override fun onProviderDisabled(provider: String) {}
            }

            val locationManager = this.getSystemService(Context.LOCATION_SERVICE) as LocationManager
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000.toLong(), 1.toFloat(), locationListener)
        }
    }
}
```

### 5. 起動するとパーミッション許可が表示され、 許可すれば GPS 情報を取得できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/095a32d9-3272-55e6-7016-91788507f2c6.png)

この動作はAndroid 6.0 （API Level23） 以降で実装されたものらしく、Android 6.0 （API Level23） 以前では動作が異なります。
Android 6.0（API Level23） 以前ではアプリを実行したときではなく、アプリをインストールするときに尋ねられるようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79387/72b84109-0d1b-d0fc-dda5-a890b9ff9fcd.png)

# おわりに

* パーミッションごとに保護レベルが定義され、パーミッション許可の方法が異なる
* 保護レベルは Normal, Signature, Dangerous と3種類ある
* Normal はユーザーの個人情報や他のアプリに影響が少ない権限
* Normal は AndroidManifest.xml にパーミッション許可を記述すれば使えるようになる
* Dangerous はユーザーの個人情報や他のアプリに影響がある権限
* Dangerous では AndroidManifest.xml にパーミッション許可を記述するだけでなく、  
  アプリでパーミッション許可を促すダイアログを表示しユーザーから許可を得なければ使えない。

# 参考文献
- [Perissions overview | Android Developers](https://developer.android.com/guide/topics/permissions/overview)
- [Manifest.permission | Android Developers](https://developer.android.com/reference/android/Manifest.permission.html#SYSTEM_ALERT_WINDOW)