From 09bab5db90b8056dee26202773ca8c9e581c14b9 Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Sat, 11 Jul 2026 19:13:12 +0000 Subject: [PATCH] Fix Samsung unfolded long presses --- .../se/ajpanton/navbuttons/ActionExecutor.kt | 5 ++- .../ajpanton/navbuttons/NavButtonConstants.kt | 1 + .../java/se/ajpanton/navbuttons/NavButtons.kt | 1 + .../navbuttons/PrivilegedActionBridge.kt | 14 ++++-- .../navbuttons/SamsungHoneyspaceHooks.kt | 43 ++++++++++++++++--- .../navbuttons/SamsungHoneyspaceSupport.kt | 7 +-- 6 files changed, 55 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/se/ajpanton/navbuttons/ActionExecutor.kt b/app/src/main/java/se/ajpanton/navbuttons/ActionExecutor.kt index 5d874b3..96d1561 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/ActionExecutor.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/ActionExecutor.kt @@ -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 diff --git a/app/src/main/java/se/ajpanton/navbuttons/NavButtonConstants.kt b/app/src/main/java/se/ajpanton/navbuttons/NavButtonConstants.kt index a6f9881..003b108 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/NavButtonConstants.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/NavButtonConstants.kt @@ -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 = diff --git a/app/src/main/java/se/ajpanton/navbuttons/NavButtons.kt b/app/src/main/java/se/ajpanton/navbuttons/NavButtons.kt index 978aaa9..a8f7bcb 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/NavButtons.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/NavButtons.kt @@ -68,6 +68,7 @@ class NavButtons() : XposedModule() { }, killForegroundApp = actions::killForegroundApp, toggleFlashlight = actions::toggleFlashlight, + toggleAutoRotate = actions::toggleAutoRotate, sendKeyPress = actions::sendKeyPress, ) private val sideArrowLongPresses: SideArrowLongPressController = SideArrowLongPressController( diff --git a/app/src/main/java/se/ajpanton/navbuttons/PrivilegedActionBridge.kt b/app/src/main/java/se/ajpanton/navbuttons/PrivilegedActionBridge.kt index 025196f..8943045 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/PrivilegedActionBridge.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/PrivilegedActionBridge.kt @@ -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? { diff --git a/app/src/main/java/se/ajpanton/navbuttons/SamsungHoneyspaceHooks.kt b/app/src/main/java/se/ajpanton/navbuttons/SamsungHoneyspaceHooks.kt index b344048..5d25be6 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/SamsungHoneyspaceHooks.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/SamsungHoneyspaceHooks.kt @@ -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() + private val customLongPressRunnables = WeakHashMap() private val trackedNavContainers = Collections.newSetFromMap(WeakHashMap()) private val nativeSideArrowStates = WeakHashMap() 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 -> diff --git a/app/src/main/java/se/ajpanton/navbuttons/SamsungHoneyspaceSupport.kt b/app/src/main/java/se/ajpanton/navbuttons/SamsungHoneyspaceSupport.kt index 5aa18ba..4a74998 100644 --- a/app/src/main/java/se/ajpanton/navbuttons/SamsungHoneyspaceSupport.kt +++ b/app/src/main/java/se/ajpanton/navbuttons/SamsungHoneyspaceSupport.kt @@ -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(