From bb666e54bff8363fd64ce1c4a7ed14ca889ead77 Mon Sep 17 00:00:00 2001
From: mcocdaa <2021137961@qq.com>
Date: Wed, 14 Oct 2026 11:27:29 +0200
Subject: [PATCH] fix(android): wire roundIcon and keep.xml so AGP 9 R8
preserves launcher mipmaps
---
.../release/configure_android_system_bars.py | 44 +++++++++++++++++++
.../test_configure_android_system_bars.py | 33 +++++++++++++-
2 files changed, 76 insertions(+), 1 deletion(-)
diff --git a/scripts/release/configure_android_system_bars.py b/scripts/release/configure_android_system_bars.py
index ef4cea63..be98ddb6 100644
--- a/scripts/release/configure_android_system_bars.py
+++ b/scripts/release/configure_android_system_bars.py
@@ -79,6 +79,49 @@ def configure_activity(source: str) -> str:
)
+_MANIFEST_ICON = 'android:icon="@mipmap/ic_launcher"'
+_MANIFEST_ROUND_ICON = 'android:roundIcon="@mipmap/ic_launcher_round"'
+_KEEP_XML = (
+ '\n'
+ '\n'
+)
+
+
+def configure_manifest_round_icon(manifest_source: str) -> str:
+ """Ensure AndroidManifest.xml references @mipmap/ic_launcher_round so AGP 9 R8 keeps it."""
+
+ if _MANIFEST_ROUND_ICON in manifest_source:
+ return manifest_source
+ if _MANIFEST_ICON not in manifest_source:
+ raise ValueError("unsupported Tauri AndroidManifest template")
+ return manifest_source.replace(
+ _MANIFEST_ICON,
+ f"{_MANIFEST_ICON}\n {_MANIFEST_ROUND_ICON}",
+ 1,
+ )
+
+
+def preserve_launcher_resources(activity_path: Path) -> None:
+ """Wire android:roundIcon in AndroidManifest.xml and write res/raw/keep.xml."""
+
+ main_dir = next((p for p in activity_path.parents if p.name == "main"), None)
+ if main_dir is None:
+ return
+ manifest_path = main_dir / "AndroidManifest.xml"
+ if manifest_path.is_file():
+ original_manifest = manifest_path.read_text()
+ updated_manifest = configure_manifest_round_icon(original_manifest)
+ if updated_manifest != original_manifest:
+ manifest_path.write_text(updated_manifest)
+ res_dir = main_dir / "res"
+ if res_dir.is_dir() or manifest_path.is_file():
+ keep_path = res_dir / "raw" / "keep.xml"
+ keep_path.parent.mkdir(parents=True, exist_ok=True)
+ keep_path.write_text(_KEEP_XML)
+
+
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--activity", type=Path, required=True)
@@ -87,6 +130,7 @@ def main() -> int:
configured = configure_activity(original)
if configured != original:
args.activity.write_text(configured)
+ preserve_launcher_resources(args.activity)
print(f"configured Android system bars: {args.activity}")
return 0
diff --git a/tests/release/test_configure_android_system_bars.py b/tests/release/test_configure_android_system_bars.py
index 11e09aab..bb49617f 100644
--- a/tests/release/test_configure_android_system_bars.py
+++ b/tests/release/test_configure_android_system_bars.py
@@ -1,8 +1,14 @@
from __future__ import annotations
+from pathlib import Path
+
import pytest
-from scripts.release.configure_android_system_bars import configure_activity
+from scripts.release.configure_android_system_bars import (
+ configure_activity,
+ configure_manifest_round_icon,
+ preserve_launcher_resources,
+)
TAURI_ACTIVITY = """package io.termflow.client
@@ -31,6 +37,31 @@ def test_configures_orientation_aware_transient_status_bar_once() -> None:
assert configure_activity(configured) == configured
+def test_preserves_round_launcher_icon_in_manifest_and_keep_xml(tmp_path: Path) -> None:
+ main_dir = tmp_path / "src" / "main"
+ activity = main_dir / "java" / "io" / "termflow" / "client" / "MainActivity.kt"
+ activity.parent.mkdir(parents=True, exist_ok=True)
+ activity.write_text(TAURI_ACTIVITY)
+ manifest = main_dir / "AndroidManifest.xml"
+ manifest.write_text(
+ '\n'
+ ' \n'
+ ' \n'
+ '\n'
+ )
+
+ preserve_launcher_resources(activity)
+
+ updated = manifest.read_text()
+ assert 'android:roundIcon="@mipmap/ic_launcher_round"' in updated
+ assert configure_manifest_round_icon(updated) == updated
+ keep_xml = main_dir / "res" / "raw" / "keep.xml"
+ assert keep_xml.is_file()
+ assert "@mipmap/ic_launcher_round" in keep_xml.read_text()
+
+
@pytest.mark.parametrize(
"source",
[