Add local release workflow and foldable home guard
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user