Skip to content

fix(webui): keep embedded ActivityWatch URLs inside WebView#161

Merged
ErikBjare merged 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:bob/issue-153-settings-not-applied
May 22, 2026
Merged

fix(webui): keep embedded ActivityWatch URLs inside WebView#161
ErikBjare merged 2 commits into
ActivityWatch:masterfrom
TimeToBuildBob:bob/issue-153-settings-not-applied

Conversation

@TimeToBuildBob
Copy link
Copy Markdown
Contributor

Summary

  • keep loopback ActivityWatch URLs inside the embedded WebView instead of opening them in the external browser
  • treat both 127.0.0.1 and localhost as internal embedded-server hosts
  • add a small unit test for the host-classification helper

Why

The Android app serves the embedded UI from http://127.0.0.1:5600, but the current WebView override only whitelists localhost. That means internal navigations can get kicked out to the external browser, which uses different web storage than the in-app WebView. That matches the settings saved but not applied symptom from #153.

Testing

  • added WebUIFragmentTest coverage for internal vs external host handling
  • could not run Gradle tests in this environment because java is not installed on Bob's VM

Refs #153

@ErikBjare ErikBjare marked this pull request as ready for review May 22, 2026 13:17
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 88314aaf84

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

return when (uri.host?.lowercase()) {
"localhost", "127.0.0.1", "::1" -> true
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Strip IPv6 brackets before loopback host check

isEmbeddedActivityWatchUrl compares uri.host to "::1", but java.net.URI("http://[::1]:5600").host includes brackets ("[::1]"). As a result, IPv6 loopback URLs are misclassified as external and opened in the system browser, which breaks the intended in-WebView navigation/storage behavior for those links.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 696e8d5. java.net.URI.getHost() does return IPv6 with brackets — changed the match to "[::1]". Good catch.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR fixes WebView navigation by replacing a //localhost: string-contains check with a proper isEmbeddedActivityWatchUrl() helper that uses java.net.URI for host extraction, adding 127.0.0.1 and [::1] loopback support alongside localhost to keep all embedded-server navigations inside the app WebView.

  • WebUIFragment.kt: Introduces isEmbeddedActivityWatchUrl(url: String): Boolean as an internal top-level function; shouldOverrideUrlLoading now delegates to it instead of the old string-contains check.
  • WebUIFragmentTest.kt: New JUnit test class exercises all three loopback hosts across both http and https schemes, plus the external-host rejection path. Tests were not executed in the author's environment due to a missing Java toolchain.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to host classification in the WebView URL router and correctly handles all three loopback forms.

The fix is logically sound: java.net.URI correctly extracts hosts for all tested forms (localhost, 127.0.0.1, [::1]), the routing logic in shouldOverrideUrlLoading is unchanged structurally, and the new helper is covered by unit tests across the full scheme/host matrix. The only exception path (silent false on parse error) is a pre-existing pattern with a minor observability gap, not a regression.

No files require special attention.

Important Files Changed

Filename Overview
mobile/src/main/java/net/activitywatch/android/fragments/WebUIFragment.kt Replaces string-contains localhost check with isEmbeddedActivityWatchUrl() using java.net.URI for proper host extraction, adding 127.0.0.1 and [::1] loopback support. Logic and routing behavior are correct.
mobile/src/test/java/net/activitywatch/android/fragments/WebUIFragmentTest.kt New unit test covering http/https x localhost/127.0.0.1/[::1] (internal) and external host cases; tests were not executed in the PR environment due to missing Java toolchain.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[shouldOverrideUrlLoading] --> B{URLUtil.isNetworkUrl?}
    B -- No --> C[return true: block load]
    B -- Yes --> D{starts with http/https?}
    D -- No --> E[return false: load in WebView]
    D -- Yes --> F[isEmbeddedActivityWatchUrl]
    F --> G{parse URI via java.net.URI}
    G -- parse error --> H[return false: open externally]
    G -- OK --> I{scheme http or https?}
    I -- No --> H
    I -- Yes --> J{host is localhost / 127.0.0.1 / ::1?}
    J -- Yes --> K[return true: keep in WebView]
    J -- No --> H
Loading

Reviews (2): Last reviewed commit: "fix(webui): fix IPv6 bracket handling an..." | Re-trigger Greptile

Comment on lines +40 to +43
return when (uri.host?.lowercase()) {
"localhost", "127.0.0.1", "::1" -> true
else -> false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 No port restriction on loopback hosts

isEmbeddedActivityWatchUrl returns true for every port on loopback, not just 5600. If the in-app WebView ever renders a page from 127.0.0.1:5600 that links to another local service on a different port (e.g., a dev server at 127.0.0.1:3000), that link will silently stay inside the WebView rather than opening in the external browser. The original check used url.contains("//localhost:") which implicitly scoped to the scheme+host+port triple; the new helper broadens that. Consider adding a port check (port == 5600, or a small set of known ports) if this widened scope is not intentional.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The broader scope is intentional — ActivityWatch can run on non-default ports and we want to keep all loopback traffic in-WebView regardless of port. The original url.contains("//localhost:") check was actually port-agnostic too (it just required a port to be present). Leaving as-is.

Comment on lines +9 to +13
fun `treats local embedded server hosts as internal`() {
assertTrue(isEmbeddedActivityWatchUrl("http://127.0.0.1:5600/#/settings/"))
assertTrue(isEmbeddedActivityWatchUrl("http://localhost:5600/#/settings/"))
assertTrue(isEmbeddedActivityWatchUrl("http://[::1]:5600/#/settings/"))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 HTTPS loopback variant not tested

The isEmbeddedActivityWatchUrl function explicitly allows both http and https schemes for loopback hosts, but the "internal" test only exercises http:// URLs. Adding assertTrue(isEmbeddedActivityWatchUrl("https://localhost:5600/")) and its 127.0.0.1/::1 equivalents would verify the full scheme matrix the function guards.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 696e8d5 — added https variants for all three loopback hosts plus a no-port case.

…rage

java.net.URI.getHost() returns IPv6 addresses with brackets ("[::1]" not
"::1"), so the loopback check was silently misclassifying IPv6 loopback
URLs as external. Fix by matching "[::1]" directly.

Also add https loopback variants and a no-port case to the unit test to
cover the full scheme matrix the function guards.
@TimeToBuildBob
Copy link
Copy Markdown
Contributor Author

All review feedback addressed in 696e8d5:

  • IPv6 loopback (::1): fixed host match to "[::1]" since URI.getHost() returns brackets for IPv6
  • Port scope: intentionally broad — ActivityWatch can run on non-default ports, and the original url.contains("//localhost:") was also port-agnostic
  • Test coverage: added https variants for all three loopback hosts plus a no-port case

The Get latest versionCode CI failure is a pre-existing Fastlane secrets issue (the age-decryption key isn't available in CI) — master branch shows the same failure pattern. Build aw-server-rust is still running but unrelated to this change.

PR is ready for merge when you get a chance, @ErikBjare.

@ErikBjare ErikBjare merged commit 2e9e733 into ActivityWatch:master May 22, 2026
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants