Fix Samsung unfolded long presses

This commit is contained in:
ajp_anton
2026-07-11 19:13:12 +00:00
parent 963dbd7b5f
commit 09bab5db90
6 changed files with 55 additions and 16 deletions
@@ -31,7 +31,7 @@ internal class ActionExecutor(
NavButtonAction.ASSISTANT -> sendConfiguredKeyPress(KeyEvent.KEYCODE_ASSIST)
NavButtonAction.RECENTS -> sendConfiguredKeyPress(KeyEvent.KEYCODE_APP_SWITCH)
NavButtonAction.KILL_FOREGROUND_APP -> performPrivilegedActionOrDirect(PRIVILEGED_ACTION_KILL_FOREGROUND_APP)
NavButtonAction.TOGGLE_AUTO_ROTATE -> toggleAutoRotate()
NavButtonAction.TOGGLE_AUTO_ROTATE -> performPrivilegedActionOrDirect(PRIVILEGED_ACTION_TOGGLE_AUTO_ROTATE)
NavButtonAction.TOGGLE_FLASHLIGHT -> performPrivilegedActionOrDirect(PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT)
}
}
@@ -120,13 +120,14 @@ internal class ActionExecutor(
when (action) {
PRIVILEGED_ACTION_KILL_FOREGROUND_APP -> killForegroundApp()
PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT -> toggleFlashlight()
PRIVILEGED_ACTION_TOGGLE_AUTO_ROTATE -> toggleAutoRotate()
}
} else {
requestPrivilegedAction(action, null)
}
}
private fun toggleAutoRotate() {
fun toggleAutoRotate() {
val context = contextProvider() ?: run {
log("Context unavailable, cannot toggle auto rotate.")
return
@@ -72,6 +72,7 @@ internal const val EXTRA_PRIVILEGED_ACTION_TOKEN = "privileged_action_token"
internal const val EXTRA_PRIVILEGED_KEY_CODE = "privileged_key_code"
internal const val PRIVILEGED_ACTION_KILL_FOREGROUND_APP = "kill_foreground_app"
internal const val PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT = "toggle_flashlight"
internal const val PRIVILEGED_ACTION_TOGGLE_AUTO_ROTATE = "toggle_auto_rotate"
internal const val PRIVILEGED_ACTION_SEND_KEY_PRESS = "send_key_press"
internal const val HONEYSPACE_NAV_BUTTON_VIEW =
@@ -68,6 +68,7 @@ class NavButtons() : XposedModule() {
},
killForegroundApp = actions::killForegroundApp,
toggleFlashlight = actions::toggleFlashlight,
toggleAutoRotate = actions::toggleAutoRotate,
sendKeyPress = actions::sendKeyPress,
)
private val sideArrowLongPresses: SideArrowLongPressController = SideArrowLongPressController(
@@ -17,6 +17,7 @@ internal class PrivilegedActionBridge(
private val log: (String, Throwable?) -> Unit,
private val killForegroundApp: () -> Unit,
private val toggleFlashlight: () -> Unit,
private val toggleAutoRotate: () -> Unit,
private val sendKeyPress: (Int) -> Unit,
) {
private var receiverRegistered = false
@@ -78,6 +79,7 @@ internal class PrivilegedActionBridge(
when (action) {
PRIVILEGED_ACTION_KILL_FOREGROUND_APP -> killForegroundApp()
PRIVILEGED_ACTION_TOGGLE_FLASHLIGHT -> toggleFlashlight()
PRIVILEGED_ACTION_TOGGLE_AUTO_ROTATE -> toggleAutoRotate()
PRIVILEGED_ACTION_SEND_KEY_PRESS -> {
if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
sendKeyPress(keyCode)
@@ -92,12 +94,12 @@ internal class PrivilegedActionBridge(
senderPackage: String?,
senderUid: Int,
): Boolean {
if (senderPackage == SYSTEMUI_PACKAGE || senderPackage in AOSP_LAUNCHER_PACKAGES) {
if (isTrustedUiPackage(senderPackage)) {
return true
}
val caller = getParcelableExtraCompat(intent, EXTRA_PRIVILEGED_CALLER, PendingIntent::class.java)
if (caller?.creatorPackage == SYSTEMUI_PACKAGE || caller?.creatorPackage in AOSP_LAUNCHER_PACKAGES) {
if (isTrustedUiPackage(caller?.creatorPackage)) {
return true
}
if (caller?.creatorUid == Process.SYSTEM_UID) {
@@ -117,7 +119,13 @@ internal class PrivilegedActionBridge(
return true
}
return context.packageManager.getPackagesForUid(senderUid)
?.any { it == SYSTEMUI_PACKAGE || it in AOSP_LAUNCHER_PACKAGES } == true
?.any(::isTrustedUiPackage) == true
}
private fun isTrustedUiPackage(packageName: String?): Boolean {
return packageName == SYSTEMUI_PACKAGE ||
packageName == SAMSUNG_LAUNCHER_PACKAGE ||
packageName in AOSP_LAUNCHER_PACKAGES
}
private fun readToken(): String? {
@@ -3,6 +3,7 @@ package se.ajpanton.navbuttons
import android.content.Context
import android.os.Handler
import android.os.SystemClock
import android.view.HapticFeedbackConstants
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
@@ -28,6 +29,7 @@ internal class SamsungHoneyspaceHooks(
private val log: (String) -> Unit,
) {
private val longPressTriggered = WeakHashMap<Any, Boolean>()
private val customLongPressRunnables = WeakHashMap<Any, Runnable>()
private val trackedNavContainers = Collections.newSetFromMap(WeakHashMap<Any, Boolean>())
private val nativeSideArrowStates = WeakHashMap<Any, Boolean>()
private val homeStockLongPressGuard = HomeStockLongPressGuard(
@@ -86,20 +88,26 @@ internal class SamsungHoneyspaceHooks(
when (motionEvent.actionMasked) {
MotionEvent.ACTION_DOWN -> {
longPressTriggered[navButtonView] = false
cancelCustomLongPress(navButtonView)
homeStockLongPressGuard.cancel(navButtonView)
homeStockLongPressGuard.clear(navButtonView)
val result = chain.proceed()
rescheduleHoneyspaceLongPress(
navButtonView,
runnableClass,
configProvider(buttonId).longPressDurationMs,
)
val config = configProvider(buttonId)
if (config.longPressAction != NavButtonAction.STOCK) {
cancelHoneyspaceLongPress(navButtonView, runnableClass)
scheduleCustomLongPress(
navButtonView,
config.longPressAction,
config.longPressDurationMs,
)
}
homeStockLongPressGuard.schedule(navButtonView, buttonId)
result
}
MotionEvent.ACTION_UP -> {
val triggered = longPressTriggered[navButtonView] == true
cancelCustomLongPress(navButtonView)
if (triggered) {
return@hookMethod proceedWithCanceledAction(motionEvent) {
chain.proceed()
@@ -137,6 +145,7 @@ internal class SamsungHoneyspaceHooks(
}
MotionEvent.ACTION_CANCEL -> {
cancelCustomLongPress(navButtonView)
homeStockLongPressGuard.cancel(navButtonView)
longPressTriggered.remove(navButtonView)
homeStockLongPressGuard.clear(navButtonView)
@@ -179,6 +188,30 @@ internal class SamsungHoneyspaceHooks(
return true
}
private fun scheduleCustomLongPress(
navButtonView: Any,
action: NavButtonAction,
durationMs: Int,
) {
val view = navButtonView as? View ?: return
val runnable = Runnable {
updateContextFromView(navButtonView)
longPressTriggered[navButtonView] = true
markLongClicked(navButtonView)
homeStockLongPressGuard.cancel(navButtonView)
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
performAction(action)
}
customLongPressRunnables[navButtonView] = runnable
view.postDelayed(runnable, durationMs.toLong())
}
private fun cancelCustomLongPress(navButtonView: Any) {
customLongPressRunnables.remove(navButtonView)?.let { runnable ->
(navButtonView as? View)?.removeCallbacks(runnable)
}
}
fun refreshSideArrowLayouts(reason: String) {
val containers = trackedNavContainers.toList()
containers.forEach { container ->
@@ -48,15 +48,10 @@ internal fun isHoneyspaceNavButtonContainer(view: View): Boolean {
honeyspaceViewIdName(view) == "navbar_button_container"
}
internal fun rescheduleHoneyspaceLongPress(
navButtonView: Any,
runnableClass: Class<*>,
durationMs: Int,
) {
internal fun cancelHoneyspaceLongPress(navButtonView: Any, runnableClass: Class<*>) {
val view = navButtonView as? View ?: return
val longPressRunnable = findHoneyspaceLongPressRunnable(navButtonView, runnableClass) ?: return
view.removeCallbacks(longPressRunnable)
view.postDelayed(longPressRunnable, durationMs.toLong())
}
internal fun createHoneyspaceExtraButton(