2012年1月6日金曜日

ノブレス・オブリージュ

今日こそは、位置情報の取得について更新しようと思っていたのですが・・・。
帰りの電車で神山健治監督のつぶやきを眺めてしまったのが運の尽き。

https://twitter.com/#!/PH9/status/155257322854301696

21:00『東のエデン』TVシリーズニコ生にて一挙放送! http://goo.gl/XIUwe まずは神山監督 山本P 石井Pでエデンについて語ります!#edenoftheeast #PH9


というわけで、完全に東のエデンモードにはいっています。
東のエデンは、物語にARが登場するお気に入りのアニメの一つでして。

まぁ、僕は残念な半オタクっ子なので、大半のアニメは恥じることなく見ますし、ひとりでアニメを見に映画館にも行くわけですが、それができないおっさんでも、大手を振ってみて問題のない作品かと思います。

ちなみに、21:00からの作品に関する紹介番組で、Blu-ray Boxが発売されることも知ってしまいました。





【Amazon.co.jp限定】『東のエデン』 Blu-ray "Noblesse Oblige" BOX(表紙描き下ろし設定資料集付き)


2009年4~6月フジテレビ“ノイタミナ”ほかにてTVシリーズ11話が放送され話題になり、2009年11月に「東のエデン 劇場版I The King of Eden」、2010年3月に「東のエデン 劇場版II Paradise Lost」が全国劇場公開され大ヒットを記録!
TVシリーズから続き、劇場版でだんだんと明らかになっていく謎を1パッケージですべて全て収録!

これが2万以下なら安いだろう!ってことで、3月発売だから自分への誕生日プレゼントということで予約しちゃいました。


というわけで、今夜は東のエデンの日ということで、技術ブログは2日連続で持ち越し・・・ごめんなさい。


あ、リンクはアフェリエイトのなので、いやだなと思うひとは、自分で検索かけて、予約してください。

ノブレス・オブリージュ、今後も救世主たらんことを。


2012年1月5日木曜日

LARK買ってみました

さて、ちょっと調べものはひと休憩して、買い物記録。
Apple Storeの初売りを眺めようと見に行ったら、安くなってるのと別の品に目が止まりました。




どうみても濃いパッケージなんですが、この歯を出して笑っている男性の腕にまいているのが、今回の品です。こんな形をしていますが、目覚まし時計です。iPhoneとセットで使うようにできています。




小汚い部屋の片隅でとったのでよけいなものが移ってますが、テーブルの上に乗っているのが中身です。就寝までの時間、充電しておくDockとACアダプタ、腕にまくバンド、そしてセンサー付きの目覚ましです。
普通の目覚ましと違うのは、まずアラーム音はしないです。振動して起こすタイプですね。

パッケージの男性がどや顔なのは、傍らの女性を起こさずに目が覚めたからということなんでしょう。まぁ、僕には無縁の話ではあるのですが。
じゃ、なんでこんなのをかったのかというと、睡眠の履歴を見てみたかったからです。




年末の疲れがたっぷり残っていたので、いつもより睡眠時間が長めだったので、まだあまり参考になっていないのですが、眠りについた時間と、起床時間が記録されています。



ただそれだけなのか?というと、実は違っていて、眠っている間の寝返り等の動作から、眠りの深さと周期を計測してくれます。7日間分のデータで、眠りのパターンや改善点を示唆してくれるらしいです。

どこかのサイトでは、おきやすいところで起こしてくれるとかいう記述をみたのですが、どうも気のせいだった模様。今のところアラームの設定時間に起こされている気がします。

ちなみに、起きた時の気分や、その日の状態をメモできる機能もあり、どんな時のどういう眠りの傾向にあるのか記録して分析してみるにはよさそうです。

ちなみに、このデータ、Web上のサーバーにも転送しているらしく、メーカーページに自分のアカウントでログインすると、ブラウザでも状態が表示されます。プライバシーを気にする方には、ちょっと嫌な機能かもしれませんね。

まぁ、僕は、探されると住所も氏名も電話番号も、趣味や性癖までネットで把握されちゃうであろうくらいそのあたりがずぼらにできているので、そんなに気にしていませんけどね。

