Skip to content

Add WearDns for handling DNS resolution on Wear devices #5170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

yschimke
Copy link
Contributor

@yschimke yschimke commented Mar 30, 2025

Summary

This commit introduces a new WearDns class to handle DNS resolution on Wear OS devices. It attempts to use the system DNS and falls back to mobile DNS if there's an issue.

The implementation includes:

  • mobile_network_helper: Constant for the mobile DNS capability.
  • /dnsLookup: Constant for the mobile DNS request.

Any other notes

I'm experimenting to address #1866

If this isn't working, then next option to explore is requiring Wifi. See https://google.github.io/horologist/network-awareness/

This commit introduces a new `WearDns` class to handle DNS resolution on Wear OS devices. It attempts to use the system DNS and falls back to mobile DNS if there's an issue. The implementation includes:
- `dns_via_mobile`: Constant for the mobile DNS capability.
This commit introduces a new `WearDns` class to handle DNS resolution on Wear OS devices. It attempts to use the system DNS and falls back to mobile DNS if there's an issue. The implementation includes:
- `dns_via_mobile`: Constant for the mobile DNS capability.
# Conflicts:
#	wear/src/main/kotlin/io/homeassistant/companion/android/data/WearDns.kt
@yschimke yschimke marked this pull request as ready for review March 31, 2025 05:38
@yschimke
Copy link
Contributor Author

Open Questions

  • Should this be a user preference
  • Can we detect specific cases to retry (BT active, lan/local hostname)

import okhttp3.Dns

@AndroidEntryPoint
class WearDataListener : WearableListenerService() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you envision this class to evolve? The name seems quite generic. Could you add a description within the code of the scope of the listener?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, happy for guidance here.

I'll document the current scope (mobile providing helper APIs for networking/data).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case if you envision it to be for general networking/data I would suggest to follow Google documentation

For best performance, limit the number of listeners and use mutually exclusive filters where there are multiple listeners. A common pattern is to use prefix filters to listen to different namespaces within an application.

And introduce a namespace in the intent-filter something like android:pathPrefix="/network" and update the path to look like /network/dnsLookup

Copy link
Collaborator

@TimoPtr TimoPtr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that for now we don't need a user preference for it (but it can be added later if needed). But I think it worse mentioning this behavior inside the https://companion.home-assistant.io/ (github https://github.com/home-assistant/companion.home-assistant).

For the retry I don't know we can go with your proposal and see if people are raising new issues about it.

Comment on lines +17 to +20
/**
* Wear specific implementation of Dns that defaults to the System DNS,
* but can fall back to relying on mobile Dns to resolve dns issues.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be a bit more specific saying that it uses the messageAPI to query /dnsLookup with the hostname. Add a mention about the caching mechanism in place.


return addresses
} catch (e2: Exception) {
e2.addSuppressed(e)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two error combined (UnknownHostException) might be a bit hard to read. I think you better with proper logging here.
Add a log in the first catch something like that

Timber.e(e, "DNS resolution using the system DNS failed, fallback onto mobile DNS lookup."

if (path == "/dnsLookup") {
val hostname = String(request, Charsets.UTF_8)
return mainScope.async(Dispatchers.IO) {
Dns.SYSTEM.lookup(hostname).first().address
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only returning the first? If I'm not wrong you could get at two InnetAddress one for ipv4 and one for ipv6. The wear could benefits having the two.

It avoid having some logic here you just forward whatever you received without transformation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Should I json encode the request and response? Would be more inline with other uses?

This actually reminds me, we should prefer IPv4, as I'm not sure the BT proxied connection supports IPv6.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with JSON. Even if we should prefer IPv4 let's not take any decision at this level. The wear should deal with that not the mobile.

import okhttp3.Dns

@AndroidEntryPoint
class WearDataListener : WearableListenerService() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case if you envision it to be for general networking/data I would suggest to follow Google documentation

For best performance, limit the number of listeners and use mutually exclusive filters where there are multiple listeners. A common pattern is to use prefix filters to listen to different namespaces within an application.

And introduce a namespace in the intent-filter something like android:pathPrefix="/network" and update the path to look like /network/dnsLookup

Comment on lines +97 to +102
sealed interface CacheResult {
val storedAt: Instant
}

data class NegativeCacheHit(val exception: Exception, override val storedAt: Instant) : CacheResult
data class PositiveCacheHit(val value: List<InetAddress>, override val storedAt: Instant) : CacheResult
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this private?

Comment on lines +85 to +87
if (response.isEmpty()) {
throw UnknownHostException("Mobile helper unable to resolve $hostname")
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we should do that, the normal behavior of OkHttp is just passing the empty list down the chain. If you received an empty list for the API forward it OkHttp will fail later.

Since we inject this class in the HomeAssistantAPI I rather replicate exactly what the default behavior does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@home-assistant
Copy link

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@home-assistant home-assistant bot marked this pull request as draft March 31, 2025 10:30
@TimoPtr
Copy link
Collaborator

TimoPtr commented Mar 31, 2025

Would you mind adding some Unit test? I've added recently all the necessary tools to add them.

@yschimke
Copy link
Contributor Author

yschimke commented Apr 1, 2025

Would you mind adding some Unit test? I've added recently all the necessary tools to add them.

@TimoPtr testing is hard with the DataLayer. In either instrumentation (need paired devices), or unit test (robolectric with custom shadows?).

I'll refactor to have some API we control for the DataLayer parts and test the rest of the structure.

I'm assuming unit tests based on this being recently added https://github.com/home-assistant/android/blob/master/common/src/test/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/entities/ConversationResponseTest.kt

And put them under wear/src/test/kotlin

@TimoPtr
Copy link
Collaborator

TimoPtr commented Apr 1, 2025

Would you mind adding some Unit test? I've added recently all the necessary tools to add them.

@TimoPtr testing is hard with the DataLayer. In either instrumentation (need paired devices), or unit test (robolectric with custom shadows?).

I'll refactor to have some API we control for the DataLayer parts and test the rest of the structure.

I'm assuming unit tests based on this being recently added https://github.com/home-assistant/android/blob/master/common/src/test/kotlin/io/homeassistant/companion/android/common/data/websocket/impl/entities/ConversationResponseTest.kt

And put them under wear/src/test/kotlin

Yes unit test, unit test with robolectric and instrumentation test have been setup recently. For now it only contains very simple tests, I used them to work on the CI that run everything now. Lemme know if you need anything for writing the tests. I can help since it's still WIP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants