From fbf580866d3f31108081d17081b20d29c0fe7efc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:43:36 +0000 Subject: [PATCH 01/43] Bump com.android.application from 8.6.0 to 8.6.1 Bumps com.android.application from 8.6.0 to 8.6.1. --- 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 2e25c41..66cb5ca 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.6.0' apply false + id 'com.android.application' version '8.6.1' apply false id 'com.android.library' version '8.6.0' apply false id 'org.jetbrains.kotlin.android' version '2.0.20' apply false } From dce3e94c3509d3e4a6b964e5f104764e4cf1cb84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:34:14 +0000 Subject: [PATCH 02/43] Bump com.android.library from 8.6.0 to 8.6.1 Bumps com.android.library from 8.6.0 to 8.6.1. --- 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 66cb5ca..12e116b 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.6.1' apply false - id 'com.android.library' version '8.6.0' apply false + id 'com.android.library' version '8.6.1' apply false id 'org.jetbrains.kotlin.android' version '2.0.20' apply false } From bfb202035eb9edda84cc3135482f4a87a7fe2954 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:22:56 +0000 Subject: [PATCH 03/43] Bump com.android.application from 8.6.1 to 8.7.0 Bumps com.android.application from 8.6.1 to 8.7.0. --- updated-dependencies: - dependency-name: com.android.application 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 1ff4a1b..300d9d5 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.6.1' apply false + id 'com.android.application' version '8.7.0' apply false id 'com.android.library' version '8.6.1' apply false id 'org.jetbrains.kotlin.android' version '2.0.20' apply false } From d07c49d6b9f954de5ddd304fd4fe18d27788ed90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 06:01:21 +0000 Subject: [PATCH 04/43] Bump com.android.library from 8.6.1 to 8.7.0 Bumps com.android.library from 8.6.1 to 8.7.0. --- updated-dependencies: - dependency-name: com.android.library 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 300d9d5..b2101d4 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.0' apply false - id 'com.android.library' version '8.6.1' apply false + id 'com.android.library' version '8.7.0' apply false id 'org.jetbrains.kotlin.android' version '2.0.20' apply false } From a4ff0fe2bb782ce91be4183b15cde4b71a997822 Mon Sep 17 00:00:00 2001 From: Nikita Date: Wed, 2 Oct 2024 09:03:54 +0300 Subject: [PATCH 05/43] Update gradle --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index be4176d..06760df 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Thu Feb 03 20:40:17 MSK 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From e39f334b1faf320baedcc0bb11972a5c21334652 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:25:36 +0000 Subject: [PATCH 06/43] Bump org.jetbrains.kotlin.android from 2.0.20 to 2.0.21 Bumps [org.jetbrains.kotlin.android](https://github.com/JetBrains/kotlin) from 2.0.20 to 2.0.21. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v2.0.21/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.0.20...v2.0.21) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.android 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 b2101d4..0c1c51e 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'com.android.application' version '8.7.0' apply false id 'com.android.library' version '8.7.0' apply false - id 'org.jetbrains.kotlin.android' version '2.0.20' apply false + id 'org.jetbrains.kotlin.android' version '2.0.21' apply false } String versionName = "1.14.3" From 863040071ac9d2ad9678df3dde1e32084c3c9f9b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:26:25 +0000 Subject: [PATCH 07/43] Bump org.jetbrains.kotlin:kotlin-bom from 2.0.20 to 2.0.21 Bumps [org.jetbrains.kotlin:kotlin-bom](https://github.com/JetBrains/kotlin) from 2.0.20 to 2.0.21. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v2.0.21/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.0.20...v2.0.21) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-bom dependency-type: direct:production update-type: version-update:semver-patch ... 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 7da0810..99cb1a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ android { } dependencies { - implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.0.20")) + implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.0.21")) implementation 'androidx.core:core-ktx:1.13.1' // Xposed Framework API dependencies From 23a0b7774275f000de2da49b8b659e4a62b0d6b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:01:24 +0000 Subject: [PATCH 08/43] Bump com.android.application from 8.7.0 to 8.7.1 Bumps com.android.application from 8.7.0 to 8.7.1. --- 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 0c1c51e..042b261 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.0' apply false + id 'com.android.application' version '8.7.1' apply false id 'com.android.library' version '8.7.0' apply false id 'org.jetbrains.kotlin.android' version '2.0.21' apply false } From bfdc47f9e0bd890c8921fd8f2ec7956fd4a0ef45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 08:49:08 +0000 Subject: [PATCH 09/43] Bump com.android.library from 8.7.0 to 8.7.1 Bumps com.android.library from 8.7.0 to 8.7.1. --- 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 042b261..a49e34d 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.1' apply false - id 'com.android.library' version '8.7.0' apply false + id 'com.android.library' version '8.7.1' apply false id 'org.jetbrains.kotlin.android' version '2.0.21' apply false } From 9342b349fa2a1efcf28c1c8ffcf16d1b5568aa79 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 19 Oct 2024 16:10:03 +0300 Subject: [PATCH 10/43] Add test branch to CI build --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5e0b45..64b6ac9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,7 @@ on: branches: - main - dev + - test env: BUILD_TYPE: ${{ inputs.build-type || 'debug' }} From ea5a671b61f80041d739e16ed91bc067bfa9bf16 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 19 Oct 2024 16:56:01 +0300 Subject: [PATCH 11/43] Fix xposed repo --- settings.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/settings.gradle b/settings.gradle index 35ecb80..801f447 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,12 +8,12 @@ pluginManagement { dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - google() - jcenter() - mavenCentral() maven { url "https://api.xposed.info/" } + google() + jcenter() + mavenCentral() } } rootProject.name = "VolumeKeyMusicManagerModule" From d5acac5c66d95b66327979d03f46ece2afadd5c9 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 19 Oct 2024 16:58:50 +0300 Subject: [PATCH 12/43] Add vibration when sending audio event --- .../VolumeControlModule.kt | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt index fa32b87..f2fe1b3 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt @@ -1,11 +1,16 @@ package ru.hepolise.volumekeytrackcontrolmodule +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.media.AudioManager +import android.os.Build import android.os.Handler import android.os.PowerManager import android.os.SystemClock +import android.os.VibrationEffect +import android.os.Vibrator +import android.os.VibratorManager import android.util.Log import android.view.KeyEvent import android.view.ViewConfiguration @@ -81,6 +86,7 @@ class VolumeControlModule : IXposedHookLoadPackage { // private static int mButtonsPressed = 0; private lateinit var mAudioManager: AudioManager private lateinit var mPowerManager: PowerManager + private lateinit var mVibrator: Vibrator private fun log(text: String) { if (BuildConfig.DEBUG) XposedBridge.log(text) } @@ -172,10 +178,14 @@ class VolumeControlModule : IXposedHookLoadPackage { log("!mPowerManager.isInteractive: ${!mPowerManager.isInteractive}, required: true") log("mIsDownPressed: ${mIsDownPressed}") log("mIsUpPressed: ${mIsUpPressed}") - log("needHook: ${(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) - && event.flags and KeyEvent.FLAG_FROM_SYSTEM != 0 - && (!mPowerManager.isInteractive || mIsDownPressed || mIsUpPressed) - && mAudioManager.mode == AudioManager.MODE_NORMAL}") + log( + "needHook: ${ + (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) + && event.flags and KeyEvent.FLAG_FROM_SYSTEM != 0 + && (!mPowerManager.isInteractive || mIsDownPressed || mIsUpPressed) + && mAudioManager.mode == AudioManager.MODE_NORMAL + }" + ) return (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) && event.flags and KeyEvent.FLAG_FROM_SYSTEM != 0 && (!mPowerManager.isInteractive || mIsDownPressed || mIsUpPressed) @@ -183,10 +193,20 @@ class VolumeControlModule : IXposedHookLoadPackage { } private fun initManagers(ctx: Context) { - mAudioManager = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager? - ?: throw NullPointerException("Unable to obtain audio service") - mPowerManager = ctx.getSystemService(Context.POWER_SERVICE) as PowerManager? - ?: throw NullPointerException("Unable to obtain power service") + with(ctx) { + mAudioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager? + ?: throw NullPointerException("Unable to obtain audio service") + mPowerManager = getSystemService(Context.POWER_SERVICE) as PowerManager? + ?: throw NullPointerException("Unable to obtain power service") + mVibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = + getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator + } else { + @Suppress("DEPRECATION") + getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + } + } } private val isMusicActive: Boolean @@ -207,6 +227,7 @@ class VolumeControlModule : IXposedHookLoadPackage { return false } + @SuppressLint("MissingPermission") private fun sendMediaButtonEvent(code: Int) { val eventTime = SystemClock.uptimeMillis() val keyIntent = Intent(Intent.ACTION_MEDIA_BUTTON, null) @@ -216,6 +237,22 @@ class VolumeControlModule : IXposedHookLoadPackage { keyEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_UP) keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent) dispatchMediaButtonEvent(keyEvent) + triggerVibration() + } + + @SuppressLint("MissingPermission") + private fun triggerVibration() { + val millis = 50L + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mVibrator.vibrate( + VibrationEffect.createOneShot( + millis, + VibrationEffect.DEFAULT_AMPLITUDE + ) + ) + } else { + mVibrator.vibrate(millis) // Deprecated in API 26 but still works for lower versions + } } private fun dispatchMediaButtonEvent(keyEvent: KeyEvent) { From bf664d756a1dff35562c009448324a55ad18e55b Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 19 Oct 2024 17:05:54 +0300 Subject: [PATCH 13/43] Remove SuppressLint annotation --- .../hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt index f2fe1b3..915892d 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt @@ -227,7 +227,6 @@ class VolumeControlModule : IXposedHookLoadPackage { return false } - @SuppressLint("MissingPermission") private fun sendMediaButtonEvent(code: Int) { val eventTime = SystemClock.uptimeMillis() val keyIntent = Intent(Intent.ACTION_MEDIA_BUTTON, null) From 77b21e1c3d6744545c5d790ffe3014fd92c0c9c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:54:36 +0000 Subject: [PATCH 14/43] Bump androidx.core:core-ktx from 1.13.1 to 1.15.0 Bumps androidx.core:core-ktx from 1.13.1 to 1.15.0. --- updated-dependencies: - dependency-name: androidx.core:core-ktx 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 99cb1a3..0836abd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -32,7 +32,7 @@ android { dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom:2.0.21")) - implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.core:core-ktx:1.15.0' // Xposed Framework API dependencies compileOnly 'de.robv.android.xposed:api:82' From 0dd45fdb107a2c2b469f2730e679ac35f54e3db4 Mon Sep 17 00:00:00 2001 From: Nikita Date: Thu, 31 Oct 2024 18:45:03 +0300 Subject: [PATCH 15/43] Bump SDK --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0836abd..a3e86b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,12 +4,12 @@ plugins { } android { - compileSdk 34 + compileSdk 35 defaultConfig { applicationId "ru.hepolise.volumekeymusicmanagermodule" minSdk 25 - targetSdk 34 + targetSdk 35 versionCode = Integer.parseInt(rootProject.ext["appVersionCode"].toString()) versionName = rootProject.ext["appVersionName"].toString() } From 04e24b82f4bc52b3748b35726db11d42598f8e8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:28:34 +0000 Subject: [PATCH 16/43] Bump com.android.library from 8.7.1 to 8.7.2 Bumps com.android.library from 8.7.1 to 8.7.2. --- 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 a49e34d..6816b44 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.1' apply false - id 'com.android.library' version '8.7.1' apply false + id 'com.android.library' version '8.7.2' apply false id 'org.jetbrains.kotlin.android' version '2.0.21' apply false } From d8fb6db17a8d4b808e7f8cd6b684688c5ead9bf6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Nov 2024 09:41:50 +0000 Subject: [PATCH 17/43] Bump com.android.application from 8.7.1 to 8.7.2 Bumps com.android.application from 8.7.1 to 8.7.2. --- 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 6816b44..3087f85 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.1' apply false + 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 } From eed3732b035968641ae3b0a9d915378be968bef7 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 8 Nov 2024 19:38:09 +0300 Subject: [PATCH 18/43] Bump version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 3087f85..007c1cd 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.14.3" -Integer versionCode = 6 +String versionName = "1.15.0" +Integer versionCode = 7 rootProject.ext.set("appVersionName", versionName) rootProject.ext.set("appVersionCode", versionCode) From 755d6a9a03a497f87491a5b7153c4994ee9b5356 Mon Sep 17 00:00:00 2001 From: Nikita Date: Fri, 8 Nov 2024 19:43:05 +0300 Subject: [PATCH 19/43] Add Android 15 source link --- .../volumekeytrackcontrolmodule/VolumeControlModule.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt index 915892d..ffe97ba 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt @@ -42,8 +42,9 @@ class VolumeControlModule : IXposedHookLoadPackage { log("Using HyperOS-specific method signature") } catch (e1: NoSuchMethodError) { try { - // Try the Android 14 (Upside Down Cake) signature + // Try the 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 XposedHelpers.findAndHookMethod( CLASS_PHONE_WINDOW_MANAGER, classLoader, "init", Context::class.java, CLASS_WINDOW_MANAGER_FUNCS, From 13279f0d6735df6e5ec1949268be5a5a197c6b63 Mon Sep 17 00:00:00 2001 From: coolcart <83957950+coolcart@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:51:37 +0100 Subject: [PATCH 20/43] Improve haptics on newer devices --- .../volumekeytrackcontrolmodule/VolumeControlModule.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt index ffe97ba..bc2047e 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt @@ -243,7 +243,14 @@ class VolumeControlModule : IXposedHookLoadPackage { @SuppressLint("MissingPermission") private fun triggerVibration() { val millis = 50L - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + mVibrator.vibrate( + VibrationEffect.createPredefined( + VibrationEffect.EFFECT_CLICK + ) + ) + } + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mVibrator.vibrate( VibrationEffect.createOneShot( millis, From 68ad9147c669f6fe9f619ee647f3a8c6a16b9c1b Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 19 Nov 2024 14:28:56 +0300 Subject: [PATCH 21/43] 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 020f940d041c04157f68a42da4f5c54c2870dcc8 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 19 Nov 2024 23:42:21 +0300 Subject: [PATCH 22/43] Refactor: remove xposed sources dependency --- app/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index a3e86b9..08c1103 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,5 +36,4 @@ dependencies { // Xposed Framework API dependencies compileOnly 'de.robv.android.xposed:api:82' - compileOnly 'de.robv.android.xposed:api:82:sources' } From b19c3e7c9df173168b5db68e85921334440a2eaa Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 19 Nov 2024 23:42:45 +0300 Subject: [PATCH 23/43] Refactor: remove jcenter repository --- settings.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 801f447..9eef2a5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,7 +12,6 @@ dependencyResolutionManagement { url "https://api.xposed.info/" } google() - jcenter() mavenCentral() } } From 3e34e326b2896a9b3580e03e41868971d6d407ca Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 19 Nov 2024 23:49:28 +0300 Subject: [PATCH 24/43] Refactor module code --- .../volumekeytrackcontrolmodule/LogHelper.kt | 9 + .../VolumeControlModule.kt | 363 +++--------------- .../VolumeKeyHandlers.kt | 231 +++++++++++ .../extension/AudioManagerExtension.kt | 30 ++ .../extension/VibratorExtension.kt | 30 ++ .../model/HookInfo.kt | 21 + 6 files changed, 381 insertions(+), 303 deletions(-) create mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/LogHelper.kt create mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt create mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/AudioManagerExtension.kt create mode 100644 app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/VibratorExtension.kt create 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 new file mode 100644 index 0000000..9f0a0e4 --- /dev/null +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/LogHelper.kt @@ -0,0 +1,9 @@ +package ru.hepolise.volumekeytrackcontrolmodule + +import de.robv.android.xposed.XposedBridge + +object LogHelper { + fun log(text: String) { + if (BuildConfig.DEBUG) XposedBridge.log(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 bc2047e..85f3c4f 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeControlModule.kt @@ -1,28 +1,27 @@ package ru.hepolise.volumekeytrackcontrolmodule -import android.annotation.SuppressLint import android.content.Context -import android.content.Intent -import android.media.AudioManager -import android.os.Build -import android.os.Handler -import android.os.PowerManager -import android.os.SystemClock -import android.os.VibrationEffect -import android.os.Vibrator -import android.os.VibratorManager -import android.util.Log import android.view.KeyEvent -import android.view.ViewConfiguration import androidx.annotation.Keep import de.robv.android.xposed.IXposedHookLoadPackage -import de.robv.android.xposed.XC_MethodHook -import de.robv.android.xposed.XposedBridge 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 @Keep class VolumeControlModule : IXposedHookLoadPackage { + + companion object { + private const val CLASS_PHONE_WINDOW_MANAGER = + "com.android.server.policy.PhoneWindowManager" + private const val CLASS_IWINDOW_MANAGER = "android.view.IWindowManager" + private const val CLASS_WINDOW_MANAGER_FUNCS = + "com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs" + } + @Throws(Throwable::class) override fun handleLoadPackage(lpparam: LoadPackageParam) { if (lpparam.packageName != "android") { @@ -32,302 +31,60 @@ class VolumeControlModule : IXposedHookLoadPackage { } private fun init(classLoader: ClassLoader) { - try { - // Try the HyperOS-specific signature - XposedHelpers.findAndHookMethod( - CLASS_PHONE_WINDOW_MANAGER, classLoader, "init", - Context::class.java, CLASS_WINDOW_MANAGER_FUNCS, CLASS_IWINDOW_MANAGER, - handleConstructPhoneWindowManager - ) - log("Using HyperOS-specific method signature") - } catch (e1: NoSuchMethodError) { + 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" + ), + ) + + var foundMethod = false + for (hookInfo in hookInfoList) { try { - // Try the 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 XposedHelpers.findAndHookMethod( CLASS_PHONE_WINDOW_MANAGER, classLoader, "init", - Context::class.java, CLASS_WINDOW_MANAGER_FUNCS, - handleConstructPhoneWindowManager + *hookInfo.params, handleConstructPhoneWindowManager ) - log("Using Android 14 method signature") - } catch (e2: NoSuchMethodError) { - // Fallback to the Android 13 signature - // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/android13-dev/services/core/java/com/android/server/policy/PhoneWindowManager.java#1873 - XposedHelpers.findAndHookMethod( - CLASS_PHONE_WINDOW_MANAGER, classLoader, "init", - Context::class.java, CLASS_IWINDOW_MANAGER, CLASS_WINDOW_MANAGER_FUNCS, - handleConstructPhoneWindowManager - ) - log("Using Android 13 method signature") - } - } - - // 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 - ) - } - - companion object { - private const val CLASS_PHONE_WINDOW_MANAGER = - "com.android.server.policy.PhoneWindowManager" - private const val CLASS_IWINDOW_MANAGER = "android.view.IWindowManager" - private const val CLASS_WINDOW_MANAGER_FUNCS = - "com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs" - private var mIsLongPress = false - private var mIsDownPressed = false - private var mIsUpPressed = false - - // private static int mButtonsPressed = 0; - private lateinit var mAudioManager: AudioManager - private lateinit var mPowerManager: PowerManager - private lateinit var mVibrator: Vibrator - private fun log(text: String) { - if (BuildConfig.DEBUG) XposedBridge.log(text) - } - - private val handleInterceptKeyBeforeQueueing: XC_MethodHook = object : XC_MethodHook() { - override fun beforeHookedMethod(param: MethodHookParam) { - val event = param.args[0] as KeyEvent - val keyCode = event.keyCode - initManagers(XposedHelpers.getObjectField(param.thisObject, "mContext") as Context) - if (needHook(keyCode, event)) { - if (event.action == KeyEvent.ACTION_DOWN) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) mIsDownPressed = true - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) mIsUpPressed = true - log("down action received, down: $mIsDownPressed, up: $mIsUpPressed") - mIsLongPress = false - if (mIsUpPressed && mIsDownPressed) { - log("aborting delayed skip") - handleVolumeSkipPressAbort(param.thisObject) - } else { - // only one button pressed - if (isMusicActive) { - log("music is active, creating delayed skip") - handleVolumeSkipPress(param.thisObject, keyCode) - } - log("creating delayed play pause") - handleVolumePlayPausePress(param.thisObject) - } - } else { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) mIsDownPressed = false - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) mIsUpPressed = false - log("up action received, down: $mIsDownPressed, up: $mIsUpPressed") - handleVolumeAllPressAbort(param.thisObject) - if (!mIsLongPress && isMusicActive) { - log("adjusting music volume") - mAudioManager.adjustStreamVolume( - AudioManager.STREAM_MUSIC, - if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) AudioManager.ADJUST_RAISE else AudioManager.ADJUST_LOWER, - 0 - ) - } - } - param.setResult(0) - } + foundMethod = true + log(hookInfo.logMessage) + break + } catch (ignored: NoSuchMethodError) { } } - private val handleConstructPhoneWindowManager: XC_MethodHook = object : XC_MethodHook() { - override fun afterHookedMethod(param: MethodHookParam) { - val mVolumeUpLongPress = Runnable { - log("sending next") - mIsLongPress = true - sendMediaButtonEvent(KeyEvent.KEYCODE_MEDIA_NEXT) - } - val mVolumeDownLongPress = Runnable { - log("sending prev") - mIsLongPress = true - sendMediaButtonEvent(KeyEvent.KEYCODE_MEDIA_PREVIOUS) - } - val mVolumeBothLongPress = Runnable { - if (mIsUpPressed && mIsDownPressed) { - log("sending play/pause") - mIsLongPress = true - sendMediaButtonEvent(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) - } else { - log("NOT sending play/pause, down: $mIsDownPressed, up: $mIsUpPressed") - } - } - XposedHelpers.setAdditionalInstanceField( - param.thisObject, - "mVolumeUpLongPress", - mVolumeUpLongPress - ) - XposedHelpers.setAdditionalInstanceField( - param.thisObject, - "mVolumeDownLongPress", - mVolumeDownLongPress - ) - XposedHelpers.setAdditionalInstanceField( - param.thisObject, - "mVolumeBothLongPress", - mVolumeBothLongPress - ) - } - } - - private fun needHook(keyCode: Int, event: KeyEvent): Boolean { - log("========") - log("current audio manager mode: ${mAudioManager.mode}, required: ${AudioManager.MODE_NORMAL}") - log("keyCode: ${keyCode}, required: ${KeyEvent.KEYCODE_VOLUME_DOWN} or ${KeyEvent.KEYCODE_VOLUME_UP}") - log("!mPowerManager.isInteractive: ${!mPowerManager.isInteractive}, required: true") - log("mIsDownPressed: ${mIsDownPressed}") - log("mIsUpPressed: ${mIsUpPressed}") - log( - "needHook: ${ - (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) - && event.flags and KeyEvent.FLAG_FROM_SYSTEM != 0 - && (!mPowerManager.isInteractive || mIsDownPressed || mIsUpPressed) - && mAudioManager.mode == AudioManager.MODE_NORMAL - }" - ) - return (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) - && event.flags and KeyEvent.FLAG_FROM_SYSTEM != 0 - && (!mPowerManager.isInteractive || mIsDownPressed || mIsUpPressed) - && mAudioManager.mode == AudioManager.MODE_NORMAL - } - - private fun initManagers(ctx: Context) { - with(ctx) { - mAudioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager? - ?: throw NullPointerException("Unable to obtain audio service") - mPowerManager = getSystemService(Context.POWER_SERVICE) as PowerManager? - ?: throw NullPointerException("Unable to obtain power service") - mVibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val vibratorManager = - getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager - vibratorManager.defaultVibrator - } else { - @Suppress("DEPRECATION") - getSystemService(Context.VIBRATOR_SERVICE) as Vibrator - } - } - } - - private val isMusicActive: Boolean - get() { - // check local - if (mAudioManager.isMusicActive) return true - // check remote - try { - if (XposedHelpers.callMethod( - mAudioManager, - "isMusicActiveRemotely" - ) as Boolean - ) return true - } catch (t: Throwable) { - t.localizedMessage?.let { Log.e("xposed", it) } - t.printStackTrace() - } - return false - } - - private fun 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) - triggerVibration() - } - - @SuppressLint("MissingPermission") - private fun triggerVibration() { - val millis = 50L - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - mVibrator.vibrate( - VibrationEffect.createPredefined( - VibrationEffect.EFFECT_CLICK - ) - ) - } - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - mVibrator.vibrate( - VibrationEffect.createOneShot( - millis, - VibrationEffect.DEFAULT_AMPLITUDE - ) - ) - } else { - mVibrator.vibrate(millis) // Deprecated in API 26 but still works for lower versions - } - } - - private fun dispatchMediaButtonEvent(keyEvent: KeyEvent) { - try { - mAudioManager.dispatchMediaKeyEvent(keyEvent) - } catch (t: Throwable) { - t.localizedMessage?.let { Log.e("xposed", it) } - t.printStackTrace() - } - } - - private fun handleVolumePlayPausePress(phoneWindowManager: Any) { - val mHandler = XposedHelpers.getObjectField(phoneWindowManager, "mHandler") as Handler - val mVolumeBothLongPress = XposedHelpers.getAdditionalInstanceField( - phoneWindowManager, - "mVolumeBothLongPress" - ) as Runnable - mHandler.postDelayed( - mVolumeBothLongPress, - ViewConfiguration.getLongPressTimeout().toLong() - ) - } - - private fun handleVolumeSkipPress(phoneWindowManager: Any, keycode: Int) { - val mHandler = XposedHelpers.getObjectField(phoneWindowManager, "mHandler") as Handler - val mVolumeUpLongPress = XposedHelpers.getAdditionalInstanceField( - phoneWindowManager, - "mVolumeUpLongPress" - ) as Runnable - val mVolumeDownLongPress = XposedHelpers.getAdditionalInstanceField( - phoneWindowManager, - "mVolumeDownLongPress" - ) as Runnable - mHandler.postDelayed( - if (keycode == KeyEvent.KEYCODE_VOLUME_UP) mVolumeUpLongPress else mVolumeDownLongPress, - ViewConfiguration.getLongPressTimeout().toLong() + 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 + XposedHelpers.findAndHookMethod( + CLASS_PHONE_WINDOW_MANAGER, + classLoader, + "interceptKeyBeforeQueueing", + KeyEvent::class.java, + Int::class.javaPrimitiveType, + handleInterceptKeyBeforeQueueing ) - } - - private fun handleVolumeSkipPressAbort(phoneWindowManager: Any) { - log("aborting skip") - val mHandler = XposedHelpers.getObjectField(phoneWindowManager, "mHandler") as Handler - val mVolumeUpLongPress = XposedHelpers.getAdditionalInstanceField( - phoneWindowManager, - "mVolumeUpLongPress" - ) as Runnable - val mVolumeDownLongPress = XposedHelpers.getAdditionalInstanceField( - phoneWindowManager, - "mVolumeDownLongPress" - ) as Runnable - mHandler.removeCallbacks(mVolumeUpLongPress) - mHandler.removeCallbacks(mVolumeDownLongPress) - } - - private fun handleVolumePlayPausePressAbort(phoneWindowManager: Any) { - log("aborting play/pause") - val mHandler = XposedHelpers.getObjectField(phoneWindowManager, "mHandler") as Handler - val mVolumeBothLongPress = XposedHelpers.getAdditionalInstanceField( - phoneWindowManager, - "mVolumeBothLongPress" - ) as Runnable - mHandler.removeCallbacks(mVolumeBothLongPress) - } - - private fun handleVolumeAllPressAbort(phoneWindowManager: Any) { - log("aborting all") - handleVolumePlayPausePressAbort(phoneWindowManager) - handleVolumeSkipPressAbort(phoneWindowManager) + } else { + log("Method hook failed for init!") } } } diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt new file mode 100644 index 0000000..77a9eb1 --- /dev/null +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyHandlers.kt @@ -0,0 +1,231 @@ +package ru.hepolise.volumekeytrackcontrolmodule + +import android.content.Context +import android.media.AudioManager +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.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.LogHelper.log +import ru.hepolise.volumekeytrackcontrolmodule.extension.AudioManagerExtension.sendMediaButtonEvent +import ru.hepolise.volumekeytrackcontrolmodule.extension.VibratorExtension.triggerVibration + +object VolumeKeyHandlers { + + private const val VOLUME_UP_LONG_PRESS = "mVolumeUpLongPress" + private const val VOLUME_DOWN_LONG_PRESS = "mVolumeDownLongPress" + private const val VOLUME_BOTH_LONG_PRESS = "mVolumeBothLongPress" + + private val TIMEOUT = ViewConfiguration.getLongPressTimeout().toLong() + + private var isLongPress = false + private var isDownPressed = false + private var isUpPressed = false + + private lateinit var audioManager: AudioManager + private lateinit var powerManager: PowerManager + private lateinit var vibrator: Vibrator + + 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) + if (needHook(keyCode, event)) { + doHook(keyCode, event, param) + } + } + } + + 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, + ).forEach { (key, runnable) -> + XposedHelpers.setAdditionalInstanceField(param.thisObject, key, runnable) + } + } + } + + private fun needHook(keyCode: Int, event: KeyEvent): Boolean { + 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("isDownPressed: $isDownPressed") + log("isUpPressed: $isUpPressed") + val needHook = + (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) + && event.flags and KeyEvent.FLAG_FROM_SYSTEM != 0 + && (!powerManager.isInteractive || isDownPressed || isUpPressed) + && audioManager.mode == AudioManager.MODE_NORMAL + log("needHook: $needHook") + return needHook + } + + private fun initManagers(ctx: Context) { + with(ctx) { + 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") + vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = + getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator + } else { + @Suppress("DEPRECATION") + getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + } + } + } + + private fun doHook(keyCode: Int, event: KeyEvent, param: MethodHookParam) { + if (event.action == KeyEvent.ACTION_DOWN) { + handleDownAction(keyCode, param) + } else { + handleUpAction(keyCode, param) + } + param.setResult(0) + } + + private fun handleDownAction(keyCode: Int, param: MethodHookParam) { + when (keyCode) { + KeyEvent.KEYCODE_VOLUME_DOWN -> isDownPressed = true + KeyEvent.KEYCODE_VOLUME_UP -> isUpPressed = true + } + log("down action received, down: $isDownPressed, up: $isUpPressed") + isLongPress = false + if (isUpPressed && isDownPressed) { + log("aborting delayed skip") + handleVolumeSkipPressAbort(param.thisObject) + } else { + // only one button pressed + if (isMusicActive) { + log("music is active, creating delayed skip") + handleVolumeSkipPress(param.thisObject, keyCode) + } + log("creating delayed play pause") + handleVolumePlayPausePress(param.thisObject) + } + } + + private fun handleUpAction(keyCode: Int, param: MethodHookParam) { + when (keyCode) { + KeyEvent.KEYCODE_VOLUME_DOWN -> isDownPressed = false + KeyEvent.KEYCODE_VOLUME_UP -> isUpPressed = false + } + log("up action received, down: $isDownPressed, up: $isUpPressed") + handleVolumeAllPressAbort(param.thisObject) + if (!isLongPress && isMusicActive) { + log("adjusting music volume") + val direction = when (keyCode) { + KeyEvent.KEYCODE_VOLUME_UP -> AudioManager.ADJUST_RAISE + KeyEvent.KEYCODE_VOLUME_DOWN -> AudioManager.ADJUST_LOWER + else -> return + } + audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, 0) + } + } + + 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() + } + return false + } + + private fun sendMediaButtonEventAndTriggerVibration(code: Int) { + audioManager.sendMediaButtonEvent(code) + vibrator.triggerVibration() + } + + private fun handleVolumePlayPausePress(instance: Any) { + val handler = instance.getHandler() + handler.postDelayed(getRunnable(instance, VOLUME_BOTH_LONG_PRESS), TIMEOUT) + } + + private fun handleVolumeSkipPress(instance: Any, keyCode: Int) { + val handler = instance.getHandler() + handler.postDelayed( + when (keyCode) { + KeyEvent.KEYCODE_VOLUME_UP -> getRunnable(instance, VOLUME_UP_LONG_PRESS) + KeyEvent.KEYCODE_VOLUME_DOWN -> getRunnable(instance, VOLUME_DOWN_LONG_PRESS) + else -> return + }, + TIMEOUT + ) + } + + private fun handleVolumeSkipPressAbort(instance: Any) { + log("aborting skip") + val handler = instance.getHandler() + listOf(VOLUME_UP_LONG_PRESS, VOLUME_DOWN_LONG_PRESS).forEach { + handler.removeCallbacks(getRunnable(instance, it)) + } + } + + private fun handleVolumePlayPausePressAbort(instance: Any) { + log("aborting play/pause") + val handler = instance.getHandler() + handler.removeCallbacks(getRunnable(instance, VOLUME_BOTH_LONG_PRESS)) + } + + private fun handleVolumeAllPressAbort(phoneWindowManager: Any) { + log("aborting all") + handleVolumePlayPausePressAbort(phoneWindowManager) + handleVolumeSkipPressAbort(phoneWindowManager) + } + + private fun getRunnable(instance: Any, fieldName: String): Runnable { + return XposedHelpers.getAdditionalInstanceField(instance, fieldName) as Runnable + } + + private fun Any.getHandler(): Handler { + return getObjectField("mHandler") as Handler + } + + private fun Any.getObjectField(fieldName: String): Any { + return XposedHelpers.getObjectField(this, fieldName) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/AudioManagerExtension.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/AudioManagerExtension.kt new file mode 100644 index 0000000..788761f --- /dev/null +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/AudioManagerExtension.kt @@ -0,0 +1,30 @@ +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 diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/VibratorExtension.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/VibratorExtension.kt new file mode 100644 index 0000000..e760438 --- /dev/null +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/extension/VibratorExtension.kt @@ -0,0 +1,30 @@ +package ru.hepolise.volumekeytrackcontrolmodule.extension + +import android.annotation.SuppressLint +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator + +object VibratorExtension { + + @SuppressLint("MissingPermission") + fun Vibrator.triggerVibration() { + val millis = 50L + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + this.vibrate( + VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK) + ) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + this.vibrate( + VibrationEffect.createOneShot( + millis, + VibrationEffect.DEFAULT_AMPLITUDE + ) + ) + } else { + @Suppress("deprecation") + this.vibrate(millis) // Deprecated in API 26 but still works for lower versions + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/model/HookInfo.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/model/HookInfo.kt new file mode 100644 index 0000000..5922a49 --- /dev/null +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/model/HookInfo.kt @@ -0,0 +1,21 @@ +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 b82eb6611b69aa36c13838155b2fe72be99864c5 Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 19 Nov 2024 23:51:50 +0300 Subject: [PATCH 25/43] 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 26/43] 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 27/43] 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 28/43] 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 29/43] 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 30/43] 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 31/43] 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 32/43] 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 33/43] 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 34/43] 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 35/43] 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 36/43] 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 } From 9d1c48f9be04e0cf6edb6c2cba1d153f37f34640 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 21 Dec 2024 13:35:05 +0300 Subject: [PATCH 37/43] Fix: include more states to check if the music is active --- .../VolumeKeyControlModuleHandlers.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt index 000e5d3..6b04955 100644 --- a/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt +++ b/app/src/main/java/ru/hepolise/volumekeytrackcontrolmodule/VolumeKeyControlModuleHandlers.kt @@ -210,9 +210,15 @@ object VolumeKeyControlModuleHandlers { return mediaControllers?.firstOrNull()?.also { log("chosen media controller: ${it.packageName}") } } - private fun isMusicActive() = - getActiveMediaController()?.let { it.playbackState?.state == PlaybackState.STATE_PLAYING } - ?: false + private fun isMusicActive() = getActiveMediaController()?.let { + when (it.playbackState?.state) { + PlaybackState.STATE_PLAYING, + PlaybackState.STATE_FAST_FORWARDING, + PlaybackState.STATE_REWINDING, + PlaybackState.STATE_BUFFERING -> true + else -> false + } + } ?: false private fun sendMediaButtonEventAndTriggerVibration(keyCode: Int) { getActiveMediaController()?.transportControls?.also { controls -> From 1d800ff9e3d757eba6ebf53531907cda7eacbd96 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 4 Jan 2025 16:20:17 +0300 Subject: [PATCH 38/43] Update release.yml --- .github/workflows/release.yml | 39 +++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3952c22..03fefce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,7 @@ name: Publish Release on: - - workflow_dispatch + workflow_dispatch: jobs: call-build: @@ -9,17 +9,38 @@ jobs: secrets: inherit with: build-type: release + publish-release: runs-on: ubuntu-latest - needs: call-build + needs: call-build steps: - - uses: actions/download-artifact@v4 + - name: Download Build Artifacts + uses: actions/download-artifact@v4 with: name: Build Artifacts - - name: Publish APK - uses: marvinpinto/action-automatic-releases@latest + + - name: Create Release + id: create_release + uses: actions/create-release@v1 with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - files: | - *.apk - automatic_release_tag: "latest" + tag_name: latest + release_name: Release latest + body: | + Automatically generated release. + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload APKs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + for file in *.apk; do + echo "Uploading $file..." + curl -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/vnd.android.package-archive" \ + --data-binary @"$file" \ + "$(jq -r '.upload_url' <<< '${{ steps.create_release.outputs.upload_url }}' | sed 's/{?name,label}//')?name=$(basename "$file")" + done From ec26a44ab9ef35ce6f0b608c21d83b3fb35466fd Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 4 Jan 2025 16:26:38 +0300 Subject: [PATCH 39/43] Update release.yml --- .github/workflows/release.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03fefce..8f70194 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,12 +19,16 @@ jobs: with: name: Build Artifacts + - name: Generate Dynamic Tag + id: tag + run: echo "TAG_NAME=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_ENV + - name: Create Release id: create_release uses: actions/create-release@v1 with: - tag_name: latest - release_name: Release latest + tag_name: ${{ env.TAG_NAME }} + release_name: Release ${{ env.TAG_NAME }} body: | Automatically generated release. draft: false From 34f3c33e0369d08cd93e13bac5c06f8bb53acdca Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 4 Jan 2025 16:35:14 +0300 Subject: [PATCH 40/43] Bump version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index ff07c29..a15e35e 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ plugins { id 'org.jetbrains.kotlin.android' version '2.1.0' apply false } -String versionName = "1.15.1" -Integer versionCode = 8 +String versionName = "1.15.2" +Integer versionCode = 9 rootProject.ext.set("appVersionName", versionName) rootProject.ext.set("appVersionCode", versionCode) From 3b49e6d006a880319e5bf0d6664b0dda314a146d Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 4 Jan 2025 16:40:37 +0300 Subject: [PATCH 41/43] Update release.yml --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f70194..112281f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,9 +42,10 @@ jobs: run: | for file in *.apk; do echo "Uploading $file..." + upload_url=$(jq -r '.upload_url' <<< "${{ steps.create_release.outputs.upload_url }}" | sed 's/{?name,label}//') curl -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Content-Type: application/vnd.android.package-archive" \ --data-binary @"$file" \ - "$(jq -r '.upload_url' <<< '${{ steps.create_release.outputs.upload_url }}' | sed 's/{?name,label}//')?name=$(basename "$file")" + "$upload_url?name=$(basename "$file")" done From 12789cb2bafd7c0b5e94315313be45f371051184 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 4 Jan 2025 16:44:20 +0300 Subject: [PATCH 42/43] Update release.yml --- .github/workflows/release.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 112281f..38837a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,16 +36,24 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Log Create Release Output + run: echo "${{ steps.create_release.outputs.upload_url }}" + - name: Upload APKs env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | + echo "Release Upload URL: ${!steps.create_release.outputs.upload_url}" for file in *.apk; do echo "Uploading $file..." - upload_url=$(jq -r '.upload_url' <<< "${{ steps.create_release.outputs.upload_url }}" | sed 's/{?name,label}//') + upload_url="${{ steps.create_release.outputs.upload_url }}" + echo "Extracted upload URL: $upload_url" + # Clean up the URL + clean_url=$(echo "$upload_url" | sed 's/{?name,label}//') + echo "Cleaned upload URL: $clean_url" curl -X POST \ -H "Authorization: token $GITHUB_TOKEN" \ -H "Content-Type: application/vnd.android.package-archive" \ --data-binary @"$file" \ - "$upload_url?name=$(basename "$file")" + "$clean_url?name=$(basename "$file")" done From 42c9b98c4d0a8db3e9a3ec25b2b11a69f210f6e9 Mon Sep 17 00:00:00 2001 From: Nikita Date: Sat, 4 Jan 2025 16:48:47 +0300 Subject: [PATCH 43/43] Update release.yml --- .github/workflows/release.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 38837a7..1ff5c61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,14 +36,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Log Create Release Output - run: echo "${{ steps.create_release.outputs.upload_url }}" - - name: Upload APKs env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - echo "Release Upload URL: ${!steps.create_release.outputs.upload_url}" + echo "Release Upload URL: ${{ steps.create_release.outputs.upload_url }}" for file in *.apk; do echo "Uploading $file..." upload_url="${{ steps.create_release.outputs.upload_url }}"