Support AOSP API 34 and Google APIs

This commit is contained in:
ajp_anton
2026-06-22 22:05:36 +00:00
parent 465dec356d
commit c2ee5a5581
3 changed files with 206 additions and 69 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ android {
defaultConfig {
applicationId = "se.ajpanton.navbuttons"
minSdk = 35
minSdk = 34
targetSdk = 35
versionCode = project.findProperty("nbVersionCode")?.toString()?.toIntOrNull() ?: 1777070659
versionName = project.findProperty("nbVersionName")?.toString() ?: "0.3"
@@ -48,14 +48,14 @@ import java.util.Collections
import java.util.WeakHashMap
class NavButtons : XposedModule() {
private val remotePreferences: SharedPreferences? by lazy(LazyThreadSafetyMode.NONE) {
runCatching {
getRemotePreferences(NavButtonSettingsStore.PREFERENCES_NAME)
}.getOrElse { error ->
log("Remote preferences unavailable: ${error.javaClass.simpleName}")
null
}
}
@Volatile
private var remotePreferences: SharedPreferences? = null
@Volatile
private var remotePreferencesRetryAfterMs = 0L
@Volatile
private var remotePreferencesLoading = false
private var context: Context? = null
private var mainHandler: Handler? = null
@@ -140,7 +140,7 @@ class NavButtons : XposedModule() {
return
}
if (AOSP_LAUNCHER_PACKAGE == packageName) {
if (packageName in AOSP_LAUNCHER_PACKAGES) {
if (aospTaskbarHookInstalled) {
return
}
@@ -157,6 +157,56 @@ class NavButtons : XposedModule() {
if (mainHandler == null) {
mainHandler = Handler(Looper.getMainLooper())
}
startSettingsPreferencesLoad()
}
private fun getSettingsPreferences(): SharedPreferences? {
remotePreferences?.let { return it }
if (Looper.myLooper() == Looper.getMainLooper()) {
startSettingsPreferencesLoad()
return null
}
return loadSettingsPreferences()
}
private fun startSettingsPreferencesLoad() {
if (remotePreferences != null || remotePreferencesLoading) {
return
}
val now = SystemClock.uptimeMillis()
if (now < remotePreferencesRetryAfterMs) {
return
}
remotePreferencesLoading = true
Thread {
try {
repeat(REMOTE_PREFERENCES_LOAD_ATTEMPTS) {
if (loadSettingsPreferences() != null) {
return@Thread
}
Thread.sleep(REMOTE_PREFERENCES_RETRY_DELAY_MS)
}
} finally {
remotePreferencesLoading = false
}
}.start()
}
private fun loadSettingsPreferences(): SharedPreferences? {
remotePreferences?.let { return it }
val now = SystemClock.uptimeMillis()
if (now < remotePreferencesRetryAfterMs) {
return null
}
return runCatching {
getRemotePreferences(NavButtonSettingsStore.PREFERENCES_NAME)
}.getOrElse { error ->
remotePreferencesRetryAfterMs = now + REMOTE_PREFERENCES_RETRY_DELAY_MS
log("Remote preferences unavailable: ${error.javaClass.simpleName}")
null
}?.also {
remotePreferences = it
}
}
private fun getCurrentApplication(): Context? {
@@ -170,8 +220,8 @@ class NavButtons : XposedModule() {
}
private fun hookSystemUiNavigationButtons(classLoader: ClassLoader?): Boolean {
val keyButtonClass = findClassIfExists(SYSTEMUI_KEY_BUTTON_VIEW, classLoader) ?: run {
log("SystemUI key button class not found: $SYSTEMUI_KEY_BUTTON_VIEW")
val keyButtonClass = findFirstClassIfExists(SYSTEMUI_KEY_BUTTON_VIEW_CLASSES, classLoader) ?: run {
log("SystemUI key button class not found: ${SYSTEMUI_KEY_BUTTON_VIEW_CLASSES.joinToString()}")
return false
}
val onTouchEventMethod = findMethodIfExists(
@@ -184,7 +234,7 @@ class NavButtons : XposedModule() {
}
val runnableClass = findClassIfExists(
"$SYSTEMUI_KEY_BUTTON_VIEW\$1",
"${keyButtonClass.name}\$1",
classLoader,
) ?: run {
log("SystemUI long-press runnable not found.")
@@ -378,6 +428,16 @@ class NavButtons : XposedModule() {
Int::class.javaPrimitiveType!!,
)
val systemReadyMethod = findMethodIfExists(phoneWindowManagerClass, "systemReady")
val keyDispatchMethods = findMethodsNamed(phoneWindowManagerClass, "interceptKeyBeforeDispatching")
.filter { method ->
method.returnType == Long::class.javaPrimitiveType &&
method.parameterTypes.any { it == KeyEvent::class.java }
}
val keyQueueingMethods = findMethodsNamed(phoneWindowManagerClass, "interceptKeyBeforeQueueing")
.filter { method ->
method.returnType == Int::class.javaPrimitiveType &&
method.parameterTypes.any { it == KeyEvent::class.java }
}
if (systemReadyMethod != null) {
deoptimizeMethodIfPresent(systemReadyMethod)
@@ -390,6 +450,47 @@ class NavButtons : XposedModule() {
installed = true
}
keyDispatchMethods.forEach { method ->
deoptimizeMethodIfPresent(method)
hook(method).intercept { chain ->
val keyEvent = chain.args.firstOrNull { it is KeyEvent } as? KeyEvent
?: return@intercept chain.proceed()
val buttonId = buttonIdForKeyCode(keyEvent.keyCode) ?: return@intercept chain.proceed()
val pressAction = getButtonConfig(buttonId).pressAction
if (pressAction == NavButtonAction.STOCK) {
return@intercept chain.proceed()
}
updateContextFromField(chain.thisObject, "mContext")
if (keyEvent.action == KeyEvent.ACTION_UP && !keyEvent.isCanceled) {
performConfiguredAction(pressAction)
}
-1L
}
installed = true
}
keyQueueingMethods.forEach { method ->
deoptimizeMethodIfPresent(method)
hook(method).intercept { chain ->
val keyEvent = chain.args.firstOrNull { it is KeyEvent } as? KeyEvent
?: return@intercept chain.proceed()
val buttonId = buttonIdForKeyCode(keyEvent.keyCode) ?: return@intercept chain.proceed()
val pressAction = getButtonConfig(buttonId).pressAction
if (pressAction == NavButtonAction.STOCK) {
return@intercept chain.proceed()
}
updateContextFromField(chain.thisObject, "mContext")
if (keyEvent.action == KeyEvent.ACTION_UP && !keyEvent.isCanceled) {
performConfiguredAction(pressAction)
}
val result = chain.proceed() as? Int ?: 0
result and ACTION_PASS_TO_USER.inv()
}
installed = true
}
if (longPressOnHomeWithEvent != null) {
deoptimizeMethodIfPresent(longPressOnHomeWithEvent)
hook(longPressOnHomeWithEvent).intercept { chain ->
@@ -441,10 +542,13 @@ class NavButtons : XposedModule() {
}
private fun hookAospSystemUiSideArrows(classLoader: ClassLoader?, keyButtonClass: Class<*>) {
if (aospSystemUiSideArrowHookInstalled || Build.MANUFACTURER.equals("samsung", ignoreCase = true)) {
if (aospSystemUiSideArrowHookInstalled ||
Build.MANUFACTURER.equals("samsung", ignoreCase = true) ||
Build.VERSION.SDK_INT < 35
) {
return
}
val inflaterClass = findClassIfExists(AOSP_NAVIGATION_BAR_INFLATER_VIEW, classLoader) ?: return
val inflaterClass = findFirstClassIfExists(AOSP_NAVIGATION_BAR_INFLATER_VIEW_CLASSES, classLoader) ?: return
aospSystemUiSideArrowHookInstalled = true
registerSettingsChangedReceiver()
@@ -685,8 +789,8 @@ class NavButtons : XposedModule() {
val view = value as? View ?: return null
val classLoader = view.javaClass.classLoader ?: return null
return try {
val keyButtonDrawableClass = findClass(AOSP_KEY_BUTTON_DRAWABLE, classLoader)
val stateClass = findClass(AOSP_KEY_BUTTON_DRAWABLE_STATE, classLoader)
val keyButtonDrawableClass = findFirstClass(AOSP_KEY_BUTTON_DRAWABLE_CLASSES, classLoader)
val stateClass = findFirstClass(AOSP_KEY_BUTTON_DRAWABLE_STATE_CLASSES, classLoader)
val state = stateClass
.getDeclaredConstructor(
Int::class.javaPrimitiveType,
@@ -743,10 +847,7 @@ class NavButtons : XposedModule() {
"sendBackKeyEvent",
Int::class.javaPrimitiveType!!,
Boolean::class.javaPrimitiveType!!,
) ?: run {
log("AOSP taskbar sendBackKeyEvent() method not found.")
return false
}
)
deoptimizeMethodIfPresent(clickMethod)
deoptimizeMethodIfPresent(longClickMethod)
@@ -791,35 +892,37 @@ class NavButtons : XposedModule() {
true
}
hook(sendBackKeyEventMethod).intercept { chain ->
val pressAction = getButtonConfig(NavButtonId.BACK).pressAction
val longPressAction = getButtonConfig(NavButtonId.BACK).longPressAction
val action = chain.args[0] as? Int ?: return@intercept chain.proceed()
val canceled = chain.args.getOrNull(1) as? Boolean ?: false
val customLongPress = longPressAction != NavButtonAction.STOCK
val longPressTriggered = aospTaskbarBackLongPressTriggered[chain.thisObject] == true
if (sendBackKeyEventMethod != null) {
hook(sendBackKeyEventMethod).intercept { chain ->
val pressAction = getButtonConfig(NavButtonId.BACK).pressAction
val longPressAction = getButtonConfig(NavButtonId.BACK).longPressAction
val action = chain.args[0] as? Int ?: return@intercept chain.proceed()
val canceled = chain.args.getOrNull(1) as? Boolean ?: false
val customLongPress = longPressAction != NavButtonAction.STOCK
val longPressTriggered = aospTaskbarBackLongPressTriggered[chain.thisObject] == true
if (pressAction == NavButtonAction.STOCK && !customLongPress) {
return@intercept chain.proceed()
if (pressAction == NavButtonAction.STOCK && !customLongPress) {
return@intercept chain.proceed()
}
updateContextFromField(chain.thisObject, "mContext")
if (action == KeyEvent.ACTION_UP) {
aospTaskbarBackLongPressTriggered.remove(chain.thisObject)
}
if (customLongPress && action == KeyEvent.ACTION_DOWN) {
return@intercept null
}
if (action == KeyEvent.ACTION_UP && !canceled && !longPressTriggered) {
performAospTaskbarAction(
chain.thisObject,
if (pressAction == NavButtonAction.STOCK) NavButtonAction.BACK else pressAction,
)
}
null
}
updateContextFromField(chain.thisObject, "mContext")
if (action == KeyEvent.ACTION_UP) {
aospTaskbarBackLongPressTriggered.remove(chain.thisObject)
}
if (customLongPress && action == KeyEvent.ACTION_DOWN) {
return@intercept null
}
if (action == KeyEvent.ACTION_UP && !canceled && !longPressTriggered) {
performAospTaskbarAction(
chain.thisObject,
if (pressAction == NavButtonAction.STOCK) NavButtonAction.BACK else pressAction,
)
}
null
}
return true
@@ -1678,7 +1781,7 @@ class NavButtons : XposedModule() {
}
private fun sendSideArrowKeyPress(keyCode: Int) {
if (context?.packageName == AOSP_LAUNCHER_PACKAGE) {
if (context?.packageName in AOSP_LAUNCHER_PACKAGES) {
requestPrivilegedAction(PRIVILEGED_ACTION_SEND_KEY_PRESS, keyCode)
} else {
sendKeyPress(keyCode)
@@ -1701,13 +1804,13 @@ class NavButtons : XposedModule() {
private fun getSideArrowLongPressAction(): SideArrowLongPressAction {
return SideArrowLongPressAction.fromStorageValue(
remotePreferences?.getString(NavButtonSettingsStore.KEY_SIDE_ARROW_LONG_PRESS_ACTION, null),
getSettingsPreferences()?.getString(NavButtonSettingsStore.KEY_SIDE_ARROW_LONG_PRESS_ACTION, null),
)
}
private fun getSideArrowLongPressDurationMs(): Int {
return (
remotePreferences?.getInt(
getSettingsPreferences()?.getInt(
NavButtonSettingsStore.KEY_SIDE_ARROW_LONG_PRESS_DURATION_MS,
ViewConfiguration.getLongPressTimeout(),
) ?: ViewConfiguration.getLongPressTimeout()
@@ -1716,7 +1819,7 @@ class NavButtons : XposedModule() {
private fun sideArrowRepeatIntervalMs(): Long {
val speed = sanitizeSideArrowRepeatSpeed(
remotePreferences?.getFloat(
getSettingsPreferences()?.getFloat(
NavButtonSettingsStore.KEY_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
NavButtonSettingsStore.DEFAULT_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
) ?: NavButtonSettingsStore.DEFAULT_SIDE_ARROW_REPEAT_STEPS_PER_SECOND,
@@ -1886,6 +1989,9 @@ class NavButtons : XposedModule() {
}
private fun hookFoldedSideArrows(classLoader: ClassLoader?) {
if (!Build.MANUFACTURER.equals("samsung", ignoreCase = true)) {
return
}
hookFoldedLayoutProvider(classLoader)
hookSamsungNavigationBarView(classLoader)
registerSettingsChangedReceiver()
@@ -2237,7 +2343,7 @@ class NavButtons : XposedModule() {
}
sideArrowsEnabledOverride = intent.getBooleanExtra(
NavButtonSettingsStore.EXTRA_SIDE_ARROWS_ENABLED,
remotePreferences?.getBoolean(NavButtonSettingsStore.KEY_SIDE_ARROWS_ENABLED, false) == true,
getSettingsPreferences()?.getBoolean(NavButtonSettingsStore.KEY_SIDE_ARROWS_ENABLED, false) == true,
)
reInflateTrackedSamsungNavigationBars()
refreshTrackedAospSystemUiSideArrowLayouts()
@@ -2327,7 +2433,7 @@ class NavButtons : XposedModule() {
senderPackage: String?,
senderUid: Int,
): Boolean {
if (senderPackage == AOSP_LAUNCHER_PACKAGE) {
if (senderPackage in AOSP_LAUNCHER_PACKAGES) {
return true
}
@@ -2344,11 +2450,11 @@ class NavButtons : XposedModule() {
return true
}
return context.packageManager.getPackagesForUid(senderUid)
?.contains(AOSP_LAUNCHER_PACKAGE) == true
?.any { it in AOSP_LAUNCHER_PACKAGES } == true
}
private fun readPrivilegedActionToken(): String? {
return remotePreferences?.getString(NavButtonSettingsStore.KEY_PRIVILEGED_ACTION_TOKEN, null)
return getSettingsPreferences()?.getString(NavButtonSettingsStore.KEY_PRIVILEGED_ACTION_TOKEN, null)
}
private fun requestPrivilegedAction(action: String, keyCode: Int? = null) {
@@ -2361,7 +2467,7 @@ class NavButtons : XposedModule() {
}
return
}
val token = remotePreferences?.getString(
val token = getSettingsPreferences()?.getString(
NavButtonSettingsStore.KEY_PRIVILEGED_ACTION_TOKEN,
null,
)
@@ -2587,7 +2693,7 @@ class NavButtons : XposedModule() {
private fun isSideArrowsEnabled(): Boolean {
return sideArrowsEnabledOverride
?: (remotePreferences?.getBoolean(NavButtonSettingsStore.KEY_SIDE_ARROWS_ENABLED, false) == true)
?: (getSettingsPreferences()?.getBoolean(NavButtonSettingsStore.KEY_SIDE_ARROWS_ENABLED, false) == true)
}
private fun injectKeyEvent(inputManager: Any, keyEvent: KeyEvent) {
@@ -2773,7 +2879,7 @@ class NavButtons : XposedModule() {
}
private fun getButtonConfig(buttonId: NavButtonId): NavButtonConfig {
val preferences = remotePreferences
val preferences = getSettingsPreferences()
val defaultDurationMs = ViewConfiguration.getLongPressTimeout().coerceLongPressDuration()
return NavButtonConfig(
pressAction = NavButtonAction.fromStorageValue(
@@ -2828,7 +2934,7 @@ class NavButtons : XposedModule() {
return packageName == null ||
packageName == SYSTEMUI_PACKAGE ||
packageName == SAMSUNG_LAUNCHER_PACKAGE ||
packageName == AOSP_LAUNCHER_PACKAGE ||
packageName in AOSP_LAUNCHER_PACKAGES ||
packageName == defaultLauncher
}
@@ -3137,6 +3243,22 @@ class NavButtons : XposedModule() {
}
}
private fun findFirstClass(classNames: List<String>, classLoader: ClassLoader?): Class<*> {
classNames.forEach { className ->
findClassIfExists(className, classLoader)?.let { return it }
}
throw ClassNotFoundException(classNames.joinToString())
}
private fun findFirstClassIfExists(
classNames: List<String>,
classLoader: ClassLoader?,
): Class<*>? {
return classNames.firstNotNullOfOrNull { className ->
findClassIfExists(className, classLoader)
}
}
private fun findMethodIfExists(
clazz: Class<*>,
methodName: String,
@@ -3361,14 +3483,19 @@ class NavButtons : XposedModule() {
const val ASSIST_INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS = 5
const val INPUT_MANAGER_CLASS = "android.hardware.input.InputManager"
const val INPUT_EVENT_INJECTION_SYNC_NONE = 0
const val ACTION_PASS_TO_USER = 1
const val HOME_LONG_PRESS_DEDUP_WINDOW_MS = 1_000L
const val REMOTE_PREFERENCES_RETRY_DELAY_MS = 1_000L
const val REMOTE_PREFERENCES_LOAD_ATTEMPTS = 15
const val STOCK_LONG_PRESS_GUARD_EARLY_MS = 75
const val STOCK_LONG_PRESS_GUARD_MIN_DELAY_MS = 100
const val SYSTEMUI_PACKAGE = "com.android.systemui"
const val ANDROID_PACKAGE = "android"
const val SYSTEM_SERVER_PHONE_WINDOW_MANAGER = "com.android.server.policy.PhoneWindowManager"
const val SYSTEMUI_KEY_BUTTON_VIEW =
"com.android.systemui.navigationbar.views.buttons.KeyButtonView"
val SYSTEMUI_KEY_BUTTON_VIEW_CLASSES = listOf(
"com.android.systemui.navigationbar.views.buttons.KeyButtonView",
"com.android.systemui.navigationbar.buttons.KeyButtonView",
)
const val SAMSUNG_PHONE_LAYOUT_PROVIDER =
"com.android.systemui.navigationbar.layout.LayoutProviderImpl"
const val SAMSUNG_TABLET_LAYOUT_PROVIDER =
@@ -3376,7 +3503,10 @@ class NavButtons : XposedModule() {
const val SAMSUNG_NAVIGATION_BAR_VIEW =
"com.android.systemui.navigationbar.views.SamsungNavigationBarView"
const val SAMSUNG_LAUNCHER_PACKAGE = "com.sec.android.app.launcher"
const val AOSP_LAUNCHER_PACKAGE = "com.android.launcher3"
val AOSP_LAUNCHER_PACKAGES = setOf(
"com.android.launcher3",
"com.google.android.apps.nexuslauncher",
)
const val AOSP_TASKBAR_NAV_BUTTON_CONTROLLER =
"com.android.launcher3.taskbar.TaskbarNavButtonController"
const val AOSP_TASKBAR_BACK_BUTTON = 1
@@ -3385,12 +3515,18 @@ class NavButtons : XposedModule() {
const val AOSP_NAVBAR_BUTTONS_VIEW_CONTROLLER =
"com.android.launcher3.taskbar.NavbarButtonsViewController"
const val AOSP_TASKBAR_SIDE_BUTTON_SHIFT_RATIO = 0.45f
const val AOSP_NAVIGATION_BAR_INFLATER_VIEW =
"com.android.systemui.navigationbar.views.NavigationBarInflaterView"
const val AOSP_KEY_BUTTON_DRAWABLE =
"com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable"
const val AOSP_KEY_BUTTON_DRAWABLE_STATE =
"com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable\$ShadowDrawableState"
val AOSP_NAVIGATION_BAR_INFLATER_VIEW_CLASSES = listOf(
"com.android.systemui.navigationbar.views.NavigationBarInflaterView",
"com.android.systemui.navigationbar.NavigationBarInflaterView",
)
val AOSP_KEY_BUTTON_DRAWABLE_CLASSES = listOf(
"com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable",
"com.android.systemui.navigationbar.buttons.KeyButtonDrawable",
)
val AOSP_KEY_BUTTON_DRAWABLE_STATE_CLASSES = listOf(
"com.android.systemui.navigationbar.views.buttons.KeyButtonDrawable\$ShadowDrawableState",
"com.android.systemui.navigationbar.buttons.KeyButtonDrawable\$ShadowDrawableState",
)
const val AOSP_SYSUI_LEFT_ARROW_BUTTON = "key(21)"
const val AOSP_SYSUI_RIGHT_ARROW_BUTTON = "key(22)"
const val ACTION_PRIVILEGED_ACTION =
@@ -2,5 +2,6 @@ android
system
com.android.systemui
com.android.launcher3
com.google.android.apps.nexuslauncher
com.sec.android.app.launcher
com.samsung.systemui.navillera