From 8c32a297e32dee020413209d41c211ed71b221b3 Mon Sep 17 00:00:00 2001 From: Adib Hanna Date: Fri, 28 Aug 2026 11:34:52 -0500 Subject: [PATCH] fix: an unreadable content:// attachment no longer crashes the app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Android vitals for 1.1.11 (2 events, 1 user): the process died inside Capacitor's WebViewLocalServer while it served a content:// asset. The WebView requested an attachment from a folder (SAF) vault, the document provider raised IllegalArgumentException for it (removed, renamed, or its permission gone), and nothing between ContentResolver.openInputStream and Chromium's request interceptor catches that, so an unreadable image was a fatal crash instead of a broken one. MainActivity now installs a BridgeWebViewClient subclass whose shouldInterceptRequest wraps the local server: any RuntimeException while resolving a request answers 404 and the app carries on. Reproduced on the API 35 emulator with the shipped 1.1.11 APK — an pointed at content://media/external/file/nope (MediaProvider throws IllegalArgumentException 'Unknown URL', the exact frames in the vitals stack) killed the process; with this change the same request fires the image's onerror and the process survives. Local attachments still render. --- .../main/java/md/zennotes/MainActivity.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/android/app/src/main/java/md/zennotes/MainActivity.java b/android/app/src/main/java/md/zennotes/MainActivity.java index 802bc0a..401c006 100644 --- a/android/app/src/main/java/md/zennotes/MainActivity.java +++ b/android/app/src/main/java/md/zennotes/MainActivity.java @@ -10,7 +10,15 @@ import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsControllerCompat; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; + import com.getcapacitor.BridgeActivity; +import com.getcapacitor.BridgeWebViewClient; + +import java.io.ByteArrayInputStream; +import java.util.Collections; public class MainActivity extends BridgeActivity { @@ -26,6 +34,7 @@ public void onCreate(Bundle savedInstanceState) { // WebView drains the inbox after the vault opens (importPendingShares). ShareInboxPlugin.stashFromIntent(this, getIntent()); neutralizeDoubleKeyboardInset(); + installCrashProofWebViewClient(); // Fullscreen writing (#22): while the JS shell hides the status bar // via the StatusBar plugin, a swipe from the top edge should peek it // transiently instead of bringing it back for good. The behavior is @@ -80,6 +89,32 @@ private void neutralizeDoubleKeyboardInset() { WindowCompat.setDecorFitsSystemWindows(getWindow(), false); } + /** + * Android vitals, 1.1.11 (2 events, 1 user): the process died in + * Capacitor's WebViewLocalServer while it served a content:// asset — + * the WebView asked for an attachment in a folder (SAF) vault, the + * document provider threw IllegalArgumentException for the document + * (removed, renamed, or its permission gone), and nothing between + * ContentResolver.openInputStream and Chromium's request interceptor + * catches it, so an unreadable image was a fatal crash instead of a + * broken image. Wrap the interceptor: any runtime failure while + * resolving a request answers 404 and the app carries on. + */ + private void installCrashProofWebViewClient() { + getBridge().setWebViewClient(new BridgeWebViewClient(getBridge()) { + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + try { + return super.shouldInterceptRequest(view, request); + } catch (RuntimeException e) { + return new WebResourceResponse( + "text/plain", "utf-8", 404, "Not Found", + Collections.emptyMap(), new ByteArrayInputStream(new byte[0])); + } + } + }); + } + private int webViewMajorVersion() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { return 0;