From Wrist to Hand: Display Mobile Device Battery Level on Your Galaxy Watch

Samiul Hossain

Engineer, Samsung Developer Program

Galaxy Watch apps can offer a range of features and services, but the watch's smaller size compared to a mobile device means there are limitations, including fewer hardware resources and a smaller screen.

To make the most of the capabilities of a mobile device, you can develop a companion mobile application for the wearable application. The companion application handles complex and resource-intensive tasks while the wearable application provides a seamless experience.

Previously in this series, we showed how to create a companion mobile application for a Galaxy Watch running Wear OS powered by Samsung and use the Wearable Data Layer API to send messages from the watch to the mobile device.

While it is easy to check the watch's battery level from the mobile device, the reverse is more complex. This tutorial describes how to establish two-way communication between the wearable and mobile applications and use it to check the mobile device's battery level from the watch.

Prerequisites

To develop a wearable application and its companion mobile application, create a multi-module project in Android Studio. The steps are described in the previous tutorial, and the same dependencies and modifications are required for the wearable application in this tutorial:

  1. Add the following dependencies to the build.gradle file
    dependencies {
        ...
        implementation "com.google.android.gms:play-services-wearable:xx.x.x"
        implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
        implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:x.x.x"
        implementation "androidx.lifecycle:lifecycle-extensions:x.x.x"
        implementation "androidx.lifecycle:lifecycle-runtime-ktx:x.x.x"
        implementation "androidx.appcompat:appcompat:x.x.x"
    }
    
  2. Modify the MainActivity class to inherit AppCompatActivity() instead of Activity().
  3. In the "AndroidManifest.xml" file, in the <application> element, change the android:theme attribute value to "@style/Theme.AppCompat.NoActionBar" or another custom theme.

To test the project, you need a Galaxy Watch running Wear OS powered by Samsung and a connected Galaxy mobile device.

Request battery information from the companion application

To be able to retrieve the mobile device's battery level as a percentage from the watch, the companion application on the mobile device must advertise the "battery_percentage" capability.

In the mobile application, create an XML file named "wear.xml" in the "res/values/" directory with the following content:

<!--XML configuration file-->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
  tools:keep="@array/android_wear_capabilities">
  <string-array name="android_wear_capabilities">
    <item>battery_percentage</item>
  </string-array>
</resources>

While a watch can be connected to only one device at a time, a mobile device can be connected to multiple wearables at the same time. To determine which node (connected device) corresponds to the watch, use the CapabilityClient class of the Wearable Data Layer API to retrieve all the available nodes and select the best or closest node to deliver your message.

// MainActivity class in the wearable application
private var batteryNodeId: String? = null
private fun setupBatteryPercentage() {
    // Store the reachable nodes
    val capabilityInfo: CapabilityInfo = Tasks.await( 
        Wearable.getCapabilityClient(applicationContext)
            // Retrieve all connected nodes with the 'battery_percentage' capability
            .getCapability( 
                BATTERY_PERCENTAGE_CAPABILITY_NAME,
                CapabilityClient.FILTER_REACHABLE
            )
    )

    // Use a listener to retrieve the reachable nodes
    updateBatteryCapability(capabilityInfo).also { capabilityListener ->
        Wearable.getCapabilityClient(this @MainActivity).addListener( 
            capabilityListener,
            BATTERY_PERCENTAGE_CAPABILITY_NAME
        )
    }
}

private fun pickBestNodeId(nodes: Set<Node>): String? {
    // Find the best node
    return nodes.firstOrNull { it.isNearby }?.id ?: nodes.firstOrNull()?.id
}

private fun updateBatteryCapability(capabilityInfo: CapabilityInfo) {
    // Specify the recipient node for the message
    batteryNodeId = pickBestNodeId(capabilityInfo.nodes)
}

companion object{
    private const val TAG = "MainWearActivity"
    private const val BATTERY_PERCENTAGE_CAPABILITY_NAME = "battery_percentage"
    private const val BATTERY_MESSAGE_PATH = "/message_battery"
}

To implement bi-directional communication between watch and mobile device, you can use the MessageClient class of the Wearable Data Layer API.

In the wearable application UI, create a button and a textView.

To display the mobile device's battery level on the textView when the button is tapped, implement the button's onClickListener() function. Send the battery level request message through a specific message path to the mobile device, using a coroutine that calls the setupBatteryPercentage() and requestBatteryPercentage() methods on a separate thread. A separate thread must be used because these are synchronous calls that block the UI thread.

