Android 12では「おおよそ」の位置情報が選択できるようになるが、LocationManagerでは位置情報の更新を受け取れないらしい

Android 12からは以下のようにランタイムパーミッションのダイアログや、設定画面で「正確」な位置情報を許可せず、「おおよそ」の位置情報だけを許可するということができるようになるようです。

|パーミッションダイアログ|アプリ設定|
|:-:|:-:|
|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/9741e77a-8983-d18b-6c7a-e1241c83f342.png)|![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155171/8032f426-514c-69a7-0c54-62f2a751d55e.png)|

「おおよそ」の位置情報が許可された状態、正確な位置情報がオフの状態というのは、`ACCESS_FINE_LOCATION`は許可されず、`ACCESS_COARSE_LOCATION`だけが許可された状態になっています。
Android 12までは`ACCESS_FINE_LOCATION`をリクエストしていた場合は、`ACCESS_FINE_LOCATION`を許可するかどうかしかユーザーは選択できず、`ACCESS_FINE_LOCATION`を許可されれば`ACCESS_COARSE_LOCATION`は許可されるので、その動作を前提に割り切った実装をしていた場合は、`ACCESS_COARSE_LOCATION`だけが許可された状態を考慮するように変更が必要ですね。

# ACCESS_COARSE_LOCATIONではLocationManagerで位置情報の更新が受け取れない？

さて、これだけなら「ユーザーの選択肢が増えた」で終わりなのですが、**Android 4.2**のころから、`ACCESS_COARSE_LOCATION`だけが許可された状態では`LocationManager.requestLocationUpdates`の`onLocationChanged`がコールされないという問題があるらしいという噂を聞きました。

https://issuetracker.google.com/issues/36975498

ということで検証してみます。

```kotlin
Log.e("XXXX", "ACCESS_COARSE_LOCATION: ${checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION)}")
Log.e("XXXX", "ACCESS_FINE_LOCATION: ${checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)}")

val manager: LocationManager = getSystemService()!!
manager.allProviders.forEach { provider ->
    Log.e("XXXX", "getLastKnownLocation: $provider ${manager.getLastKnownLocation(provider)}")
    manager.requestLocationUpdates(
        provider, 1, 1f, LocationListenerAdapter {
            Log.e("XXXX", "onLocationChanged: $provider $it")
        })
    LocationManagerCompat.getCurrentLocation(manager, provider, null, ContextCompat.getMainExecutor(this), {
        Log.e("XXXX", "getCurrentLocation: $provider $it")
    })
}
```

各Providerに対して、`getLastKnownLocation`/`requestLocationUpdates`/`getCurrentLocation`をコールして結果を表示させています。
最初にパーミッションの状態をチェックしています。実装は以下です。

```kotlin
private fun checkPermission(permission: String): String =
    if (PermissionChecker.checkSelfPermission(this, permission) ==
        PermissionChecker.PERMISSION_GRANTED
    ) "GRANTED" else "DENIED"
```

検証環境は Android 12 Beta 5 の Pixel5 です。

## 正確な位置情報が許可された状態

まずは、FINE_LOCATIONが許可された状態で見てみます。

```
ACCESS_COARSE_LOCATION: GRANTED
ACCESS_FINE_LOCATION: GRANTED
getLastKnownLocation: passive Location[fused 34.xxxxxx,135.xxxxxx hAcc=20.0 et=+1d22h2m57s39ms alt=37.860162605487275 vAcc=3.0]
getLastKnownLocation: network Location[network 34.xxxxxx,135.xxxxxx hAcc=20.0 et=+1d22h2m41s72ms alt=38.30000305175781 vAcc=1.3333334 {Bundle[mParcelledData.dataSize=68]}]
getLastKnownLocation: fused Location[fused 34.xxxxxx,135.xxxxxx hAcc=20.0 et=+1d22h2m57s39ms alt=37.860162605487275 vAcc=3.0]
getLastKnownLocation: gps null
onLocationChanged: passive Location[fused 34.xxxxxx,135.xxxxxx hAcc=20.0 et=+1d22h2m57s39ms alt=37.860162605487275 vAcc=3.0]
getCurrentLocation: passive Location[fused 34xxxxxx,135.xxxxxx hAcc=20.0 et=+1d22h2m57s39ms alt=37.860162605487275 vAcc=3.0]
getCurrentLocation: network Location[network 34.xxxxxx,135.xxxxxx hAcc=20.0 et=+1d22h2m41s72ms alt=38.30000305175781 vAcc=1.3333334 {Bundle[mParcelledData.dataSize=68]}]
getCurrentLocation: fused Location[fused 34.xxxxxx,135.xxxxxx hAcc=20.0 et=+1d22h2m57s39ms alt=37.860162605487275 vAcc=3.0]
onLocationChanged: fused Location[fused 34.xxxxxx,135.xxxxxx hAcc=20.0 et=+1d22h3m2s50ms alt=37.860162605487275 vAcc=3.0]
onLocationChanged: network Location[network 34.xxxxxx,135.xxxxxx hAcc=12.191 et=+1d22h3m3s604ms alt=38.30000305175781 vAcc=1.3333334 {Bundle[mParcelledData.dataSize=68]}]
getCurrentLocation: gps null
```

