From 68ad9147c669f6fe9f619ee647f3a8c6a16b9c1b Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 19 Nov 2024 14:28:56 +0300 Subject: [PATCH 01/13] Update build.yml --- .github/workflows/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64b6ac9..6e4c4dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,11 @@ on: - main - dev - test + pull_request: + branches: + - main + - test + - dev env: BUILD_TYPE: ${{ inputs.build-type || 'debug' }} From b82eb6611b69aa36c13838155b2fe72be99864c5 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 19 Nov 2024 23:51:50 +0300 Subject: [PATCH 02/13] Update build.yml [skip ci] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e4c4dc..1e8793d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,8 +22,8 @@ on: pull_request: branches: - main - - test - dev + - test env: BUILD_TYPE: ${{ inputs.build-type || 'debug' }} From 9564ea30cc4f5a8a4f23b14ab0170114d2236d0c Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 20 Nov 2024 20:42:44 +0300 Subject: [PATCH 03/13] Refactor module code --- .../volumekeytrackcontrolmodule/LogHelper.kt | 4 +- .../VolumeControlModule.kt | 111 ++++++++++-------- .../VolumeKeyHandlers.kt | 3 +- .../model/HookInfo.kt | 21 ---- 4 files changed, 63 insertions(+), 76 deletions(-) delete mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/model/HookInfo.kt diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/LogHelper.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/LogHelper.kt index 9f0a0e4..ababd45 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/LogHelper.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/LogHelper.kt @@ -3,7 +3,7 @@ package ru.hepolise.volumekeytrackcontrolmodule import de.robv.android.xposed.XposedBridge object LogHelper { - fun log(text: String) { - if (BuildConfig.DEBUG) XposedBridge.log(text) + fun log(prefix: String, text: String) { + if (BuildConfig.DEBUG) XposedBridge.log("[$prefix] $text") } } \ No newline at end of file diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt index 85f3c4f..14e8949 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt @@ -6,10 +6,9 @@ import androidx.annotation.Keep import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam -import ru.hepolise.volumekeytrackcontrolmodule.LogHelper.log import ru.hepolise.volumekeytrackcontrolmodule.VolumeKeyHandlers.handleConstructPhoneWindowManager import ru.hepolise.volumekeytrackcontrolmodule.VolumeKeyHandlers.handleInterceptKeyBeforeQueueing -import ru.hepolise.volumekeytrackcontrolmodule.model.HookInfo +import java.io.Serializable @Keep class VolumeControlModule : IXposedHookLoadPackage { @@ -20,6 +19,9 @@ class VolumeControlModule : IXposedHookLoadPackage { private const val CLASS_IWINDOW_MANAGER = "android.view.IWindowManager" private const val CLASS_WINDOW_MANAGER_FUNCS = "com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs" + + private fun log(text: String) = + LogHelper.log(VolumeControlModule::class.java.simpleName, text) } @Throws(Throwable::class) @@ -30,61 +32,66 @@ class VolumeControlModule : IXposedHookLoadPackage { init(lpparam.classLoader) } + private val initMethodSignatures = mapOf( + // Android 14 & 15 signature + // https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r18/services/core/java/com/android/server/policy/PhoneWindowManager.java#2033 + // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android15-release/services/core/java/com/android/server/policy/PhoneWindowManager.java#2199 + arrayOf( + Context::class.java, + CLASS_WINDOW_MANAGER_FUNCS + ) to "Using Android 14 or 15 method signature", + + // Android 13 signature + // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-dev/services/core/java/com/android/server/policy/PhoneWindowManager.java#1873 + arrayOf( + Context::class.java, + CLASS_IWINDOW_MANAGER, + CLASS_WINDOW_MANAGER_FUNCS + ) to "Using Android 13 method signature", + + // HyperOS-specific signature + arrayOf( + Context::class.java, + CLASS_WINDOW_MANAGER_FUNCS, + CLASS_IWINDOW_MANAGER + ) to "Using HyperOS-specific method signature" + ) + private fun init(classLoader: ClassLoader) { - val hookInfoList = listOf( - // Android 14 & 15 signature - // https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r18/services/core/java/com/android/server/policy/PhoneWindowManager.java#2033 - // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android15-release/services/core/java/com/android/server/policy/PhoneWindowManager.java#2199 - HookInfo( - params = arrayOf(Context::class.java, CLASS_WINDOW_MANAGER_FUNCS), - logMessage = "Using Android 14 or 15 method signature" - ), - // Android 13 signature - // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-dev/services/core/java/com/android/server/policy/PhoneWindowManager.java#1873 - HookInfo( - params = arrayOf( - Context::class.java, - CLASS_IWINDOW_MANAGER, - CLASS_WINDOW_MANAGER_FUNCS - ), - logMessage = "Using Android 13 method signature" - ), - // HyperOS-specific signature - HookInfo( - params = arrayOf( - Context::class.java, - CLASS_WINDOW_MANAGER_FUNCS, - CLASS_IWINDOW_MANAGER - ), - logMessage = "Using HyperOS-specific method signature" - ), - ) + val foundMethod = initMethodSignatures.any { (params, logMessage) -> + tryHookMethod(classLoader, params, logMessage) + } - var foundMethod = false - for (hookInfo in hookInfoList) { - try { - XposedHelpers.findAndHookMethod( - CLASS_PHONE_WINDOW_MANAGER, classLoader, "init", - *hookInfo.params, handleConstructPhoneWindowManager - ) - foundMethod = true - log(hookInfo.logMessage) - break - } catch (ignored: NoSuchMethodError) { - } + if (!foundMethod) { + log("Method hook failed for init!") + return } - if (foundMethod) { - // https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r18/services/core/java/com/android/server/policy/PhoneWindowManager.java#4117 + + // https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r18/services/core/java/com/android/server/policy/PhoneWindowManager.java#4117 + XposedHelpers.findAndHookMethod( + CLASS_PHONE_WINDOW_MANAGER, + classLoader, + "interceptKeyBeforeQueueing", + KeyEvent::class.java, + Int::class.javaPrimitiveType, + handleInterceptKeyBeforeQueueing + ) + } + + private fun tryHookMethod( + classLoader: ClassLoader, + params: Array, + logMessage: String + ): Boolean { + return try { XposedHelpers.findAndHookMethod( - CLASS_PHONE_WINDOW_MANAGER, - classLoader, - "interceptKeyBeforeQueueing", - KeyEvent::class.java, - Int::class.javaPrimitiveType, - handleInterceptKeyBeforeQueueing + CLASS_PHONE_WINDOW_MANAGER, classLoader, "init", + *params, handleConstructPhoneWindowManager ) - } else { - log("Method hook failed for init!") + log(logMessage) + true + } catch (ignored: NoSuchMethodError) { + false } } } diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt index 77a9eb1..c9d965a 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt @@ -13,7 +13,6 @@ import android.view.ViewConfiguration import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook.MethodHookParam import de.robv.android.xposed.XposedHelpers -import ru.hepolise.volumekeytrackcontrolmodule.LogHelper.log import ru.hepolise.volumekeytrackcontrolmodule.extension.AudioManagerExtension.sendMediaButtonEvent import ru.hepolise.volumekeytrackcontrolmodule.extension.VibratorExtension.triggerVibration @@ -33,6 +32,8 @@ object VolumeKeyHandlers { private lateinit var powerManager: PowerManager private lateinit var vibrator: Vibrator + private fun log(text: String) = LogHelper.log(VolumeControlModule::class.java.simpleName, text) + val handleInterceptKeyBeforeQueueing: XC_MethodHook = object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as KeyEvent diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/model/HookInfo.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/model/HookInfo.kt deleted file mode 100644 index 5922a49..0000000 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/model/HookInfo.kt +++ /dev/null @@ -1,21 +0,0 @@ -package ru.hepolise.volumekeytrackcontrolmodule.model - -data class HookInfo(val params: Array, val logMessage: String) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as HookInfo - - if (!params.contentEquals(other.params)) return false - if (logMessage != other.logMessage) return false - - return true - } - - override fun hashCode(): Int { - var result = params.contentHashCode() - result = 31 * result + logMessage.hashCode() - return result - } -} From d677e3713c65b1e6bb1dd237432af1721d99aa81 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 20 Nov 2024 20:45:31 +0300 Subject: [PATCH 04/13] Refactor module code --- .../volumekeytrackcontrolmodule/VolumeControlModule.kt | 4 ++-- ...VolumeKeyHandlers.kt => VolumeKeyControlModuleHandlers.kt} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/{VolumeKeyHandlers.kt => VolumeKeyControlModuleHandlers.kt} (99%) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt index 14e8949..6c5758d 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt @@ -6,8 +6,8 @@ import androidx.annotation.Keep import de.robv.android.xposed.IXposedHookLoadPackage import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam -import ru.hepolise.volumekeytrackcontrolmodule.VolumeKeyHandlers.handleConstructPhoneWindowManager -import ru.hepolise.volumekeytrackcontrolmodule.VolumeKeyHandlers.handleInterceptKeyBeforeQueueing +import ru.hepolise.volumekeytrackcontrolmodule.VolumeKeyControlModuleHandlers.handleConstructPhoneWindowManager +import ru.hepolise.volumekeytrackcontrolmodule.VolumeKeyControlModuleHandlers.handleInterceptKeyBeforeQueueing import java.io.Serializable @Keep diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt similarity index 99% rename from app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt rename to app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt index c9d965a..d737679 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt @@ -16,7 +16,7 @@ import de.robv.android.xposed.XposedHelpers import ru.hepolise.volumekeytrackcontrolmodule.extension.AudioManagerExtension.sendMediaButtonEvent import ru.hepolise.volumekeytrackcontrolmodule.extension.VibratorExtension.triggerVibration -object VolumeKeyHandlers { +object VolumeKeyControlModuleHandlers { private const val VOLUME_UP_LONG_PRESS = "mVolumeUpLongPress" private const val VOLUME_DOWN_LONG_PRESS = "mVolumeDownLongPress" From af738935e52b0ff46ed6bb76167e774491bebee8 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 21 Nov 2024 22:01:58 +0300 Subject: [PATCH 05/13] Improved music state handling, fixed module working with remote audio playing --- .../VolumeKeyControlModuleHandlers.kt | 161 ++++++++++++------ .../extension/AudioManagerExtension.kt | 30 ---- 2 files changed, 106 insertions(+), 85 deletions(-) delete mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/AudioManagerExtension.kt diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt index d737679..219a490 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt @@ -1,21 +1,24 @@ package ru.hepolise.volumekeytrackcontrolmodule import android.content.Context +import android.hardware.display.DisplayManager import android.media.AudioManager +import android.media.session.MediaController +import android.media.session.PlaybackState import android.os.Build import android.os.Handler import android.os.PowerManager import android.os.Vibrator import android.os.VibratorManager -import android.util.Log +import android.view.Display import android.view.KeyEvent import android.view.ViewConfiguration import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook.MethodHookParam import de.robv.android.xposed.XposedHelpers -import ru.hepolise.volumekeytrackcontrolmodule.extension.AudioManagerExtension.sendMediaButtonEvent import ru.hepolise.volumekeytrackcontrolmodule.extension.VibratorExtension.triggerVibration + object VolumeKeyControlModuleHandlers { private const val VOLUME_UP_LONG_PRESS = "mVolumeUpLongPress" @@ -30,15 +33,18 @@ object VolumeKeyControlModuleHandlers { private lateinit var audioManager: AudioManager private lateinit var powerManager: PowerManager + private lateinit var displayManager: DisplayManager private lateinit var vibrator: Vibrator + private var mediaControllers: List? = null + private fun log(text: String) = LogHelper.log(VolumeControlModule::class.java.simpleName, text) val handleInterceptKeyBeforeQueueing: XC_MethodHook = object : XC_MethodHook() { override fun beforeHookedMethod(param: MethodHookParam) { val event = param.args[0] as KeyEvent val keyCode = event.keyCode - initManagers(param.thisObject.getObjectField("mContext") as Context) + initManagers(param) if (needHook(keyCode, event)) { doHook(keyCode, event, param) } @@ -47,30 +53,28 @@ object VolumeKeyControlModuleHandlers { val handleConstructPhoneWindowManager: XC_MethodHook = object : XC_MethodHook() { override fun afterHookedMethod(param: MethodHookParam) { - val volumeUpLongPress = Runnable { - log("sending next") - KeyEvent::KEYCODE_MEDIA_PREVIOUS.name - isLongPress = true - sendMediaButtonEventAndTriggerVibration(KeyEvent.KEYCODE_MEDIA_NEXT) - } - val volumeDownLongPress = Runnable { - log("sending prev") - isLongPress = true - sendMediaButtonEventAndTriggerVibration(KeyEvent.KEYCODE_MEDIA_PREVIOUS) - } - val volumeBothLongPress = Runnable { - if (isUpPressed && isDownPressed) { - log("sending play/pause") - isLongPress = true - sendMediaButtonEventAndTriggerVibration(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) - } else { - log("NOT sending play/pause, down: $isDownPressed, up: $isUpPressed") - } - } mapOf( - VOLUME_UP_LONG_PRESS to volumeUpLongPress, - VOLUME_DOWN_LONG_PRESS to volumeDownLongPress, - VOLUME_BOTH_LONG_PRESS to volumeBothLongPress, + VOLUME_UP_LONG_PRESS to Runnable { + log("sending next") + isLongPress = true + sendMediaButtonEventAndTriggerVibration(KeyEvent.KEYCODE_MEDIA_NEXT) + }, + VOLUME_DOWN_LONG_PRESS to Runnable { + log("sending prev") + isLongPress = true + sendMediaButtonEventAndTriggerVibration(KeyEvent.KEYCODE_MEDIA_PREVIOUS) + }, + VOLUME_BOTH_LONG_PRESS to Runnable { + if (isUpPressed && isDownPressed) { + log("sending play/pause") + isLongPress = true + getActiveMediaController()?.transportControls?.also { + sendMediaButtonEventAndTriggerVibration(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) + } + } else { + log("NOT sending play/pause, down: $isDownPressed, up: $isUpPressed") + } + }, ).forEach { (key, runnable) -> XposedHelpers.setAdditionalInstanceField(param.thisObject, key, runnable) } @@ -81,24 +85,46 @@ object VolumeKeyControlModuleHandlers { log("========") log("current audio manager mode: ${audioManager.mode}, required: ${AudioManager.MODE_NORMAL}") log("keyCode: ${keyCode}, required: ${KeyEvent.KEYCODE_VOLUME_DOWN} or ${KeyEvent.KEYCODE_VOLUME_UP}") - log("!powerManager.isInteractive: ${!powerManager.isInteractive}, required: true") + log("displayInteractive: ${isDisplayInteractive()}, required: false") log("isDownPressed: $isDownPressed") log("isUpPressed: $isUpPressed") + log("hasActiveMediaController: ${hasActiveMediaController()}, required: true") val needHook = (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) && event.flags and KeyEvent.FLAG_FROM_SYSTEM != 0 - && (!powerManager.isInteractive || isDownPressed || isUpPressed) + && (!isDisplayInteractive() || isDownPressed || isUpPressed) && audioManager.mode == AudioManager.MODE_NORMAL + && hasActiveMediaController() log("needHook: $needHook") return needHook } - private fun initManagers(ctx: Context) { - with(ctx) { + private fun isDisplayInteractive(): Boolean { + log("powerManager.isInteractive: ${powerManager.isInteractive}") + if (!powerManager.isInteractive) { + return false + } + log("displays count: $${displayManager.displays.size}") + // TODO + if (displayManager.displays.size > 1) { + return true + } + val display = displayManager.displays[0] + val disabledStates = + listOf(Display.STATE_OFF, Display.STATE_DOZE, Display.STATE_DOZE_SUSPEND) + log("checking display: ${display.displayId}, state: ${display.state}, required: $disabledStates") + return !disabledStates.contains(display.state) + } + + private fun initManagers(param: MethodHookParam) { + val context = param.thisObject.getContext() + with(context) { audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager? ?: throw NullPointerException("Unable to obtain audio service") powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager? ?: throw NullPointerException("Unable to obtain power service") + displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager? + ?: throw NullPointerException("Unable to obtain display service") vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { val vibratorManager = getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager @@ -107,14 +133,33 @@ object VolumeKeyControlModuleHandlers { @Suppress("DEPRECATION") getSystemService(Context.VIBRATOR_SERVICE) as Vibrator } + + val mediaSessionHelperClass = XposedHelpers.findClass( + "android.media.session.MediaSessionLegacyHelper", + param.thisObject.javaClass.classLoader + ) + val helper = + XposedHelpers.callStaticMethod(mediaSessionHelperClass, "getHelper", context) + val mSessionManager = XposedHelpers.getObjectField(helper, "mSessionManager") + val comName = XposedHelpers.findClass( + "android.content.ComponentName", + param.thisObject.javaClass.classLoader + ) + + @Suppress("UNCHECKED_CAST") + mediaControllers = XposedHelpers.callMethod( + mSessionManager, + "getActiveSessions", + arrayOf(comName), + null + ) as List? } } private fun doHook(keyCode: Int, event: KeyEvent, param: MethodHookParam) { - if (event.action == KeyEvent.ACTION_DOWN) { - handleDownAction(keyCode, param) - } else { - handleUpAction(keyCode, param) + when (event.action) { + KeyEvent.ACTION_DOWN -> handleDownAction(keyCode, param) + KeyEvent.ACTION_UP -> handleUpAction(keyCode, param) } param.setResult(0) } @@ -131,7 +176,7 @@ object VolumeKeyControlModuleHandlers { handleVolumeSkipPressAbort(param.thisObject) } else { // only one button pressed - if (isMusicActive) { + if (isMusicActive()) { log("music is active, creating delayed skip") handleVolumeSkipPress(param.thisObject, keyCode) } @@ -147,7 +192,7 @@ object VolumeKeyControlModuleHandlers { } log("up action received, down: $isDownPressed, up: $isUpPressed") handleVolumeAllPressAbort(param.thisObject) - if (!isLongPress && isMusicActive) { + if (!isLongPress && isMusicActive()) { log("adjusting music volume") val direction = when (keyCode) { KeyEvent.KEYCODE_VOLUME_UP -> AudioManager.ADJUST_RAISE @@ -158,27 +203,29 @@ object VolumeKeyControlModuleHandlers { } } - private val isMusicActive: Boolean - get() { - // check local - if (audioManager.isMusicActive) return true - // check remote - try { - if (XposedHelpers.callMethod( - audioManager, - "isMusicActiveRemotely" - ) as Boolean - ) return true - } catch (t: Throwable) { - t.localizedMessage?.let { Log.e("xposed", it) } - t.printStackTrace() + private fun hasActiveMediaController() = getActiveMediaController() != null + + private fun getActiveMediaController(): MediaController? { + return mediaControllers?.first()?.also { log("chosen media controller: ${it.packageName}") } + } + + private fun isMusicActive() = + getActiveMediaController()?.let { it.playbackState?.state == PlaybackState.STATE_PLAYING } + ?: false + + private fun sendMediaButtonEventAndTriggerVibration(keyCode: Int) { + getActiveMediaController()?.transportControls?.also { controls -> + when (keyCode) { + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { + if (isMusicActive()) controls.pause() else controls.play() + } + + KeyEvent.KEYCODE_MEDIA_NEXT -> controls.skipToNext() + KeyEvent.KEYCODE_MEDIA_PREVIOUS -> controls.skipToPrevious() + else -> return } - return false + vibrator.triggerVibration() } - - private fun sendMediaButtonEventAndTriggerVibration(code: Int) { - audioManager.sendMediaButtonEvent(code) - vibrator.triggerVibration() } private fun handleVolumePlayPausePress(instance: Any) { @@ -222,6 +269,10 @@ object VolumeKeyControlModuleHandlers { return XposedHelpers.getAdditionalInstanceField(instance, fieldName) as Runnable } + private fun Any.getContext(): Context { + return getObjectField("mContext") as Context + } + private fun Any.getHandler(): Handler { return getObjectField("mHandler") as Handler } diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/AudioManagerExtension.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/AudioManagerExtension.kt deleted file mode 100644 index 788761f..0000000 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/AudioManagerExtension.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.hepolise.volumekeytrackcontrolmodule.extension - -import android.content.Intent -import android.media.AudioManager -import android.os.SystemClock -import android.util.Log -import android.view.KeyEvent - -object AudioManagerExtension { - - fun AudioManager.sendMediaButtonEvent(code: Int) { - val eventTime = SystemClock.uptimeMillis() - val keyIntent = Intent(Intent.ACTION_MEDIA_BUTTON, null) - var keyEvent = KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, code, 0) - keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent) - dispatchMediaButtonEvent(keyEvent) - keyEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP) - keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent) - dispatchMediaButtonEvent(keyEvent) - } - - private fun AudioManager.dispatchMediaButtonEvent(keyEvent: KeyEvent) { - try { - this.dispatchMediaKeyEvent(keyEvent) - } catch (t: Throwable) { - t.localizedMessage?.let { Log.e("xposed", it) } - t.printStackTrace() - } - } -} \ No newline at end of file From 1f03f7302faef7d49f79d14a9b0bfe87ae8a83d0 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 21 Nov 2024 22:05:17 +0300 Subject: [PATCH 06/13] Code refactor --- .../VolumeKeyControlModuleHandlers.kt | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt index 219a490..1c3c4f4 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt @@ -25,6 +25,10 @@ object VolumeKeyControlModuleHandlers { private const val VOLUME_DOWN_LONG_PRESS = "mVolumeDownLongPress" private const val VOLUME_BOTH_LONG_PRESS = "mVolumeBothLongPress" + private const val CLASS_MEDIA_SESSION_LEGACY_HELPER = + "android.media.session.MediaSessionLegacyHelper" + private const val CLASS_COMPONENT_NAME = "android.content.ComponentName" + private val TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong() private var isLongPress = false @@ -133,27 +137,24 @@ object VolumeKeyControlModuleHandlers { @Suppress("DEPRECATION") getSystemService(Context.VIBRATOR_SERVICE) as Vibrator } - - val mediaSessionHelperClass = XposedHelpers.findClass( - "android.media.session.MediaSessionLegacyHelper", - param.thisObject.javaClass.classLoader - ) - val helper = - XposedHelpers.callStaticMethod(mediaSessionHelperClass, "getHelper", context) - val mSessionManager = XposedHelpers.getObjectField(helper, "mSessionManager") - val comName = XposedHelpers.findClass( - "android.content.ComponentName", - param.thisObject.javaClass.classLoader - ) - - @Suppress("UNCHECKED_CAST") - mediaControllers = XposedHelpers.callMethod( - mSessionManager, - "getActiveSessions", - arrayOf(comName), - null - ) as List? } + + val mediaSessionHelperClass = XposedHelpers.findClass( + CLASS_MEDIA_SESSION_LEGACY_HELPER, + param.thisObject.javaClass.classLoader + ) + val helper = XposedHelpers.callStaticMethod(mediaSessionHelperClass, "getHelper", context) + val mSessionManager = XposedHelpers.getObjectField(helper, "mSessionManager") + val componentNameClass = + XposedHelpers.findClass(CLASS_COMPONENT_NAME, param.thisObject.javaClass.classLoader) + + @Suppress("UNCHECKED_CAST") + mediaControllers = XposedHelpers.callMethod( + mSessionManager, + "getActiveSessions", + arrayOf(componentNameClass), + null + ) as List? } private fun doHook(keyCode: Int, event: KeyEvent, param: MethodHookParam) { From b443dcb28a5442c207f6627ce8abc0c9bd1458d9 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 21 Nov 2024 23:42:51 +0300 Subject: [PATCH 07/13] Fix crash when no media controllers --- .../VolumeKeyControlModuleHandlers.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt index 1c3c4f4..4aed641 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt @@ -207,7 +207,7 @@ object VolumeKeyControlModuleHandlers { private fun hasActiveMediaController() = getActiveMediaController() != null private fun getActiveMediaController(): MediaController? { - return mediaControllers?.first()?.also { log("chosen media controller: ${it.packageName}") } + return mediaControllers?.firstOrNull()?.also { log("chosen media controller: ${it.packageName}") } } private fun isMusicActive() = @@ -281,4 +281,4 @@ object VolumeKeyControlModuleHandlers { private fun Any.getObjectField(fieldName: String): Any { return XposedHelpers.getObjectField(this, fieldName) } -} \ No newline at end of file +} From 7a7503374606ac3dc0ca61a4d67ff96d9f4a6b84 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 22 Nov 2024 22:21:31 +0300 Subject: [PATCH 08/13] fix typo --- .../VolumeKeyControlModuleHandlers.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt index 4aed641..000e5d3 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt @@ -108,7 +108,7 @@ object VolumeKeyControlModuleHandlers { if (!powerManager.isInteractive) { return false } - log("displays count: $${displayManager.displays.size}") + log("displays count: ${displayManager.displays.size}") // TODO if (displayManager.displays.size > 1) { return true From b628598709cee0a1156a489fc743d7131d3e1012 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 23 Nov 2024 18:44:11 +0300 Subject: [PATCH 09/13] Bump version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 007c1cd..340761d 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ plugins { id 'org.jetbrains.kotlin.android' version '2.0.21' apply false } -String versionName = "1.15.0" -Integer versionCode = 7 +String versionName = "1.15.1" +Integer versionCode = 8 rootProject.ext.set("appVersionName", versionName) rootProject.ext.set("appVersionCode", versionCode) From 4cff436f8be16cea47714e59c520f040c5bcaeef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:33:45 +0000 Subject: [PATCH 10/13] Bump org.jetbrains.kotlin.android from 2.0.21 to 2.1.0 Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 2.0.21 to 2.1.0. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v2.1.0/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.0.21...v2.1.0) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.android dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 340761d..6f99b8d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'com.android.application' version '8.7.2' apply false id 'com.android.library' version '8.7.2' apply false - id 'org.jetbrains.kotlin.android' version '2.0.21' apply false + id 'org.jetbrains.kotlin.android' version '2.1.0' apply false } String versionName = "1.15.1" From 77fa6202aef149f06e4ff8333510ab045ecd2d6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:34:20 +0000 Subject: [PATCH 11/13] Bump org.jetbrains.kotlin:kotlin-bom from 2.0.21 to 2.1.0 Bumps [org.jetbrains.kotlin:kotlin-bom](https://github.com/JetBrains/kotlin) from 2.0.21 to 2.1.0. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v2.1.0/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.0.21...v2.1.0) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-bom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 08c1103..41bc54a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ android { } dependencies { - implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.0.21")) + implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.1.0")) implementation 'androidx.core:core-ktx:1.15.0' // Xposed Framework API dependencies From 3e10064a751558fc7f925704e6903eb2fbf42263 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:24:03 +0000 Subject: [PATCH 12/13] Bump com.android.application from 8.7.2 to 8.7.3 Bumps com.android.application from 8.7.2 to 8.7.3. --- updated-dependencies: - dependency-name: com.android.application dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6f99b8d..3e5c6bb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.7.2' apply false + id 'com.android.application' version '8.7.3' apply false id 'com.android.library' version '8.7.2' apply false id 'org.jetbrains.kotlin.android' version '2.1.0' apply false } From 098f08df2025d6352593d0e96b5c002bd09e2236 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 06:28:22 +0000 Subject: [PATCH 13/13] Bump com.android.library from 8.7.2 to 8.7.3 Bumps com.android.library from 8.7.2 to 8.7.3. --- updated-dependencies: - dependency-name: com.android.library dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3e5c6bb..ff07c29 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { id 'com.android.application' version '8.7.3' apply false - id 'com.android.library' version '8.7.2' apply false + id 'com.android.library' version '8.7.3' apply false id 'org.jetbrains.kotlin.android' version '2.1.0' apply false }