diff --git a/apps/.i18n/native-source.json b/apps/.i18n/native-source.json
index 721a3c4bd80..b407225c1cf 100644
--- a/apps/.i18n/native-source.json
+++ b/apps/.i18n/native-source.json
@@ -1,6 +1,22 @@
{
"version": 1,
"entries": [
+ {
+ "kind": "conditional-branch",
+ "line": 89,
+ "path": "apps/android/app/src/main/java/ai/openclaw/app/AppLanguage.kt",
+ "source": "OpenClaw translations · $languageTag",
+ "surface": "android",
+ "id": "native.android.85cc8de945e3929c"
+ },
+ {
+ "kind": "conditional-branch",
+ "line": 90,
+ "path": "apps/android/app/src/main/java/ai/openclaw/app/AppLanguage.kt",
+ "source": "Follow Android · $systemLanguageTag",
+ "surface": "android",
+ "id": "native.android.914725056e04bd7a"
+ },
{
"kind": "conditional-branch",
"line": 217,
@@ -131,7 +147,7 @@
},
{
"kind": "ui-named-argument",
- "line": 247,
+ "line": 277,
"path": "apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt",
"source": "OPENCLAW",
"surface": "android",
@@ -139,7 +155,7 @@
},
{
"kind": "ui-state-text",
- "line": 143,
+ "line": 155,
"path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt",
"source": "Searching…",
"surface": "android",
@@ -147,7 +163,7 @@
},
{
"kind": "ui-state-text",
- "line": 243,
+ "line": 255,
"path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt",
"source": "Mic off",
"surface": "android",
@@ -155,7 +171,7 @@
},
{
"kind": "ui-state-text",
- "line": 253,
+ "line": 265,
"path": "apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt",
"source": "Off",
"surface": "android",
@@ -2571,7 +2587,7 @@
},
{
"kind": "ui-named-argument",
- "line": 222,
+ "line": 228,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Provider limits and quota health.",
"surface": "android",
@@ -2579,7 +2595,7 @@
},
{
"kind": "ui-named-argument",
- "line": 222,
+ "line": 228,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Usage",
"surface": "android",
@@ -2587,7 +2603,7 @@
},
{
"kind": "ui-named-argument",
- "line": 242,
+ "line": 248,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connect the gateway to load usage.",
"surface": "android",
@@ -2595,7 +2611,7 @@
},
{
"kind": "ui-named-argument",
- "line": 247,
+ "line": 253,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No usage data yet.",
"surface": "android",
@@ -2603,7 +2619,7 @@
},
{
"kind": "ui-named-argument",
- "line": 248,
+ "line": 254,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Provider limits will appear here when your gateway reports them.",
"surface": "android",
@@ -2611,7 +2627,7 @@
},
{
"kind": "ui-named-argument",
- "line": 283,
+ "line": 289,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Cron Jobs",
"surface": "android",
@@ -2619,7 +2635,7 @@
},
{
"kind": "ui-named-argument",
- "line": 283,
+ "line": 289,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Scheduled OpenClaw work from your gateway.",
"surface": "android",
@@ -2627,7 +2643,7 @@
},
{
"kind": "ui-named-argument",
- "line": 294,
+ "line": 300,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Android shows scheduled work status. Create and edit schedules from the desktop app.",
"surface": "android",
@@ -2635,7 +2651,7 @@
},
{
"kind": "ui-named-argument",
- "line": 304,
+ "line": 310,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connect the gateway to load cron jobs.",
"surface": "android",
@@ -2643,7 +2659,7 @@
},
{
"kind": "ui-named-argument",
- "line": 309,
+ "line": 315,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No scheduled jobs.",
"surface": "android",
@@ -2651,7 +2667,7 @@
},
{
"kind": "ui-named-argument",
- "line": 310,
+ "line": 316,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Create recurring OpenClaw work from the desktop app.",
"surface": "android",
@@ -2659,7 +2675,7 @@
},
{
"kind": "ui-named-argument",
- "line": 345,
+ "line": 351,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Inspect scheduled gateway work.",
"surface": "android",
@@ -2667,7 +2683,7 @@
},
{
"kind": "ui-named-argument",
- "line": 359,
+ "line": 365,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connect the gateway to inspect cron jobs.",
"surface": "android",
@@ -2675,7 +2691,7 @@
},
{
"kind": "conditional-branch",
- "line": 367,
+ "line": 373,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Cron job not loaded.",
"surface": "android",
@@ -2683,7 +2699,7 @@
},
{
"kind": "conditional-branch",
- "line": 367,
+ "line": 373,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Loading cron job…",
"surface": "android",
@@ -2691,7 +2707,7 @@
},
{
"kind": "ui-named-argument",
- "line": 389,
+ "line": 395,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Agents",
"surface": "android",
@@ -2699,7 +2715,7 @@
},
{
"kind": "ui-named-argument",
- "line": 389,
+ "line": 395,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Choose and inspect the assistants available on this gateway.",
"surface": "android",
@@ -2707,7 +2723,7 @@
},
{
"kind": "ui-named-argument",
- "line": 400,
+ "line": 406,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connect the gateway to load agents.",
"surface": "android",
@@ -2715,7 +2731,7 @@
},
{
"kind": "ui-named-argument",
- "line": 404,
+ "line": 410,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No agents loaded yet.",
"surface": "android",
@@ -2723,7 +2739,7 @@
},
{
"kind": "ui-named-argument",
- "line": 430,
+ "line": 436,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Approvals",
"surface": "android",
@@ -2731,7 +2747,7 @@
},
{
"kind": "ui-named-argument",
- "line": 430,
+ "line": 436,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Review actions that need your attention.",
"surface": "android",
@@ -2739,7 +2755,7 @@
},
{
"kind": "conditional-branch",
- "line": 441,
+ "line": 447,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Refresh",
"surface": "android",
@@ -2747,7 +2763,7 @@
},
{
"kind": "conditional-branch",
- "line": 441,
+ "line": 447,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Refreshing",
"surface": "android",
@@ -2755,7 +2771,7 @@
},
{
"kind": "ui-named-argument",
- "line": 454,
+ "line": 460,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Gateway disconnected.",
"surface": "android",
@@ -2763,7 +2779,7 @@
},
{
"kind": "ui-named-argument",
- "line": 455,
+ "line": 461,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connect the gateway to load approval requests in the app.",
"surface": "android",
@@ -2771,7 +2787,7 @@
},
{
"kind": "ui-named-argument",
- "line": 461,
+ "line": 467,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No gateway approvals.",
"surface": "android",
@@ -2779,7 +2795,7 @@
},
{
"kind": "ui-named-argument",
- "line": 462,
+ "line": 468,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Exec approval requests will appear here while this phone is connected.",
"surface": "android",
@@ -2787,7 +2803,7 @@
},
{
"kind": "ui-named-argument",
- "line": 469,
+ "line": 475,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Session activity",
"surface": "android",
@@ -2795,7 +2811,7 @@
},
{
"kind": "ui-named-argument",
- "line": 470,
+ "line": 476,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Chat tool calls waiting in the active session remain visible here.",
"surface": "android",
@@ -2803,7 +2819,7 @@
},
{
"kind": "ui-named-argument",
- "line": 484,
+ "line": 490,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "How this phone appears to OpenClaw.",
"surface": "android",
@@ -2811,7 +2827,7 @@
},
{
"kind": "ui-named-argument",
- "line": 484,
+ "line": 490,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Profile",
"surface": "android",
@@ -2819,7 +2835,7 @@
},
{
"kind": "ui-named-argument",
- "line": 487,
+ "line": 493,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Device name",
"surface": "android",
@@ -2827,7 +2843,7 @@
},
{
"kind": "ui-named-argument",
- "line": 488,
+ "line": 494,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Save Profile",
"surface": "android",
@@ -2835,7 +2851,7 @@
},
{
"kind": "ui-named-argument",
- "line": 507,
+ "line": 513,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Configure voice, transport, and playback.",
"surface": "android",
@@ -2843,7 +2859,7 @@
},
{
"kind": "ui-named-argument",
- "line": 507,
+ "line": 513,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Talk Provider Setup",
"surface": "android",
@@ -2851,7 +2867,7 @@
},
{
"kind": "ui-named-argument",
- "line": 510,
+ "line": 516,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Audio Test",
"surface": "android",
@@ -2859,7 +2875,7 @@
},
{
"kind": "ui-named-argument",
- "line": 511,
+ "line": 517,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Check that OpenClaw can speak clearly on this phone.",
"surface": "android",
@@ -2867,7 +2883,7 @@
},
{
"kind": "conditional-branch",
- "line": 514,
+ "line": 520,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Enable speaker",
"surface": "android",
@@ -2875,7 +2891,7 @@
},
{
"kind": "conditional-branch",
- "line": 514,
+ "line": 520,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Mute speaker",
"surface": "android",
@@ -2883,7 +2899,7 @@
},
{
"kind": "conditional-branch",
- "line": 515,
+ "line": 521,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Assistant speech muted",
"surface": "android",
@@ -2891,7 +2907,7 @@
},
{
"kind": "conditional-branch",
- "line": 515,
+ "line": 521,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Replies play aloud",
"surface": "android",
@@ -2899,7 +2915,7 @@
},
{
"kind": "conditional-branch",
- "line": 517,
+ "line": 523,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Muted",
"surface": "android",
@@ -2907,7 +2923,7 @@
},
{
"kind": "conditional-branch",
- "line": 517,
+ "line": 523,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "On",
"surface": "android",
@@ -2915,7 +2931,7 @@
},
{
"kind": "ui-named-argument",
- "line": 521,
+ "line": 527,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Done",
"surface": "android",
@@ -2923,7 +2939,7 @@
},
{
"kind": "ui-named-argument",
- "line": 531,
+ "line": 537,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Realtime Talk",
"surface": "android",
@@ -2931,7 +2947,7 @@
},
{
"kind": "ui-named-argument",
- "line": 532,
+ "line": 538,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Dictation",
"surface": "android",
@@ -2939,7 +2955,7 @@
},
{
"kind": "conditional-branch",
- "line": 660,
+ "line": 666,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Allowlist",
"surface": "android",
@@ -2947,7 +2963,7 @@
},
{
"kind": "conditional-branch",
- "line": 660,
+ "line": 666,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Blocklist",
"surface": "android",
@@ -2955,7 +2971,7 @@
},
{
"kind": "ui-named-argument",
- "line": 693,
+ "line": 699,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Choose what reaches OpenClaw.",
"surface": "android",
@@ -2963,7 +2979,7 @@
},
{
"kind": "ui-named-argument",
- "line": 693,
+ "line": 699,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Notifications",
"surface": "android",
@@ -2971,7 +2987,7 @@
},
{
"kind": "conditional-branch",
- "line": 697,
+ "line": 703,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Alerts stay on this phone.",
"surface": "android",
@@ -2979,7 +2995,7 @@
},
{
"kind": "conditional-branch",
- "line": 697,
+ "line": 703,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "OpenClaw can receive selected alerts.",
"surface": "android",
@@ -2987,7 +3003,7 @@
},
{
"kind": "conditional-branch",
- "line": 709,
+ "line": 715,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Granted",
"surface": "android",
@@ -2995,7 +3011,7 @@
},
{
"kind": "conditional-branch",
- "line": 709,
+ "line": 715,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Setup",
"surface": "android",
@@ -3003,7 +3019,7 @@
},
{
"kind": "conditional-branch",
- "line": 714,
+ "line": 720,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Check Access",
"surface": "android",
@@ -3011,7 +3027,7 @@
},
{
"kind": "conditional-branch",
- "line": 714,
+ "line": 720,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Open System Access",
"surface": "android",
@@ -3019,7 +3035,7 @@
},
{
"kind": "ui-named-argument",
- "line": 724,
+ "line": 730,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Forwarding Mode",
"surface": "android",
@@ -3027,7 +3043,7 @@
},
{
"kind": "ui-named-argument",
- "line": 773,
+ "line": 779,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "App Filter",
"surface": "android",
@@ -3035,7 +3051,7 @@
},
{
"kind": "conditional-branch",
- "line": 780,
+ "line": 786,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Close App Picker",
"surface": "android",
@@ -3043,7 +3059,7 @@
},
{
"kind": "conditional-branch",
- "line": 780,
+ "line": 786,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Open App Picker",
"surface": "android",
@@ -3051,7 +3067,7 @@
},
{
"kind": "ui-named-argument",
- "line": 785,
+ "line": 791,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Search apps",
"surface": "android",
@@ -3059,7 +3075,7 @@
},
{
"kind": "ui-named-argument",
- "line": 788,
+ "line": 794,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Show System Apps",
"surface": "android",
@@ -3067,7 +3083,7 @@
},
{
"kind": "ui-named-argument",
- "line": 789,
+ "line": 795,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Include Android and background packages.",
"surface": "android",
@@ -3075,7 +3091,7 @@
},
{
"kind": "ui-named-argument",
- "line": 796,
+ "line": 802,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No matching apps.",
"surface": "android",
@@ -3083,7 +3099,7 @@
},
{
"kind": "ui-named-argument",
- "line": 807,
+ "line": 813,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Showing ${visibleApps.size} of ${apps.size}. Refine search for more.",
"surface": "android",
@@ -3091,7 +3107,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1046,
+ "line": 1052,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Choose what this phone can share.",
"surface": "android",
@@ -3099,7 +3115,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1046,
+ "line": 1052,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Phone Capabilities",
"surface": "android",
@@ -3107,7 +3123,7 @@
},
{
"kind": "conditional-branch",
- "line": 1055,
+ "line": 1061,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Allow photo library access.",
"surface": "android",
@@ -3115,7 +3131,7 @@
},
{
"kind": "conditional-branch",
- "line": 1055,
+ "line": 1061,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Selected or full photo access granted.",
"surface": "android",
@@ -3123,7 +3139,7 @@
},
{
"kind": "conditional-branch",
- "line": 1065,
+ "line": 1071,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "App list stays on this phone.",
"surface": "android",
@@ -3131,7 +3147,7 @@
},
{
"kind": "conditional-branch",
- "line": 1065,
+ "line": 1071,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "OpenClaw can list launcher-visible apps.",
"surface": "android",
@@ -3139,7 +3155,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1076,
+ "line": 1082,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Location",
"surface": "android",
@@ -3147,7 +3163,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1084,
+ "line": 1090,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Always allows requested location checks while OpenClaw is in the background; Android shows this in the persistent node notification.",
"surface": "android",
@@ -3155,7 +3171,7 @@
},
{
"kind": "ui-call",
- "line": 1109,
+ "line": 1115,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Allow background location?",
"surface": "android",
@@ -3163,7 +3179,7 @@
},
{
"kind": "ui-call",
- "line": 1111,
+ "line": 1117,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "OpenClaw only checks location when your paired Gateway requests it. ",
"surface": "android",
@@ -3171,7 +3187,7 @@
},
{
"kind": "ui-call",
- "line": 1124,
+ "line": 1130,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Open Settings",
"surface": "android",
@@ -3179,7 +3195,7 @@
},
{
"kind": "ui-call",
- "line": 1129,
+ "line": 1135,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Not Now",
"surface": "android",
@@ -3187,7 +3203,7 @@
},
{
"kind": "ui-call",
- "line": 1177,
+ "line": 1183,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Replace gateway setup?",
"surface": "android",
@@ -3195,7 +3211,7 @@
},
{
"kind": "ui-call",
- "line": 1192,
+ "line": 1198,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Replace setup",
"surface": "android",
@@ -3203,7 +3219,7 @@
},
{
"kind": "ui-call",
- "line": 1208,
+ "line": 1214,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Forget gateway?",
"surface": "android",
@@ -3211,7 +3227,7 @@
},
{
"kind": "ui-call",
- "line": 1221,
+ "line": 1227,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Cancel",
"surface": "android",
@@ -3219,7 +3235,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1227,
+ "line": 1233,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connection between this phone and OpenClaw.",
"surface": "android",
@@ -3227,7 +3243,7 @@
},
{
"kind": "conditional-branch",
- "line": 1231,
+ "line": 1237,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connected",
"surface": "android",
@@ -3235,7 +3251,7 @@
},
{
"kind": "conditional-branch",
- "line": 1231,
+ "line": 1237,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Offline",
"surface": "android",
@@ -3243,7 +3259,7 @@
},
{
"kind": "conditional-branch",
- "line": 1232,
+ "line": 1238,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Not paired",
"surface": "android",
@@ -3251,7 +3267,7 @@
},
{
"kind": "conditional-branch",
- "line": 1232,
+ "line": 1238,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Online",
"surface": "android",
@@ -3259,7 +3275,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1242,
+ "line": 1248,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Reconnect",
"surface": "android",
@@ -3267,7 +3283,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1243,
+ "line": 1249,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Disconnect",
"surface": "android",
@@ -3275,7 +3291,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1247,
+ "line": 1253,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Gateways",
"surface": "android",
@@ -3283,7 +3299,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1249,
+ "line": 1255,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No paired gateways.",
"surface": "android",
@@ -3291,7 +3307,7 @@
},
{
"kind": "ui-call",
- "line": 1269,
+ "line": 1275,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Forget",
"surface": "android",
@@ -3299,7 +3315,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1291,
+ "line": 1297,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Gateway setup",
"surface": "android",
@@ -3307,7 +3323,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1293,
+ "line": 1299,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Scan or paste a setup code to add another gateway.",
"surface": "android",
@@ -3315,7 +3331,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1300,
+ "line": 1306,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Add gateway",
"surface": "android",
@@ -3323,7 +3339,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1301,
+ "line": 1307,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Setup Code",
"surface": "android",
@@ -3331,7 +3347,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1305,
+ "line": 1311,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Android can scan or paste an existing setup code, but this gateway does not expose setup-code generation to the app yet. Generate the QR/code on the gateway host with openclaw qr, then scan it here or paste the setup code below.",
"surface": "android",
@@ -3339,7 +3355,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1314,
+ "line": 1320,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connection Setup",
"surface": "android",
@@ -3347,7 +3363,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1315,
+ "line": 1321,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Setup code",
"surface": "android",
@@ -3355,7 +3371,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1317,
+ "line": 1323,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Host",
"surface": "android",
@@ -3363,7 +3379,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1318,
+ "line": 1324,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Port",
"surface": "android",
@@ -3371,7 +3387,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1320,
+ "line": 1326,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Connection security",
"surface": "android",
@@ -3379,7 +3395,7 @@
},
{
"kind": "conditional-branch",
- "line": 1324,
+ "line": 1330,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Secure (TLS)",
"surface": "android",
@@ -3387,7 +3403,7 @@
},
{
"kind": "conditional-branch",
- "line": 1324,
+ "line": 1330,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Unencrypted",
"surface": "android",
@@ -3395,7 +3411,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1341,
+ "line": 1347,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Token",
"surface": "android",
@@ -3403,7 +3419,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1342,
+ "line": 1348,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Bootstrap",
"surface": "android",
@@ -3411,7 +3427,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1344,
+ "line": 1350,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Password",
"surface": "android",
@@ -3419,7 +3435,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1349,
+ "line": 1355,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Save & Connect",
"surface": "android",
@@ -3427,7 +3443,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1366,
+ "line": 1372,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Enter a valid setup code or gateway address.",
"surface": "android",
@@ -3435,15 +3451,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1389,
- "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
- "source": "A calm, high-contrast OpenClaw interface.",
- "surface": "android",
- "id": "native.android.6f19c75742af5126"
- },
- {
- "kind": "ui-named-argument",
- "line": 1389,
+ "line": 1398,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Appearance",
"surface": "android",
@@ -3451,15 +3459,47 @@
},
{
"kind": "ui-named-argument",
- "line": 1400,
+ "line": 1398,
+ "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
+ "source": "Theme and translated Android text.",
+ "surface": "android",
+ "id": "native.android.15d4461b5fbdc203"
+ },
+ {
+ "kind": "ui-named-argument",
+ "line": 1410,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Theme",
"surface": "android",
"id": "native.android.0e5de6796bc8d8f9"
},
+ {
+ "kind": "ui-named-argument",
+ "line": 1420,
+ "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
+ "source": "App language",
+ "surface": "android",
+ "id": "native.android.bd997c088145929f"
+ },
+ {
+ "kind": "ui-named-argument",
+ "line": 1422,
+ "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
+ "source": "Changes Android text that OpenClaw has translated. Screens with English-only copy stay unchanged.",
+ "surface": "android",
+ "id": "native.android.1fc742aceae3f137"
+ },
+ {
+ "kind": "ui-named-argument",
+ "line": 1459,
+ "path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
+ "source": "Selected",
+ "surface": "android",
+ "id": "native.android.3d640706b2b6b93a"
+ },
{
"kind": "conditional-branch",
- "line": 1445,
+ "line": 1505,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Ready",
"surface": "android",
@@ -3467,7 +3507,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1474,
+ "line": 1534,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "About",
"surface": "android",
@@ -3475,7 +3515,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1474,
+ "line": 1534,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "OpenClaw for Android.",
"surface": "android",
@@ -3483,7 +3523,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1487,
+ "line": 1547,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Gateway",
"surface": "android",
@@ -3491,7 +3531,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1489,
+ "line": 1549,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Runtime",
"surface": "android",
@@ -3499,7 +3539,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1492,
+ "line": 1552,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Update",
"surface": "android",
@@ -3507,7 +3547,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1503,
+ "line": 1563,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "© 2026 OpenClaw Foundation — MIT License.",
"surface": "android",
@@ -3515,7 +3555,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1520,
+ "line": 1580,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "OpenClaw logo",
"surface": "android",
@@ -3523,7 +3563,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1522,
+ "line": 1582,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "OpenClaw",
"surface": "android",
@@ -3531,7 +3571,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1523,
+ "line": 1583,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Personal AI on your devices",
"surface": "android",
@@ -3539,7 +3579,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1582,
+ "line": 1642,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Licenses",
"surface": "android",
@@ -3547,7 +3587,7 @@
},
{
"kind": "conditional-branch",
- "line": 1583,
+ "line": 1643,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "OpenClaw appreciates its partners in the open-source community.",
"surface": "android",
@@ -3555,7 +3595,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1592,
+ "line": 1652,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No license notices are packaged in this build.",
"surface": "android",
@@ -3563,7 +3603,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1618,
+ "line": 1678,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Open ${license.title}",
"surface": "android",
@@ -3571,7 +3611,7 @@
},
{
"kind": "conditional-branch",
- "line": 1653,
+ "line": 1713,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Check",
"surface": "android",
@@ -3579,7 +3619,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1686,
+ "line": 1746,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Back",
"surface": "android",
@@ -3587,7 +3627,7 @@
},
{
"kind": "conditional-branch",
- "line": 1760,
+ "line": 1820,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Sending",
"surface": "android",
@@ -3595,7 +3635,7 @@
},
{
"kind": "conditional-branch",
- "line": 1769,
+ "line": 1829,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Allow Once",
"surface": "android",
@@ -3603,7 +3643,7 @@
},
{
"kind": "conditional-branch",
- "line": 1769,
+ "line": 1829,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Allowing",
"surface": "android",
@@ -3611,7 +3651,7 @@
},
{
"kind": "conditional-branch",
- "line": 1777,
+ "line": 1837,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Always",
"surface": "android",
@@ -3619,7 +3659,7 @@
},
{
"kind": "conditional-branch",
- "line": 1777,
+ "line": 1837,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Saving",
"surface": "android",
@@ -3627,7 +3667,7 @@
},
{
"kind": "conditional-branch",
- "line": 1785,
+ "line": 1845,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Deny",
"surface": "android",
@@ -3635,7 +3675,7 @@
},
{
"kind": "conditional-branch",
- "line": 1785,
+ "line": 1845,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Denying",
"surface": "android",
@@ -3643,7 +3683,7 @@
},
{
"kind": "conditional-branch",
- "line": 1810,
+ "line": 1870,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Review",
"surface": "android",
@@ -3651,7 +3691,7 @@
},
{
"kind": "conditional-branch",
- "line": 1838,
+ "line": 1898,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Issue",
"surface": "android",
@@ -3659,7 +3699,7 @@
},
{
"kind": "conditional-branch",
- "line": 1868,
+ "line": 1928,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Enabled",
"surface": "android",
@@ -3667,7 +3707,7 @@
},
{
"kind": "conditional-branch",
- "line": 1882,
+ "line": 1942,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No",
"surface": "android",
@@ -3675,7 +3715,7 @@
},
{
"kind": "conditional-branch",
- "line": 1882,
+ "line": 1942,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Yes",
"surface": "android",
@@ -3683,7 +3723,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1900,
+ "line": 1960,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Last Error",
"surface": "android",
@@ -3691,7 +3731,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1903,
+ "line": 1963,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Delivery Error",
"surface": "android",
@@ -3699,7 +3739,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1942,
+ "line": 2002,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Tap to copy",
"surface": "android",
@@ -3707,7 +3747,7 @@
},
{
"kind": "ui-toast",
- "line": 1957,
+ "line": 2017,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "$title copied",
"surface": "android",
@@ -3715,7 +3755,7 @@
},
{
"kind": "conditional-branch",
- "line": 1995,
+ "line": 2055,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Default assistant",
"surface": "android",
@@ -3723,7 +3763,7 @@
},
{
"kind": "conditional-branch",
- "line": 1997,
+ "line": 2057,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Default",
"surface": "android",
@@ -3731,7 +3771,7 @@
},
{
"kind": "conditional-branch",
- "line": 2053,
+ "line": 2113,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Needs attention",
"surface": "android",
@@ -3739,7 +3779,7 @@
},
{
"kind": "conditional-branch",
- "line": 2056,
+ "line": 2116,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Waiting ${minutes}m",
"surface": "android",
@@ -3747,7 +3787,7 @@
},
{
"kind": "conditional-branch",
- "line": 2056,
+ "line": 2116,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Waiting for review",
"surface": "android",
@@ -3755,7 +3795,7 @@
},
{
"kind": "conditional-branch",
- "line": 2084,
+ "line": 2144,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "${job.scheduleLabel} · ${formatCronWake(job.nextRunAtMs)} · ${job.promptPreview}",
"surface": "android",
@@ -3763,7 +3803,7 @@
},
{
"kind": "conditional-branch",
- "line": 2091,
+ "line": 2151,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "No limits reported",
"surface": "android",
@@ -3771,7 +3811,7 @@
},
{
"kind": "conditional-branch",
- "line": 2122,
+ "line": 2182,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Off",
"surface": "android",
@@ -3779,7 +3819,7 @@
},
{
"kind": "conditional-branch",
- "line": 2143,
+ "line": 2203,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "System Event Text",
"surface": "android",
@@ -3787,7 +3827,7 @@
},
{
"kind": "conditional-branch",
- "line": 2144,
+ "line": 2204,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Agent Prompt",
"surface": "android",
@@ -3795,7 +3835,7 @@
},
{
"kind": "conditional-branch",
- "line": 2145,
+ "line": 2205,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Command",
"surface": "android",
@@ -3803,7 +3843,7 @@
},
{
"kind": "conditional-branch",
- "line": 2146,
+ "line": 2206,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt",
"source": "Payload Text",
"surface": "android",
@@ -3811,7 +3851,7 @@
},
{
"kind": "ui-named-argument",
- "line": 437,
+ "line": 438,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Overview",
"surface": "android",
@@ -3819,7 +3859,7 @@
},
{
"kind": "ui-named-argument",
- "line": 482,
+ "line": 483,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "No recent sessions",
"surface": "android",
@@ -3827,7 +3867,7 @@
},
{
"kind": "ui-named-argument",
- "line": 483,
+ "line": 484,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Start a chat and your active OpenClaw conversations will appear here.",
"surface": "android",
@@ -3835,7 +3875,7 @@
},
{
"kind": "ui-named-argument",
- "line": 484,
+ "line": 485,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Start Chat",
"surface": "android",
@@ -3843,7 +3883,7 @@
},
{
"kind": "ui-named-argument",
- "line": 530,
+ "line": 531,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "OpenClaw",
"surface": "android",
@@ -3851,7 +3891,7 @@
},
{
"kind": "ui-named-argument",
- "line": 538,
+ "line": 539,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Search",
"surface": "android",
@@ -3859,7 +3899,7 @@
},
{
"kind": "ui-named-argument",
- "line": 590,
+ "line": 591,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "ACTIVE AGENT",
"surface": "android",
@@ -3867,7 +3907,7 @@
},
{
"kind": "ui-named-argument",
- "line": 599,
+ "line": 600,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "View",
"surface": "android",
@@ -3875,7 +3915,7 @@
},
{
"kind": "conditional-branch",
- "line": 602,
+ "line": 603,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$pendingRunCount active",
"surface": "android",
@@ -3883,7 +3923,7 @@
},
{
"kind": "conditional-branch",
- "line": 602,
+ "line": 603,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Idle",
"surface": "android",
@@ -3891,7 +3931,7 @@
},
{
"kind": "ui-named-argument",
- "line": 602,
+ "line": 603,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Runs",
"surface": "android",
@@ -3899,7 +3939,7 @@
},
{
"kind": "conditional-branch",
- "line": 603,
+ "line": 604,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$sessionCount recent",
"surface": "android",
@@ -3907,7 +3947,7 @@
},
{
"kind": "ui-named-argument",
- "line": 603,
+ "line": 604,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Sessions",
"surface": "android",
@@ -3915,7 +3955,7 @@
},
{
"kind": "ui-named-argument",
- "line": 604,
+ "line": 605,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Cron",
"surface": "android",
@@ -3923,7 +3963,7 @@
},
{
"kind": "ui-named-argument",
- "line": 607,
+ "line": 608,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Chat",
"surface": "android",
@@ -3931,7 +3971,7 @@
},
{
"kind": "ui-named-argument",
- "line": 611,
+ "line": 612,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Reconnect gateway",
"surface": "android",
@@ -3939,7 +3979,7 @@
},
{
"kind": "ui-named-argument",
- "line": 759,
+ "line": 760,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Open ${card.title}",
"surface": "android",
@@ -3947,7 +3987,7 @@
},
{
"kind": "ui-named-argument",
- "line": 827,
+ "line": 828,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Talk",
"surface": "android",
@@ -3955,7 +3995,7 @@
},
{
"kind": "ui-named-argument",
- "line": 828,
+ "line": 829,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Open Talk",
"surface": "android",
@@ -3963,7 +4003,7 @@
},
{
"kind": "ui-named-argument",
- "line": 830,
+ "line": 831,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Talk settings",
"surface": "android",
@@ -3971,7 +4011,7 @@
},
{
"kind": "ui-named-argument",
- "line": 838,
+ "line": 839,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Recent Sessions",
"surface": "android",
@@ -3979,7 +4019,7 @@
},
{
"kind": "ui-named-argument",
- "line": 848,
+ "line": 849,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "View all",
"surface": "android",
@@ -3987,7 +4027,7 @@
},
{
"kind": "conditional-branch",
- "line": 987,
+ "line": 988,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$onlineNodes/$nodeCount",
"surface": "android",
@@ -3995,7 +4035,7 @@
},
{
"kind": "conditional-branch",
- "line": 987,
+ "line": 988,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "None",
"surface": "android",
@@ -4003,7 +4043,7 @@
},
{
"kind": "conditional-branch",
- "line": 1019,
+ "line": 1020,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Recent conversations",
"surface": "android",
@@ -4011,7 +4051,7 @@
},
{
"kind": "conditional-branch",
- "line": 1026,
+ "line": 1027,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Browse",
"surface": "android",
@@ -4019,7 +4059,7 @@
},
{
"kind": "conditional-branch",
- "line": 1026,
+ "line": 1027,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Offline",
"surface": "android",
@@ -4027,7 +4067,7 @@
},
{
"kind": "conditional-branch",
- "line": 1078,
+ "line": 1079,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Working · $pendingRunCount active ${pluralize(\"run\", pendingRunCount)}",
"surface": "android",
@@ -4035,7 +4075,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1199,
+ "line": 1200,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Needs attention",
"surface": "android",
@@ -4043,7 +4083,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1264,
+ "line": 1265,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Open ${row.title}",
"surface": "android",
@@ -4051,7 +4091,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1372,
+ "line": 1373,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Open session",
"surface": "android",
@@ -4059,7 +4099,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1486,
+ "line": 1488,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Back",
"surface": "android",
@@ -4067,7 +4107,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1489,
+ "line": 1491,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Settings",
"surface": "android",
@@ -4075,7 +4115,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1492,
+ "line": 1494,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Search settings",
"surface": "android",
@@ -4083,7 +4123,7 @@
},
{
"kind": "conditional-branch",
- "line": 1516,
+ "line": 1518,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$readyProviderCount ready",
"surface": "android",
@@ -4091,7 +4131,7 @@
},
{
"kind": "conditional-branch",
- "line": 1516,
+ "line": 1518,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Review readiness",
"surface": "android",
@@ -4099,7 +4139,7 @@
},
{
"kind": "conditional-branch",
- "line": 1534,
+ "line": 1536,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Speaker muted",
"surface": "android",
@@ -4107,7 +4147,7 @@
},
{
"kind": "conditional-branch",
- "line": 1534,
+ "line": 1536,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Speaker on",
"surface": "android",
@@ -4115,7 +4155,7 @@
},
{
"kind": "conditional-branch",
- "line": 1536,
+ "line": 1538,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Off",
"surface": "android",
@@ -4123,7 +4163,7 @@
},
{
"kind": "conditional-branch",
- "line": 1536,
+ "line": 1538,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Smart delivery",
"surface": "android",
@@ -4131,7 +4171,7 @@
},
{
"kind": "conditional-branch",
- "line": 1537,
+ "line": 1539,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Camera enabled",
"surface": "android",
@@ -4139,7 +4179,7 @@
},
{
"kind": "conditional-branch",
- "line": 1537,
+ "line": 1539,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Locked",
"surface": "android",
@@ -4147,7 +4187,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1579,
+ "line": 1586,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "OpenClaw ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
"surface": "android",
@@ -4155,7 +4195,7 @@
},
{
"kind": "conditional-branch",
- "line": 1582,
+ "line": 1589,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "All systems operational",
"surface": "android",
@@ -4163,7 +4203,7 @@
},
{
"kind": "conditional-branch",
- "line": 1582,
+ "line": 1589,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Gateway not connected",
"surface": "android",
@@ -4171,7 +4211,7 @@
},
{
"kind": "conditional-branch",
- "line": 1614,
+ "line": 1621,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "No provider usage",
"surface": "android",
@@ -4179,7 +4219,7 @@
},
{
"kind": "conditional-branch",
- "line": 1615,
+ "line": 1622,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "1 provider",
"surface": "android",
@@ -4187,7 +4227,7 @@
},
{
"kind": "conditional-branch",
- "line": 1616,
+ "line": 1623,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$count providers",
"surface": "android",
@@ -4195,7 +4235,7 @@
},
{
"kind": "conditional-branch",
- "line": 1622,
+ "line": 1629,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$ready/${skills.size} ready",
"surface": "android",
@@ -4203,7 +4243,7 @@
},
{
"kind": "conditional-branch",
- "line": 1622,
+ "line": 1629,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "No skills",
"surface": "android",
@@ -4211,7 +4251,7 @@
},
{
"kind": "conditional-branch",
- "line": 1636,
+ "line": 1643,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$pending pending",
"surface": "android",
@@ -4219,7 +4259,7 @@
},
{
"kind": "conditional-branch",
- "line": 1636,
+ "line": 1643,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "1 pending",
"surface": "android",
@@ -4227,7 +4267,7 @@
},
{
"kind": "conditional-branch",
- "line": 1641,
+ "line": 1648,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$held held",
"surface": "android",
@@ -4235,7 +4275,7 @@
},
{
"kind": "conditional-branch",
- "line": 1641,
+ "line": 1648,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "1 held",
"surface": "android",
@@ -4243,7 +4283,7 @@
},
{
"kind": "conditional-branch",
- "line": 1642,
+ "line": 1649,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "$applied applied",
"surface": "android",
@@ -4251,7 +4291,7 @@
},
{
"kind": "conditional-branch",
- "line": 1642,
+ "line": 1649,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "1 applied",
"surface": "android",
@@ -4259,7 +4299,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1823,
+ "line": 1830,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "OpenClaw mobile",
"surface": "android",
@@ -4267,7 +4307,7 @@
},
{
"kind": "ui-named-argument",
- "line": 1827,
+ "line": 1834,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Open profile",
"surface": "android",
@@ -4275,7 +4315,7 @@
},
{
"kind": "conditional-branch",
- "line": 1912,
+ "line": 1919,
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt",
"source": "Main session",
"surface": "android",
diff --git a/apps/android/README.md b/apps/android/README.md
index c5eaed54bab..067bf2d1122 100644
--- a/apps/android/README.md
+++ b/apps/android/README.md
@@ -19,6 +19,7 @@ OpenClaw Android is the officially released Google Play app. It connects to an O
- [x] Voice tab full functionality
- [x] Screen tab full functionality
- [x] Skill Workshop settings can filter proposals, inspect proposal content, and apply/reject/quarantine drafts through Gateway RPCs
+- [x] Per-app language selection for translated resources follows Android system settings and persistence
## Open in Android Studio
diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts
index 1863bb7f1c2..73be1f4ff13 100644
--- a/apps/android/app/build.gradle.kts
+++ b/apps/android/app/build.gradle.kts
@@ -124,11 +124,48 @@ android {
}
}
+ bundle {
+ language {
+ // The in-app picker can select a locale outside the device language list.
+ // Without a Play Core download path, every translated resource must stay installed.
+ enableSplit = false
+ }
+ }
+
buildFeatures {
compose = true
buildConfig = true
}
+ androidResources {
+ generateLocaleConfig = true
+ localeFilters +=
+ listOf(
+ "ar",
+ "de",
+ "en",
+ "es",
+ "fa",
+ "fr",
+ "hi",
+ "in",
+ "it",
+ "ja",
+ "ko",
+ "nl",
+ "pl",
+ "pt-rBR",
+ "ru",
+ "sv",
+ "th",
+ "tr",
+ "uk",
+ "vi",
+ "zh-rCN",
+ "zh-rTW",
+ )
+ }
+
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
@@ -200,6 +237,8 @@ dependencies {
androidTestImplementation(composeBom)
implementation(libs.androidx.core.ktx)
+ // AppCompat owns per-app locale persistence and Activity recreation on API 31-32.
+ implementation(libs.androidx.appcompat)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.webkit)
diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml
index 2aa7eeb0810..89a94ab0867 100644
--- a/apps/android/app/src/main/AndroidManifest.xml
+++ b/apps/android/app/src/main/AndroidManifest.xml
@@ -52,6 +52,14 @@
android:name=".NodeForegroundService"
android:exported="false"
android:foregroundServiceType="connectedDevice|microphone" />
+
+
+
language.languageTag?.equals(exactTag, ignoreCase = true) == true }
+ if (exactMatch != null) return exactMatch
+
+ return entries.firstOrNull { language ->
+ val supportedLocale = language.languageTag?.let(Locale::forLanguageTag) ?: return@firstOrNull false
+ LocaleListCompat.matchesLanguageAndScript(locale, supportedLocale)
+ }
+ }
+ }
+}
+
+internal fun appLanguageFromLocales(locales: LocaleListCompat): AppLanguage =
+ if (locales.isEmpty) {
+ AppLanguage.System
+ } else {
+ (0 until locales.size()).firstNotNullOfOrNull { index -> locales[index]?.let(AppLanguage::fromLocale) }
+ ?: AppLanguage.System
+ }
+
+internal fun currentAppLanguage(): AppLanguage = appLanguageFromLocales(AppCompatDelegate.getApplicationLocales())
+
+internal fun localesForAppLanguage(language: AppLanguage): LocaleListCompat = language.languageTag?.let(LocaleListCompat::forLanguageTags) ?: LocaleListCompat.getEmptyLocaleList()
+
+internal fun setAppLanguage(language: AppLanguage) {
+ val locales = localesForAppLanguage(language)
+ if (locales != AppCompatDelegate.getApplicationLocales()) {
+ AppCompatDelegate.setApplicationLocales(locales)
+ }
+}
+
+internal fun currentSystemLanguageTag(context: Context): String {
+ val systemLocales = LocaleManagerCompat.getSystemLocales(context)
+ val locale = systemLocales[0] ?: Resources.getSystem().configuration.locales[0]
+ return locale.toLanguageTag()
+}
+
+internal fun appLanguageRowSubtitle(
+ language: AppLanguage,
+ systemLanguageTag: String,
+): String {
+ val languageTag = language.languageTag
+ if (languageTag != null) return "OpenClaw translations · $languageTag"
+ return "Follow Android · $systemLanguageTag"
+}
diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt
index fd2a1aca187..67018220846 100644
--- a/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt
+++ b/apps/android/app/src/main/java/ai/openclaw/app/MainActivity.kt
@@ -5,9 +5,9 @@ import ai.openclaw.app.ui.RootScreen
import android.content.Intent
import android.os.Bundle
import android.view.WindowManager
-import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
@@ -38,7 +38,7 @@ import kotlinx.coroutines.withContext
/**
* Main Android activity that owns Compose UI attachment and runtime UI wiring.
*/
-class MainActivity : ComponentActivity() {
+class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
private lateinit var permissionRequester: PermissionRequester
private var initializedViewModel: MainViewModel? = null
@@ -103,7 +103,9 @@ class MainActivity : ComponentActivity() {
override fun onStop() {
foreground = false
- initializedViewModel?.setForeground(false)
+ if (shouldNotifyRuntimeBackgrounded(isChangingConfigurations)) {
+ initializedViewModel?.setForeground(false)
+ }
super.onStop()
}
@@ -123,6 +125,9 @@ class MainActivity : ComponentActivity() {
initializedViewModel = readyViewModel
readyViewModel.setForeground(foreground)
startViewModelCollectors(readyViewModel)
+ if (!readyViewModel.claimInitialIntentRouting()) {
+ pendingIntentRouter.discardInitialIntent()
+ }
pendingIntentRouter.activate { initialIntent ->
handleAssistantIntent(viewModel = readyViewModel, intent = initialIntent)
}
@@ -186,9 +191,13 @@ class MainActivity : ComponentActivity() {
internal class MainActivityPendingIntentRouter {
private var activated = false
private var pendingIntent: Intent? = null
+ private var pendingIntentIsInitial = false
fun setInitialIntent(intent: Intent?) {
- if (!activated) pendingIntent = intent
+ if (!activated) {
+ pendingIntent = intent
+ pendingIntentIsInitial = true
+ }
}
fun onNewIntent(
@@ -200,6 +209,13 @@ internal class MainActivityPendingIntentRouter {
return
}
pendingIntent = intent
+ pendingIntentIsInitial = false
+ }
+
+ fun discardInitialIntent() {
+ if (activated || !pendingIntentIsInitial) return
+ pendingIntent = null
+ pendingIntentIsInitial = false
}
fun activate(routeIntent: (Intent) -> Unit): Boolean {
@@ -207,10 +223,24 @@ internal class MainActivityPendingIntentRouter {
activated = true
pendingIntent?.let(routeIntent)
pendingIntent = null
+ pendingIntentIsInitial = false
return true
}
}
+/** Keeps launch intents one-shot across same-process Activity recreation, but not process death. */
+internal class MainActivityInitialIntentGate {
+ private var claimed = false
+
+ fun claim(): Boolean {
+ if (claimed) return false
+ claimed = true
+ return true
+ }
+}
+
+internal fun shouldNotifyRuntimeBackgrounded(isChangingConfigurations: Boolean): Boolean = !isChangingConfigurations
+
/** Preserves one-shot runtime UI startup while allowing screenshot fixtures to skip side effects. */
internal class MainActivityRuntimeUiStarter {
private var completed = false
diff --git a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt
index ff6ac8d2397..1effd9fe342 100644
--- a/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt
+++ b/apps/android/app/src/main/java/ai/openclaw/app/MainViewModel.kt
@@ -69,6 +69,7 @@ class MainViewModel(
@Volatile private var foreground = false
@Volatile private var runtimeStartupQueued = false
+ private val initialIntentGate = MainActivityInitialIntentGate()
private val _requestedHomeDestination = MutableStateFlow(null)
val requestedHomeDestination: StateFlow = _requestedHomeDestination
@@ -92,9 +93,20 @@ class MainViewModel(
return runtime
}
+ internal fun claimInitialIntentRouting(): Boolean = initialIntentGate.claim()
+
internal fun enterScreenshotFixtureMode(scene: AndroidScreenshotScene) {
check(BuildConfig.DEBUG) { "Android screenshot fixtures require a debug build" }
- check(runtimeRef.value == null) { "Screenshot fixture mode must be selected before runtime startup" }
+ runtimeRef.value?.let { runtime ->
+ // The ViewModel survives locale recreation; keep the fixture runtime instead of
+ // treating the restored Activity as a second fixture startup.
+ check(runtime.mode == NodeRuntimeMode.ScreenshotFixture) {
+ "Screenshot fixture mode must be selected before live runtime startup"
+ }
+ runtime.setForeground(foreground)
+ _requestedHomeDestination.value = scene.homeDestination
+ return
+ }
prefs.setOnboardingCompleted(true)
prefs.setAppearanceThemeMode(AppearanceThemeMode.Dark)
prefs.setDisplayName("Pixel")
@@ -302,6 +314,9 @@ class MainViewModel(
* Starts runtime on foreground entry only after onboarding has completed.
*/
fun setForeground(value: Boolean) {
+ // The ViewModel survives configuration recreation. Ignore the replacement
+ // Activity's duplicate true edge so it cannot restart gateway work.
+ if (foreground == value) return
foreground = value
if (
shouldStartRuntimeOnForeground(
diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt
index c035253d29c..b45971c2714 100644
--- a/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt
+++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt
@@ -1,6 +1,7 @@
package ai.openclaw.app.ui
import ai.openclaw.app.AndroidLicenseNotice
+import ai.openclaw.app.AppLanguage
import ai.openclaw.app.AppearanceThemeMode
import ai.openclaw.app.BuildConfig
import ai.openclaw.app.GatewayAgentSummary
@@ -17,7 +18,10 @@ import ai.openclaw.app.LocationMode
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.NotificationPackageFilterMode
import ai.openclaw.app.SensitiveFeatureConfig
+import ai.openclaw.app.appLanguageRowSubtitle
import ai.openclaw.app.chat.ChatPendingToolCall
+import ai.openclaw.app.currentAppLanguage
+import ai.openclaw.app.currentSystemLanguageTag
import ai.openclaw.app.gateway.GatewayRegistryEntryKind
import ai.openclaw.app.gatewayTalkSetupDescription
import ai.openclaw.app.gatewayTalkSetupStatusText
@@ -27,6 +31,7 @@ import ai.openclaw.app.loadAndroidLicenseNotices
import ai.openclaw.app.locationModeAfterBackgroundSettings
import ai.openclaw.app.node.DeviceNotificationListenerService
import ai.openclaw.app.photoReadPermissionsForRequest
+import ai.openclaw.app.setAppLanguage
import ai.openclaw.app.ui.design.ClawDetailRow
import ai.openclaw.app.ui.design.ClawIconBadge
import ai.openclaw.app.ui.design.ClawListItem
@@ -96,6 +101,7 @@ import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.GraphicEq
import androidx.compose.material.icons.filled.Image
import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Mic
@@ -1385,12 +1391,16 @@ private fun AppearanceSettingsScreen(
onBack: () -> Unit,
) {
val themeMode by viewModel.appearanceThemeMode.collectAsState()
+ val context = LocalContext.current
+ var appLanguage by remember { mutableStateOf(currentAppLanguage()) }
+ val systemLanguageTag = currentSystemLanguageTag(context)
- SettingsDetailFrame(title = "Appearance", subtitle = "A calm, high-contrast OpenClaw interface.", icon = Icons.Default.Palette, onBack = onBack) {
+ SettingsDetailFrame(title = "Appearance", subtitle = "Theme and translated Android text.", icon = Icons.Default.Palette, onBack = onBack) {
SettingsMetricPanel(
rows =
listOf(
SettingsMetric("Theme", appearanceThemeSummary(themeMode)),
+ SettingsMetric("Language", appLanguage.displayName),
SettingsMetric("Contrast", "High"),
SettingsMetric("Typography", "Readable"),
),
@@ -1405,9 +1415,59 @@ private fun AppearanceSettingsScreen(
)
}
}
+ ClawPanel {
+ Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
+ Text(text = "App language", style = ClawTheme.type.section, color = ClawTheme.colors.text)
+ Text(
+ text = "Changes Android text that OpenClaw has translated. Screens with English-only copy stay unchanged.",
+ style = ClawTheme.type.caption,
+ color = ClawTheme.colors.textMuted,
+ )
+ AppLanguage.entries.forEachIndexed { index, language ->
+ if (index > 0) HorizontalDivider(color = ClawTheme.colors.border)
+ AppLanguageRow(
+ language = language,
+ selected = language == appLanguage,
+ systemLanguageTag = systemLanguageTag,
+ onClick = {
+ appLanguage = language
+ setAppLanguage(language)
+ },
+ )
+ }
+ }
+ }
}
}
+@Composable
+private fun AppLanguageRow(
+ language: AppLanguage,
+ selected: Boolean,
+ systemLanguageTag: String,
+ onClick: () -> Unit,
+) {
+ ClawListItem(
+ title = language.displayName,
+ subtitle = appLanguageRowSubtitle(language = language, systemLanguageTag = systemLanguageTag),
+ leading = { ClawIconBadge(Icons.Default.Language) },
+ trailing =
+ if (selected) {
+ {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Selected",
+ modifier = Modifier.size(18.dp),
+ tint = ClawTheme.colors.primary,
+ )
+ }
+ } else {
+ null
+ },
+ onClick = onClick,
+ )
+}
+
internal fun appearanceThemeSummary(mode: AppearanceThemeMode): String = mode.displayLabel
internal fun appearanceThemeOptions(): List = AppearanceThemeMode.entries.map { it.displayLabel }
diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt
index cfc1b88ed90..88875624144 100644
--- a/apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt
+++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/ShellScreen.kt
@@ -15,6 +15,7 @@ import ai.openclaw.app.MainViewModel
import ai.openclaw.app.NodeRuntime
import ai.openclaw.app.R
import ai.openclaw.app.chat.ChatSessionEntry
+import ai.openclaw.app.currentAppLanguage
import ai.openclaw.app.ui.chat.ChatScreen
import ai.openclaw.app.ui.design.ClawBottomNav
import ai.openclaw.app.ui.design.ClawDesignTheme
@@ -1469,6 +1470,7 @@ private fun SettingsShellScreen(
SettingsDetailScreen(viewModel = viewModel, route = route, onBack = onBack)
return
}
+ val appLanguage = currentAppLanguage()
ClawScaffold(
contentPadding = PaddingValues(start = 16.dp, top = 10.dp, end = 16.dp, bottom = 4.dp),
@@ -1535,7 +1537,12 @@ private fun SettingsShellScreen(
SettingsRow("Canvas", "Screen surface", Icons.AutoMirrored.Filled.ScreenShare, status = isConnected, route = SettingsRoute.Canvas),
SettingsRow("Notifications", if (notificationForwardingEnabled) "Smart delivery" else "Off", Icons.Default.Notifications, route = SettingsRoute.Notifications),
SettingsRow("Phone Capabilities", if (cameraEnabled) "Camera enabled" else "Locked", Icons.Default.Lock, status = !cameraEnabled, route = SettingsRoute.PhoneCapabilities),
- SettingsRow("Appearance", appearanceThemeSummary(appearanceThemeMode), Icons.Default.Palette, route = SettingsRoute.Appearance),
+ SettingsRow(
+ "Appearance",
+ "${appearanceThemeSummary(appearanceThemeMode)} · ${appLanguage.displayName}",
+ Icons.Default.Palette,
+ route = SettingsRoute.Appearance,
+ ),
SettingsRow("About", "Version and update", Icons.Default.Storage, route = SettingsRoute.About),
SettingsRow("Health", "Diagnostics", Icons.Default.Settings, status = isConnected, route = SettingsRoute.Health),
)
diff --git a/apps/android/app/src/main/res/resources.properties b/apps/android/app/src/main/res/resources.properties
new file mode 100644
index 00000000000..92481bb0b1f
--- /dev/null
+++ b/apps/android/app/src/main/res/resources.properties
@@ -0,0 +1 @@
+unqualifiedResLocale=en
diff --git a/apps/android/app/src/test/java/ai/openclaw/app/AppLanguageTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/AppLanguageTest.kt
new file mode 100644
index 00000000000..ad6d4d39b11
--- /dev/null
+++ b/apps/android/app/src/test/java/ai/openclaw/app/AppLanguageTest.kt
@@ -0,0 +1,114 @@
+package ai.openclaw.app
+
+import androidx.core.os.LocaleListCompat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.annotation.Config
+import org.xmlpull.v1.XmlPullParser
+
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [34])
+class AppLanguageTest {
+ @Test
+ fun supportedLanguagesMatchPackagedTranslations() {
+ assertEquals(
+ setOf(
+ "ar",
+ "de",
+ "en",
+ "es",
+ "fa",
+ "fr",
+ "hi",
+ "id",
+ "it",
+ "ja",
+ "ko",
+ "nl",
+ "pl",
+ "pt-BR",
+ "ru",
+ "sv",
+ "th",
+ "tr",
+ "uk",
+ "vi",
+ "zh-CN",
+ "zh-TW",
+ ),
+ AppLanguage.entries.mapNotNull(AppLanguage::languageTag).toSet(),
+ )
+ }
+
+ @Test
+ fun everyLanguageRoundTripsThroughAndroidLocales() {
+ AppLanguage.entries.forEach { language ->
+ assertEquals(language, appLanguageFromLocales(localesForAppLanguage(language)))
+ }
+ }
+
+ @Test
+ fun systemUsesAnEmptyLocaleList() {
+ assertTrue(localesForAppLanguage(AppLanguage.System).isEmpty)
+ assertEquals(AppLanguage.System, appLanguageFromLocales(LocaleListCompat.getEmptyLocaleList()))
+ }
+
+ @Test
+ fun languageTagsNormalizeAtThePlatformBoundary() {
+ assertEquals(AppLanguage.Indonesian, AppLanguage.fromLanguageTag("in"))
+ assertEquals(AppLanguage.PortugueseBrazil, AppLanguage.fromLanguageTag("PT-br"))
+ assertEquals(AppLanguage.English, AppLanguage.fromLanguageTag("en-US"))
+ assertEquals(AppLanguage.German, AppLanguage.fromLanguageTag("de-DE"))
+ assertEquals(AppLanguage.ChineseTraditional, AppLanguage.fromLanguageTag("zh-Hant-HK"))
+ assertEquals(AppLanguage.System, AppLanguage.fromLanguageTag(null))
+ }
+
+ @Test
+ fun requestedLocaleListUsesTheFirstSupportedLanguage() {
+ assertEquals(
+ AppLanguage.French,
+ appLanguageFromLocales(LocaleListCompat.forLanguageTags("xx,fr-FR,de-DE")),
+ )
+ }
+
+ @Test
+ fun generatedLocaleConfigMatchesPickerLanguages() {
+ val parser = RuntimeEnvironment.getApplication().resources.getXml(R.xml._generated_res_locale_config)
+ val packagedTags = mutableSetOf()
+ var defaultLocaleTag: String? = null
+ while (parser.eventType != XmlPullParser.END_DOCUMENT) {
+ if (parser.eventType == XmlPullParser.START_TAG) {
+ when (parser.name) {
+ "locale-config" -> defaultLocaleTag = parser.getAttributeValue(androidNamespace, "defaultLocale")
+ "locale" -> packagedTags += AppLanguage.fromLanguageTag(parser.getAttributeValue(androidNamespace, "name")).languageTag
+ }
+ }
+ parser.next()
+ }
+
+ assertEquals("en", defaultLocaleTag)
+ assertEquals(AppLanguage.entries.mapNotNull(AppLanguage::languageTag).toSet(), packagedTags)
+ }
+
+ @Test
+ fun everyPickerOptionHasAUniqueLabel() {
+ val labels = AppLanguage.entries.map(AppLanguage::displayName)
+ assertFalse(labels.any(String::isBlank))
+ assertEquals(labels.size, labels.toSet().size)
+ }
+
+ @Test
+ fun systemSubtitleReportsTheActualSystemLocale() {
+ assertEquals("Follow Android · en-US", appLanguageRowSubtitle(AppLanguage.System, "en-US"))
+ assertEquals("OpenClaw translations · ja", appLanguageRowSubtitle(AppLanguage.Japanese, "en-US"))
+ }
+
+ private companion object {
+ const val androidNamespace = "http://schemas.android.com/apk/res/android"
+ }
+}
diff --git a/apps/android/app/src/test/java/ai/openclaw/app/MainActivityLifecycleTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/MainActivityLifecycleTest.kt
index a7174ad3234..6210fb40eea 100644
--- a/apps/android/app/src/test/java/ai/openclaw/app/MainActivityLifecycleTest.kt
+++ b/apps/android/app/src/test/java/ai/openclaw/app/MainActivityLifecycleTest.kt
@@ -41,6 +41,47 @@ class MainActivityLifecycleTest {
assertEquals(listOf(next), routed)
}
+ @Test
+ fun pendingIntentRouterDiscardsOnlyRecreatedInitialIntent() {
+ val router = MainActivityPendingIntentRouter()
+ val routed = mutableListOf()
+
+ router.setInitialIntent(Intent("recreated"))
+ router.discardInitialIntent()
+
+ assertTrue(router.activate(routed::add))
+ assertTrue(routed.isEmpty())
+ }
+
+ @Test
+ fun pendingIntentRouterKeepsNewIntentAcrossRecreationGate() {
+ val router = MainActivityPendingIntentRouter()
+ val routed = mutableListOf()
+ val replacement = Intent("replacement")
+
+ router.setInitialIntent(Intent("recreated"))
+ router.onNewIntent(replacement, routed::add)
+ router.discardInitialIntent()
+
+ assertTrue(router.activate(routed::add))
+ assertEquals(listOf(replacement), routed)
+ }
+
+ @Test
+ fun initialIntentGateDistinguishesRecreationFromProcessRestoration() {
+ val retainedGate = MainActivityInitialIntentGate()
+
+ assertTrue(retainedGate.claim())
+ assertFalse(retainedGate.claim())
+ assertTrue(MainActivityInitialIntentGate().claim())
+ }
+
+ @Test
+ fun runtimeStaysForegroundAcrossConfigurationRecreation() {
+ assertFalse(shouldNotifyRuntimeBackgrounded(isChangingConfigurations = true))
+ assertTrue(shouldNotifyRuntimeBackgrounded(isChangingConfigurations = false))
+ }
+
@Test
fun runtimeUiStarterWaitsForReadinessAndStartsOnce() {
val starter = MainActivityRuntimeUiStarter()
diff --git a/apps/android/gradle/libs.versions.toml b/apps/android/gradle/libs.versions.toml
index 3898e741d73..7fda068b369 100644
--- a/apps/android/gradle/libs.versions.toml
+++ b/apps/android/gradle/libs.versions.toml
@@ -1,6 +1,7 @@
[versions]
agp = "9.2.1"
androidx-activity = "1.13.0"
+androidx-appcompat = "1.7.1"
androidx-benchmark = "1.4.1"
androidx-camera = "1.6.0"
androidx-compose-bom = "2026.06.01"
@@ -30,6 +31,7 @@ serialization-json = "1.11.0"
[libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
+androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-benchmark-macro-junit4 = { module = "androidx.benchmark:benchmark-macro-junit4", version.ref = "androidx-benchmark" }
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" }
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" }