とりあえず、今年の目標は、最低4時間半睡眠は確保する事で、今のところ順調に進んでいるので、このまま、記録をとりつつ、眠りの質を改善してみたいなと思っています。

P.S.
一応アプリは日本語ですが、コーチの機能や、Webページの多くが英語です。
もう少しがんばって翻訳して欲しいなぁ。

2012年1月3日火曜日

位置情報取得について調べてみた(その3)

さて、ではGpsLocationProviderを中心に調べてみる事にしよう。
前回、GpsLocationProviderを呼び出すとnativeの初期化関数が呼び出されるという話をした。実際に何をやっているのかというと以下のようになっています。


static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
    int err;
    hw_module_t* module;

    method_reportLocation             = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V");
    method_reportStatus                   = env->GetMethodID(clazz, "reportStatus", "(I)V");
    method_reportSvStatus              = env->GetMethodID(clazz, "reportSvStatus", "()V");
    method_reportAGpsStatus         = env->GetMethodID(clazz, "reportAGpsStatus", "(III)V");
    method_reportNmea                    = env->GetMethodID(clazz, "reportNmea", "(J)V");
    method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(I)V");
    method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
    method_reportNiNotification     = env->GetMethodID(clazz, "reportNiNotification",
                                                                "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
    method_requestRefLocation      = env->GetMethodID(clazz,"requestRefLocation","(I)V");
    method_requestSetID                   = env->GetMethodID(clazz,"requestSetID","(I)V");
    method_requestUtcTime             = env->GetMethodID(clazz,"requestUtcTime","()V");

    err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
    if (err == 0) {
        hw_device_t* device;
        err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);
        if (err == 0) {
            gps_device_t* gps_device = (gps_device_t *)device;
            sGpsInterface = gps_device->get_gps_interface(gps_device);
        }
    }

    if (sGpsInterface) {
        sGpsXtraInterface      = (const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE);
        sAGpsInterface           = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
        sGpsNiInterface         = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
        sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
        sAGpsRilInterface     = (const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);
    }
}

やっている事は以下の事になります。
  1. Native層から呼び出すいくつかのJavaのMethod IDを入手し、globalに保存
  2. gpsのHAL実装ライブラリのloadとModuleのopen, Interfaceの入手
  3. 拡張Interface情報の取得
なお、上記で入手している以下の情報については、HAL層の実装(gps.xxxxx.so)で用意する必要があります。
  • hw_module_t
  • hw_device_t (gps_device_t)

これらの基本的なstructは、hardware/libhardware/include/hardware/hardware.h に定義されています。

最初の hw_module_t は、hw_get_module()関数で、以下のようにして取得されます。

    const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
    hmi = (struct hw_module_t *)dlsym(handle, sym);

この事から、HAL(gps.xxxx.so)内部では、HAL_MODULE_INFO_SYM_AS_STRの名前のhw_module_t構造体が実体定義されている必要があります。

なお、hardware.hの中の定義は以下の通りです。

#define HAL_MODUULE_INFO_SYM HMI
#define HAL_MODUULE_INFO_SYM_AS_STR HMI

typedef struct hw_module_t {
    uint32_t tag;
    uint16_t version_major;
    uint16_t version_minor;
    const char *id;
    const char *name;
    const char *author;
    struct hw_module_methods_t* methods;
    void* dso;
    uint32_t reserved[32-7];
} hw_module_t;

typedef struct hw_module_methods_t {
    int (*open)(const struct hw_module_t* module, const char* id,
            struct hw_device_t** device);
} hw_module_methods_t;


実際、gpsの実装例であるqcom用のコードでは以下のようになっています。
[hardware/qcom/gps/loc_api/libloc_api/gps.c]

static struct hw_module_methods_t gps_module_methods = {
    .open = open_gps
};

const struct hw_module_t HAL_MODULE_INFO_SYM = {
    .tag = HARDWARE_MODULE_TAG,
    .version_major = 1,
    .version_minor = 0,
    .id = GPS_HARDWARE_MODULE_ID,
    .name = "loc_api GPS Module",
    .author = "Qualcomm USA, Inc.",
    .methods = &gps_module_methods,
};

