package org.skatespots.app; import android.content.Intent; import android.os.Bundle; import android.webkit.WebSettings; import android.webkit.WebView; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import com.getcapacitor.BridgeActivity; /** * SkateSpots launcher activity. * * SAFE BY DESIGN: this extends the stock Capacitor BridgeActivity and does NOT * override the WebViewClient (an earlier custom version did and caused a * black-screen crash). The ONLY thing we add is the registration of the * ActivityResultLauncher used by AmberIntentPlugin. * * WHY THE LAUNCHER LIVES HERE * ─────────────────────────── * AndroidX requires registerForActivityResult(...) to be called BEFORE the * Activity is STARTED (i.e. during onCreate). A Capacitor Plugin is instantiated * too late to register its own launcher, so we create it here and hand it to the * plugin via AmberIntentPlugin.setLauncher(). Results are routed straight into * AmberIntentPlugin.handleResult(). * * This is the Amethyst NIP-55 pattern: a foreground-intent signer whose result * comes back through a properly-registered ActivityResultLauncher — the reliable * path that OPENS Amber's Approve/Reject screen and never hangs, on stock Android * and on GrapheneOS alike. * * Injected into the generated android/ project at build time by * scripts/inject-main-activity.mjs. */ public class MainActivity extends BridgeActivity { private ActivityResultLauncher amberLauncher; @Override public void onCreate(Bundle savedInstanceState) { // Register the launcher BEFORE super.onCreate() puts us past STARTED. amberLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> AmberIntentPlugin.handleResult(result) ); AmberIntentPlugin.setLauncher(amberLauncher); // Register our native plugin so the WebView can call it. registerPlugin(AmberIntentPlugin.class); super.onCreate(savedInstanceState); // ── Allow the on-device Citrine relay (ws://127.0.0.1) ────────────── // The app is served to the WebView over the https scheme, and Citrine // listens on a PLAINTEXT WebSocket (ws://, not wss://). Modern Android // WebViews treat "secure page → insecure ws" as mixed content and block // it BEFORE it ever reaches the network layer — which is why the local // relay showed 0 connections despite allowMixedContent + the loopback // network-security-config (neither fully covers WebSocket mixed content). // // MIXED_CONTENT_ALWAYS_ALLOW on the bridge WebView lets the localhost // ws:// connection through. All real traffic remains wss:// / https://; // this only unblocks the loopback relay. We do NOT touch the // WebViewClient (overriding it previously caused a black-screen crash). try { WebView webView = getBridge().getWebView(); if (webView != null) { WebSettings settings = webView.getSettings(); settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } } catch (Exception e) { // Non-fatal: if this fails the app still runs, the local relay just // won't connect. Never crash the launcher over an optional feature. } } }