aboutsummaryrefslogtreecommitdiffstats
path: root/android/app/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-04 20:53:13 -0700
committerAdam Soltys <[email protected]>2026-09-04 20:53:13 -0700
commite5e375d8ea2f1193ffe01ff02639f002f91f8035 (patch)
treedac59b4eace3befeb978263acd749f2822957cfe /android/app/src
parent8d27a5d2c59e0f1824753d59a7326b6b76887bf7 (diff)
downloadcoinosv3-e5e375d8ea2f1193ffe01ff02639f002f91f8035.tar.gz
coinosv3-e5e375d8ea2f1193ffe01ff02639f002f91f8035.zip
Android wrapper v1.1: scheme + NFC intents move onto a per-intent trampoline (IntentActivity, noHistory) — a TWA already on screen ignores a new intent delivered to its still-alive launcher, so a second lightning: tap did nothing; the trampoline maps the URI to ?u= form and relaunches the TWA with CLEAR_TOP, which navigates the open app. Verified on a real phone over adb: URL-bar-less fullscreen (assetlinks verified after the site began serving /.well-known/assetlinks.json — the deploy hook's dist/* glob was blind to dot-directories, fixed on cs), lightning: intent lands on Send with the invoice filled, and a second bitcoin: intent fired while the app was open navigated it (BIP-21 parsed, address + amount). NFC NDEF filters ride the trampoline for bolt cards / NDEF tags
Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/main/AndroidManifest.xml18
-rw-r--r--android/app/src/main/java/io/coinos/app/IntentActivity.java35
-rw-r--r--android/app/src/main/java/io/coinos/app/LauncherActivity.java16
3 files changed, 51 insertions, 18 deletions
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index c6b7a01..4eb96a7 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -32,9 +32,18 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="v3.coinos.io" />
</intent-filter>
- <!-- the payment + nostr schemes: the reason this wrapper exists.
- lnurlw/lnurlp ride along — bolt cards and LNURL NFC tags emit
- those schemes. -->
+ </activity>
+ <!-- Scheme + NFC intents land on this per-intent trampoline, not the
+ launcher: a TWA already on screen ignores a new intent to its
+ launcher, so a second lightning: tap used to do nothing. The
+ trampoline relaunches the TWA with the new URL and finishes.
+ lnurlw/lnurlp ride along — bolt cards and LNURL NFC tags emit
+ those schemes; NDEF dispatch needs no NFC permission. -->
+ <activity
+ android:name="io.coinos.app.IntentActivity"
+ android:exported="true"
+ android:noHistory="true"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -46,9 +55,6 @@
<data android:scheme="lnurlw" />
<data android:scheme="lnurlp" />
</intent-filter>
- <!-- NFC: an NDEF tag carrying one of our URIs (a bolt card tap, an
- address sticker) dispatches here directly — same forwarding path
- as a VIEW intent, no NFC permission needed for NDEF dispatch. -->
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/android/app/src/main/java/io/coinos/app/IntentActivity.java b/android/app/src/main/java/io/coinos/app/IntentActivity.java
new file mode 100644
index 0000000..3de1d62
--- /dev/null
+++ b/android/app/src/main/java/io/coinos/app/IntentActivity.java
@@ -0,0 +1,35 @@
+package io.coinos.app;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+/**
+ * Trampoline for scheme + NFC intents. A TWA already on screen ignores a
+ * new intent delivered to its (still-alive) launcher, so a second
+ * lightning: tap used to do nothing. This activity exists per-intent:
+ * it maps the URI to the web app's ?u= form and relaunches LauncherActivity
+ * with CLEAR_TOP — the Chrome activity above it is cleared, the launcher is
+ * recreated with the new data, and the TWA navigates. Then it's gone.
+ */
+public class IntentActivity extends android.app.Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Uri url = Uri.parse("https://v3.coinos.io/");
+ Uri data = getIntent().getData();
+ if (data != null) {
+ String scheme = data.getScheme();
+ if (scheme != null && !scheme.equals("https") && !scheme.equals("http")) {
+ url = Uri.parse("https://v3.coinos.io/?u=" + Uri.encode(data.toString()));
+ } else {
+ url = data;
+ }
+ }
+ Intent i = new Intent(this, LauncherActivity.class);
+ i.setData(url);
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(i);
+ finish();
+ }
+}
diff --git a/android/app/src/main/java/io/coinos/app/LauncherActivity.java b/android/app/src/main/java/io/coinos/app/LauncherActivity.java
index d0b56bb..c3950ac 100644
--- a/android/app/src/main/java/io/coinos/app/LauncherActivity.java
+++ b/android/app/src/main/java/io/coinos/app/LauncherActivity.java
@@ -3,11 +3,9 @@ package io.coinos.app;
import android.net.Uri;
/**
- * Forwards scheme intents into the web app. A lightning:/bitcoin:/nostr:/
- * lnurl: URI arrives as this activity's data; the web app receives it as
- * https://v3.coinos.io/?u=<encoded original URI> and routes it (send form
- * for payments, profile for nostr npubs). https links to the site pass
- * through untouched, everything else opens the default URL.
+ * The TWA launcher. Scheme/NFC intents arrive via IntentActivity (the
+ * trampoline that survives the running-TWA case); this activity only needs
+ * to honor a full URL handed to it, falling back to the site root.
*/
public class LauncherActivity
extends com.google.androidbrowserhelper.trusted.LauncherActivity {
@@ -15,13 +13,7 @@ public class LauncherActivity
@Override
protected Uri getLaunchingUrl() {
Uri data = getIntent().getData();
- if (data != null) {
- String scheme = data.getScheme();
- if (scheme != null && !scheme.equals("https") && !scheme.equals("http")) {
- return Uri.parse("https://v3.coinos.io/?u=" + Uri.encode(data.toString()));
- }
- if ("v3.coinos.io".equals(data.getHost())) return data;
- }
+ if (data != null && "v3.coinos.io".equals(data.getHost())) return data;
return super.getLaunchingUrl();
}
}