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の取得を調べてみたいと思います。

0 件のコメント:

コメントを投稿