Search code examples
androidkotlinwear-osandroid-wear-data-apiwearables

WearOS and Phone application does not communicate


Desired Application:

When I click the button on WearOS application, it should send a message data to the Phone app. When message received from phone app, the data should displayed on the screen or logcat.

I am trying to communicate a phone application and a wear OS application using the Kotlin language on Android studio. The application I want to do is simply send the data I set to the phone application when the button is pressed on WearOS. I've set up the MessageClient on both sides, and while I can send data from the Wear OS to the phone app, the onMessageReceived function on the phone app never seems to be triggered.Devices are paired.

Wear OS Code:

class MainActivity : ComponentActivity(),
    DataClient.OnDataChangedListener,
    MessageClient.OnMessageReceivedListener{

    private var activityContext: Context? = null
    private var dataClient: DataClient? = null
    private var messageClient: MessageClient? = null

    companion object {
        const val MESSAGE_PATH_SEND = "/send_message"
        const val MESSAGE_PATH_RECEIVE = "/receive_message"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            WearApp("Android")
        }

        activityContext = this

        dataClient = Wearable.getDataClient(this)
        messageClient = Wearable.getMessageClient(this)

    }



    @Composable
    fun WearApp(greetingName: String) {
        WearOSCommunicationTheme {
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .background(MaterialTheme.colors.background),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                //Greeting(greetingName = greetingName)
                Text(text = "Example text")
                Button(
                    modifier = Modifier
                        .padding(10.dp)
                        .fillMaxWidth(),
                    onClick = { sendMessageToPhone("this is an example message") }) {

                    Text(text = "Send Msg")

                }
            }
        }
    }

    private fun sendMessageToPhone(message: String) {
        Wearable.getNodeClient(activityContext!!).connectedNodes.addOnSuccessListener { nodes ->
            for (node in nodes) {
                if (node.isNearby) {  // Check if the node is reachable
                    Wearable.getMessageClient(activityContext!!).sendMessage(node.id, MESSAGE_PATH_SEND, message.toByteArray(Charsets.UTF_8))
                        .addOnSuccessListener {
                            // Handle success here with a toast
                            Log.d("WearOSLog", "Message sent successfully to ${node.id}")
                        }
                        .addOnFailureListener { exception ->
                            // Handle failure here with a toast
                            Log.e("WearOSLog", "Failed to send message to ${node.id}", exception)
                        }
                } else {
                    Log.d("WearOSLog", "Node ${node.id} is not reachable")
                }
            }
        }.addOnFailureListener { exception ->
            // Handle failure to get connected nodes here
            Log.e("WearOSLog", "Failed to get connected nodes", exception)
        }
    }


    override fun onDataChanged(dataEvents: DataEventBuffer) {
        for (event in dataEvents) {
            if (event.type == DataEvent.TYPE_CHANGED){
                // Handle data change
            }
        }
    }

    override fun onMessageReceived(messageEvent: MessageEvent) {
        TODO("Not yet implemented")
    }


    override fun onResume() {
        super.onResume()

        try {
            Wearable.getDataClient(activityContext!!).addListener(this)
            Wearable.getMessageClient(activityContext!!).addListener(this)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun onPause() {
        super.onPause()
        Wearable.getDataClient(activityContext!!).removeListener(this)
        Wearable.getMessageClient(activityContext!!).removeListener(this)
    }

}

Phone App Code:

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

    companion object {
        const val MESSAGE_PATH_SEND = "/send_message"
        const val MESSAGE_PATH_RECEIVE = "/receive_message"
    }

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

    override fun onMessageReceived(messageEvent: MessageEvent) {
        try {
            if (messageEvent.path == MESSAGE_PATH_SEND) {
                val message = String(messageEvent.data, Charsets.UTF_8)
                Log.d("PhoneSide", "Message received: $message");
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Log.d("PhoneSide", "error")
        }
    }

    override fun onResume() {
        super.onResume()

        try {
            Wearable.getDataClient(this).addListener(this)
            Wearable.getMessageClient(this).addListener(this)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun onPause() {
        super.onPause()

        try {
            Wearable.getDataClient(this).removeListener(this)
            Wearable.getMessageClient(this).removeListener(this)
        }  catch (e: Exception) {
            e.printStackTrace()
        }
    }


    override fun onDataChanged(p0: DataEventBuffer) {
        TODO("Not yet implemented")
    }
}

Also related manifest documents are:

WearOS Manifets File:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <uses-feature android:name="android.hardware.type.watch" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@android:style/Theme.DeviceDefault">
        <uses-library
            android:name="com.google.android.wearable"
            android:required="true" />

        <!--
               Set to true if your app is Standalone, that is, it does not require the handheld
               app to run.
        -->
        <meta-data
            android:name="com.google.android.wearable.standalone"
            android:value="true" />

        <activity
            android:name=".presentation.MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.DeviceDefault">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Phone App Manifest File:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.WAKE_LOCK" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.WearOSCommunication"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

I read related parts of Android Developer page but I couldn't find any related path for this type of applications. I could not find almost any information or tutorial on this subject on the internet. I would be glad if you help.


Solution

  • The documentation on the Android developer page is under the Handling Data topic.

    For your issue, please review the following security requirements, otherwise your apps won't communicate to each other:

    • The package name must match across devices.
    • The signature of the package must match across devices.

    After fixing your issue, consider improving your code to use capabilities to only deliver the message to the node that contains your app capabilities.

    Other sources of information/tutorial on this subject: