Edited at

FusedLocationApiを使うときはSettingsApiを使ってユーザの設定を確認すべし (GPSオンだけでは不十分な時がある)

More than 3 years have passed since last update.


まとめ


  • Google様のFusedLocationProviderApiはユーザがGPS設定をOnにしてても位置情報を返してくれない場合がある(後述)


  • SettingsApiを使うとユーザの設定を確認し、設定を促すダイアログを表示出来る


FusedLocationProviderApiにはどういう問題があるか

Google Play Serviceに含まれているFusedLocationProviderApiは、GPSベースの位置測定と、ネットワークベースの位置測定をよしなに組み合わせた位置情報取得を行ってくれる(Google I/O 2013の発表を見ると組み合わせがいかにパワフルかわかる)。アプリ開発者は、以下の様な感じでお手軽に位置情報を取得できる

// (GoogleApiClientの初期化などは省略)

private void requestLocationUpdate() {
if (mGoogleApiClient.isConnected()) {
// 位置情報の取得が完了したタイミングでListnerが呼ばれる
FusedLocationApi.requestLocationUpdates(
mGoogleApiClient, createLocationRequest(), mLocationListener);
}
}

private LocationRequest createLocationRequest() {
return new LocationRequest()
.setInterval(1000)
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
}

ところが、このコードはユーザが下のように、一見すると位置情報をオンにしてるっぽい設定のときにListenerを呼び出してくれない(!)


なぜ上の例が動かないのか

手元のAndroid5.1および4.4では、位置情報をOn/Offするだけでなく、ユーザは3種類の位置情報モードを設定することが出来る。位置情報モードを「端末のみ(GPSで現在地を特定する)」にした場合「Google Play Serviceにデータを渡す許可をしていない」という風に扱われているらしく(?) FusedLocationApi.requestLocationUpdates は特に例外を投げたりするわけでもないが、何もしてくれない。

Stack overflowのこの回答によると、昔はGoogle app location settingという個別の設定項目があったらしい。現在はどうやらこの項目が上の3種類の位置情報モードと統合されたらしく、例えば位置情報モードを「高精度」に変えようとすると「Googleに情報を渡してもいいですか?」というようなダイアログが表示される。

このクソみたいな挙動は実はGoogle公認で、アプリでの位置情報の設定の管理 - Nexus ヘルプには「Googleの位置情報サービスをOnにする」ためには「位置情報」>「モード」をタップした後「高精度」または「バッテリー節約」モードを選択しよう、と書いてある


SettingsApiを使ったユーザ設定の確認とダイアログの表示

この悲しみを和らげるために、同じくGoogle Play Serviceに含まれているSettingsApiを用いることができる。このクラスの働きは大きく2つ

1. ユーザが必要な位置情報設定を満たしているか確認する

2. ユーザに位置情報設定を変更してもらうためのダイアログを表示する

具体的なコードとしては以下のような感じ

private static final int REQUEST_LOCATION_SET = 123; // 適当なリクエストコード

private void checkLocationPreference() {
// 1. ユーザが必要な位置情報設定を満たしているか確認する
PendingResult<> result = LocationServices.SettingsApi.checkLocationSettings(
mGoogleApiClient,
new LocationSettingsRequest.Builder().addLocationRequest(createLocationRequest()).build());
result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
@Override
public void onResult(LocationSettingsResult locationSettingsResult) {
final Status status = locationSettingsResult.getStatus();
switch (status.getStatusCode()) {
case LocationSettingsStatusCodes.SUCCESS:
// 位置情報が利用できる
// FusedLocationApi.requestLocationUpdatesなどを呼び出す
break;
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
try {
// 2. ユーザに位置情報設定を変更してもらうためのダイアログを表示する
status.startResolutionForResult(this /* activity */, REQUEST_LOCATION_SET);
} catch (IntentSender.SendIntentException e) {
}
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
// 位置情報が取得できず、なおかつその状態からの復帰も難しい時呼ばれるらしい
break;
}}});
}
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_LOCATION_SET) {
// ユーザのダイアログに対する応答をここで確認できる
switch (resultCode) {
case Activity.RESULT_OK:
// TODO
break;
case Activity.RESULT_CANCELED:
// TODO
break;
}
}
}

ユーザの設定を変更する必要がある場合はこういう感じのそれっぽいダイアログが表示される


参考

Googleがgithubで公開してるサンプルがなんだかんだ一番参考になる

https://github.com/googlesamples/android-play-location/tree/master/LocationSettings