diff --git a/README.md b/README.md index 9bc9877..8867bb8 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ A collection of small Xposed Modules. | [ResetAllNotificationChannels](ResetAllNotificationChannels) | [@binarynoise](https://github.com/binarynoise) | Reset all Notification Channels: vibrations, ringtones, importance etc. | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=resetAllNotificationChannels) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/de.binarynoise.resetAllNotificationChannels) | | [RotationControl](RotationControl) | [@programminghoch10](https://github.com/programminghoch10) | Force rotation for selected packages | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=RotationControl) [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/com.programminghoch10.RotationControl) | | [UpsideWifi](UpsideWifi) | [@programminghoch10](https://github.com/programminghoch10) | Turn the WiFi icon upside down | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=UpsideWifi) | +| [VolumeStepsIncrease](VolumeStepsIncrease) | [@programminghoch10](https://github.com/programminghoch10) | Increase the amount of volume steps | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=VolumeStepsIncrease) | ## License diff --git a/VolumeStepsIncrease/Readme.md b/VolumeStepsIncrease/Readme.md new file mode 100644 index 0000000..cfe8bb2 --- /dev/null +++ b/VolumeStepsIncrease/Readme.md @@ -0,0 +1,3 @@ +# VolumeStepsIncrease + +Increase the amount of volume steps. diff --git a/VolumeStepsIncrease/build.gradle.kts b/VolumeStepsIncrease/build.gradle.kts new file mode 100644 index 0000000..48b1ab2 --- /dev/null +++ b/VolumeStepsIncrease/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + alias(libs.plugins.buildlogic.android.application) + alias(libs.plugins.buildlogic.kotlin.android) +} + +android { + namespace = "com.programminghoch10.VolumeStepsIncrease" + + defaultConfig { + minSdk = 23 + targetSdk = 35 + } +} + +dependencies { + implementation(libs.androidx.preference.ktx) +} diff --git a/VolumeStepsIncrease/src/main/AndroidManifest.xml b/VolumeStepsIncrease/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b8980a2 --- /dev/null +++ b/VolumeStepsIncrease/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/VolumeStepsIncrease/src/main/assets/xposed_init b/VolumeStepsIncrease/src/main/assets/xposed_init new file mode 100644 index 0000000..2d70ba7 --- /dev/null +++ b/VolumeStepsIncrease/src/main/assets/xposed_init @@ -0,0 +1 @@ +com.programminghoch10.VolumeStepsIncrease.Hook diff --git a/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/Common.kt b/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/Common.kt new file mode 100644 index 0000000..93b579c --- /dev/null +++ b/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/Common.kt @@ -0,0 +1,61 @@ +package com.programminghoch10.VolumeStepsIncrease + +import com.programminghoch10.VolumeStepsIncrease.StockValues.MAX_STREAM_VOLUME +import com.programminghoch10.VolumeStepsIncrease.StockValues.MIN_STREAM_VOLUME +import com.programminghoch10.VolumeStepsIncrease.StockValues.STREAM_ALARM +import com.programminghoch10.VolumeStepsIncrease.StockValues.STREAM_MUSIC +import com.programminghoch10.VolumeStepsIncrease.StockValues.STREAM_SYSTEM +import com.programminghoch10.VolumeStepsIncrease.StockValues.STREAM_VOICE_CALL + +object Common { + const val SHARED_PREFERENCES_NAME = "streams" + + val STREAMS = StockValues::class.java.declaredFields.filter { it.name.startsWith("STREAM_") }.associate { it.name to it.getInt(null) } + + val systemPropertyToStream = mapOf( + "ro.config.vc_call_vol_steps" to STREAM_VOICE_CALL, + "ro.config.media_vol_steps" to STREAM_MUSIC, + "ro.config.alarm_vol_steps" to STREAM_ALARM, + "ro.config.system_vol_steps" to STREAM_SYSTEM, + ) + val streamsToSystemProperties = systemPropertyToStream.entries.associate { it.value to it.key } + + val moduleDefaultVolumeSteps = mapOf( + STREAM_MUSIC to MAX_STREAM_VOLUME[STREAM_MUSIC] * 2, + STREAM_VOICE_CALL to MAX_STREAM_VOLUME[STREAM_VOICE_CALL] * 2, + ) + + fun getSystemMaxVolumeSteps(stream: Int): Int { + val default = MAX_STREAM_VOLUME[stream] + if (streamsToSystemProperties.contains(stream)) { + val systemProperty = streamsToSystemProperties[stream] + try { + val SystemPropertiesClass = Class.forName("android.os.SystemProperties") + val getIntMethod = SystemPropertiesClass.getMethod("getInt", String::class.java, Int::class.java) + return getIntMethod.invoke(null, systemProperty, default) as Int + } catch (_: Exception) { + } + } + return default + } + + fun getModuleDefaultVolumeSteps(stream: Int): Int { + return moduleDefaultVolumeSteps[stream] ?: getSystemMaxVolumeSteps(stream) + } + + fun getModuleMaxVolumeSteps(stream: Int): Int { + return getSystemMaxVolumeSteps(stream) * 3 + } + + fun getSystemMinVolumeSteps(stream: Int): Int { + return MIN_STREAM_VOLUME[stream] + } + + fun getModuleMinVolumeSteps(stream: Int): Int { + return getSystemMinVolumeSteps(stream) + } + + fun getPreferenceKey(stream: Int): String { + return "STREAM_${stream}" + } +} diff --git a/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/Hook.kt b/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/Hook.kt new file mode 100644 index 0000000..a35707c --- /dev/null +++ b/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/Hook.kt @@ -0,0 +1,62 @@ +package com.programminghoch10.VolumeStepsIncrease + +import com.programminghoch10.VolumeStepsIncrease.Common.SHARED_PREFERENCES_NAME +import com.programminghoch10.VolumeStepsIncrease.Common.STREAMS +import com.programminghoch10.VolumeStepsIncrease.Common.getPreferenceKey +import com.programminghoch10.VolumeStepsIncrease.Common.systemPropertyToStream +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.XSharedPreferences +import de.robv.android.xposed.XposedBridge +import de.robv.android.xposed.XposedHelpers +import de.robv.android.xposed.callbacks.XC_LoadPackage +import de.robv.android.xposed.XC_MethodHook as MethodHook + +class Hook : IXposedHookLoadPackage { + override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { + when (lpparam.packageName) { + BuildConfig.APPLICATION_ID -> return + "android" -> hookAndroid(lpparam) + else -> hookApp(lpparam) + } + } + + fun hookAndroid(lpparam: XC_LoadPackage.LoadPackageParam) { + + val AudioServiceClass = XposedHelpers.findClass("com.android.server.audio.AudioService", lpparam.classLoader) + XposedBridge.hookAllConstructors(AudioServiceClass, object : MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val MAX_STREAM_VOLUME = XposedHelpers.getObjectField(param.thisObject, "MAX_STREAM_VOLUME") as IntArray + val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME) + STREAMS.filter { preferences.contains(getPreferenceKey(it.value)) } + .map { it.value to preferences.getInt(getPreferenceKey(it.value), MAX_STREAM_VOLUME[it.value]) } + .forEach { MAX_STREAM_VOLUME[it.first] = it.second } + } + }) + + hookApp(lpparam) + } + + fun hookApp(lpparam: XC_LoadPackage.LoadPackageParam) { + + val SystemPropertiesClass = XposedHelpers.findClass("android.os.SystemProperties", lpparam.classLoader) + + XposedHelpers.findAndHookMethod(SystemPropertiesClass, "getInt", String::class.java, Int::class.java, object : MethodHook() { + override fun afterHookedMethod(param: MethodHookParam) { + val key = param.args[0] as String + param.args[1] as Int + val result = param.result as Int + val streamInt = systemPropertyToStream[key] ?: return + val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME) + if (!preferences.contains(getPreferenceKey(streamInt))) return + param.result = preferences.getInt(getPreferenceKey(streamInt), result) + } + }) + + XposedHelpers.findAndHookMethod(SystemPropertiesClass, "getBoolean", String::class.java, Boolean::class.java, object : MethodHook() { + override fun beforeHookedMethod(param: MethodHookParam) { + val key = param.args[0] as String + if (key == "audio.safemedia.bypass") param.result = true + } + }) + } +} diff --git a/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/SettingsActivity.kt b/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/SettingsActivity.kt new file mode 100644 index 0000000..d683375 --- /dev/null +++ b/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/SettingsActivity.kt @@ -0,0 +1,78 @@ +package com.programminghoch10.VolumeStepsIncrease + +import kotlin.math.round +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceGroup +import androidx.preference.SeekBarPreference +import androidx.preference.children +import com.programminghoch10.VolumeStepsIncrease.Common.SHARED_PREFERENCES_NAME +import com.programminghoch10.VolumeStepsIncrease.Common.STREAMS +import com.programminghoch10.VolumeStepsIncrease.Common.getModuleDefaultVolumeSteps +import com.programminghoch10.VolumeStepsIncrease.Common.getModuleMaxVolumeSteps +import com.programminghoch10.VolumeStepsIncrease.Common.getModuleMinVolumeSteps +import com.programminghoch10.VolumeStepsIncrease.Common.getPreferenceKey +import com.programminghoch10.VolumeStepsIncrease.Common.getSystemMaxVolumeSteps + +class SettingsActivity : FragmentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.settings_activity) + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment()).commit() + } + actionBar?.setDisplayHomeAsUpEnabled(true) + } + + override fun onNavigateUp(): Boolean { + finish() + return super.onNavigateUp() + } + + class SettingsFragment : PreferenceFragmentCompat() { + @SuppressLint("WorldReadableFiles") + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.root_preferences, rootKey) + preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME + preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE + + for (stream in STREAMS) { + val preference = SeekBarPreference(requireContext()) + preference.key = getPreferenceKey(stream.value) + preference.title = stream.key.replace("STREAM_", "") + preference.min = getModuleMinVolumeSteps(stream.value) + preference.max = getModuleMaxVolumeSteps(stream.value) + preference.setDefaultValue(getModuleDefaultVolumeSteps(stream.value)) + preference.showSeekBarValue = true + preference.updatesContinuously = true + preference.setOnPreferenceChangeListener { preference, newValue -> + val factor = (newValue as Int).toDouble() / getSystemMaxVolumeSteps(stream.value) + preference.summary = arrayOf( + "factor=${factor.round(1)}x ", + //"systemMin=${getSystemMinVolumeSteps(stream.value)} ", + "systemMax=${getSystemMaxVolumeSteps(stream.value)} ", + ).joinToString(" ") + true + } + preferenceScreen.addPreference(preference) + preference.onPreferenceChangeListener?.onPreferenceChange(preference, preference.value) + } + preferenceScreen.setIconSpaceReservedRecursive(false) + } + + fun Preference.setIconSpaceReservedRecursive(iconSpaceReserved: Boolean) { + this.isIconSpaceReserved = iconSpaceReserved + if (this is PreferenceGroup) children.forEach { it.setIconSpaceReservedRecursive(iconSpaceReserved) } + } + + fun Double.round(decimals: Int = 0): Double { + var multiplier = 1.0 + repeat(decimals) { multiplier *= 10 } + return round(this * multiplier) / multiplier + } + } +} diff --git a/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/StockValues.kt b/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/StockValues.kt new file mode 100644 index 0000000..6bbfce1 --- /dev/null +++ b/VolumeStepsIncrease/src/main/java/com/programminghoch10/VolumeStepsIncrease/StockValues.kt @@ -0,0 +1,94 @@ +package com.programminghoch10.VolumeStepsIncrease + +import android.media.AudioManager +import androidx.annotation.Keep + +@Keep +object StockValues { + + // from com.android.server.media.AudioService + + /** Maximum volume index values for audio streams */ + var MAX_STREAM_VOLUME: IntArray = intArrayOf( + 5, // STREAM_VOICE_CALL + 7, // STREAM_SYSTEM + 7, // STREAM_RING + 15, // STREAM_MUSIC + 7, // STREAM_ALARM + 7, // STREAM_NOTIFICATION + 15, // STREAM_BLUETOOTH_SCO + 7, // STREAM_SYSTEM_ENFORCED + 15, // STREAM_DTMF + 15, // STREAM_TTS + 15, // STREAM_ACCESSIBILITY + 15, // STREAM_ASSISTANT + ) + + /** Minimum volume index values for audio streams */ + var MIN_STREAM_VOLUME: IntArray = intArrayOf( + 1, // STREAM_VOICE_CALL + 0, // STREAM_SYSTEM + 0, // STREAM_RING + 0, // STREAM_MUSIC + 1, // STREAM_ALARM + 0, // STREAM_NOTIFICATION + 0, // STREAM_BLUETOOTH_SCO + 0, // STREAM_SYSTEM_ENFORCED + 0, // STREAM_DTMF + 0, // STREAM_TTS + 1, // STREAM_ACCESSIBILITY + 0, // STREAM_ASSISTANT + ) + + // from android.media.AudioSystem + + const val STREAM_VOICE_CALL: Int = AudioManager.STREAM_VOICE_CALL + + /** @hide Used to identify the volume of audio streams for system sounds + */ + const val STREAM_SYSTEM: Int = AudioManager.STREAM_SYSTEM + + /** @hide Used to identify the volume of audio streams for the phone ring and message alerts + */ + const val STREAM_RING: Int = AudioManager.STREAM_RING + + /** @hide Used to identify the volume of audio streams for music playback + */ + const val STREAM_MUSIC: Int = AudioManager.STREAM_MUSIC + + /** @hide Used to identify the volume of audio streams for alarms + */ + const val STREAM_ALARM: Int = AudioManager.STREAM_ALARM + + /** @hide Used to identify the volume of audio streams for notifications + */ + const val STREAM_NOTIFICATION: Int = AudioManager.STREAM_NOTIFICATION + + /** @hide + * Used to identify the volume of audio streams for phone calls when connected on bluetooth + */ + @Deprecated("use {@link #STREAM_VOICE_CALL} instead ") + const val STREAM_BLUETOOTH_SCO: Int = 6 + + /** @hide Used to identify the volume of audio streams for enforced system sounds in certain + * countries (e.g camera in Japan) + */ + const val STREAM_SYSTEM_ENFORCED: Int = 7 + + /** @hide Used to identify the volume of audio streams for DTMF tones + */ + const val STREAM_DTMF: Int = AudioManager.STREAM_DTMF + + /** @hide Used to identify the volume of audio streams exclusively transmitted through the + * speaker (TTS) of the device + */ + const val STREAM_TTS: Int = 9 + + /** @hide Used to identify the volume of audio streams for accessibility prompts + */ + const val STREAM_ACCESSIBILITY: Int = AudioManager.STREAM_ACCESSIBILITY + + /** @hide Used to identify the volume of audio streams for virtual assistant + */ + const val STREAM_ASSISTANT: Int = 11 +} diff --git a/VolumeStepsIncrease/src/main/res/layout/settings_activity.xml b/VolumeStepsIncrease/src/main/res/layout/settings_activity.xml new file mode 100644 index 0000000..620409c --- /dev/null +++ b/VolumeStepsIncrease/src/main/res/layout/settings_activity.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/VolumeStepsIncrease/src/main/res/values-v21/themes.xml b/VolumeStepsIncrease/src/main/res/values-v21/themes.xml new file mode 100644 index 0000000..6ee0359 --- /dev/null +++ b/VolumeStepsIncrease/src/main/res/values-v21/themes.xml @@ -0,0 +1,4 @@ + + + + diff --git a/VolumeStepsIncrease/src/main/res/xml/root_preferences.xml b/VolumeStepsIncrease/src/main/res/xml/root_preferences.xml new file mode 100644 index 0000000..bdbca14 --- /dev/null +++ b/VolumeStepsIncrease/src/main/res/xml/root_preferences.xml @@ -0,0 +1,4 @@ + + + + diff --git a/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/full_description.txt b/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/full_description.txt new file mode 100644 index 0000000..4977cfb --- /dev/null +++ b/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/full_description.txt @@ -0,0 +1 @@ +Increase the amount of volume steps. diff --git a/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/short_description.txt b/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/short_description.txt new file mode 100644 index 0000000..4977cfb --- /dev/null +++ b/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/short_description.txt @@ -0,0 +1 @@ +Increase the amount of volume steps. diff --git a/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/title.txt b/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/title.txt new file mode 100644 index 0000000..ac5f80f --- /dev/null +++ b/metadata/com.programminghoch10.VolumeStepsIncrease/en-US/title.txt @@ -0,0 +1 @@ +VolumeStepsIncrease \ No newline at end of file diff --git a/modules.gradle.kts b/modules.gradle.kts index 352f7fa..b1f4cf1 100644 --- a/modules.gradle.kts +++ b/modules.gradle.kts @@ -19,3 +19,4 @@ include(":ResetAllNotificationChannels") include(":RotationControl") include(":SensorMod") include(":UpsideWifi") +include(":VolumeStepsIncrease")