From 5c95d04fab0aeaed5c7c211de690a6ddfa447832 Mon Sep 17 00:00:00 2001 From: 7015725 Date: Tue, 12 May 2026 10:16:31 +0800 Subject: [PATCH] feat: add system navigation button actions --- code/th_11_action.js | 60 +++++++++++++++++++++++++++++++++++++ code/th_14_panels.js | 71 ++++++++++++++++++++++++++++++++++++++++++-- manifest.json | 10 +++---- manifest.sig | 2 +- 4 files changed, 134 insertions(+), 9 deletions(-) diff --git a/code/th_11_action.js b/code/th_11_action.js index 7d5eb4b..58ae8b5 100644 --- a/code/th_11_action.js +++ b/code/th_11_action.js @@ -1,5 +1,50 @@ // @version 1.0.1 // =======================【WM 线程:按钮动作执行】====================== +FloatBallAppWM.prototype.execSystemNavigation = function(navAction) { + var ret = { ok: false, method: "", action: String(navAction || ""), err: "" }; + var a = ret.action; + var keyCode = 0; + if (a === "back") keyCode = android.view.KeyEvent.KEYCODE_BACK; + else if (a === "home") keyCode = android.view.KeyEvent.KEYCODE_HOME; + else if (a === "recents") keyCode = android.view.KeyEvent.KEYCODE_APP_SWITCH; + else { ret.err = "unknown nav action: " + a; return ret; } + + try { + var now = android.os.SystemClock.uptimeMillis(); + var KeyEvent = android.view.KeyEvent; + var InputDevice = android.view.InputDevice; + var InputManager = android.hardware.input.InputManager; + var down = new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0, KeyEvent.KEYCODE_UNKNOWN, 0, KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD); + var up = new KeyEvent(now, android.os.SystemClock.uptimeMillis(), KeyEvent.ACTION_UP, keyCode, 0, 0, KeyEvent.KEYCODE_UNKNOWN, 0, KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD); + var im = InputManager.getInstance(); + var mode = InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH; + var ok1 = im.injectInputEvent(down, mode); + var ok2 = im.injectInputEvent(up, mode); + ret.ok = !!(ok1 && ok2); + ret.method = "InputManager.keyevent"; + if (!ret.ok) ret.err = "injectInputEvent returned false"; + return ret; + } catch (eKey) { + ret.err = String(eKey); + } + + try { + var cmd = "input keyevent " + String(keyCode); + var b64 = encodeBase64Utf8(cmd); + var r = this.execShellSmart(b64, true); + if (r && r.ok) { + ret.ok = true; + ret.method = "BroadcastBridge.input_keyevent"; + ret.err = ""; + return ret; + } + ret.err = ret.err + "; shell fallback failed: " + JSON.stringify(r || {}); + } catch (eShell) { + ret.err = ret.err + "; shell fallback exception: " + String(eShell); + } + return ret; +}; + FloatBallAppWM.prototype.execButtonAction = function(btn, idx) { // # 点击防抖 // 这段代码的主要内容/用途:防止在按钮面板上连续/乱点导致重复执行与 UI 状态机冲突(可能触发 system_server 异常重启)。 @@ -52,6 +97,21 @@ FloatBallAppWM.prototype.execButtonAction = function(btn, idx) { return; } + if (t === "nav") { + var navAction = btn.navAction ? String(btn.navAction) : ""; + if (!navAction && btn.key) navAction = String(btn.key); + if (!navAction) { this.toast("按钮#" + idx + " 缺少导航动作"); return; } + try { this.hideAllPanels(); } catch (eHideNav) {} + var rn = this.execSystemNavigation(navAction); + if (rn && rn.ok) { + safeLog(this.L, 'i', "nav ok action=" + navAction + " via=" + String(rn.method || "")); + return; + } + this.toast("导航键执行失败: " + navAction); + safeLog(this.L, 'e', "nav fail action=" + navAction + " ret=" + JSON.stringify(rn || {})); + return; + } + if (t === "app") { var pkg = btn.pkg ? String(btn.pkg) : ""; if (!pkg) { this.toast("按钮#" + idx + " 缺少 pkg"); return; } diff --git a/code/th_14_panels.js b/code/th_14_panels.js index d6b9f2c..1cd4f58 100644 --- a/code/th_14_panels.js +++ b/code/th_14_panels.js @@ -506,6 +506,13 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { if(btnCfg.type === "app") desc = "应用: " + (btnCfg.pkg||""); else if(btnCfg.type === "broadcast") desc = "广播: " + (btnCfg.action||""); else if(btnCfg.type === "shortcut") desc = "快捷方式"; + else if(btnCfg.type === "nav") { + var na = String(btnCfg.navAction || btnCfg.key || ""); + if (na === "back") desc = "系统导航: 返回"; + else if (na === "home") desc = "系统导航: 主页"; + else if (na === "recents") desc = "系统导航: 最近任务"; + else desc = "系统导航"; + } else desc = "命令: " + (btnCfg.cmd || "").substring(0, 20) + "..."; detailTv.setText(desc); detailTv.setTextColor(subTextColor); @@ -1985,7 +1992,8 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() { { id: 1, val: "shell", txt: "Shell" }, { id: 2, val: "app", txt: "App" }, { id: 3, val: "broadcast", txt: "发送广播" }, - { id: 4, val: "shortcut", txt: "快捷方式" } + { id: 4, val: "shortcut", txt: "快捷方式" }, + { id: 5, val: "nav", txt: "系统导航" } ]; // 初始化选中值 @@ -2167,6 +2175,51 @@ appWrap.addView(inputAppLaunchUser.view); bcWrap.addView(inputExtras.view); dynamicContainer.addView(bcWrap); + // --- System Navigation --- + // 这段代码的主要内容/用途:为全面屏手势场景提供虚拟返回/主页/最近任务按钮,不依赖屏幕底部导航栏。 + var navWrap = new android.widget.LinearLayout(context); + navWrap.setOrientation(android.widget.LinearLayout.VERTICAL); + navWrap.setPadding(0, self.dp(4), 0, self.dp(8)); + var navLbl = new android.widget.TextView(context); + navLbl.setText("导航键功能"); + navLbl.setTextColor(subTextColor); + navLbl.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + navWrap.addView(navLbl); + var navGroup = new android.widget.RadioGroup(context); + navGroup.setOrientation(android.widget.RadioGroup.VERTICAL); + var selectedNavAction = targetBtn.navAction ? String(targetBtn.navAction) : (targetBtn.key ? String(targetBtn.key) : "back"); + var navItems = [ + { id: 101, val: "back", txt: "返回键(Back)" }, + { id: 102, val: "home", txt: "主页键(Home)" }, + { id: 103, val: "recents", txt: "最近任务键(Recents)" } + ]; + for (var ni = 0; ni < navItems.length; ni++) { + var nr = new android.widget.RadioButton(context); + nr.setId(navItems[ni].id); + nr.setText(navItems[ni].txt); + nr.setTextColor(textColor); + nr.setTag(navItems[ni].val); + nr.setPadding(self.dp(8), self.dp(4), self.dp(8), self.dp(4)); + navGroup.addView(nr); + if (navItems[ni].val === selectedNavAction) navGroup.check(navItems[ni].id); + } + navGroup.setOnCheckedChangeListener(new android.widget.RadioGroup.OnCheckedChangeListener({ + onCheckedChanged: function(group, checkedId) { + try { + var rb = group.findViewById(checkedId); + if (rb) selectedNavAction = String(rb.getTag()); + } catch(eNavChg) { safeLog(null, 'e', "catch " + String(eNavChg)); } + } + })); + navWrap.addView(navGroup); + var navHint = new android.widget.TextView(context); + navHint.setText("优先用 InputManager 注入按键;失败时自动回退到 Shell 广播桥 input keyevent。"); + navHint.setTextColor(subTextColor); + navHint.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 11); + navHint.setPadding(self.dp(8), self.dp(4), self.dp(8), 0); + navWrap.addView(navHint); + dynamicContainer.addView(navWrap); + // --- Shortcut --- // 新增:启动系统/应用快捷方式(Launcher Shortcuts) // 字段说明: @@ -3174,6 +3227,7 @@ shortcutWrap.addView(scBody); shellWrap.setVisibility(typeVal === "shell" ? android.view.View.VISIBLE : android.view.View.GONE); appWrap.setVisibility(typeVal === "app" ? android.view.View.VISIBLE : android.view.View.GONE); bcWrap.setVisibility(typeVal === "broadcast" ? android.view.View.VISIBLE : android.view.View.GONE); + navWrap.setVisibility(typeVal === "nav" ? android.view.View.VISIBLE : android.view.View.GONE); shortcutWrap.setVisibility(typeVal === "shortcut" ? android.view.View.VISIBLE : android.view.View.GONE); } @@ -3233,6 +3287,8 @@ shortcutWrap.addView(scBody); delete newBtn.action; delete newBtn.extras; delete newBtn.extra; delete newBtn.uri; delete newBtn.shortcutId; + delete newBtn.shortcutRunMode; + delete newBtn.navAction; delete newBtn.key; delete newBtn.launchUserId; var isValid = true; @@ -3264,6 +3320,15 @@ try { try { newBtn.extras = JSON.parse(ex); inputExtras.setError(null); } catch(e) { inputExtras.setError("JSON 格式错误"); isValid=false; } } + } else if (newBtn.type === "nav") { + var nv = selectedNavAction ? String(selectedNavAction) : "back"; + if (nv !== "back" && nv !== "home" && nv !== "recents") nv = "back"; + newBtn.navAction = nv; + if (!newBtn.title || String(newBtn.title).trim().length === 0) { + if (nv === "back") newBtn.title = "返回"; + else if (nv === "home") newBtn.title = "主页"; + else newBtn.title = "最近任务"; + } } else if (newBtn.type === "shortcut") { var sp = inputScPkg.getValue(); var sid = inputScId.getValue(); @@ -3276,10 +3341,10 @@ try { try { newBtn.userId = scSelectedUserId; } catch(eSUID2) { newBtn.userId = 0; } // # 保存:快捷方式 JS 启动代码(自动生成/可手动编辑) try { if (inputScJsCode) newBtn.shortcutJsCode = String(inputScJsCode.getValue()); } catch(eSaveJs) { safeLog(null, 'e', "catch " + String(eSaveJs)); } - } // # 保存:快捷方式仅使用 JavaScript 执行(取消 Shell/兜底) newBtn.shortcutRunMode = "js"; - if (!isValid) return; + } + if (!isValid) return; diff --git a/manifest.json b/manifest.json index 256ce3c..19d178b 100644 --- a/manifest.json +++ b/manifest.json @@ -42,8 +42,8 @@ "size": 1094 }, "th_11_action.js": { - "sha256": "a0142d26621f3d076bd1b749f2885af2c0806c9f206e362a3b3680a5d2312b31", - "size": 13545 + "sha256": "207d0544ef8f09828374869e16f54c730d5e986fe1267dcedb2132cc5c1e64f2", + "size": 16095 }, "th_12_rebuild.js": { "sha256": "7b820e813d2dd8866778fefe8bfeb6aca227bb1a32a89d318de830178f19824f", @@ -54,8 +54,8 @@ "size": 20386 }, "th_14_panels.js": { - "sha256": "a237b164e29f4a581893b86bd7d3f9776dc0ecc7a56ecc53f9465525fa117253", - "size": 217295 + "sha256": "800659ce981097c2aa41880572c2d960ca7b978fa70e1d83ad4bcec20f51dbf6", + "size": 220822 }, "th_15_extra.js": { "sha256": "c8e10fe54a965fe7b27f0996b2f2636655077e39df774d6cbb1e73c5b36553fc", @@ -68,5 +68,5 @@ }, "keyId": "toolhub-targets-2026-rsa3072", "schema": 2, - "version": 20260512020008 + "version": 20260512021612 } diff --git a/manifest.sig b/manifest.sig index a39360c..e3eff81 100644 --- a/manifest.sig +++ b/manifest.sig @@ -1 +1 @@ -axGRqLze6TvsjEgpjNjRGL0u2CHxBCSwWZ8AKQBZ2Q9Gr8rMajyTkNt6vRW0aHPWaJhM9Silyh/3VIoy4iz0Id2RltAwADV7XygLGL2jMGlHsjC2U1g8bavgL1JKK/UY0WvnQ1fUgI2bDqWA2eN/SQvP0/DbRwOeheCvcNrf5aHxMuEYyorIbbhFxsqGXb8Uut6ieKe2aUV5jSL1LOatt2QKgnOM5ylpRmf3M9w3g8f4SDjHKhCup6K7H52ooazoFmMP030suXVhn1vmujrZtQtcgmzMU6hI1ZSZOcPIekgWxzIlJ2ktXxR/b9TlQ4JcEtxG2D/jIx02M0mQXE9kfZZJ7I3g6h2FNc4YdJS51pXr7YvtGPevR6yxP+/J5e6fVcm+W5EW8eFHzrtyhBI8zvG7vJSCYBV+jwl/XNznwnx0whduQ7RqZMM4++xdzyew5wtIWnlVxI4wqX+B852cnSU/EdTPu2HoBt+TfqEO5p7oDok1pO01i5BWghkvujKt +fEqZf1T4Wfmv7K3rRtrfYbwKxGXlBqyyZ2aTpf5JxE1IuJkKNnn12c2KLNBMlZ39BKrahGYjd+5vu/Ey7LbDrgmNmOtlr5Kc6+7dhuX5jkao97i0Gcyt/+4H5OIbhZlkESZZ0ErEsL5hriUyIZe701GKURJhV81oPV0MstlcvUk4T/0FnGQbU3mp6eT60CEXa0acV2rlNvfaFrcfZGYVJ8o9Bqtx2KnlqGR3So6BOxhx1UBrVNrXFw/IrItRvA3omAlDE+iVTeX3N6HXtKuttWUIFb3XbMzl5fM34cRml1AxdbfEUak7wDl3Z/805R5+y5EKMrzAOSLm8/nKk6dEgx4LmR09WQzRArqt0/Ws8XitfK8FDyHbVa02VfjMG9hFrE8VwVZ9B+fRyBnkUb5kqPUgzYRtKJbGtFcxNKLe6A5IFdOTyCk/qwu4R2A6jpuaa3uw2ss07V4nfHnlXiNbG4DZ/9j5whD0EfjB+dLqPlRNSy8FYIMvV8ihIju/C4Ps