`GPS_PROVIDER`がnullを返していますが、それぞれ何らかのPROVIDERが位置情報を返しています。
`NETWORK_PROVIDER`で位置情報がとれているので`ACCESS_COARSE_LOCATION`の状態でも位置情報がとれそうです。

## おおよその位置情報だけが許可された状態

続いて、`ACCESS_COARSE_LOCATION`だけが許可された状態で実行してみます。

```
ACCESS_COARSE_LOCATION: GRANTED
ACCESS_FINE_LOCATION: DENIED
getLastKnownLocation: passive Location[fused 34.xxxxxx,135.xxxxxx hAcc=2000.0 et=+1d22h8m57s325ms]
getLastKnownLocation: network Location[network 34.xxxxxx,135.xxxxxx hAcc=2000.0 et=+1d22h5m18s717ms]
getLastKnownLocation: fused Location[fused 34.xxxxxx,135.xxxxxx hAcc=2000.0 et=+1d22h8m57s325ms]
getLastKnownLocation: gps null
getCurrentLocation: passive null
getCurrentLocation: network null
getCurrentLocation: fused null
getCurrentLocation: gps null
```

`getLastKnownLocation`では位置情報がとれていますが、`getCurrentLocation`ではすべてnull、`requestLocationUpdates`では`onLocationChanged`がコールされません。

うーん、どういうことでしょう？

# FusedLocationProviderClientの場合

FusedLocationProviderClientが登場してからは、こちらが推奨されていることもあり、位置情報は使うが、LocationManagerは使っていないというアプリも多いかと思います。（GoogleAPIが使えない環境では使えませんが）
FusedLocationProviderClientの場合、動作はどうなっているのでしょうか？確認してみます。

```kotlin
Log.e("XXXX", "ACCESS_COARSE_LOCATION: ${checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION)}")
Log.e("XXXX", "ACCESS_FINE_LOCATION: ${checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)}")

val client = LocationServices.getFusedLocationProviderClient(this)
client.requestLocationUpdates(LocationRequest.create(), object : LocationCallback() {
    override fun onLocationResult(location: LocationResult) {
        Log.e("XXXX", "onLocationResult: $location")
    }
}, Looper.getMainLooper())
val task = client.getCurrentLocation(LocationRequest.PRIORITY_LOW_POWER, null)
task.addOnSuccessListener {
    Log.e("XXXX", "onSuccess: $it")
}
```

## 正確な位置情報が許可された状態

当然、FINE_LOCATIONが許可されていれば問題ありません。

```
ACCESS_COARSE_LOCATION: GRANTED
ACCESS_FINE_LOCATION: GRANTED
onLocationResult: LocationResult[locations: [Location[fused 34.xxxxxx,135.xxxxxx hAcc=12.552 et=+1d22h20m3s986ms alt=37.799487734693756 vAcc=3.0 {Bundle[mParcelledData.dataSize=52]}]]]
onSuccess: Location[fused 34.xxxxxx,135.xxxxxx hAcc=12.552 et=+1d22h22m40s542ms alt=37.799487734693756 vAcc=3.0 {Bundle[mParcelledData.dataSize=52]}]
```

## おおよその位置情報だけが許可された状態

FusedLocationProviderClientであれば、COARSE_LOCATIONのみの場合も問題無く取得できます。

```
ACCESS_COARSE_LOCATION: GRANTED
ACCESS_FINE_LOCATION: DENIED
onLocationResult: LocationResult[locations: [Location[fused 34.738739,135.394036 hAcc=2000.0 et=+1d22h19m14s26ms]]]
onSuccess: Location[fused 34.xxxxxx,135.xxxxxx hAcc=2000.0 et=+1d22h20m0s359ms]
```

# まとめ

`ACCESS_COARSE_LOCATION`だけが許可された状態ではLocationManagerでは位置情報の更新を受け取ることができず、`getLastKnownLocation`でしか位置情報をとることができません。
一方、**FusedLocationProviderClientであれば問題無く位置情報の更新を受け取ることができるようです。**

FusedLocationProviderClientを導入すれば問題はありません。GoogleAPIが使えない環境であれば、`getLastKnownLocation`をうまく利用する必要がありそうです。

ただ、`onLocationChanged`がコールされないのが仕様通りという訳ではないと思いますが、Android 4.2から動いていないという話が本当なら、なぜ未だに修正されないのか分かりません。謎です。

以上です