Add local release workflow and foldable home guard

This commit is contained in:
ajp_anton
2026-06-16 21:28:34 +00:00
parent 27a1367fc1
commit 7239c28284
4 changed files with 134 additions and 129 deletions
+2 -2
View File
@@ -56,8 +56,8 @@ android {
applicationId = "se.ajpanton.navbuttons"
minSdk = 35
targetSdk = 35
versionCode = 1777070659
versionName = "0.3"
versionCode = project.findProperty("nbVersionCode")?.toString()?.toIntOrNull() ?: 1777070659
versionName = project.findProperty("nbVersionName")?.toString() ?: "0.3"
}
buildTypes {
@@ -10,7 +10,6 @@ import android.content.pm.PackageManager
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Process
@@ -52,6 +51,8 @@ class NavButtons : XposedModule() {
private val systemUiHomeStockLongPressGuards = WeakHashMap<Any, Runnable>()
private val systemUiHomeStockPressCanceled = WeakHashMap<Any, Boolean>()
private val honeyspaceLongPressTriggered = WeakHashMap<Any, Boolean>()
private val honeyspaceHomeStockLongPressGuards = WeakHashMap<Any, Runnable>()
private val honeyspaceHomeStockPressCanceled = WeakHashMap<Any, Boolean>()
override fun onModuleLoaded(param: XposedModuleInterface.ModuleLoadedParam) {
if (!HiddenApiBypass.addHiddenApiExemptions("")) {
@@ -374,111 +375,6 @@ class NavButtons : XposedModule() {
return installed
}
private fun hookSystemUiHomeLongClickIfPresent(navigationBarClass: Class<*>?) {
if (navigationBarClass == null) {
log("SystemUI navigation bar class not found: $SYSTEMUI_NAVIGATION_BAR")
return
}
val onHomeLongClickMethod = findMethodIfExists(
navigationBarClass,
"onHomeLongClick",
View::class.java,
) ?: run {
log("SystemUI onHomeLongClick() method not found.")
return
}
deoptimizeMethodIfPresent(onHomeLongClickMethod)
hook(onHomeLongClickMethod).intercept { chain ->
val longPressAction = getButtonConfig(NavButtonId.HOME).longPressAction
if (longPressAction == NavButtonAction.STOCK) {
return@intercept chain.proceed()
}
traceHome("NavigationBar.onHomeLongClick", "long=${longPressAction.storageValue}")
val homeView = chain.args[0] as? View
if (homeView != null) {
updateContextFromView(homeView)
systemUiLongPressTriggered[homeView] = true
markSystemUiLongClicked(homeView)
abortSystemUiNavigationGesture(chain.thisObject, homeView)
}
performHomeLongPressAction(longPressAction)
true
}
}
private fun hookSystemUiAssistStartIfPresent(assistManagerClass: Class<*>?) {
if (assistManagerClass == null) {
log("SystemUI assist manager class not found: $SYSTEMUI_ASSIST_MANAGER")
return
}
val startAssistMethod = findMethodIfExists(
assistManagerClass,
"startAssist",
Bundle::class.java,
) ?: run {
log("SystemUI startAssist(Bundle) method not found.")
return
}
deoptimizeMethodIfPresent(startAssistMethod)
hook(startAssistMethod).intercept { chain ->
val args = chain.args[0] as? Bundle ?: return@intercept chain.proceed()
val invocationType = args.getInt(ASSIST_INVOCATION_TYPE_KEY, ASSIST_INVOCATION_TYPE_UNKNOWN)
traceHome("AssistManager.startAssist", "invocationType=$invocationType")
if (invocationType != ASSIST_INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS) {
return@intercept chain.proceed()
}
val longPressAction = getButtonConfig(NavButtonId.HOME).longPressAction
if (longPressAction == NavButtonAction.STOCK) {
return@intercept chain.proceed()
}
traceHome("AssistManager.startAssist", "blocked long=${longPressAction.storageValue}")
performHomeLongPressAction(longPressAction)
null
}
}
private fun hookSystemUiOverviewProxyStartAssistantIfPresent(overviewProxyServiceClass: Class<*>?) {
if (overviewProxyServiceClass == null) {
log("SystemUI overview proxy service class not found: $SYSTEMUI_OVERVIEW_PROXY_SERVICE")
return
}
val startAssistantMethod = findMethodIfExists(
overviewProxyServiceClass,
"startAssistant",
Bundle::class.java,
) ?: run {
log("SystemUI OverviewProxyService.startAssistant(Bundle) method not found.")
return
}
deoptimizeMethodIfPresent(startAssistantMethod)
hook(startAssistantMethod).intercept { chain ->
val args = chain.args[0] as? Bundle ?: return@intercept chain.proceed()
val invocationType = args.getInt(ASSIST_INVOCATION_TYPE_KEY, ASSIST_INVOCATION_TYPE_UNKNOWN)
traceHome("OverviewProxyService.startAssistant", "invocationType=$invocationType")
if (invocationType != ASSIST_INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS) {
return@intercept chain.proceed()
}
val longPressAction = getButtonConfig(NavButtonId.HOME).longPressAction
if (longPressAction == NavButtonAction.STOCK) {
return@intercept chain.proceed()
}
traceHome("OverviewProxyService.startAssistant", "blocked long=${longPressAction.storageValue}")
performHomeLongPressAction(longPressAction)
null
}
}
private fun hookHoneyspaceTaskbar(classLoader: ClassLoader?): Boolean {
val navButtonClass = findClassIfExists(HONEYSPACE_NAV_BUTTON_VIEW, classLoader) ?: run {
log("Launcher class not found: $HONEYSPACE_NAV_BUTTON_VIEW")
@@ -516,6 +412,8 @@ class NavButtons : XposedModule() {
when (motionEvent.actionMasked) {
MotionEvent.ACTION_DOWN -> {
honeyspaceLongPressTriggered[navButtonView] = false
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
honeyspaceHomeStockPressCanceled.remove(navButtonView)
if (buttonId == NavButtonId.HOME) {
val config = getButtonConfig(buttonId)
traceHome(
@@ -525,6 +423,7 @@ class NavButtons : XposedModule() {
}
val result = chain.proceed()
rescheduleHoneyspaceLongPress(navButtonView, runnableClass, buttonId)
scheduleHoneyspaceHomeStockLongPressGuard(navButtonView, buttonId)
result
}
@@ -537,14 +436,18 @@ class NavButtons : XposedModule() {
return@intercept proceedWithCanceledAction(motionEvent) {
chain.proceed()
}.also {
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
honeyspaceLongPressTriggered.remove(navButtonView)
honeyspaceHomeStockPressCanceled.remove(navButtonView)
}
}
val pressAction = getButtonConfig(buttonId).pressAction
val stockHomePressCanceled =
buttonId == NavButtonId.HOME && honeyspaceHomeStockPressCanceled[navButtonView] == true
val shouldOverridePress =
pressAction != NavButtonAction.STOCK &&
(navButtonView as? View)?.isPressed == true &&
((navButtonView as? View)?.isPressed == true || stockHomePressCanceled) &&
!longPressTriggered
if (shouldOverridePress) {
@@ -552,13 +455,21 @@ class NavButtons : XposedModule() {
}
val result = chain.proceed()
if (stockHomePressCanceled && pressAction == NavButtonAction.STOCK) {
dispatchHoneyspaceHomeTap(navButtonView)
}
if (shouldOverridePress) {
updateContextFromView(navButtonView)
performConfiguredAction(pressAction)
}
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
honeyspaceLongPressTriggered.remove(navButtonView)
honeyspaceHomeStockPressCanceled.remove(navButtonView)
if (buttonId == NavButtonId.HOME) {
traceHome("Honeyspace.onTouchEvent UP", "override=$shouldOverridePress")
traceHome(
"Honeyspace.onTouchEvent UP",
"override=$shouldOverridePress ${honeyspaceHomeState(navButtonView)}",
)
}
result
}
@@ -567,7 +478,9 @@ class NavButtons : XposedModule() {
if (buttonId == NavButtonId.HOME) {
traceHome("Honeyspace.onTouchEvent CANCEL", "clearing long-press state")
}
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
honeyspaceLongPressTriggered.remove(navButtonView)
honeyspaceHomeStockPressCanceled.remove(navButtonView)
chain.proceed()
}
@@ -601,6 +514,7 @@ class NavButtons : XposedModule() {
}
updateContextFromView(navButtonView)
markHoneyspaceLongClicked(navButtonView)
cancelHoneyspaceHomeStockLongPressGuard(navButtonView)
performConfiguredAction(longPressAction)
null
}
@@ -1037,6 +951,35 @@ class NavButtons : XposedModule() {
(keyButtonView as? View)?.removeCallbacks(guard)
}
private fun scheduleHoneyspaceHomeStockLongPressGuard(navButtonView: Any, buttonId: NavButtonId) {
if (buttonId != NavButtonId.HOME) {
return
}
if (getButtonConfig(buttonId).longPressAction == NavButtonAction.STOCK) {
return
}
val view = navButtonView as? View ?: return
val delayMs = (ViewConfiguration.getLongPressTimeout() - STOCK_LONG_PRESS_GUARD_EARLY_MS)
.coerceAtLeast(STOCK_LONG_PRESS_GUARD_MIN_DELAY_MS)
val guard = Runnable {
if (!view.isPressed || honeyspaceLongPressTriggered[navButtonView] == true) {
return@Runnable
}
traceHome("Honeyspace stock long-press guard", "canceling original Home key after ${delayMs}ms")
honeyspaceHomeStockPressCanceled[navButtonView] = true
sendHoneyspaceCanceledKeyUp(navButtonView)
}
honeyspaceHomeStockLongPressGuards[navButtonView] = guard
view.postDelayed(guard, delayMs.toLong())
}
private fun cancelHoneyspaceHomeStockLongPressGuard(navButtonView: Any) {
val guard = honeyspaceHomeStockLongPressGuards.remove(navButtonView) ?: return
(navButtonView as? View)?.removeCallbacks(guard)
}
private fun abortSystemUiKeyGesture(keyButtonView: Any) {
try {
callMethod(keyButtonView, "abortCurrentGesture")
@@ -1059,6 +1002,45 @@ class NavButtons : XposedModule() {
}
}
private fun sendHoneyspaceCanceledKeyUp(navButtonView: Any) {
if (callIntIntMethodIfExists(
navButtonView,
listOf("sendEvent", "sendKeyEvent"),
KeyEvent.ACTION_UP,
KeyEvent.FLAG_CANCELED or KeyEvent.FLAG_CANCELED_LONG_PRESS,
)
) {
return
}
try {
val inputManager = callStaticMethod(
findClass(INPUT_MANAGER_CLASS, null),
"getInstance",
) ?: run {
log("InputManager unavailable for Honeyspace Home cancel.")
return
}
val now = SystemClock.uptimeMillis()
injectKeyEvent(
inputManager,
KeyEvent(
now,
now,
KeyEvent.ACTION_UP,
KeyEvent.KEYCODE_HOME,
0,
0,
KeyEvent.KEYCODE_UNKNOWN,
0,
KeyEvent.FLAG_CANCELED or KeyEvent.FLAG_CANCELED_LONG_PRESS,
),
)
} catch (error: Throwable) {
log("Honeyspace Home cancel failed: ${error.javaClass.simpleName}")
}
}
private fun dispatchSystemUiHomeTap(keyButtonView: Any) {
traceHome("dispatchSystemUiHomeTap", "sending stock tap after guard cancel")
try {
@@ -1081,19 +1063,14 @@ class NavButtons : XposedModule() {
}
}
private fun abortSystemUiNavigationGesture(navigationBar: Any?, homeView: View) {
try {
callMethod(homeView, "abortCurrentGesture")
} catch (_: Throwable) {
// Ignore variants without this helper on the home button view.
}
val navigationBarView = getFieldValue(navigationBar, "mView")
try {
callMethod(navigationBarView ?: return, "abortCurrentGesture")
} catch (_: Throwable) {
// Ignore variants without this helper on the navigation bar view.
private fun dispatchHoneyspaceHomeTap(navButtonView: Any) {
traceHome("dispatchHoneyspaceHomeTap", "sending stock tap after guard cancel")
if (callIntIntMethodIfExists(navButtonView, listOf("sendEvent", "sendKeyEvent"), KeyEvent.ACTION_DOWN, 0) &&
callIntIntMethodIfExists(navButtonView, listOf("sendEvent", "sendKeyEvent"), KeyEvent.ACTION_UP, 0)
) {
return
}
sendKeyPress(KeyEvent.KEYCODE_HOME)
}
private fun rescheduleSystemUiLongPress(keyButtonView: Any, buttonId: NavButtonId) {
@@ -1199,6 +1176,12 @@ class NavButtons : XposedModule() {
"stockCanceled=${systemUiHomeStockPressCanceled[keyButtonView] == true}"
}
private fun honeyspaceHomeState(navButtonView: Any): String {
return "pressed=${(navButtonView as? View)?.isPressed == true} " +
"triggered=${honeyspaceLongPressTriggered[navButtonView] == true} " +
"stockCanceled=${honeyspaceHomeStockPressCanceled[navButtonView] == true}"
}
private fun sendEventArgsToTraceString(args: Any?): String {
val values = argsToList(args)
return values.mapIndexed { index, arg ->
@@ -1306,10 +1289,35 @@ class NavButtons : XposedModule() {
return findMethod(target.javaClass, methodName, *parameterTypes).invoke(target, *args)
}
private fun callIntIntMethodIfExists(
target: Any,
methodNames: List<String>,
first: Int,
second: Int,
): Boolean {
methodNames.forEach { methodName ->
findMethodsNamed(target.javaClass, methodName)
.firstOrNull { method ->
val types = method.parameterTypes
types.size >= 2 &&
types[0] == Int::class.javaPrimitiveType &&
types[1] == Int::class.javaPrimitiveType
}
?.let { method ->
return try {
method.invoke(target, first, second)
true
} catch (_: Throwable) {
false
}
}
}
return false
}
private companion object {
const val DEBUG_TRACE = true
const val DEBUG_TRACE = false
const val TAG = "NavButtons"
const val ASSIST_INVOCATION_TYPE_KEY = "invocation_type"
const val ASSIST_INVOCATION_TYPE_UNKNOWN = 0
const val ASSIST_INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS = 5
const val INPUT_MANAGER_CLASS = "android.hardware.input.InputManager"
@@ -1319,13 +1327,8 @@ class NavButtons : XposedModule() {
const val STOCK_LONG_PRESS_GUARD_MIN_DELAY_MS = 100
const val SYSTEMUI_PACKAGE = "com.android.systemui"
const val SYSTEM_SERVER_PHONE_WINDOW_MANAGER = "com.android.server.policy.PhoneWindowManager"
const val SYSTEMUI_ASSIST_MANAGER = "com.android.systemui.assist.AssistManager"
const val SYSTEMUI_KEY_BUTTON_VIEW =
"com.android.systemui.navigationbar.views.buttons.KeyButtonView"
const val SYSTEMUI_NAVIGATION_BAR =
"com.android.systemui.navigationbar.NavigationBar"
const val SYSTEMUI_OVERVIEW_PROXY_SERVICE =
"com.android.systemui.recents.OverviewProxyService"
const val SAMSUNG_LAUNCHER_PACKAGE = "com.sec.android.app.launcher"
const val HONEYSPACE_NAV_BUTTON_VIEW =
"com.honeyspace.ui.honeypots.taskbar.presentation.NavigationBarKeyButtonView"