// MainActivity class in the wearable application
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
    Log.d(TAG, "onCreate()")

    binding.apply{
        phoneButton.setOnClickListener{
            lifecycleScope.launch(Dispatchers.IO){
                setupBatteryPercentage()
                requestBatteryPercentage("Battery".toByteArray())
            }
        }
    }
}
// Deliver the message to the selected node
private fun requestBatteryPercentage(data: ByteArray) {
    batteryNodeId?.also { nodeId ->
        val sendTask: Task<*> = Wearable.getMessageClient(this @MainActivity).sendMessage(
            nodeId,
            BATTERY_MESSAGE_PATH,
            data
        ).apply {
            addOnSuccessListener { Log.d(TAG, "OnSuccess") }
            addOnFailureListener { Log.d(TAG, "OnFailure") }
        }
    }
 }

Receive the message on the companion application

The companion application must be able to receive and respond to the message from the background. To accomplish this, implement a service that listens for incoming messages.

In the mobile application, create a class that extends WearableListenerService() and add the service to the application manifest file:

<!--Android manifest file for the mobile application-->
<service
    android:name=".PhoneListenerService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
        <data
            android:host="*"
            android:pathPrefix="/"
            android:scheme="wear" />
    </intent-filter>
</service>

Use the batteryManager class to retrieve the current battery level of the device. To send the retrieved value back to the wearable application, use the sendMessage() function again. For simplicity, send the message to the first connected node on the mobile device. Alternatively, you can broadcast to all connected nodes.

Implement the OnMessageReceived() function to receive the incoming request for battery level and send the retrieved value to the wearable application.

// Service class in the mobile application
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
private var batteryNodeId: String? = null

override fun onDestroy() {
    scope.cancel()
    super.onDestroy()
}

override fun onMessageReceived(messageEvent: MessageEvent) {
    Log.d(TAG, "onMessageReceived(): $messageEvent")
    Log.d(TAG, String(messageEvent.data))
    if (messageEvent.path == BATTERY_MESSAGE_PATH && String(messageEvent.data) == "Battery") { 
        // Check that the request and path are correct
        val batteryManager = applicationContext.getSystemService(BATTERY_SERVICE) as BatteryManager

        val batteryValue:Int = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        scope.launch(Dispatchers.IO){
            // Send the message to the first node
            batteryNodeId = getNodes().first()?.also { nodeId-> 
                val sendTask: Task<*> = Wearable.getMessageClient(applicationContext).sendMessage(
                    nodeId,
                    BATTERY_MESSAGE_PATH,
                    batteryValue.toString().toByteArray()
                ).apply {
                    addOnSuccessListener { Log.d(TAG, "OnSuccess") }
                    addOnFailureListener { Log.d(TAG, "OnFailure") }
                }
            }
        }
    }
    onDestroy()
}

private fun getNodes(): Collection<String> {
    return Tasks.await(Wearable.getNodeClient(this).connectedNodes).map { it.id }
}

companion object{
    private const val TAG = "PhoneListenerService"
    private const val BATTERY_MESSAGE_PATH = "/message_battery"
}

Display the battery information on the wearable application

When receiving the battery information on the wearable application, because the user is actively interacting with the application, a resource-intensive service is not needed and registering a live listener is sufficient. Use the addListener() method of the MessageClient class to implement the MessageClient.OnMessageReceivedListener interface within the MainActivity class in the wearable application.

// MainActivity class in the wearable application
override fun onResume(){
    super.onResume()
    Log.d(TAG, "onResume()")
    // Wearable API clients are not resource-intensive
    Wearable.getMessageClient(this).addListener(this) 
}

override fun onPause(){
    super.onPause()
    Log.d(TAG, "onPause()")
    Wearable.getMessageClient(this).removeListener(this)
}

override fun onMessageReceived(messageEvent: MessageEvent) {
    // Receive the message and display it
    if(messageEvent.path == BATTERY_MESSAGE_PATH){
        Log.d(TAG, "Mobile battery percentage: " + String(messageEvent.data) + "%")
        binding.phoneTextView.text = String(messageEvent.data)
    }
}

Conclusion

To test the project, build both applications and run them on your Galaxy Watch and mobile device. When you tap the UI button on the watch, the application retrieves the battery level from the mobile device and displays the percentage on the watch.

This demonstration has shown how the Wearable Data Layer API enables you to implement seamless bi-directional communication between a Galaxy Watch running Wear OS powered by Samsung and its connected mobile device. In addition to battery level, you can use the CapabilityClient and MessageClient classes to transfer various data between the devices in a similar way.

For more information about implementing communication between watch and mobile devices, see Send and receive messages on Wear.

If you have questions about or need help with the information in this tutorial, you can share your queries on the Samsung Developers Forum. For more specialized support, you can contact us through Samsung Developer Support.

Stay tuned for the next installment in this tutorial series.