feat: add system navigation button actions
This commit is contained in:
@@ -1,5 +1,50 @@
|
|||||||
// @version 1.0.1
|
// @version 1.0.1
|
||||||
// =======================【WM 线程:按钮动作执行】======================
|
// =======================【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) {
|
FloatBallAppWM.prototype.execButtonAction = function(btn, idx) {
|
||||||
// # 点击防抖
|
// # 点击防抖
|
||||||
// 这段代码的主要内容/用途:防止在按钮面板上连续/乱点导致重复执行与 UI 状态机冲突(可能触发 system_server 异常重启)。
|
// 这段代码的主要内容/用途:防止在按钮面板上连续/乱点导致重复执行与 UI 状态机冲突(可能触发 system_server 异常重启)。
|
||||||
@@ -52,6 +97,21 @@ FloatBallAppWM.prototype.execButtonAction = function(btn, idx) {
|
|||||||
return;
|
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") {
|
if (t === "app") {
|
||||||
var pkg = btn.pkg ? String(btn.pkg) : "";
|
var pkg = btn.pkg ? String(btn.pkg) : "";
|
||||||
if (!pkg) { this.toast("按钮#" + idx + " 缺少 pkg"); return; }
|
if (!pkg) { this.toast("按钮#" + idx + " 缺少 pkg"); return; }
|
||||||
|
|||||||
@@ -506,6 +506,13 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() {
|
|||||||
if(btnCfg.type === "app") desc = "应用: " + (btnCfg.pkg||"");
|
if(btnCfg.type === "app") desc = "应用: " + (btnCfg.pkg||"");
|
||||||
else if(btnCfg.type === "broadcast") desc = "广播: " + (btnCfg.action||"");
|
else if(btnCfg.type === "broadcast") desc = "广播: " + (btnCfg.action||"");
|
||||||
else if(btnCfg.type === "shortcut") desc = "快捷方式";
|
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) + "...";
|
else desc = "命令: " + (btnCfg.cmd || "").substring(0, 20) + "...";
|
||||||
detailTv.setText(desc);
|
detailTv.setText(desc);
|
||||||
detailTv.setTextColor(subTextColor);
|
detailTv.setTextColor(subTextColor);
|
||||||
@@ -1985,7 +1992,8 @@ FloatBallAppWM.prototype.buildButtonEditorPanelView = function() {
|
|||||||
{ id: 1, val: "shell", txt: "Shell" },
|
{ id: 1, val: "shell", txt: "Shell" },
|
||||||
{ id: 2, val: "app", txt: "App" },
|
{ id: 2, val: "app", txt: "App" },
|
||||||
{ id: 3, val: "broadcast", txt: "发送广播" },
|
{ 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);
|
bcWrap.addView(inputExtras.view);
|
||||||
dynamicContainer.addView(bcWrap);
|
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 ---
|
// --- Shortcut ---
|
||||||
// 新增:启动系统/应用快捷方式(Launcher Shortcuts)
|
// 新增:启动系统/应用快捷方式(Launcher Shortcuts)
|
||||||
// 字段说明:
|
// 字段说明:
|
||||||
@@ -3174,6 +3227,7 @@ shortcutWrap.addView(scBody);
|
|||||||
shellWrap.setVisibility(typeVal === "shell" ? android.view.View.VISIBLE : android.view.View.GONE);
|
shellWrap.setVisibility(typeVal === "shell" ? android.view.View.VISIBLE : android.view.View.GONE);
|
||||||
appWrap.setVisibility(typeVal === "app" ? 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);
|
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);
|
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.action; delete newBtn.extras; delete newBtn.extra;
|
||||||
delete newBtn.uri;
|
delete newBtn.uri;
|
||||||
delete newBtn.shortcutId;
|
delete newBtn.shortcutId;
|
||||||
|
delete newBtn.shortcutRunMode;
|
||||||
|
delete newBtn.navAction; delete newBtn.key;
|
||||||
delete newBtn.launchUserId;
|
delete newBtn.launchUserId;
|
||||||
|
|
||||||
var isValid = true;
|
var isValid = true;
|
||||||
@@ -3264,6 +3320,15 @@ try {
|
|||||||
try { newBtn.extras = JSON.parse(ex); inputExtras.setError(null); }
|
try { newBtn.extras = JSON.parse(ex); inputExtras.setError(null); }
|
||||||
catch(e) { inputExtras.setError("JSON 格式错误"); isValid=false; }
|
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") {
|
} else if (newBtn.type === "shortcut") {
|
||||||
var sp = inputScPkg.getValue();
|
var sp = inputScPkg.getValue();
|
||||||
var sid = inputScId.getValue();
|
var sid = inputScId.getValue();
|
||||||
@@ -3276,9 +3341,9 @@ try {
|
|||||||
try { newBtn.userId = scSelectedUserId; } catch(eSUID2) { newBtn.userId = 0; }
|
try { newBtn.userId = scSelectedUserId; } catch(eSUID2) { newBtn.userId = 0; }
|
||||||
// # 保存:快捷方式 JS 启动代码(自动生成/可手动编辑)
|
// # 保存:快捷方式 JS 启动代码(自动生成/可手动编辑)
|
||||||
try { if (inputScJsCode) newBtn.shortcutJsCode = String(inputScJsCode.getValue()); } catch(eSaveJs) { safeLog(null, 'e', "catch " + String(eSaveJs)); }
|
try { if (inputScJsCode) newBtn.shortcutJsCode = String(inputScJsCode.getValue()); } catch(eSaveJs) { safeLog(null, 'e', "catch " + String(eSaveJs)); }
|
||||||
}
|
|
||||||
// # 保存:快捷方式仅使用 JavaScript 执行(取消 Shell/兜底)
|
// # 保存:快捷方式仅使用 JavaScript 执行(取消 Shell/兜底)
|
||||||
newBtn.shortcutRunMode = "js";
|
newBtn.shortcutRunMode = "js";
|
||||||
|
}
|
||||||
if (!isValid) return;
|
if (!isValid) return;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,8 +42,8 @@
|
|||||||
"size": 1094
|
"size": 1094
|
||||||
},
|
},
|
||||||
"th_11_action.js": {
|
"th_11_action.js": {
|
||||||
"sha256": "a0142d26621f3d076bd1b749f2885af2c0806c9f206e362a3b3680a5d2312b31",
|
"sha256": "207d0544ef8f09828374869e16f54c730d5e986fe1267dcedb2132cc5c1e64f2",
|
||||||
"size": 13545
|
"size": 16095
|
||||||
},
|
},
|
||||||
"th_12_rebuild.js": {
|
"th_12_rebuild.js": {
|
||||||
"sha256": "7b820e813d2dd8866778fefe8bfeb6aca227bb1a32a89d318de830178f19824f",
|
"sha256": "7b820e813d2dd8866778fefe8bfeb6aca227bb1a32a89d318de830178f19824f",
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
"size": 20386
|
"size": 20386
|
||||||
},
|
},
|
||||||
"th_14_panels.js": {
|
"th_14_panels.js": {
|
||||||
"sha256": "a237b164e29f4a581893b86bd7d3f9776dc0ecc7a56ecc53f9465525fa117253",
|
"sha256": "800659ce981097c2aa41880572c2d960ca7b978fa70e1d83ad4bcec20f51dbf6",
|
||||||
"size": 217295
|
"size": 220822
|
||||||
},
|
},
|
||||||
"th_15_extra.js": {
|
"th_15_extra.js": {
|
||||||
"sha256": "c8e10fe54a965fe7b27f0996b2f2636655077e39df774d6cbb1e73c5b36553fc",
|
"sha256": "c8e10fe54a965fe7b27f0996b2f2636655077e39df774d6cbb1e73c5b36553fc",
|
||||||
@@ -68,5 +68,5 @@
|
|||||||
},
|
},
|
||||||
"keyId": "toolhub-targets-2026-rsa3072",
|
"keyId": "toolhub-targets-2026-rsa3072",
|
||||||
"schema": 2,
|
"schema": 2,
|
||||||
"version": 20260512020008
|
"version": 20260512021612
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user