Beacappを使って簡単なアプリを作ってみる（Android編）




今回はBeacappを使ったAndroidアプリ開発について、ご紹介します。
[iOS編](http://qiita.com/JMASystems/items/4726b881b27b84144f2d)はこちら。

## 環境準備
今回の開発に必要なものは以下の通りです。

* ビーキャップのアカウント(３ヶ月無料)
* Android (OS 4.3以上)
* Beacon端末または、iPhone(発信端末として使います)
* BeacappSDK for Android 1.2.1
* Mac (OS X EL Capitan)
	* Android Studio 2.1.1


##ビーキャップのアカウント作成

以下の記事を参考に、アカウント作成、イベント登録を実施します。
[Beacappを使って簡単なアプリを作ってみる（準備編）](http://qiita.com/JMASystems/items/b57c2689d22a42cbe36e)


##Androidアプリへの組み込み

アプリを組み込むプロジェクトを用意します。
既存のプロジェクトでも新規プロジェクトでも問題ありません。

今回は、新規のプロジェクトを作成します。

ちなみに、今回のAndroidサンプルはこちらに置いてあります。
[Beacapp Android Sample - Github](https://github.com/JMASystems/Beacapp-Android-Sample)

### プロジェクトの作成
MacからAndroid Studioを起動します。
[Start a new Android Studio project]を選択しましょう。

![スクリーンショット 2016-06-28 午後6.33.27.png](https://qiita-image-store.s3.amazonaws.com/0/130138/1cbe8766-fb83-7c33-636f-4be8e05944bc.png "スクリーンショット 2016-06-28 午後6.33.27.png")

適当にアプリ名を入力します。

![スクリーンショット 2016-06-28 午後6.34.16.png](https://qiita-image-store.s3.amazonaws.com/0/130138/6febd0ff-e33e-cbdf-4e90-c6bd9b1d8ace.png "スクリーンショット 2016-06-28 午後6.34.16.png")


[Phone and Tablet] を選択し、[API Lebel] 4.3以上にします。

![スクリーンショット 2016-06-28 午後6.34.29.png](https://qiita-image-store.s3.amazonaws.com/0/130138/6e6610ce-196e-0702-4cec-409421045a8d.png "スクリーンショット 2016-06-28 午後6.34.29.png")

[Empty Activity] を選択します。

![スクリーンショット 2016-06-28 午後6.34.40.png](https://qiita-image-store.s3.amazonaws.com/0/130138/a9328b4d-a18a-2b6d-01cb-1c949a2aef98.png "スクリーンショット 2016-06-28 午後6.34.40.png")


最後に適当にActivity名を入力します。

![スクリーンショット 2016-06-28 午後6.34.48.png](https://qiita-image-store.s3.amazonaws.com/0/130138/32254969-8026-0f60-b211-dbaf2673503b.png "スクリーンショット 2016-06-28 午後6.34.48.png")

これで、プロジェクトの作成は完成です。


### プロジェクトにBeacappSDKをインポートする

GithubからBeacappSDKをダウンロードします。
[BeacappSDK for Android -Github](https://github.com/JMASystems/beacapp-sdk-android)

必要なのは、BeacappSDKforAndroidフォルダ内の.jarファイルだけです。
ダウンロードしたら、Androidプロジェクトにインポートしましょう。

表示を[Project]ビューに変更します。

<img width="30%" alt="スクリーンショット 2016-06-28 午後9.40.08.png" src=https://qiita-image-store.s3.amazonaws.com/0/130138/30205e33-2a77-a948-f260-38bb55fdff2b.png "スクリーンショット 2016-06-28 午後9.40.08.png">

あとは、[libs]フォルダにドラッグ&ドロップで終わり。
<img width="30%" alt="スクリーンショット 2016-06-28 午後9.41.15.png" src=https://qiita-image-store.s3.amazonaws.com/0/130138/ed75d26a-504b-4f43-0734-fe68082076e3.png "スクリーンショット 2016-06-28 午後9.41.15.png">


### その他の必要なライブラリをインポートする

[build.gradle]に必要なライブラリを記載します。
2種類ありますが、(Module: app)と記載のあるファイルに、情報を追記します。

![スクリーンショット 2016-06-28 午後6.37.59.png](https://qiita-image-store.s3.amazonaws.com/0/130138/7c78064f-5acb-603a-f8b5-a745bc4541e1.png "スクリーンショット 2016-06-28 午後6.37.59.png")

必要なライブラリは、AWSSDK関連なので、以下のように記載します。

```build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.amazonaws:aws-android-sdk-core:2.2.10'
    compile 'com.amazonaws:aws-android-sdk-ddb:2.2.10'
    compile 'com.amazonaws:aws-android-sdk-ddb-mapper:2.2.10'
    compile 'com.amazonaws:aws-android-sdk-kinesis:2.2.10'
}
```

Gradleファイルを変更すると、右上に[Sync Now]と出てきますので、Syncしておきましよう。

これで、ライブラリ関連の準備はOKです。


### JBCPサービスを呼び出す

Beacappは、バックグラウンドでもBeaconを検知するために、サービスを使用していますので、サービスをManifestに追加します。

```AndrdoiManifest.xml
 <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <!-- Beacappを使用するために必要 -->
        <service
            android:name="com.beacapp.service.JBCPScanService"
            android:exported="false"
            android:process=":bleservice">
            <intent-filter>
                <action android:name="com.beacapp.BLEServiceAIDL"/>
                <action android:name="com.beacapp.BLEServiceCalbackAIDL"/>
            </intent-filter>
        </service>
    </application>
```


また、Bluetoothアクセス、GPSアクセス、インターネットアクセスを行いますので、パーミッションを記載する必要があります。

```AndrdoiManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
```


### Beacappを呼び出す

#### パッケージのインポート

まずは、呼び出すクラスで、必要なパッケージをインポートします。

```MainActivity.java

    import com.beacapp.FireEventListener;
    import com.beacapp.JBCPException;
    import com.beacapp.JBCPManager;
    import com.beacapp.ShouldUpdateEventsListener;
    import com.beacapp.UpdateEventsListener;

```

* com.beacapp.FireEventListener
イベントの発火を受け取ります。

* com.beacapp.JBCPException
BeacappがスローするExceptionです。

* com.beacapp.JBCPManager
BeacappSDKのコア部分。

* com.beacapp.ShouldUpdateEventsListener
イベントの更新を行うかどうかを返却します。

* com.beacapp.UpdateEventsListener
イベント更新が完了したタイミングで呼び出されます。
このタイミングで、スキャンを開始するのが良いと思います。

#### アクティベーション処理

JBCPManager.getManager()を呼び出せば良いのですが、通信処理を行うため、別スレッドから呼び出す形にしておきます。

```MainActivity.java
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //通信が走るため、別スレッドでの処理にする
        new Thread(new Runnable() {
            @Override
            public void run() {
                jbcpManager = JBCPManager.getManager(MainActivity.this,
                        "BEACAPP_REQUEST_TOKEN",
                        "BEACAPP_SECRET_KEY",
                        null);
            }
        }).start();
    }
```


#### 各種リスナーの登録

リスナーをそれぞれ生成して、

```MainActivity.java
// リスナーを生成
    private UpdateEventsListener updateEventsListener = new UpdateEventsListener() {
        @Override
        public void onProgress(int i, int i1) {

        }

        @Override
        public void onFinished(JBCPException e) {

        }
    };

    // リスナーを生成
    private ShouldUpdateEventsListener shouldUpdateEventsListener = new ShouldUpdateEventsListener() {
        @Override
        public boolean shouldUpdate(Map<String, Object> map) {
            return true;
        }
    };

    // リスナーを生成
    private FireEventListener fireEventListener = new FireEventListener() {
        @Override
        public void fireEvent(JSONObject jsonObject) {


        }
    };

```

登録します。


```MainActivity.java
//通信が走るため、別スレッドでの処理にする
        new Thread(new Runnable() {
            @Override
            public void run() {
                jbcpManager = JBCPManager.getManager(MainActivity.this,
                        "BEACAPP_REQUEST_TOKEN",
                        "BEACAPP_SECRET_KEY",
                        null);
                if (jbcpManager == null){
                    return;
                }
                // リスナーを登録
                jbcpManager.setUpdateEventsListener(updateEventsListener);
                jbcpManager.setShouldUpdateEventsListener(shouldUpdateEventsListener);
                jbcpManager.setFireEventListener(fireEventListener);

            }
        }).start();
```


最後にイベントの更新を要求します。

```MainActivity.java
                // イベントを更新する
                jbcpManager.startUpdateEvents();
```


#### リスナー内に処理を書く

イベント更新完了のタイミングで、スキャン開始

```MainActivity.java
// リスナーを生成
    private UpdateEventsListener updateEventsListener = new UpdateEventsListener() {
        @Override
        public void onProgress(int i, int i1) {

        }

        @Override
        public void onFinished(JBCPException e) {
            if (e != null)
            {
                jbcpManager.startScan();
            }
        }
    };
```


とりあえずイベントを検知したら、ログを出力するだけの処理

```MainActivity.java
    // リスナーを生成
    private FireEventListener fireEventListener = new FireEventListener() {
        @Override
        public void fireEvent(JSONObject jsonObject) {
            JSONObject action_data = jsonObject.optJSONObject("action_data");
            String action = action_data.optString("action");


            // URLの場合
            if(action.equals("jbcp_open_url"))
            {
                Log.d("DEBUG",action_data.optString("url"));
            }
            //画像の場合
            else if(action.equals("jbcp_open_image"))
            {
                Log.d("DEBUG",action_data.optString("image"));
            }
            //カスタムの場合
            else if(action.equals("jbcp_custom_key_value"))
            {
                Log.d("DEBUG",action_data.optString("key_values"));
            }
            //テキストの場合
            else if(action.equals("jbcp_open_text"))
            {
                Log.d("DEBUG",action_data.optString("text"));
            }

        }
    };
```

これでおしまい、ではなく。。。

Android6だとパーミッションに記載があっても動作しません。
なので、明示的にアラートを表示して認証をしないといけません。

最終的に、以下のようになりました。

```MainActivity.java
package jp.co.jmas.beacappandroidsample;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

import com.beacapp.FireEventListener;
import com.beacapp.JBCPException;
import com.beacapp.JBCPManager;
import com.beacapp.ShouldUpdateEventsListener;
import com.beacapp.UpdateEventsListener;

import org.json.JSONObject;

import java.util.Map;

public class MainActivity extends AppCompatActivity {

    private JBCPManager jbcpManager;

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

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String permissions[] = {
                    Manifest.permission.ACCESS_COARSE_LOCATION,
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.BLUETOOTH,
                    Manifest.permission.BLUETOOTH_ADMIN,
            };
            boolean needPermission = false;
            for (String permission : permissions) {
                if (checkSelfPermission(permission) ==
                        PackageManager.PERMISSION_GRANTED) {
                } else {
                    if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                            permission)) {
                        needPermission = true;
                    } else {
                        needPermission = true;
                    }
                }
            }
            if (needPermission) {
                showExplanationDialog(permissions, 0);
            } else {
                activate();
            }
        }
    }
    private void activate()
    {
        //通信が走るため、別スレッドでの処理にする
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    jbcpManager = JBCPManager.getManager(MainActivity.this,
                            "アクティベーションキー",
                            "シークレットキー",
                            null);
                } catch (JBCPException e) {
                    return;
                }

                if (jbcpManager == null){
                    return;
                }
                // リスナーを登録
                jbcpManager.setUpdateEventsListener(updateEventsListener);
                jbcpManager.setShouldUpdateEventsListener(shouldUpdateEventsListener);
                jbcpManager.setFireEventListener(fireEventListener);


                // イベントを更新する
                jbcpManager.startUpdateEvents();
            }
        }).start();
    }
    private void showExplanationDialog(final String permissions[], final int requestCode) {
        ActivityCompat.requestPermissions(MainActivity.this,
                permissions,requestCode);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (0 == requestCode) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                activate();
            }
        }
    }

    // リスナーを生成
    private UpdateEventsListener updateEventsListener = new UpdateEventsListener() {
        @Override
        public void onProgress(int i, int i1) {
                //何もしない

        }

        @Override
        public void onFinished(JBCPException e) {
            if (e != null)
            {
                jbcpManager.startScan();
            }

        }
    };

    // リスナーを生成
    private ShouldUpdateEventsListener shouldUpdateEventsListener = new ShouldUpdateEventsListener() {
        @Override
        public boolean shouldUpdate(Map<String, Object> map) {
            return true;
        }
    };

    // リスナーを生成
    private FireEventListener fireEventListener = new FireEventListener() {
        @Override
        public void fireEvent(JSONObject jsonObject) {
            JSONObject action_data = jsonObject.optJSONObject("action_data");
            String action = action_data.optString("action");


            // URLの場合
            if(action.equals("jbcp_open_url"))
            {
                Log.d("DEBUG",action_data.optString("url"));
            }
            //画像の場合
            else if(action.equals("jbcp_open_image"))
            {
                Log.d("DEBUG",action_data.optString("image"));
            }
            //カスタムの場合
            else if(action.equals("jbcp_custom_key_value"))
            {
                Log.d("DEBUG",action_data.optString("key_values"));
            }
            //テキストの場合
            else if(action.equals("jbcp_open_text"))
            {
                Log.d("DEBUG",action_data.optString("text"));
            }

        }
    };  
}
```


これで、実機ビルドすれば、動くと思います。
実機での確認の際には、Beacon発信アプリを用意してますので使ってみてください。
[Beacheck on the App Store - iTunes - Apple](https://itunes.apple.com/us/app/beacheck/id941020155?mt=8)

### よくあるエラー

#### アクティベーションに失敗する！
アクティベーション時に発生するよくあるエラーに、以下のものがあります。

```
Error Domain=BeacappDomain Code=1000 "ネットワークエラー(401)"
```

これが発生した場合は、以下のケースが想定されます。

・PackageNameが間違っている(AndroidManifest内に記載)
・アクティベーションキーが間違っている
・シークレットキーが間違っている

CMSに登録した内容とあっているか、確認してみてください。

#### Beaconを検知しない

デバッグ用として、BeaconEventListnerクラスを用意しています。
SCAN_MODEを2にして、リスナーを登録すると検知できます。
※あくまでもテスト用なので、リリース時には使用しないでください。

```MainActivity.java
//　リスナーを生成
    public BeaconEventListener beaconEventListener = new BeaconEventListener() {
        @Override
        public boolean targetBeaconDetected(BeaconEvent beaconEvent) {
            // CMSで検知対象に登録されているBeacon
            return false;
        }

        @Override
        public boolean nonTargetBeaconDetected(BeaconEvent beaconEvent) {
            // CMSで検知対象になっていないBeacon
            return false;
        }
    };
```

##### 何も反応しない

Android,Beacon端末の設定を確認してください。
・位置情報はONになっているか
・位置情報は許可されているか
・Bluetoothは許可されているか
・端末の故障などで、電波が出ていない

##### nonTargetBeaconDetectedだけ反応している

・Beacappに登録したBeaconのUUIDに誤りがある


##### targetBeaconDetectedが反応する

設定は正しくできています。
端末から距離を離れてみたり、動いてみたり、少し待ってみてください。

#### その他
その他、お困りごとがあれば、お気軽にお問い合わせください。
[Beacapp サポートチーム](https://cms.beacapp.com/contact/index)


今後もBeacappを使ったアプリ開発について、ご紹介させていただきます。