続いて、hw_device_tのポインタの取得になります。ただし、これには1点注意が必要で、構造体のキャストによるハックが含まれています。
hw_module_t.hw_module_methods_t.open() では、hw_device_t** にポインタが含まれて帰ります。ただし、この実体は、hw_device_t型構造体の実体ではありません。 gpsの場合、gps_device_t型の構造体のポインタとなります。

hw_device_t* でアクセスしている場合は、先頭のcommon領域にポインタがあう事になり、gps_device_t*でアクセスした場合は、get_gps_interfaceの関数ポインタまで含めたメモリ領域にアクセスできるようになります。

typedef struct hw_device_t {
    uint32_t tag;
    uint32_t version;
    struct hw_module_t* module;
    uint32_t reserved[12];
    int (*close)(struct hw_device_t* device);
} hw_device_t;

[hardware/libhardware/include/hardware/gps.h]
struct gps_device_t {
    struct hw_device_t common;
    const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev);
};

[hardware/qcom/gps/loc_api/libloc_api/gps.c]
static int open_gps(const struct hw_module_t* module, char const* name, struct hw_device_t** device)
{
    struct gps_device_t *dev = malloc(sizeof(struct gps_device_t));
    memset(dev, 0, sizeof(*dev));

    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module = (struct hw_module_t*)module;
    dev->get_gps_interface = gps__get_gps_interface;

    *device = (struct hw_device_t*)dev;
    return 0;
}

この辺りからは、HALの中でもGPS固有の処理となります。
const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev) に設定されているgps__get_gps_interfaceで、GPSドライバのインターフェースを入手します。

typedef struct {
    size_t          size;
    int   (*init)( GpsCallbacks* callbacks );
    int   (*start)( void );
    int   (*stop)( void );
    void  (*cleanup)( void );
    int   (*inject_time)(GpsUtcTime time, int64_t timeReference,int uncertainty);
    int  (*inject_location)(double latitude, double longitude, float accuracy);
    void  (*delete_aiding_data)(GpsAidingData flags);
    int   (*set_position_mode)(GpsPositionMode mode, GpsPositionRecurrence recurrence,
            uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time);
    const void* (*get_extension)(const char* name);
} GpsInterface;

つまり、HALのGPSドライバとしてはこのインターフェースを実装する事になります。
さらに、この最低限のAPIに加え、実装に応じて追加の機能を提供するための拡張用のAPIを取得するのが、get_extension関数ですね。


GPS_XTRA_INTERFACE
typedef void (* gps_xtra_download_request)();

typedef struct {
    gps_xtra_download_request download_request_cb;
    gps_create_thread create_thread_cb;
} GpsXtraCallbacks;

typedef struct {
    size_t          size;
    int  (*init)( GpsXtraCallbacks* callbacks );
    int  (*inject_xtra_data)( char* data, int length );
} GpsXtraInterface;


AGPS_INTERFACE
typedef struct {
    size_t          size;
    AGpsType        type;
    AGpsStatusValue status;
    uint32_t        ipaddr;
} AGpsStatus;

typedef void (* agps_status_callback)(AGpsStatus* status);

typedef struct {
    agps_status_callback status_cb;
    gps_create_thread create_thread_cb;
} AGpsCallbacks;

typedef struct {
    size_t          size;
    void  (*init)( AGpsCallbacks* callbacks );
    int  (*data_conn_open)( const char* apn );
    int  (*data_conn_closed)();
    int  (*data_conn_failed)();
    int  (*set_server)( AGpsType type, const char* hostname, int port );
} AGpsInterface;


GPS_NI_INTERFACE
typedef struct {
    size_t          size;
    int             notification_id;
    GpsNiType       ni_type;
    GpsNiNotifyFlags notify_flags;
    int             timeout;
    GpsUserResponseType default_response;
    char            requestor_id[GPS_NI_SHORT_STRING_MAXLEN];
    char            text[GPS_NI_LONG_STRING_MAXLEN];
    GpsNiEncodingType requestor_id_encoding;
    GpsNiEncodingType text_encoding;
    char           extras[GPS_NI_LONG_STRING_MAXLEN];
} GpsNiNotification;

typedef void (*gps_ni_notify_callback)(GpsNiNotification *notification);

typedef struct
{
    gps_ni_notify_callback notify_cb;
    gps_create_thread create_thread_cb;
} GpsNiCallbacks;

