Androidのパーミッションと6.0での処理の流れを今さら説明してみる

概要
---

先日アシスタントを担当している授業でAndroidアプリの開発を教えていたところ、ある学生さんがGPS機能を利用したアプリを作っていたのですが、**Android 6.0でのパーミッション周りの実装に困っており**、自分にもあまり知見がなかったのでかなり苦労してしまいました。
またその過程で、そもそもの**パーミッション(User Permission)の許可の仕組みも伝えなければいけなかった**ので、どうせならと思い今更ながら得られた知見を記事にまとめてみました。
なのでこの記事では

* Android OSにおけるユーザー権限許可の概要
* Android 6.0以前でのユーザー権限の取得方法
* Android 6.0以降での変更点
* GPSの位置情報を取得するアプリでの実装手順

を解説します。間違いやSDKのアップデートによる差異もあるかもしれませんが、コメント等にて教えていただければ幸いです。またパーミッションの概要の

パーミッションについて
---

そもそもパーミッション(User Permission)とはなんでしょうか。
それは**ユーザーがアプリに対して「この情報、この機能を使って何かしてもいいよ」という権限を与える仕組み**のことです。

Androidアプリは、端末の持つ様々な情報にアクセスすることができます。機能とは、例えば

* 連絡先
* カメラ
* Wi-Fi
* Bluetooth
* GPS

などのことを指します。

ただし、ユーザーがアプリをインストールした際、**何でもかんでもそのアプリにやることを許してしまうとどうなるでしょうか?** あるいは、インストールしたアプリがどんな機能にアクセスするのかを、**ユーザーに一切知らされないとしたらどうなるでしょう??**
開発者は**「電卓と見せかけて、実は連絡先を盗んで業者に垂れ流すアプリ」**や**「音楽再生アプリと見せかけて、実は勝手にカメラを起動して盗撮するアプリ」**のようなものが作れてしまいます。

![図1.png](https://qiita-image-store.s3.amazonaws.com/0/17214/70474793-ade7-113a-8031-24fa339f2309.png)

そこでアプリを作る側は、**上記に示すような機能にアクセスすることを事前に明記し、インストール前にユーザーに伝えなければいけない**のです。そしてユーザーは**インストール時にこれらの機能へのアクセス(パーミッション)を許可することで初めてアプリがインストールできるようになる**のです。

**これがパーミッションの仕組みになります。**言い換えれば、逆に**ユーザーへの通知なしに勝手に連絡先やカメラの機能を利用するアプリは作れない**ようになっているのです。ユーザーが「電卓って書いてあるのになんでカメラへアクセスする必要があるんだろう?」と怪しんでパーミッションを与えなければ、アプリはインストールされません。

![図2.png](https://qiita-image-store.s3.amazonaws.com/0/17214/1a4edad8-03de-4e58-dfb6-440da5a22e08.png)

Android 5までのパーミッションチェック
---

ここで先ほど、「パーミッションは**インストール時に許可される**」と言いました。
しかし**これはOSのバージョンが6.0未満の場合の話です**。Android 6.0以降では**アプリを使用中、その機能を使用する段階で初めてチェックされる**仕様に変更されています。
以下はAndroid公式の[原文](http://developer.android.com/intl/ja/training/permissions/requesting.html)になります。

> Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. 

訳: Android 6.0 (APIレベル23)からは、ユーザーはインストール時ではなく、アプリを起動した後にPermissionを与えるようになりました。

どういうことかイメージで説明します。まずAndroid 4.xや5.xでは、はユーザー権限の許可はアプリのインストール時にチェックされていました。[Google Play Store](https://play.google.com/store)などでアプリをインストールしようとして以下のような画面を見たことがある方も多いと思います。

![permission within Android 5.0](https://support.cdn.mozilla.net/media/uploads/gallery/images/2014-12-27-17-33-57-b8f219.png)

ユーザーが「同意する」を押すと、**アプリへのユーザー権限の許可が与えられかつインストールが開始**されていました。つまり**『アプリをインストールしている人=そのアプリのユーザー権限の許可全てを許可している人』**だったわけです。

Android 6からのユーザー権限のチェック
---

しかしAndroid 6.0 (SDK 23)から、パーミッションを得るタイミングがアプリインストール時ではなく**インストール後、その機能に初めてアクセスするタイミングで許可を求める仕様に変更**されました。このため、アプリ内でスマホのOSバージョンを判定し**6.0以上であればパーミッションを得るためのダイアログを出す必要が出てきました**。

軽くまとめると、違いは以下のような図で表されます。

![comparizon.png](https://qiita-image-store.s3.amazonaws.com/0/17214/41c2f374-2ea2-1660-1d21-bc658456eff9.png)


パーミッションを個別に与えることについて
---

ここでもう一つ重要な点があります。
それはAndroid 5以前では、**アプリが複数の機能(例えばカメラとGPSとbluetooth)にアクセスする場合、個別に許可を与えることができず、一括での許可または拒否しかできない**ということです。例えばGPSを許可してそれ以外は拒否するようなことはできません。
したがって、**アプリをインストールしたらユーザー権限の許可全てを許可したとみなされる**ことになるわけです。

一方6.0以降では**個別にユーザー権限の許可を与えるかどうかを指定できる**ようにもなったのです。
なので「カメラへのアクセスは許可するけど連絡先はだめ」というような指定を、ユーザーができるようになりました。

![comparizon2.png](https://qiita-image-store.s3.amazonaws.com/0/17214/6ac24edd-396f-458d-0712-e8899b3be30d.png)


### Facebookを例にして

これをもう少し詳しく、ここではFacebookを例に説明します(簡略化のためインターネット接続機能へのアクセスは許可されているものとします)。
Facebookアプリを入れると、友だちになった人のタイムラインを見れますよね。
もしタイムラインを見るだけであれば、Facebookアプリはまだどの機能へもアクセスしていません。

### 写真のシェア

ここであなたは写真を撮ってシェアしたいと考え、Facebook内からカメラを起動しました。
するとここで初めて、Facebookはカメラ機能へアクセスすることになるのでカメラ機能へのユーザー権限の許可を求めてきます。
そしてあなたが「許可」ボタンを押せば、無事にカメラが起動して写真を取ることができ、逆に「拒否」をした場合には元の画面に戻って何も起こりません。

![runtime-permission.PNG](https://qiita-image-store.s3.amazonaws.com/0/17214/2432c6d1-b241-cdf7-9d40-44bb3869edf2.png)

実際にAndroid 6では、これとほぼ同じ画面が現れます。

### 連絡先の取得

次にまたタイムラインに戻った時、今度は端末内の連絡先を取り込む機能があることを発見しました。
そこで「連絡先から友達を探す」というボタンを押したところ、やはり同様の画面が現れ、連絡先へアクセスするユーザー権限の許可を求められました。
しかしあなたは「なんか怖いな」と感じたため、これを拒否してしまいました。
その結果**あなたはFacebookに対し、カメラ機能へのアクセスは許可し連絡先へのアクセスは許可しなかった**ことになるのです。

まとめると、

* ユーザーからパーミッションを得るタイミングが**インストール前から実行中へ変わった**
* ユーザーは複数のパーミッションを求められた場合、**一括の許可と拒否から個別の許可と拒否が可能になった**

ということになります。

許可が降りない前提で開発する必要性
---

ではこれを開発者の視点で考えてみましょう。

Android 5まではどんなアプリでも、インストールされた時点で求めた全てのパーミッションに許可が降りたことになっているので、**そもそも「この機能に対して許可が降りなかったらどうするか」を考える必要はありませんでした**。先程の例で言えばFacebookを入れた時点で、ユーザーはカメラにも連絡先にも許可を出していた事になります。

ところがAndroid 6では**「一部（または全部）の許可が降りないことがある」という前提でアプリを作る必要が出てくる**わけです。先程の例で言えば、ユーザーが**カメラに許可を出すが連絡先には許可を出さないようなケースではどうするか**を考えて実装する必要があるということです。

このためAndroid 6のSDKには**パーミッションが降りているかどうかをチェックする[checkSelfPermission](http://developer.android.com/intl/ja/reference/android/support/v4/content/ContextCompat.html#checkSelfPermission(android.content.Context, java.lang.String))関数**が追加されています。また下りていない場合には**[requestPermissions](http://developer.android.com/intl/ja/reference/android/support/v13/app/FragmentCompat.html#requestPermissions(android.app.Fragment, java.lang.String[], int))関数を使って前述のようなダイアログを出してユーザーに許可を求めることができる**仕組みになったのです。

長くなりましたが、以上がAndroidにおけるユーザー権限の許可の概要と、OSバージョンの違いによる仕組みの差異になります。最後にGPSにアクセスする簡単なアプリを例に、具体的にどのように実装すればいいかを見ていきます。

GPSを利用するサンプルアプリ
---

以上を踏まえ実装した、簡単なサンプルプログラムを[GitHubにて公開](https://github.com/incheon/GPSPermissionTest/)したのでこれを元に概要を説明していきます。（詳細はプログラム内のコメントを確認してみてください）
内容は、ボタンを押したら許可を求めるダイアログを表示し、許可した場合は`TextView`に座標情報を表示するという簡単なプログラムです。

### `Androidmanifest.xml`での宣言
まず必要となるパーミッションを`Androidmanifest.xml`内で宣言します。位置情報取得の場合、取得方法に合わせて以下の要素を追加します。

```xml:Androidmanifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
  <!-- (中略) -->
  <!-- GPSから位置情報を取得することを許可する -->
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> 
  <!-- ネットワークから位置情報を取得することを許可する -->
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

GPSが使えない屋内などでは、通信用の電波強度を使って判定する仕組みがあるのですがそれぞれ記述が必要です。

### ボタンクリック時の挙動定義
次に適当な`Activity`(サンプルプログラムでは[MainActivity.java](https://github.com/incheon/GPSPermissionTest/blob/master/app/src/main/java/com/permission/gps/test/incheon/gpspermissiontest/MainActivity.java))にて、ボタンを表示させ、クリックしたら位置情報の許可を求めます。
この時、Android 6.0以降と5.x以下とで挙動が変わるため`if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {...`のような条件文でOSのバージョンを判定します。
そして6.0以降の場合は、`checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)`で許可が下りているかをチェックします。`checkSelfPermission`の引数には[チェックしたいパーミッションの種類を表す定数](http://developer.android.com/intl/ja/reference/android/Manifest.permission.html)を与えます。

なおここで**FINE LOCATIONが許可されれば、自動的にCOARSE LOCATIONのパーミッションも許可される**ようですのでチェックは前者のみでOKです([参考](http://stackoverflow.com/questions/15310742/if-i-have-access-fine-location-already-can-i-omit-access-coarse-location))。

パーミッションが下りていれば`LocationManager`インスタンスの取得(`initLocationManger()`の呼び出し)、いなければ`requestPermissions()`を呼び出して許可を求めるダイアログを出します。
`requestPermissions()`の引数には[得たいパーミッションの種類を表す定数](http://developer.android.com/intl/ja/reference/android/Manifest.permission.html)の配列と、そのパーミッションを識別する値(これは自分で好きに決めて良い)を与えます。

### `LocationManager`の取得

`initLocationManger()`内では何をしているでしょうか。
これは`LocationManager`という位置情報を管理するクラスのインスタンスを取得してメンバ変数に保存しているだけです。

```java
// 位置情報を管理するクラス
private LocationManager mLocationManager;
...
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
```
また`mLocationManager.requestLocationUpdates()`には位置情報を更新する条件やされた場合の挙動を定義します。詳細は省略しますが、4番目に`LocationListener`のインスタンスを渡して、`onLocationChanged()`をオーバーライドしてあげます。

これらをフローチャートにすると以下のようになります。

![flow.png](https://qiita-image-store.s3.amazonaws.com/0/17214/39b53967-7c15-bd28-545e-f312e463537a.png)


参考
---

* [AndroidでGPS（位置情報取得） (Qiita)](http://qiita.com/yasumodev/items/5f0f030f0ebfcecdff11)
* [Android MのPermissionを試してみた (Qiita)](http://qiita.com/STAR_ZERO/items/973c72cdf7813eccc006)
* [位置情報取得に関する覚え書き、その２(tomoyamkung のブログ)](http://tomoyamkung.net/2013/06/13/android-location2/)