スマートフォン(Android)とスマートウォッチ(WearOS)でメッセージの送受信

ノッチ付ディスプレイや画面内指紋認証、多眼カメラなど多種多様なスマートフォンが登場している一方で、スマートウォッチ市場は（AppleWatchを除いて）いまいち盛り上がりませんね。
とは言えスマートウォッチはそれなりの性能で様々なセンサを積んでいるので趣味や研究用途でいじるのにはおもしろいと思います。
自分も最近スマートウォッチ(WearOS)のアプリを作っていたのですが、スマートフォン(Android)との通信を実装する際の[Androidデベロッパーガイドの説明](https://developer.android.com/training/wearables/data-layer/messages?hl=ja)が少しわかりづらかったので補足してまとめておきます。

### 概要
スマートフォン(Android) -> スマートウォッチ(WearOS)でメッセージ送受信を行う際の実装を想定します。(逆の場合も実装は同様のはず)
スマートフォンとスマートウォッチにはそれぞれ以下を使いました。

- Galaxy S9 (Android 9.0)
- TicWatch E (WearOS 2.9)

以下実装についてですが、すでにWearOSアプリのプロジェクトが作成されている状態を前提とします。

### 送信側(スマートフォン)
#### 準備
プロジェクトにスマートフォンアプリのモジュールを追加します。
**File** -> **New** -> **New Module...** -> **Phone&Tablet Module** を選択
<img width="810" alt="スクリーンショット 2019-10-16 0.05.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61751/810a8b17-d5e4-0bcd-930d-9d89ceb91142.png">
ここで注意点ですがパッケージ名が初期状態だと*com.hogehoge.[Module name]*のようになっているので、これをプロジェクト作成時に設定したものと同じにする必要があります。
理由としては、送信先ノード検出処理にてcontextを指定するため各ノードのアプリケーションIDが一致していなければならないからだと予想してます。
[Androidデベロッパーガイド](https://developer.android.com/training/wearables/data-layer/messages?hl=ja)の手順には特に説明がないのですが、このあたりの作法は一般的なんですかね。
尚パッケージ名が違うと当然ですが検出されません。
(パッケージ名、アプリケーションID、Contextの関係は[この辺](https://developer.android.com/studio/build/application-id?hl=ja)を参考に)

モジュール作成後**build.gradle**(Module)にWearOS APIの依存関係を追加 -> **Sync**

```gradle:build.gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.material:material:1.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'com.google.android.gms:play-services-wearable:17.0.0' // <- https://developers.google.com/android/guides/setup を参考に
}
```

#### 実装
ノードの識別子 `WEAR_CAPABIRITY_NAME` とメッセージの識別子 `TO_WATCH_PATH` を定義

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
    }
    /* 省略　*/

    companion object {
        private const val WEAR_CAPABIRITY_NAME = "wear_capabirity"
        private const val TO_WATCH_PATH = "/hoge"
    }
}
```

送信先ノード検出処理とメッセージ送信処理を追加。今回はとりあえずすべてonCreate内に書きました。
フローティングアクションボタンを押すとメッセージを送信する感じで。
送信するデータはバイナリにする必要があります。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity() {
    var nodeSet: MutableSet<Node>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

        // 送信先ノード検出
        val capabilityInfoTask: Task<CapabilityInfo> =
            Wearable.getCapabilityClient(applicationContext).getCapability(
                WEAR_CAPABIRITY_NAME,
                CapabilityClient.FILTER_REACHABLE
            )

        capabilityInfoTask.addOnCompleteListener(object : OnCompleteListener<CapabilityInfo> {
            override fun onComplete(task: Task<CapabilityInfo>) {
                if (task.isSuccessful) {
                    nodeSet = task.result?.nodes
                } else {
                    Toast.makeText(applicationContext, "送信先が見つかりません", Toast.LENGTH_SHORT).show()
                }
            }
        })

        //ボタンを押して送信
        fab.setOnClickListener { view ->
            nodeSet?.let {
                pickBestNodeId(it)?.let {
                    val dataText = "メッセージ"
                    val data = dataText.toByteArray(Charsets.UTF_8) //バイナリ変換
                    Wearable.getMessageClient(applicationContext)
                        .sendMessage(it, TO_WATCH_PATH, data)
                        .apply {
                            addOnSuccessListener {
                                Toast.makeText(
                                    applicationContext,
                                    dataText + " を送信",
                                    Toast.LENGTH_SHORT
                                ).show()
                            }
                            addOnFailureListener {
                                Toast.makeText(applicationContext, "送信失敗", Toast.LENGTH_SHORT)
                                    .show()
                            }
                        }
                }
            }
       }
    }

    //デベロッパーガイドに記載のものそのまま
    private fun pickBestNodeId(nodes: Set<Node>): String? {
        return nodes.firstOrNull { it.isNearby }?.id ?: nodes.firstOrNull()?.id
    }

    /* 省略　*/

    companion object {
        private const val WEAR_CAPABIRITY_NAME = "wear_capabirity"
        private const val TO_WATCH_PATH = "/hoge"
    }
}
```

### 受信側(スマートウォッチ)
#### 準備
WearOSアプリモジュールの `res/values/` に **wear.xml** リソースを作成して以下のように記述。
`item`要素はスマートフォン側実装で定義した `WEAR_CAPABIRITY_NAME` に合わせます。

```xml:wear.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="android_wear_capabilities">
        <item>wear_capabirity</item>
    </string-array>
</resources>
```
#### 実装
Activityに `MessageClient.OnMessageReceivedListener` を実装して `Wearable.getMessageClient` のリスナに指定します。

```kotlin:MainActivity.kt
class MainActivity() : WearableActivity(), MessageClient.OnMessageReceivedListener {
    override fun onMessageReceived(messageEvent: MessageEvent) {
        when(messageEvent.path) {
            FROM_PHONE_PATH -> {
                val data = messageEvent.data.toString(Charsets.UTF_8) //文字列に変換
                Log.d("debug", data) // -> D/debug: メッセージ
                /* 受け取ったデータを処理 */
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Enables Always-on
        setAmbientEnabled()

        Wearable.getMessageClient(applicationContext).addListener(this) //リスナに自身を指定
    }

    companion object {
        private const val FROM_PHONE_PATH = "/hoge"
    }
}
```
`onMessageReceived` ではスマートフォン側と同様に定義したメッセージの識別子(↑の`FROM_PHONE_PATH`)に応じてメッセージを処理します。
今回はとりあえずLogcatに出力するだけ