fix: handle system gestures for ToolHub panels
This commit is contained in:
@@ -41,6 +41,7 @@ function FloatBallAppWM(logger) {
|
||||
|
||||
viewerPanel: null,
|
||||
viewerPanelLp: null,
|
||||
viewerPanelType: null,
|
||||
|
||||
mask: null,
|
||||
maskLp: null,
|
||||
|
||||
@@ -185,6 +185,7 @@ FloatBallAppWM.prototype.hideViewerPanel = function() {
|
||||
this.safeRemoveView(this.state.viewerPanel, "viewerPanel");
|
||||
this.state.viewerPanel = null;
|
||||
this.state.viewerPanelLp = null;
|
||||
this.state.viewerPanelType = null;
|
||||
this.state.addedViewer = false;
|
||||
|
||||
this.hideMask();
|
||||
@@ -193,6 +194,97 @@ FloatBallAppWM.prototype.hideViewerPanel = function() {
|
||||
this._clearHeavyCachesIfAllHidden("hideViewerPanel");
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.handlePanelBack = function(which, reason) {
|
||||
// 这段代码的主要内容/用途:适配全面屏系统返回手势/返回键,让 ToolHub 设置类 UI 能按“上一级 -> 关闭”退出。
|
||||
try {
|
||||
if (this.state.closing) return false;
|
||||
var w = which ? String(which) : "";
|
||||
if (!w && this.state.addedViewer) w = String(this.state.viewerPanelType || "viewer");
|
||||
if (!w && this.state.addedSettings) w = "settings";
|
||||
if (!w && this.state.addedPanel) w = "main";
|
||||
|
||||
if (this.state.addedViewer) {
|
||||
var vt = String(this.state.viewerPanelType || w || "viewer");
|
||||
if (vt === "btn_editor") {
|
||||
if (this.state.editingButtonIndex !== null && this.state.editingButtonIndex !== undefined) {
|
||||
this.state.editingButtonIndex = null;
|
||||
this.state.keepBtnEditorState = true;
|
||||
this.showPanelAvoidBall("btn_editor");
|
||||
return true;
|
||||
}
|
||||
this.hideViewerPanel();
|
||||
this.showPanelAvoidBall("settings");
|
||||
return true;
|
||||
}
|
||||
if (vt === "schema_editor") {
|
||||
if (this.state.editingSchemaIndex !== null && this.state.editingSchemaIndex !== undefined) {
|
||||
this.state.editingSchemaIndex = null;
|
||||
this.state.keepSchemaEditorState = true;
|
||||
this.showPanelAvoidBall("schema_editor");
|
||||
return true;
|
||||
}
|
||||
this.hideViewerPanel();
|
||||
this.showPanelAvoidBall("settings");
|
||||
return true;
|
||||
}
|
||||
this.hideViewerPanel();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.state.addedSettings) {
|
||||
this.state.previewMode = false;
|
||||
if (this.state.addedPanel) this.hideMainPanel();
|
||||
this.hideSettingsPanel();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.state.addedPanel) {
|
||||
this.hideMainPanel();
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
safeLog(this.L, 'e', "handlePanelBack fail reason=" + String(reason || "") + " err=" + String(e));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.handleSystemUiDismiss = function(reason) {
|
||||
// 这段代码的主要内容/用途:系统 Home/最近任务手势发生时关闭 ToolHub 面板,只保留悬浮球,避免 overlay 残留在桌面/多任务上。
|
||||
try {
|
||||
var r = String(reason || "");
|
||||
if (r === "homekey" || r === "recentapps" || r === "fs_gesture" || r === "gestureNav") {
|
||||
this.hideAllPanels();
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
safeLog(this.L, 'e', "handleSystemUiDismiss fail: " + String(e));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.attachPanelSystemKeyHandler = function(panel, which) {
|
||||
try {
|
||||
if (!panel) return;
|
||||
var self = this;
|
||||
panel.setFocusable(true);
|
||||
panel.setFocusableInTouchMode(true);
|
||||
panel.setOnKeyListener(new android.view.View.OnKeyListener({
|
||||
onKey: function(v, keyCode, event) {
|
||||
try {
|
||||
if (!event) return false;
|
||||
if (event.getAction() !== android.view.KeyEvent.ACTION_UP) return false;
|
||||
if (keyCode === android.view.KeyEvent.KEYCODE_BACK) return self.handlePanelBack(which, "back_key");
|
||||
if (keyCode === android.view.KeyEvent.KEYCODE_ESCAPE) return self.handlePanelBack(which, "escape_key");
|
||||
} catch (e) { safeLog(self.L, 'e', "panel key handler fail: " + String(e)); }
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
panel.post(new java.lang.Runnable({ run: function() { try { panel.requestFocus(); } catch(eFocus) {} } }));
|
||||
} catch (e) {
|
||||
safeLog(this.L, 'e', "attachPanelSystemKeyHandler fail which=" + String(which || "") + " err=" + String(e));
|
||||
}
|
||||
};
|
||||
|
||||
FloatBallAppWM.prototype.clearHeavyCaches = function(reason) {
|
||||
// 这段代码的主要内容/用途:在所有面板都关闭后,主动清理"图标/快捷方式"等重缓存,降低 system_server 常驻内存。
|
||||
// 说明:仅清理缓存引用,不强行 recycle Bitmap,避免误伤仍被使用的 Drawable。
|
||||
|
||||
@@ -1,50 +1,5 @@
|
||||
// @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 异常重启)。
|
||||
@@ -97,21 +52,6 @@ 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; }
|
||||
|
||||
@@ -506,13 +506,6 @@ 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);
|
||||
@@ -1992,8 +1985,7 @@ 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: 5, val: "nav", txt: "系统导航" }
|
||||
{ id: 4, val: "shortcut", txt: "快捷方式" }
|
||||
];
|
||||
|
||||
// 初始化选中值
|
||||
@@ -2175,51 +2167,6 @@ 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)
|
||||
// 字段说明:
|
||||
@@ -3227,7 +3174,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -3288,7 +3234,6 @@ shortcutWrap.addView(scBody);
|
||||
delete newBtn.uri;
|
||||
delete newBtn.shortcutId;
|
||||
delete newBtn.shortcutRunMode;
|
||||
delete newBtn.navAction; delete newBtn.key;
|
||||
delete newBtn.launchUserId;
|
||||
|
||||
var isValid = true;
|
||||
@@ -3320,15 +3265,6 @@ 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();
|
||||
|
||||
@@ -437,11 +437,15 @@ FloatBallAppWM.prototype.addPanel = function(panel, x, y, which) {
|
||||
lp.x = x;
|
||||
lp.y = y;
|
||||
|
||||
try { if (this.attachPanelSystemKeyHandler) this.attachPanelSystemKeyHandler(panel, which); } catch (eKeyAttach) { safeLog(this.L, 'e', "attach panel key fail which=" + String(which) + " err=" + String(eKeyAttach)); }
|
||||
|
||||
try { this.state.wm.addView(panel, lp); } catch (eAdd) { safeLog(this.L, 'e', "addPanel fail which=" + String(which) + " err=" + String(eAdd)); return; }
|
||||
|
||||
if (which === "main") { this.state.panel = panel; this.state.panelLp = lp; this.state.addedPanel = true; }
|
||||
else if (which === "settings") { this.state.settingsPanel = panel; this.state.settingsPanelLp = lp; this.state.addedSettings = true; }
|
||||
else { this.state.viewerPanel = panel; this.state.viewerPanelLp = lp; this.state.addedViewer = true; }
|
||||
else { this.state.viewerPanel = panel; this.state.viewerPanelLp = lp; this.state.viewerPanelType = which; this.state.addedViewer = true; }
|
||||
|
||||
try { panel.requestFocus(); } catch (eReqFocus) {}
|
||||
|
||||
try {
|
||||
if (this.config.ENABLE_ANIMATIONS) {
|
||||
|
||||
@@ -245,6 +245,17 @@ FloatBallAppWM.prototype.startAsync = function(entryProcInfo, closeRule) {
|
||||
);
|
||||
if (cfgRcv) this.state.receivers.push(cfgRcv);
|
||||
|
||||
var sysDlgRcv = registerReceiverOnMain("android.intent.action.CLOSE_SYSTEM_DIALOGS", function(ctx, intent) {
|
||||
try {
|
||||
var reason = "";
|
||||
try { reason = String(intent.getStringExtra("reason") || ""); } catch (eReason) { reason = ""; }
|
||||
h.post(new JavaAdapter(java.lang.Runnable, {
|
||||
run: function() { try { self.handleSystemUiDismiss(reason); } catch (eSysDlg) {} }
|
||||
}));
|
||||
} catch (eSysDlgOuter) {}
|
||||
});
|
||||
if (sysDlgRcv) this.state.receivers.push(sysDlgRcv);
|
||||
|
||||
var startBox = { ok: false, err: "启动确认超时", added: false };
|
||||
var startLatch = new java.util.concurrent.CountDownLatch(1);
|
||||
var posted = false;
|
||||
|
||||
Reference in New Issue
Block a user