mirror of
https://github.com/amnezia-vpn/amneziawg-android.git
synced 2026-10-13 21:34:07 +00:00
[PR #106] Add subscription sync, spaces, and per-item sharing #103
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
amnezia-vpn/amneziawg-android#103
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/amnezia-vpn/amneziawg-android/pull/106
Author: @uussoop
Created: 9/29/2026
Status: 🔄 Open
Base:
master← Head:master📝 Commits (2)
72a461ffix: bump libwg-go toolchain to Go 1.25.007ba1b8feat: add subscription sync, spaces, and per-item sharing📊 Changes
34 files changed (+2365 additions, -65 deletions)
View changed files
📝
gradle/libs.versions.toml(+1 -0)📝
tunnel/tools/libwg-go/Makefile(+5 -4)📝
ui/build.gradle.kts(+1 -0)📝
ui/src/main/AndroidManifest.xml(+1 -0)📝
ui/src/main/java/org/amnezia/awg/activity/MainActivity.kt(+38 -0)📝
ui/src/main/java/org/amnezia/awg/fragment/AddTunnelsSheet.kt(+9 -0)➕
ui/src/main/java/org/amnezia/awg/fragment/QrCodeDialogFragment.kt(+121 -0)➕
ui/src/main/java/org/amnezia/awg/fragment/SubscriptionDialogFragment.kt(+331 -0)📝
ui/src/main/java/org/amnezia/awg/fragment/TunnelListFragment.kt(+189 -14)➕
ui/src/main/java/org/amnezia/awg/fragment/TunnelShare.kt(+108 -0)➕
ui/src/main/java/org/amnezia/awg/model/Subscription.kt(+28 -0)➕
ui/src/main/java/org/amnezia/awg/subscription/SubscriptionFetcher.kt(+286 -0)➕
ui/src/main/java/org/amnezia/awg/subscription/SubscriptionStore.kt(+123 -0)➕
ui/src/main/java/org/amnezia/awg/subscription/SubscriptionSyncManager.kt(+336 -0)📝
ui/src/main/java/org/amnezia/awg/util/ClipboardUtils.kt(+23 -0)➕
ui/src/main/java/org/amnezia/awg/util/QrCodeRenderer.kt(+88 -0)➕
ui/src/main/java/org/amnezia/awg/util/ShareUtils.kt(+160 -0)➕
ui/src/main/res/drawable/ic_action_more.xml(+10 -0)➕
ui/src/main/res/drawable/ic_action_subscription.xml(+10 -0)➕
ui/src/main/res/drawable/ic_action_sync.xml(+10 -0)...and 14 more files
📄 Description
Summary
Adds three related things to the tunnel list: importing tunnels from a provider
subscription URL, grouping them into spaces, and a per-item share action.
Everything here hands out credentials, so the security behaviour is called out
in detail below rather than buried.
Subscriptions
Import a tunnel list from a URL. Accepted payloads are a plain
.confconcatenation, a
.zipof configs, or a JSON array of{name, config}. Theyare stored in the existing DataStore.
updated in place, so the switch position and state survive a sync. Tunnels the
provider has dropped are removed.
body leaves the existing tunnel list exactly as it was. There is no path where a
bad response empties the list.
subscription owns.
tunnel.
confirmation that states how many.
Spaces
A
Defaultspace for hand-made tunnels, one per subscription, plusAll. Thetabs filter the list; they are not containers, so a tunnel can be moved between
spaces without a re-sync.
Sharing
Every tunnel row and every subscription row has a share action offering copy,
QR code, and save to file.
authenticate first. Nothing is read from disk until authentication succeeds, so
a declined or cancelled prompt never touches the key.
confirmed with a warning rather than put behind biometrics. I think this is the
right line to draw — locking a URL behind a fingerprint is friction the
threat model does not justify — but it is a judgement call and easy to change.
selectable as a fallback for payloads too long to encode. The rendered code
has been verified to actually decode, not just render.
Config export honours the existing
disableConfigExportapp restriction, so apolicy that forbids zipping configurations also forbids handing them out one at
a time.
Security behaviour change — please look at this one
network_security_config.xmlrefuses cleartext HTTP by default, which is abehaviour change for any user whose provider is currently served over plain HTTP.
I think this belongs here rather than being left to the provider: a subscription
URL is a bearer token for the user's private keys, and sending it in the clear is
a real exposure. But it will break HTTP-only providers, and it is the kind of
change a maintainer should agree to deliberately.
The exceptions are loopback and the emulator host aliases (
localhost,127.0.0.1,10.0.2.2,10.0.3.2), so a self-hosted provider on a localnetwork can still be tested. Android's config cannot express CIDR ranges, so
arbitrary RFC1918 addresses are not covered — a provider at, say,
192.168.1.10over HTTP will still be rejected. That is a deliberate limitrather than an oversight.
If you would rather not take this on, it is self-contained in one new XML file
plus a manifest line, and the feature works without it — it would just be less
safe by default.
Testing
Driven on an emulator (
emulator-5554, Android 12) with a local fixtureprovider. 56 checks across four suites, all passing:
.conf,.zip, JSON, unparseable payloadTwo assertions are worth calling out because weaker ones would have passed:
device and runs zxing on it, then checks the decoded text is a complete
configuration. An
ImageViewexisting proves nothing about whether the codescans.
and the exact filename is asserted — this caught Android's MediaStore appending
.txttoDE-FRA.confbecause no MIME type maps to.conf.The biometric gate was tested with a real device PIN: the prompt appears, nothing
is copied before authenticating, and the copy happens after. On a device with no
credential it degrades to allowing the share, matching the existing
ZipExporterPreferencebehaviour.Known gaps
tv_tunnel_list_item.xmlis a separatelayout and this only covers the phone layout. I skipped it because the TV list
is remote-control driven with a fixed-size card, so a per-row share button is a
real design problem rather than a copy of the phone one. Happy to take it on as
a follow-up.
from each subscription row.
strings, same as any other string added to this project.
Note on the commits
This PR has two commits, and the first is unrelated to the feature:
fix: bump libwg-go toolchain to Go 1.25.0—go.modrequires 1.25.0 but theMakefile pinned 1.24.2, so the NDK cross build does not start. The feature
does not build without this, which is why it is here rather than in a
separate PR. If you would rather merge it on its own, it is a clean
one-commit cherry-pick and I will rebase the feature on top.
feat: add subscription sync, spaces, and per-item sharing— everything else.Provider contract
The wire format the app expects is documented in a separate spec I have not
included here. It is a JSON array of
{name, config}or a.zipof.conffiles, and a sync is a plain unauthenticated
GETunless the URL carries its owntoken. Happy to add the document to this repo if it is useful.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.