typedef struct
{
    size_t          size;
   void (*init) (GpsNiCallbacks *callbacks);
   void (*respond) (int notif_id, GpsUserResponseType user_response);
} GpsNiInterface;

GPS_DEBUG_INTERFACE
typedef struct {
    size_t          size;
    size_t (*get_internal_state)(char* buffer, size_t bufferSize);
} GpsDebugInterface;


AGPS_RIL_INTERFACE
typedef struct {
    agps_ril_request_set_id request_setid;
    agps_ril_request_ref_loc request_refloc;
    gps_create_thread create_thread_cb;
} AGpsRilCallbacks;

typedef struct {
    size_t          size;
    void  (*init)( AGpsRilCallbacks* callbacks );
    void (*set_ref_location) (const AGpsRefLocation *agps_reflocation, size_t sz_struct);
    void (*set_set_id) (AGpsSetIDType type, const char* setid);
    void (*ni_message) (uint8_t *msg, size_t len);
    void (*update_network_state) (int connected, int type, int roaming, const char* extra_in
fo);
    void (*update_network_availability) (int avaiable, const char* apn);
} AGpsRilInterface;

拡張のAPIも含めると、HALでは結構な数のAPIを実装する必要がありそうですね。
ちなみに、先ほどみたqcomの実装だと3種類の拡張を用意しているようですね。

static const void* loc_eng_get_extension(const char* name)
{
    if (strcmp(name, GPS_XTRA_INTERFACE) == 0)
    {
        return &sLocEngXTRAInterface;
    }
    else if (strcmp(name, AGPS_INTERFACE) == 0)
    {
        return &sLocEngAGpsInterface;
    }
    else if (strcmp(name, GPS_NI_INTERFACE) == 0)
    {
        return &sLocEngNiInterface;
    }
    return NULL;
}

というわけで、GpsProviderLocationは、まず真っ先にHALのAPIを読み込んでいます。
その後、GpsLocationProvier.isSupported()が呼ばれているわけですが、これは、

public static boolean isSupported() {
    return native_is_supported();
}

static jboolean android_location_GpsLocationProvider_is_supported()
{
    return (sGpsInterface != NULL);
}

とあるように、HALから基本となるInterfaceが取得できているかのチェックとなります。
さて、実際のLocationProviderのInterfaceですが、以下のように定義されています。

public interface LocationProviderInterface {
    String getName();
    boolean requiresNetwork();
    boolean requiresSatellite();
    boolean requiresCell();
    boolean hasMonetaryCost();
    boolean supportsAltitude();
    boolean supportsSpeed();
    boolean supportsBearing();
    int getPowerRequirement();
    boolean meetsCriteria(Criteria criteria);
    int getAccuracy();
    boolean isEnabled();
    void enable();
    void disable();
    int getStatus(Bundle extras);
    long getStatusUpdateTime();
    void enableLocationTracking(boolean enable);
    /* returns false if single shot is not supported */
    boolean requestSingleShotFix();
    String getInternalState();
    void setMinTime(long minTime, WorkSource ws);
    void updateNetworkState(int state, NetworkInfo info);
    void updateLocation(Location location);
    boolean sendExtraCommand(String command, Bundle extras);
    void addListener(int uid);
    void removeListener(int uid);
}

ちなみに、GpsLocationProviderでは
    /**
     * Returns true if the provider requires access to a
     * data network (e.g., the Internet), false otherwise.
     */
    public boolean requiresNetwork() {
        return true;
    }

    /**
     * Returns true if the provider requires access to a
     * satellite-based positioning system (e.g., GPS), false
     * otherwise.
     */
    public boolean requiresSatellite() {
        return true;
    }

    /**
     * Returns true if the provider requires access to an appropriate
     * cellular network (e.g., to make use of cell tower IDs), false
     * otherwise.
     */
    public boolean requiresCell() {
        return false;
    }

が設定されています。A-GPSかどうかに関わらず、Network必要なのか。

クライアントの処理を見た時の、LocationManager.getBestProvider(criteria,truer); が実行されると条件にあうProviderを探しながら最適なProviderを返すわけですが、この処理が以下のようになっています。

    public String getBestProvider(Criteria criteria, boolean enabledOnly) {
        List<String> goodProviders = getProviders(criteria, enabledOnly);
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Make a copy of the criteria that we can modify
        criteria = new Criteria(criteria);

        // Loosen power requirement
        int power = criteria.getPowerRequirement();
        while (goodProviders.isEmpty() && (power != Criteria.NO_REQUIREMENT)) {
            power = nextPower(power);
            criteria.setPowerRequirement(power);
            goodProviders = getProviders(criteria, enabledOnly);
        }
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Loosen accuracy requirement
        int accuracy = criteria.getAccuracy();
        while (goodProviders.isEmpty() && (accuracy != Criteria.NO_REQUIREMENT)) {
            accuracy = nextAccuracy(accuracy);
            criteria.setAccuracy(accuracy);
            goodProviders = getProviders(criteria, enabledOnly);
        }
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Remove bearing requirement
        criteria.setBearingRequired(false);
        goodProviders = getProviders(criteria, enabledOnly);
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Remove speed requirement
        criteria.setSpeedRequired(false);
        goodProviders = getProviders(criteria, enabledOnly);
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        // Remove altitude requirement
        criteria.setAltitudeRequired(false);
        goodProviders = getProviders(criteria, enabledOnly);
        if (!goodProviders.isEmpty()) {
            return best(goodProviders).getName();
        }

        return null;
    }

以下の順で条件を緩くしながら、合致するProviderを探して、条件が合致するProviderが見つかったら、その中から最適なものを返しています。
  1. ユーザー指定のCriteriaに合致するProviderを探す。
  2. Power条件を下げながらその他がCriteriaに合致するProviderを探す。
  3. 精密さの条件を下げながらその他のCriteriaに合致するProviderを探す。
  4. 方角に関する条件を削除してその他のCriteriaに合致するProviderを探す。
  5. 速度に関する条件を削除してその他のCriteriaに合致するProviderを探す。
  6. 高度に関する条件を削除してその他のCriteriaに合致するProviderを探す
この時の、条件合致は、getProviders()でProvider側で実装している
boolean meetsCriteria(Criteria criteria);
でチェックされていますね。ちなみに、GpsLocationProviderの条件はPowerしかみてません。

    public boolean meetsCriteria(Criteria criteria) {
        return (criteria.getPowerRequirement() != Criteria.POWER_LOW);
    }

とりあえず、ここまでで初期処理とProviderの選択のうっすらとしたイメージがつかめたので、次回は、ようやくLocationの取得を調べてみたいと思います。

2012年1月2日月曜日

位置情報取得について調べてみた(その2)

さて、昨日は位置情報の取得技術と、Androidのアプリからの利用方法について軽く調べてみました。というわけで、続きの調査メモです。

前回のエントリで見た位置情報関連クラスは以下の通りです。
  • Address
  • Criteria
  • Geocoder
  • GpsSatelite
  • GpsStatus
  • Location
  • LocationManager
  • LocationProvider
さて、この中で真っ先に取得するのは、LocationManager [ frameworks/base/location/java/android/location/LocationManager.java ]
でした。LocationManagerは、Binderを使ってSystem Location Service(LocationManagerService)へアクセスするためのクラスのようです。

実体は、ContextImplクラスのstaticブロックで生成・登録されています。
[src] frameworks/base/core/java/android/app/ContextImpl.java
        registerService(LOCATION_SERVICE, new StaticServiceFetcher() {
                public Object createStaticService() {
                    IBinder b = ServiceManager.getService(LOCATION_SERVICE);
                    return new LocationManager(ILocationManager.Stub.asInterface(b));
                }});

というわけで、ここでPF部として目を付けなくてはならないのは、LocationManagerServiceですね。で、このLocationManagerServiceが生成されているのは何処か?というと、いつか何処かでみた場所なんですね。この辺りはPF部でも発表してますし、さらっと呼び出しだけ見ておきましょう。

SystemServer::init2()
  ServerThread::start()
    ServerThread::run() [frameworks/base/services/java/com/android/server/SystemServer.java]
        location = new LocationManagerService(context);
        ServiceManager.addService(Context.LOCATION_SERVICE, location);
        final LocationManagerService locationF = location;
        locationF.systemReady(); [frameworks/base/services/java/com/android/server/LocationManagerService.java]
            Thread thread = new Thread(null, this, "LocationManagerService");
            thread.start();

