2012年7月27日金曜日

androidで嘘の位置情報を端末に伝える(擬似ロケーション)

エミュレータの場合

DDMSのエミュレータコントロールのLocation Controlsから、擬似座標を送信することができるので非常に簡単です。Devicesでエミュレータを選択しないとアクティブにならないという点だけ注意。

KMLというXMLベースの記述法で、位置情報のリストを作成し読み込むことができます。これにより複数の位置情報のテストも簡単に行えます。

KMLとはKeyhole Markup Languageの略称です。KeyholeとはGoogle Earthの昔の名前のことです。なので、Google Earthでピンを立てた場所を、KMLとして出力することができます。

ただしGoogle Earthが出力するフォーマットは、DDMSが認識するフォーマットより新しいのか認識しなかったり、マルチバイト文字が含まれると文字化けや読み込み不良を起こす場合があります。

うまく読み込めなかった場合は、Google Earthが出力したKMLをシンプルに整形しましょう。coordinates要素内に、Longitude、Latitude、heightのの順に、コンマ区切りで書けば認識します。順序が緯度経度、ではなく経度緯度なところだけ注意。

実機の場合

実機で擬似位置座標を送る場合は、専用のロジックが必要になります。実装したアプリケーションはマーケットを探せばありますが、以下にどうやって擬似ロケーションを発生させるアプリを作るのか述べます。調べ中の要素があるので以下の記述は不正確な恐れがあります。というか、不正確だと思った方がいいです。

開発者オプションの設定

まず、開発者オプションにて「擬似ロケーションを有効」にします。

「擬似ロケーションを有効」にしない場合、以下に述べる方法で偽の位置情報を発生させても例外が発生して成功しません。

「擬似ロケーションを有効」の設定は、アプリケーション内から確認することができます。Settings.Secureを使います。詳細はhttp://d.hatena.ne.jp/terurou/20100930/1285814213をどうぞ。onResume()でこの設定を確認することで、位置情報偽装対策を行うことができます。(ちゃんと作られているアプリは、この対策を行っているはず)

マニフェストの設定

擬似ロケーションを生成したいアプリのマニフェストで、ACCESS_MOCK_LOCATIONのパーミッションを取得する必要があります。(ACCESS_FINE_LOCATIONもいるかもしれません。)

ACCESS_MOCK_LOCATIONは擬似ロケーションを生成するためのパーミッションです。「エミュレータで擬似情報を送信するためのパーミッション」という記述を見かけたことがありますが、DDMSから位置情報を送信するのにパーミッションは必要ありません。

LocationManagerの設定

LocationManagerをシステムサービスから取得し、addTestProviderで擬似ロケーションプロバイダを作成します。

引数が大量にありますが、重要なのは第一引数のnameだけだと思います。ここに使うプロバイダ名(GPSプロバイダのモックならば、LocationManager.GPS_PROVIDER)を渡します。

name以外の引数は、ネットワークや衛星や電話回線の使用、電力コストや正確性などのプロバイダの属性を意味しています。これらは複数のロケーションプロバイダが存在するときに、端末がどのプロバイダを優先的に使うのか判断するために存在するようです。

とりあえず真理値の部分は全部falseでも動きます。消費電力と正確性はCriteriaクラスのintの定数になので、CriteriaのReferenceを参照してください。

これでLocationManagerに擬似ロケーションのプロバイダが設定されました。

Locationの生成と設定

嘘の位置情報となるLocationオブジェクトを作ります。任意の座標をセットしてください。

そしてLocationManagerのsetTestProviderLocationに、設定したnameと、作成したLocationを渡してセットします。

擬似座標情報の取得

あとは通常の手順で位置情報を取得します。

requestLocationUpdatesに、同じくnameと、位置情報取得間隔の精度を指定するminTime、minDistance、あとはコールバックなどを設定します。

minTimeは最低再取得間隔です。本来は電源節約などのために設定するものですが、0ミリ秒で問題ないと思います。サービスなどで長期に渡って擬似座標を送信するプログラムを作る場合は、大きな数字を与えてください。

minDistanceは最低再取得距離です。単位はメーターで、直近で取得したポイントから指定したメーター以上動かないと、LocationListenerのonLocationChangedが呼ばれません。0の場合は移動なしで呼ばれるかというと、端末によるのかもしれませんが、Nexus Sでは全く同一の座標だと一度しかonLocationChangedは呼ばれませんでした。

繰り返し擬似座標を生成し続ける必要がある場合、擬似乱数でLocationにほんのわずか揺らぎを与えながら、ループなりハンドラで繰り返しrequestLocationUpdatesを呼べば問題ないと思います。

onLocationChangedの引数で受け取るLocationの値が、擬似ロケーションとして与えたダミーの値と一致していれば成功です。

後片付け

最後にremoveUpdates、removeTestProviderで擬似ロケーションプロバイダを取り除きます。

コード

以下は参考になるか不明なコード抜粋。

LocationManager manager = (LocationManager)getSystemService(LOCATION_SERVICE);
manager.addTestProvider(LocationManager.GPS_PROVIDER, false, false, false, false, false, 
 false , false, android.location.Criteria.POWER_LOW, android.location.Criteria.ACCURACY_FINE);

//本当はこの辺は別スレッドでループ回してた。
//別スレッドからLocationManagerを操作する場合、ハンドラを経由するか、
//Looperを渡すオーバロードメソッドを使用します。
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(TEST_LATITUDE);
location.setLongitude(TEST_LONGTUDE);
manager.setTestProviderLocation(LocationManager.GPS_PROVIDER, location);
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);

manager.removeLocationUpdates(LocationManager.GPS_PROVIDER);
manager.removeTestProvider(LocationManager.GPS_PROVIDER);

余談

位置情報を使うアプリを作る場合、位置情報を取得するクラスはインターフェースにしておくとテストが楽だと思います。

そうすれば手の込んだことをしなくても、モックのLocationListenerにテストしたい座標を返却させるだけで済むような気がします。

0 件のコメント:

コメントを投稿