HMS Location Kit Android SDKの導入方法について

#1. Location Kitの概要
[Location Kit](https://developer.huawei.com/consumer/jp/hms/huawei-locationkit)はHMSの位置情報関連のサービスを提供するSDKです。
今回はLocation Kitの機能と使用方法について説明したいと思います。

##1.1 機能
Location Kitは以下の3つの機能を提供しています。

**1. ハイブリッド測位による位置情報の取得**
**2. ジオフェンス機能**
**3. 運動状態の識別機能**

それぞれの機能の概要と使用シナリオについては以下で紹介します。

###位置情報の取得
Location KitのAPIを使用することで、ハイブリッド測位（GNSS, WiFi, 基地局）による位置情報の取得ができます。
また、東京、大阪、名古屋などの主要都市でSuperGPS機能がサポートされ、位置情報を取得する際には、ロードサイドが分かるように精度よく位置情報を取得できます。

**利用シナリオ**
・現在地の取得
・タクシー予約アプリの場合、道路のどの側にいるのかが分かるように精度よく現在地を取得したい場合

###ジオフェンス機能
**機能**
自分の決めた条件でジオフェンスを作成し、フェンスを出入りする際の処理を自分で定義できます。

**利用シナリオ：**
店舗付近にジオフェンスを作成し、ユーザーがジオフェンスの範囲内に入ったらPush通知を送信して店舗の情報やプロモーション情報を知らせることができます。

###運動状態の識別
ユーザーの運動状態を識別して、それぞれの状態の場合の操作を定義できます。

**利用シナリオ：**
フィットネストラッキングアプリの場合、ユーザーの運動状態を自動で識別して記録を開始できます。

**機能に関してさらに詳しく知りたい方は[コチラ](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/introduction-0000001050706106-V5#EN-US_TOPIC_0000001050706106__section123391249367)から確認できます。**

##1.2. HMS Location Kitの特徴
1. 位置情報の請求の成功率が高い
2. SuperGPSによる高精度の位置情報の取得
3. 低いエネルギー消耗

#2. 導入方法
全体の流れは以下のようになります。

1. 開発者アカウントの登録
2. 開発者コンソール（AppGallery Connect）でプロジェクトを作成
3. コンソールでプロジェクトの詳細を設定
4. Androidプロジェクトに必要なファイルの導入、依頼関係の追加
5. コーディング

次に各ステップについて詳細に説明します。

#3. 詳細ステップ

##3.1 開発者アカウントの登録
HMSのSDKを使用するには、Huawei開発者アカウントを取得する必要があります。
アカウントの申請には主に以下の2つのステップがあります。

1. Huawei IDの取得（すてにHuawei IDをもっている人はこのステップが不要となります）
2. 開発者認証

詳しいガイドは以下のサイト（英語）を参照してください。
Huawei ID申請ガイド：https://developer.huawei.com/consumer/en/doc/10104
開発者認証（個人の場合）：https://developer.huawei.com/consumer/en/doc/10122

##3.2 Appgallery Connectにてアプリプロジェクトの作成とプロジェクトの設定

###3.2.1 プロジェクトの作成
[AppGallery Connect（リンク）](https://developer.huawei.com/consumer/en/console)はHuaweiの開発者サイトです。
開発者サイトにログインしたら、最初は以下のページが表示されます。
![1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/c7bc7f2c-0bbe-fa2a-8b57-7a95ca0989d1.png)
このページから、AppGallery Connectをクリックしてコンソールに入ります。
クリックしたら以下のページが表示されます。
![2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/43e7bfbc-8d25-3daa-c937-c84f0c8d8ba9.png)
次に、My appsをクリックしてアプリ管理画面に入ります。
![3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/fa2214ce-648a-4811-46d5-a861fa30c423.png)
My appsページの右上の「New app」をクリックして新しいプロジェクトを作成します。
「New apps」をクリックしたら次に下のページが表示されます。
![location1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/d34afb79-2866-cd63-458d-c3c6913b364e.png)

このページでは、アプリの種類（APK：Androidアプリの場合、RPK：[QuickApp](https://developer.huawei.com/consumer/en/huawei-quickApp/)の場合）、アプリ名やデフォルト言語を設定できます。
**また、ここでは「Add to project」にチェックを入れる必要があります。**

入力が完了したら、「OK」をクリックして作成が完了となります。
「OK」をクリックしたらアプリの詳細ページが表示されます。

次に、プロジェクトのAPI設定やFingerprint設定をする必要がありますので、左上のMy appsより、My projectsを選択してプロジェクトの詳細設定ページに入ります。

![location2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/fa72c413-2de9-6f5c-0500-0b1f0011cd03.png)

###3.2.2 プロジェクトの設定
プロジェクト詳細ページでは、以下のようにアプリの詳細情報が表示されています。
まずは、Location APIを有効にする必要がありますので、Manage APIsをクリックします。
![location3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/c16a02ca-e2c8-5cc4-d348-21b80c1b6127.png)

Manage APIsページで使用したいAPIを有効にします。今回はLocation Kitを有効にしました。
![location4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/db16c3f5-5fd9-3f5c-f9e7-5d6a658568a9.png)

次に、fingerprintを設定し、コンフィグファイル（agconnect-services.json）をダウンロードします。
![location5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/0b5ba08b-1d7d-3dc7-c11b-47a1ebc09bca.png)

以上の操作で、Location Kitを使用する準備が完了となります。

詳しいガイドは以下のサイト（英語）を参照してください。
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/config-agc-0000001057629153-V5

##3.3 Android Studioでの前準備（SDK導入）

###3.3.1 コンフィグファイルの導入
先ほどダウンロードしたagconnect-services.jsonファイルをappフォルダに入れます。
**このファイルは、アプリの認証情報が記載されたコンフィグファイルとなります。このファイルがないと、HMS SDKが機能しなくなる可能性が高いですのでかならずプロジェクトに導入してください**
![location6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/70f6f155-f9f5-cab3-b914-9157aa0c9693.png)

###3.3.2 gradleファイルに依頼関係を追加

次に、gradleファイルに依頼関係を追加します。
まずはProjectのgradleに、以下のコメントの下の行を追加します。

```gradle:build.gradle(Project)
buildscript {
    repositories {
        google()
        jcenter()

        //HMS Core SDKのMavenを追加します
        maven {url 'https://developer.huawei.com/repo/'}
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.0"

        //agcpのclasspathを追加します
        classpath 'com.huawei.agconnect:agcp:1.4.1.300'
    }
}

allprojects {
    repositories {
        google()
        jcenter()

        //HMS Core SDKのMavenを追加します
        maven {url 'https://developer.huawei.com/repo/'}
    }
}

```

追加したら一度Syncし、次にappのgradleに以下のように依頼関係を追加します。

```gradle:build.gradle(app)
plugins {
    id 'com.android.application'

    //'com.android.application'の後に以下の行を追加します
    id 'com.huawei.agconnect'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    //signingConfigsを追加します
    signingConfigs {
        config {
            storeFile file("Your_KeyStore")
            storePassword "Your_Password"
            keyAlias "Your_KeyAlias"
            keyPassword "Your_Password"
            v2SigningEnabled true
        }
    }

    defaultConfig {
        applicationId "com.example.locationdemoqiita"

       //HMS Location Kitを使う場合の制限に関しては以下のリンクより確認できます。
        //https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/emui-version-dependent-0000001050042515-V5
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

            //signingConfigsを追加します
            signingConfig signingConfigs.config
        }
        debug{
            //signingConfigsを追加します
            signingConfig signingConfigs.config
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    ...

    //HMS Location SDKを追加します。最新のバージョンは以下のリンクから確認できます。
    //https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/version-change-history-0000001050986155-V5
    implementation 'com.huawei.hms:location:5.0.4.300'
}
```

完了したらSyncをクリックしてSDKの導入が完了となります。

詳しいガイドは以下のサイト（英語）を参照してください。
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/integrate-as-sdk-0000001050168936-V5

##3.4 コーディング

###3.4.1 Permissionの追加
Location Kitを使用するにはManifestに以下のPermissionを追加する必要があります。

```xml:AndroidManifest.xml
 　　<!-- 位置情報関連のPermissionを追加します-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <!-- Android Q以降、バックグランドで位置情報を継続的に取得したい場合は以下のPermissionを追加する必要があります -->
　　<!--<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />-->
```
###3.4.2 Location Kitによって現在地を取得
以下では、Location Kitを使って現在地を取得する場合の例を示します。
Location Kitはコールバックによって位置情報を取得するようになっています。
具体的には以下のコードを参考してください。

```java:MainActivity.java
 　public class MainActivity extends AppCompatActivity {

    private static final String TAG = "LocationDemoQiita";

    private static final int PERMISSION_CODE = 0;
    private static final int LOCATION_SETTING_CHECK_CODE = 1;

    private LocationCallback mLocationCallback;
    private FusedLocationProviderClient mClient;
    private LocationRequest mLocationRequest;
    private SettingsClient mSettingsClient;

    private Location mLastLocation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initiateLocationServices();
        checkAndRequestLocationPermissions();
    }

    /**
     * Location関連のクラスをインスタンス化します
     */
    private void initiateLocationServices(){
        mClient = LocationServices.getFusedLocationProviderClient(this);
        mSettingsClient = LocationServices.getSettingsClient(this);
        mLocationRequest = new LocationRequest();

        //Locationを取得できた場合、このcallbackが実行されます
        if(mLocationCallback == null){
            mLocationCallback = new LocationCallback(){
                @Override
                public void onLocationResult(LocationResult locationResult) {
                    if(locationResult!= null){
                        //得られた位置情報を処理します
                       mLastLocation = locationResult.getLastLocation();
                        Log.d(TAG, "My Last Location is" + " ( " +mLastLocation.getLatitude() + ", " + mLastLocation.getLongitude()+ " )");
                    }
                }
            };
        }
    }

    private void checkAndRequestLocationPermissions(){
        if (ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(this,
                Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            String[] strings = {android.Manifest.permission.ACCESS_FINE_LOCATION,
                    android.Manifest.permission.ACCESS_COARSE_LOCATION};
            ActivityCompat.requestPermissions(this, strings, PERMISSION_CODE);
        } else {
            //Permissionがすでに許可された場合はCheckLocationSetting（）を実行します
            checkLocationSetting();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if(requestCode == PERMISSION_CODE && grantResults.length > 0){
            for(int i = 0;i < grantResults.length ;i ++){
                if(grantResults[i] == PackageManager.PERMISSION_DENIED){
                    Toast.makeText(this, permissions[i] + " is denied", Toast.LENGTH_SHORT).show();
                    return;
                }
            }
            checkLocationSetting();
        }
    }

    /**
     * デバイスの位置情報が有効になっているかどうかなどの設定を確認し、成功した場合はLocationのUpdateを請求します
     */
    private void checkLocationSetting(){
        LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
        builder.addLocationRequest(mLocationRequest);
        mSettingsClient.checkLocationSettings(builder.build())
                .addOnSuccessListener(new OnSuccessListener<LocationSettingsResponse>() {
                    @Override
                    public void onSuccess(LocationSettingsResponse locationSettingsResponse) {
                        //設定に問題がない場合はLocationのUpdateを請求します
                        requestLocationUpdate();
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(Exception e) {
                        int statusCode = ((ApiException) e).getStatusCode();
                        if(statusCode == LocationSettingsStatusCodes.RESOLUTION_REQUIRED){
                            //この場合の失敗の理由はHMS Coreの位置情報を有効にしていないことですので、
                            //ポップアップでユーザーにHMS Coreの位置情報の有効化を請求します。実際のポップアップは下の図を参考してください。
                            try {
                                ResolvableApiException rae = (ResolvableApiException) e;
                                //ポップアップを表示します
                                rae.startResolutionForResult(MainActivity.this, LOCATION_SETTING_CHECK_CODE);
                            } catch (IntentSender.SendIntentException sie) {
                                sie.printStackTrace();
                            }
                        }
                    }
                });
    }

    /**
     *checkLocationSettingで表示したポップアップに対するユーザーの選択について処理します
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){
            case LOCATION_SETTING_CHECK_CODE:
                switch (resultCode){
                    case RESULT_OK:
                        //「有効化」をクリックした場合、LocationのUpdateを請求します
                        requestLocationUpdate();
                        break;
                    case RESULT_CANCELED:
                        //「キャンセル」を選択した場合
                        Toast.makeText(this, "位置情報を取得するにはHMS Coreの位置情報権限をオンにする必要があります", Toast.LENGTH_SHORT).show();
                        break;
                }
        }
    }

    /**
     * 位置情報を取得します。位置情報を正しく取得した場合、mLocationCallbackのonLocationResultが実行されます
     */
    private void requestLocationUpdate(){
        mClient.requestLocationUpdates(mLocationRequest,mLocationCallback, Looper.getMainLooper())
                .addOnSuccessListener(new OnSuccessListener<Void>() {
                    @Override
                    public void onSuccess(Void aVoid) {
                        Toast.makeText(MainActivity.this, "Location Update Request Succeed", Toast.LENGTH_SHORT).show();
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(Exception e) {
                        Toast.makeText(MainActivity.this, "Location Update Request Succeed　Failed", Toast.LENGTH_SHORT).show();
                    }
                });
    }
}
```


ユーザーが端末の位置情報機能をオフにした場合、checkLocationSetting()を使うことで下のようなポップアップが表示されます。

![location7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674503/04e88e37-9d50-c288-ba63-c07d4beea5a7.png)

開発に関するさらに詳しいガイドは以下のリンクより確認できます。
https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/location-develop-steps-0000001050746143-V5

#4. トラブルシューティング
Location Kitが正しく動作しない場合には、以下の項目を確認したらいいと思います。

1. Location Kitが有効になっているかどうか（コンソール→MyProjects）
2. fingerprintは正しくコンソールに登録できているかどうか
3. agconnect-services.jsonファイルはプロジェクトに導入しているかどうか
4. Permissionは追加したかどうか

#5. その他リソース
APIリファレンス：https://developer.huawei.com/consumer/en/doc/development/HMSCore-References-V5/overview-0000001051066102-V5
サンプルコード：https://developer.huawei.com/consumer/en/doc/development/HMSCore-Examples-V5/sample-code-0000001050986087-V5