注: 例によって上記はC++チックなメソッド名の表記をしているだけです。

init周りを追いかけた時にでてきたSystemServerのServerThread中で生成され、LocationManagerServiceスレッドがstartされています。

public void LocationManagerService::run()
{
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    Looper.prepare();
    mLocationHandler = new LocationWorkerHandler();
    initialize();
    Looper.loop();
}

Runされると、initialize処理を行った後、Looperでまわしています。このLocationManagerService::initialize()の中で、呼んでいるLocationManagerSerivce::loadProviders()でAndroid共通のProviderの生成と登録、Geocoderの生成が行われています。

LocationManagerService::loadProviders()
    LocationManagerService::loadProvidersLocked()
        LocationManagerService::_loadProvidersLocked()
        {
                if (GpsLocationProvider.isSupported()) {
                    GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this);
                    mGpsStatusProvider = gpsProvider.getGpsStatusProvider();
                    mNetInitiatedListener = gpsProvider.getNetInitiatedListener();
                    addProvider(gpsProvider);
                    mGpsLocationProvider = gpsProvider;
                }
                PassiveProvider passiveProvider = new PassiveProvider(this);
                addProvider(passiveProvider);
                mEnabledProviders.add(passiveProvider.getName());

                PackageManager pm = mContext.getPackageManager();
                if (mNetworkLocationProviderPackageName != null &&
                    pm.resolveService(new Intent(mNetworkLocationProviderPackageName), 0) != null) {
                    mNetworkLocationProvider =
                        new LocationProviderProxy(mContext, LocationManager.NETWORK_PROVIDER,
                                mNetworkLocationProviderPackageName, mLocationHandler);
                    addProvider(mNetworkLocationProvider);
                }

                if (mGeocodeProviderPackageName != null &&
                         pm.resolveService(new Intent(mGeocodeProviderPackageName), 0) != null) {
                    mGeocodeProvider = new GeocoderProxy(mContext, mGeocodeProviderPackageName);
                }
                updateProvidersLocked();
        }

生成/AddされているProviderは、3つ
  • public class GpsLocationProvider implements LocationProviderInterface
  • public class PassiveProvider implements LocationProviderInterface
  • public class LocationProviderProxy implements LocationProviderInterface
GeocodeProviderとしては
  • public class GeocoderProxy
が生成されています。

またこのうちPassiveProviderは、mNetworkLocationProviderPackageNameのパッケージがある場合に生成されています。これは、リソース中のcom.android.internal.R.string.config_networkLocationProvider の文字列になります。
また、GeocodeProxyについても、mGeocodeProviderPackageNameのパッケージがある場合にのみ生成されており、こちらは、com.android.internal.R.string.config_geocodeProvider の文字列になります。

4.0のソースコードに含まれるtunaのconfig設定 [
device/samsung/tuna/overlay/frameworks/base/core/res/res/values/config.xml]  ですと

<string name="config_networkLocationProvider">com.google.android.location.NetworkLocationProvider</string>
<string name="config_geocodeProvider">com.google.android.location.GeocodeProvider</string>

となっています。
ちなみに、GplLocationProvider classには

    static { class_init_native(); }

となっています。ですので、最初のif文の時にこの関数が呼び出されます。
こちらは、nativeコードで実装されており、実際には

android_location_GpsLocationProvider_class_init_native()

で、その中でhw_get_module("gps",....)が呼び出されます。この辺りはHALの実装の周りの部分でして、必要になるライブラリを探して行きます。4.0のコードでは以下の順にライブラリを探し見つかったものをdlopenを使ってZygoteとリンクします。


  1. /vendor/lib/hw/gps.<ro.hardware>.so
  2. /system/lib/hw/gps.<ro.hardware>.so
  3. /vendor/lib/hw/gps.<ro.product.board>.so
  4. /system/lib/hw/gps.<ro.product.board>.so
  5. /vendor/lib/hw/gps.<ro.board.platform>.so
  6. /system/lib/hw/gps.<ro.board.platform>.so
  7. /vendor/lib/hw/gps.<ro.arch>.so
  8. /system/lib/hw/gps.<ro.arch>.so
  9. /system/lib/hw/gps.default.so

以前は、libがついていた気がするんですけどね。最近はlibがつかないのかな。

ついでにいうと、implements LocationProviderInterfaceなclassは他にもあって
  • public class MockProvider implements LocationProviderInterface
というのがあります。こちらは、LocationManager::addTestProviderを行った時に内部でaddProviderされるProviderのようです。CTS等のテスト時に使われるようですね。


さて、とりあえずここまでで見た限りは、LocationManagerはLocation情報を管理しているわけではなく、LocationProviderの管理をしていると見るのが自然なようです。
というわけで、次回は、LocationProviderについて、Gpsを中心にもう少し調べてみたいなと思います。

2012年1月1日日曜日

位置情報取得について調べてみた(その1)

Androidに関する位置情報についての下回りをちょっと調べてみようかと思っています。GPSやその他の位置情報取得に関する記事なんてそれこそ山のようにあるため、あえてエントリにする必要もないのですが、自分用メモということで。

位置情報取得というと、すぐに思い浮かぶのはGPSや基地局情報だったりするのですが、現在はそれらの組み合わせや、Wi-Fiの位置データベースを使った方法など、多くの方法が存在するようです。Androidと直接関係が無いものもあるでしょうが、まずは基礎知識を知らなければ駄目でしょうということで調べてみました。まぁ、仕様書を読んだわけではなく、いろいろなサイトを眺めて歩いただけなので、間違いもあるかもしれませんけど。

位置情報取得方法


・GPS測位
複数のGPS衛星から送信されている電波を受信することで、位置を特定する技術です。
GPS衛星からは、2種類のデータが30秒周期で送信されています。
  - アルマナックデータ (すべての衛星の軌道データ)
  - エフェメリスデータ (自身の正確な位置データとこのデータ発送時の正確な時刻データ)
GPS受信機は、初期状態ではアルマナックデータを使い、位置測定に利用できる衛星を確認し、最初のエフェメリスデータの時刻情報で時刻を設定後、次のデータでデータ受信の遅延を計測することで、衛星までの距離を計算します。
これを最低3つの衛星で繰り返すと、三角交差法を使い、経度と緯度を導きだす事ができます。4つの衛星で行えば高度も出せるという仕組みです。。
アルマナックデータはだいたい1週間程度、エフェメリスデータは1時間半程度は有効なため、2つのデータの有効時間中は、時刻情報から距離を割り出すだけで、位置情報を取得できますが、データの存在しない初期処理からの測位では数分、その後の位置情報収集は最低でも30秒程度はかかるようですね。

・DGPS(Differential GPS)
もともとGPSは軍事目的の衛星で、軍用に使う正確な位置を割り出せる暗号データとあえてノイズを含めた若干不正確なGPSデータを送信していたそうです。
このため、一般の利用では、100m程度の誤差がでていたものを補正するために考案された手法で、位置のわかっている地上の基地局との比較で誤差補正を行い制度を高める技術です。

・A-GPS(Assisted GPS)
GPSは、情報の取得を開始してから最低でも30秒程度、最悪数分間は位置を特定できません。また、起動データや位置データの受信は建物内にいるとノイズによりデータ受信は難しく、位置の特定が困難になります。
携帯電話は、例えば基地局情報などで、携帯端末のおおざっぱな位置が特定できています。そこで3G回線等別の経路で全衛星軌道データと、衛星位置情報を取得し、GPSからは比較的ノイズに強い時刻情報だけを取得することで、位置情報をGPSより高速に、建物内でもわりと正確に特定できるようにする技術がA-GPSです。


・セルベース測位
現行の携帯電話は、セル方式というものをとり、無線基地局を多数設置し、ある一定の範囲に留めることで、同じ周波数帯域をできるだけ再利用するように設計されているそうです。セルというのは、無線基地局の電波が届く範囲の事になります。携帯電話に通知を行うために、携帯電話が最後に確認できたセルIDが記録されており、定期的に更新されています。このセルIDを元にどの位置に居るのかを特定するという技術です。
携帯でGPSサービスと言われるものが出始めた初期の頃は、実際にGPS受信をしておらず、このおおよその位置情報を使った機種も多かったようですよね。
位置に関する誤差については、セルの広さによりますので、狭い範囲に設置されている都市部は小さく、田園部とか湾岸部等の場合は誤差が大きくなるようです。

