every Tech Blog

株式会社エブリーのTech Blogです。

wear OS について

目次

はじめに

every Tech Blog Advent Calendar 23日目の記事になります!
こんにちは トモニテ でAndroidアプリの開発を行っている岡田です。
今回は、挑戦WEEK中にAndroidスマホで表示している動画をスマートウォッチで操作するアプリを作成したので、その内容についてご紹介させていただきます。

弊社の挑戦WEEKの取り組みについてはこちらをご覧ください!
https://tech.every.tv/entry/2023/10/13/172151

Wear OSとは

正式名称は Wear OS by Google。
Googleが開発したウェアラブルデバイス向けのOSです。
2023年12月現在、一般的に普及しているウェアラブルデバイスといえばスマートウォッチくらいだと思いますので、スマートウォッチに搭載されるのが主な用途だと思います。

環境

IDE: Android Studio Iguana | 2023.2.1 Canary 11
言語: Kotlin

今回実装するアプリについて

Android Appで再生している動画をWear Appで操作するアプリを作成します。
ここではWear Appで再生ボタンが押された時に、それをAndroid Appに通知する処理を実装します。
したがってAndroid Appが受信側、Wear Appが送信側になります。

実装の流れ

以下の流れで実装しました。
1. プロジェクトの作成
2. 受信側が機能をアドバタイズする
3. 送信側でノードを取得する
4. 送信側でメッセージを送信する
5. 受信側でメッセージを受信する

これらは公式のガイドを参考に作成しました。

1. プロジェクトの作成

AndroidStudioのNew Projectに空のWear APPとAndroid APPを作成するテンプレートがあるので、これを利用しました。

2. 受信側が機能をアドバタイズする

受信側のres/values/にXML形式で機能を文字列で記述し、機能をアドバタイズします。

アドバタイズとは、Bluetooth Low Energy(BLE)などを使用して、デバイスの情報を周囲の他のデバイスに知らせる行為のことを指します。

Androidスマホは複数のスマートウォッチを接続できるため、Wear Appは使用する接続ノードが特定の機能を備えているのか判断する必要があります。
したがって、Android Appでは実行されているノードで特定の機能を提供していることをアドバタイズする必要があります。

例えば今回作成するアプリでは、ノードがAVPlayerを操作する機能を備えているかを判別する必要があります。
機能を識別するためにplayer_operationとし、以下のように記述しました。

<resources xmlns:tools="http://schemas.android.com/tools"
        tools:keep="@array/android_wear_capabilities">
    <string-array name="android_wear_capabilities">
        <item>player_operation</item>
    </string-array>
</resources>

3. 送信側でノードを取得する

送信側で受信先と通信するためのノードを取得します。
CapabilityClientクラスのgetCapability()を呼び出すことで、必要な機能を備えたノードを検出できます。
取得したノードのidはidupdateOperationCapability()にてoperationNodeIdで保持します。

companion object {
    // 受信側でアドバタイズしたものと同じ必要がある
    private const val PLAYER_OPERATION_NAME = "player_operation"
}

private var operationNodeId: String? = null

fun setupOperation() {
    viewModelScope.launch(Dispatchers.IO) {
        val capabilityInfo: CapabilityInfo = Tasks.await(
            Wearable.getCapabilityClient(context)
                .getCapability(
                    PLAYER_OPERATION_NAME,
                    CapabilityClient.FILTER_REACHABLE
                )
        )
        updateOperationCapability(capabilityInfo)
    }
}

private fun updateOperationCapability(capabilityInfo: CapabilityInfo) {
    operationNodeId = pickBestNodeId(capabilityInfo.nodes)
}

private fun pickBestNodeId(nodes: Set<Node>): String? {
    // デバイスに直接接続されているノードがあるかit.isNearbyで識別する
    return nodes.firstOrNull { it.isNearby }?.id ?: nodes.firstOrNull()?.id   
}

4. 送信側でメッセージを送信する

いよいよメッセージを送信します。
CapabilityClientクラスのsendMessage()を呼び出すことで、指定したノードにメッセージを送信できます。
3 で取得したノードidと、任意のPath、そして送信するテキストをByteArray型で指定します。

const val PLAYER_OPERATION_MESSAGE_PATH = "/player_operation"

private fun requestOperationn(textData: ByteArray) {
    val context = getApplication<Application>()
    operationNodeId?.also { nodeId ->
        Wearable.getMessageClient(context).sendMessage(
            nodeId,
            ClientPath.MESSAGE.path,
            textData
        ).apply {
            addOnSuccessListener {
                // 成功時の処理
             }
            addOnFailureListener {
                // 失敗時の処理
             }
        }
    }
}

// ClickEventで呼び出す
fun onPlayButtonClick() {
    val dataText = "play"
    val data = dataText.toByteArray(Charsets.UTF_8)
    requestOperation(data)
}

...

今回は送信側の再生ボタンにonPlayButtonClick()のようなメソッドを用意して呼び出しました。
他にもボタンを用意し、対応するメソッドを作成・呼び出してメッセージを送信します。

5. 受信側でメッセージを受信する

MessageClient.OnMessageReceivedListenerインターフェースを実装します。
addListener()を使用してリスナーを登録することで、メッセージを受け取ることができます。

onMessageReceived()では、受信したMessageEventを用いて処理を記述していきます。
例えば「送信されたMessageが"play"だったら、動画の再生・一時停止処理を行う」といった処理を記述しています。

class MainActivity : AppCompatActivity(), MessageClient.OnMessageReceivedListener {
    ...

    const val PLAYER_OPERATION_MESSAGE_PATH = "/player_operation"

    override fun onMessageReceived(messageEvent: MessageEvent) {
        when (messageEvent.path) {
            PLAYER_OPERATION_MESSAGE_PATH -> {
                val message = messageEvent.data.toString(Charsets.UTF_8) //文字列に変換
                when (message){
                    "play" -> {
                        if (binding.videoView.player?.isPlaying == true){
                            binding.videoView.player?.pause()
                        } else {
                            binding.videoView.player?.play()
                        }
                    }
                    ...
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        Wearable.getMessageClient(this).addListener(this)
    }

    ...
}

まとめと感想

まとめ

  • Androidスマホで表示している動画をスマートウォッチで操作するアプリを作成
  • 受信側では機能のアドバタイズと受信処理を記述
    • 機能のアドバタイズはres/values/にXMLで記述
    • 受信はMessageClient.OnMessageReceivedListenerインターフェースを実装
  • 送信側ではノードの取得とメッセージ送信処理を記述
    • ノードを検出はCapabilityClientクラスのgetCapability()で処理
    • メッセージ送信はCapabilityClientクラスのsendMessage()で処理

感想

  • アドバタイズする機能やPathなど共通で扱うものは、共通で使用するModuleで管理するのが良さそう
  • 送信するデータはByteArray型であることに注意しないといけない

終わりに

今回はWear OS端末を触ってみました!
最近はスマートウォッチと連携しているアプリも多く、新世代ウェアラブルデバイスの普及次第ではもっと盛り上がる技術だと思いますので、今後も注目して追っていきたいと思います!

参考