aboutsummaryrefslogtreecommitdiffstats
path: root/android/app/src/main/java
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-12 10:32:01 -0700
committerAdam Soltys <[email protected]>2026-09-12 10:32:01 -0700
commit60e755286302647d93bc82070c61fcf93ddbc3b9 (patch)
tree5cbe3c780010e975d61193138a29870f295427af /android/app/src/main/java
parenta846211262f37238c001e5210938749294f8db6a (diff)
downloadcoinosv3-60e755286302647d93bc82070c61fcf93ddbc3b9.tar.gz
coinosv3-60e755286302647d93bc82070c61fcf93ddbc3b9.zip
A second Android build that can wake the wallet, because it holds it.
The TWA can't. Chrome renders the site, so the wallet's keys live in Chrome's storage for its own origin, and nothing outside Chrome can reach them — a UnifiedPush message could raise a notification and no more. On a phone with Google Play Services none of this matters; without it, background answering was simply unavailable. So: a second flavour, io.coinos.webview, "Coinos (UnifiedPush)". The wallet runs in a WebView this app owns, which means a push can start it off-screen and let it answer for itself. WakeService loads it with ?wake=<payload> into the same storage the visible app uses, the page subscribes and answers whatever the payload was about, then calls CoinosHost.done() so the service stops — capped at 25 seconds from the page and 30 from the service, because nothing here is allowed to sit in the background costing battery. Its foreground notification says what it's doing rather than apologising for existing. The WebView carries what the wallet actually needs: DOM storage and IndexedDB, the camera for the QR scanner (granted only once Android has granted it to us), a file chooser for attaching pictures, and links to anywhere but coinos.io handed to a real browser. The trade is named in the code and worth naming here: a separate app is a separate wallet. Its storage is its own, so signing in there is signing in again. That's why this is a second build rather than a replacement — the TWA is untouched, still the better browser for anyone whose phone has push. The shared push receiver now asks the system for whatever front door this build has, and asks a per-flavour Wake class whether the wallet can be woken at all — no flavour checks in the middle of it. Both APKs build and ship: coinos.apk as before, coinos-unifiedpush.apk beside it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
Diffstat (limited to 'android/app/src/main/java')
-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.java54
-rw-r--r--android/app/src/main/java/io/coinos/app/PushReceiver.java11
3 files changed, 9 insertions, 91 deletions
diff --git a/android/app/src/main/java/io/coinos/app/IntentActivity.java b/android/app/src/main/java/io/coinos/app/IntentActivity.java
deleted file mode 100644
index 3de1d62..0000000
--- a/android/app/src/main/java/io/coinos/app/IntentActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-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
deleted file mode 100644
index 654265b..0000000
--- a/android/app/src/main/java/io/coinos/app/LauncherActivity.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package io.coinos.app;
-
-import android.Manifest;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-
-import java.net.URLEncoder;
-
-/**
- * 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.
- *
- * <p>It also carries the UnifiedPush endpoint across: the Android side is
- * what a distributor talks to, and the web app is what knows which pubkeys
- * to watch, so the endpoint rides into the web app as ?up= exactly the way
- * payment intents ride in as ?u=.
- */
-public class LauncherActivity
- extends com.google.androidbrowserhelper.trusted.LauncherActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- // Ask the distributor on every launch: it's idempotent, and it's how a
- // distributor installed after us ever gets noticed.
- Push.register(this);
- // Android 13+ won't show our notifications without this, and the TWA's
- // own permission covers the browser's, not ours.
- if (Build.VERSION.SDK_INT >= 33
- && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
- && Push.distributor(this) != null) {
- try { requestPermissions(new String[] { Manifest.permission.POST_NOTIFICATIONS }, 1); } catch (Exception ignored) {}
- }
- }
-
- @Override
- protected Uri getLaunchingUrl() {
- Uri data = getIntent().getData();
- Uri base = (data != null && "v3.coinos.io".equals(data.getHost())) ? data : super.getLaunchingUrl();
- String endpoint = Push.endpoint(this);
- if (endpoint == null || Push.endpointDelivered(this)) return base;
- try {
- String sep = base.toString().contains("?") ? "&" : "?";
- Uri withEndpoint = Uri.parse(base + sep + "up=" + URLEncoder.encode(endpoint, "UTF-8"));
- Push.markEndpointDelivered(this);
- return withEndpoint;
- } catch (Exception e) {
- return base;
- }
- }
-}
diff --git a/android/app/src/main/java/io/coinos/app/PushReceiver.java b/android/app/src/main/java/io/coinos/app/PushReceiver.java
index 07cabeb..faabfaf 100644
--- a/android/app/src/main/java/io/coinos/app/PushReceiver.java
+++ b/android/app/src/main/java/io/coinos/app/PushReceiver.java
@@ -44,7 +44,11 @@ public class PushReceiver extends BroadcastReceiver {
case Push.ACTION_MESSAGE: {
byte[] bytes = intent.getByteArrayExtra(Push.EXTRA_BYTES_MESSAGE);
String body = bytes != null ? new String(bytes) : intent.getStringExtra(Push.EXTRA_MESSAGE);
- notify(context, body);
+ // On the WebView build the wallet lives in THIS app's storage, so it
+ // can be woken and told to deal with whatever arrived — no tap. On
+ // the TWA build the wallet is Chrome's and out of reach, so the
+ // notification is the whole of what we can do.
+ if (!Wake.start(context, body)) notify(context, body);
Push.ack(context, intent.getStringExtra(Push.EXTRA_ID));
break;
}
@@ -84,7 +88,10 @@ public class PushReceiver extends BroadcastReceiver {
nm.createNotificationChannel(ch);
}
- Intent open = new Intent(c, LauncherActivity.class);
+ // whichever front door this build has — the TWA's launcher or the
+ // WebView's main activity; the receiver is shared by both
+ Intent open = c.getPackageManager().getLaunchIntentForPackage(c.getPackageName());
+ if (open == null) open = new Intent(Intent.ACTION_VIEW);
open.setAction(Intent.ACTION_VIEW);
open.setData(Uri.parse(deepLink != null && deepLink.startsWith("https://v3.coinos.io")
? deepLink : "https://v3.coinos.io/"));