・基地局測位
この辺りからキャリアによっていろいろ変わってたりするようで、いまいちつかめていません。
KDDIでは、3つ以上の基地局との同期信号を使って、位置を計算するようです。GPSの衛星からの時刻データの代わりに、基地局との同期信号を使うことで測定するようですね。
DoCoMoさんでは、基地局に同期された時間情報を持っていないため、何か別の方法で測位しているようですが。
ウィルコムは、アンテナの受信強度等から特定しているような記載をみかけましたし、
ソフトバンクはどうなんだろう。もうちょっと調べてみたいなと思っていますが、とりあえずおいておきましょう。

・Wi-Fiアクセスポイントによる測位
Wi-FiのアクセスポイントのMACアドレスと位置情報のデータベースを使って、現在の位置を特定する方法
ローリングで都市を回り、Wi-Fiのアンテナ情報を一気に収集したものと位置情報をあわせて保持している企業のデータを使って特定するようなので。そういえば、僕のHTC AriaのWi-Fiの位置が、自宅に登録されていそうなんですよね。外出先でAria経由で接続すると自宅に居る事になるし・・・・。


とまぁ、こんなあたりが、ざっと主要な位置情報取得技術となるでしょうか。


Android Frameworkでの実装
さて、ようやく本題のAndroidについてです。
いきなり下回りのコードを読むにもあたりがつかないとならないので、クライアント側でおこなうサンプルをみながら、キーになる所を抜粋してみてみました。

(1) LocationManagerの取得
LocationManager locMgr  = (LocationManager)getSystemService(LOCATION_SERVICE);

(2) 最適なLocationProviderを選択
Criteria criteria = new Criteria();
String provider = locMgr.getBestProvider(criteria, true);

(3) 最後にわかっている位置情報を取得
Location location = locMgr.getLastKnownLocation(provider);

(4) 定期的に位置の変化を取得するためのListner登録

LocationListener listener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
    }

    @Override
    public void onProviderDisabled(String provider) {
    }

    @Override
    public void onProviderEnabled(String provider) {
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
    }
};

long minTime = 15000;
float minDistance = 1.0;
locMge.requestLocationUpdates(provider, minTime, minDistance, listener);

とすると、minTime[msec]以上の間隔で、minDistance[m]以上の変化があれば情報が取得される事になります。


(5) 経度、緯度、高度等の情報から住所情報への変換
Geocoder geocoder = new Geocoder(context, Locale.JAPAN);
List<Address> addressList = geocoder.getFromLocation(latitude, longitude, 5);

(6) GPSの状態取得/衛星情報の取得
GpsStatus gpsStat = locMgr.getGpsStatus(null);
Iterable<GpsSatellite> satellites = gpsStat.getSatellites();

(7) GPSの状態変化イベント取得方法

GpsStatus.Listener statListner = new GpsStatus.Listener() {
    @Override
    public void onGpsStatusChanged(int event) {
        switch(event){
        case GpsStatus.GPS_EVENT_STARTED:
            break;
        case GpsStatus.GPS_EVENT_STOPPED:
            break;
        case GpsStatus.GPS_EVENT_FIRST_FIX:
            break;
        case GpsStatus.GPS_EVENT_SATELLITE_STATUS:
            break;
        }
    }
}
locMgr.addGpsStatusListener(statListner);


とまぁ、位置情報周りの主要なクラスの使い方というのはこんな所でしょうか。

アプリ側は、管理クラス LocationManagerから位置情報を提供するLocationProviderを選択し、Locationを取得するという流れとなっています。ここまで見た感じだと、Frameworkの実装としては、位置情報はGPSに限らずProviderから取得できるように設計されており、アプリケーションは、Criteriaを使ってProviderの条件を取得して、適切なProviderを選択させる事が可能です。もちろん、getBestProvider()を使わずに、providerにLocationManager.GPS_PROVIDERを指定することで、GPSの情報だけを使うようにも実装できるわけですが。

どんなProviderが実装されているのかについては、List<String> getProviders(boolean enabledOnly); を呼び出せば、取得できるようですね。あとで、自分の端末で試してみようかな。

各クラスの詳細やその先の実装はまた次回ということで。