Skip to content

Commit 020f7cc

Browse files
Implement VolumeStepsIncrease (#22)
This module allows the user to increase the amount of volume steps. Default is double the steps, but configurable by the user from 1 to triple.
1 parent 6c28e64 commit 020f7cc

File tree

19 files changed

+401
-0
lines changed

19 files changed

+401
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ A collection of small Xposed Modules.
2222
| [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) |
2323
| [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) |
2424
| [UpsideWifi](UpsideWifi) | [@programminghoch10](https://github.com/programminghoch10) | Turn the WiFi icon upside down | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=UpsideWifi) |
25+
| [VolumeStepsIncrease](VolumeStepsIncrease) | [@programminghoch10](https://github.com/programminghoch10) | Increase the amount of volume steps | [GitHub](https://github.com/binarynoise/XposedModulets/releases?q=VolumeStepsIncrease) |
2526
<!--@formatter:on-->
2627

2728
## License

VolumeStepsIncrease/Readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# VolumeStepsIncrease
2+
3+
Increase the amount of volume steps.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
plugins {
2+
alias(libs.plugins.buildlogic.android.application)
3+
alias(libs.plugins.buildlogic.kotlin.android)
4+
}
5+
6+
android {
7+
namespace = "com.programminghoch10.VolumeStepsIncrease"
8+
9+
defaultConfig {
10+
minSdk = 23
11+
targetSdk = 35
12+
}
13+
}
14+
15+
dependencies {
16+
implementation(libs.androidx.preference.ktx)
17+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest
3+
xmlns:android="http://schemas.android.com/apk/res/android">
4+
5+
<application android:label="@string/app_name">
6+
<activity
7+
android:name=".SettingsActivity"
8+
android:exported="true"
9+
android:label="@string/title_activity_settings"
10+
android:theme="@style/AppTheme"
11+
android:excludeFromRecents="true"
12+
>
13+
<intent-filter>
14+
<action android:name="android.intent.action.MAIN" />
15+
<category android:name="de.robv.android.xposed.category.MODULE_SETTINGS" />
16+
</intent-filter>
17+
</activity>
18+
19+
<meta-data
20+
android:name="xposedmodule"
21+
android:value="true"
22+
/>
23+
<meta-data
24+
android:name="xposeddescription"
25+
android:value="@string/description"
26+
/>
27+
<meta-data
28+
android:name="xposedminversion"
29+
android:value="93"
30+
/>
31+
<meta-data
32+
android:name="xposedscope"
33+
android:resource="@array/scope"
34+
/>
35+
</application>
36+
37+
</manifest>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.programminghoch10.VolumeStepsIncrease.Hook
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.programminghoch10.VolumeStepsIncrease
2+
3+
import com.programminghoch10.VolumeStepsIncrease.StockValues.MAX_STREAM_VOLUME
4+
import com.programminghoch10.VolumeStepsIncrease.StockValues.MIN_STREAM_VOLUME
5+
import com.programminghoch10.VolumeStepsIncrease.StockValues.STREAM_ALARM
6+
import com.programminghoch10.VolumeStepsIncrease.StockValues.STREAM_MUSIC
7+
import com.programminghoch10.VolumeStepsIncrease.StockValues.STREAM_SYSTEM
8+
import com.programminghoch10.VolumeStepsIncrease.StockValues.STREAM_VOICE_CALL
9+
10+
object Common {
11+
const val SHARED_PREFERENCES_NAME = "streams"
12+
13+
val STREAMS = StockValues::class.java.declaredFields.filter { it.name.startsWith("STREAM_") }.associate { it.name to it.getInt(null) }
14+
15+
val systemPropertyToStream = mapOf(
16+
"ro.config.vc_call_vol_steps" to STREAM_VOICE_CALL,
17+
"ro.config.media_vol_steps" to STREAM_MUSIC,
18+
"ro.config.alarm_vol_steps" to STREAM_ALARM,
19+
"ro.config.system_vol_steps" to STREAM_SYSTEM,
20+
)
21+
val streamsToSystemProperties = systemPropertyToStream.entries.associate { it.value to it.key }
22+
23+
val moduleDefaultVolumeSteps = mapOf(
24+
STREAM_MUSIC to MAX_STREAM_VOLUME[STREAM_MUSIC] * 2,
25+
STREAM_VOICE_CALL to MAX_STREAM_VOLUME[STREAM_VOICE_CALL] * 2,
26+
)
27+
28+
fun getSystemMaxVolumeSteps(stream: Int): Int {
29+
val default = MAX_STREAM_VOLUME[stream]
30+
if (streamsToSystemProperties.contains(stream)) {
31+
val systemProperty = streamsToSystemProperties[stream]
32+
try {
33+
val SystemPropertiesClass = Class.forName("android.os.SystemProperties")
34+
val getIntMethod = SystemPropertiesClass.getMethod("getInt", String::class.java, Int::class.java)
35+
return getIntMethod.invoke(null, systemProperty, default) as Int
36+
} catch (_: Exception) {
37+
}
38+
}
39+
return default
40+
}
41+
42+
fun getModuleDefaultVolumeSteps(stream: Int): Int {
43+
return moduleDefaultVolumeSteps[stream] ?: getSystemMaxVolumeSteps(stream)
44+
}
45+
46+
fun getModuleMaxVolumeSteps(stream: Int): Int {
47+
return getSystemMaxVolumeSteps(stream) * 3
48+
}
49+
50+
fun getSystemMinVolumeSteps(stream: Int): Int {
51+
return MIN_STREAM_VOLUME[stream]
52+
}
53+
54+
fun getModuleMinVolumeSteps(stream: Int): Int {
55+
return getSystemMinVolumeSteps(stream)
56+
}
57+
58+
fun getPreferenceKey(stream: Int): String {
59+
return "STREAM_${stream}"
60+
}
61+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.programminghoch10.VolumeStepsIncrease
2+
3+
import com.programminghoch10.VolumeStepsIncrease.Common.SHARED_PREFERENCES_NAME
4+
import com.programminghoch10.VolumeStepsIncrease.Common.STREAMS
5+
import com.programminghoch10.VolumeStepsIncrease.Common.getPreferenceKey
6+
import com.programminghoch10.VolumeStepsIncrease.Common.systemPropertyToStream
7+
import de.robv.android.xposed.IXposedHookLoadPackage
8+
import de.robv.android.xposed.XSharedPreferences
9+
import de.robv.android.xposed.XposedBridge
10+
import de.robv.android.xposed.XposedHelpers
11+
import de.robv.android.xposed.callbacks.XC_LoadPackage
12+
import de.robv.android.xposed.XC_MethodHook as MethodHook
13+
14+
class Hook : IXposedHookLoadPackage {
15+
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
16+
when (lpparam.packageName) {
17+
BuildConfig.APPLICATION_ID -> return
18+
"android" -> hookAndroid(lpparam)
19+
else -> hookApp(lpparam)
20+
}
21+
}
22+
23+
fun hookAndroid(lpparam: XC_LoadPackage.LoadPackageParam) {
24+
25+
val AudioServiceClass = XposedHelpers.findClass("com.android.server.audio.AudioService", lpparam.classLoader)
26+
XposedBridge.hookAllConstructors(AudioServiceClass, object : MethodHook() {
27+
override fun afterHookedMethod(param: MethodHookParam) {
28+
val MAX_STREAM_VOLUME = XposedHelpers.getObjectField(param.thisObject, "MAX_STREAM_VOLUME") as IntArray
29+
val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
30+
STREAMS.filter { preferences.contains(getPreferenceKey(it.value)) }
31+
.map { it.value to preferences.getInt(getPreferenceKey(it.value), MAX_STREAM_VOLUME[it.value]) }
32+
.forEach { MAX_STREAM_VOLUME[it.first] = it.second }
33+
}
34+
})
35+
36+
hookApp(lpparam)
37+
}
38+
39+
fun hookApp(lpparam: XC_LoadPackage.LoadPackageParam) {
40+
41+
val SystemPropertiesClass = XposedHelpers.findClass("android.os.SystemProperties", lpparam.classLoader)
42+
43+
XposedHelpers.findAndHookMethod(SystemPropertiesClass, "getInt", String::class.java, Int::class.java, object : MethodHook() {
44+
override fun afterHookedMethod(param: MethodHookParam) {
45+
val key = param.args[0] as String
46+
param.args[1] as Int
47+
val result = param.result as Int
48+
val streamInt = systemPropertyToStream[key] ?: return
49+
val preferences = XSharedPreferences(BuildConfig.APPLICATION_ID, SHARED_PREFERENCES_NAME)
50+
if (!preferences.contains(getPreferenceKey(streamInt))) return
51+
param.result = preferences.getInt(getPreferenceKey(streamInt), result)
52+
}
53+
})
54+
55+
XposedHelpers.findAndHookMethod(SystemPropertiesClass, "getBoolean", String::class.java, Boolean::class.java, object : MethodHook() {
56+
override fun beforeHookedMethod(param: MethodHookParam) {
57+
val key = param.args[0] as String
58+
if (key == "audio.safemedia.bypass") param.result = true
59+
}
60+
})
61+
}
62+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.programminghoch10.VolumeStepsIncrease
2+
3+
import kotlin.math.round
4+
import android.annotation.SuppressLint
5+
import android.os.Bundle
6+
import androidx.fragment.app.FragmentActivity
7+
import androidx.preference.Preference
8+
import androidx.preference.PreferenceFragmentCompat
9+
import androidx.preference.PreferenceGroup
10+
import androidx.preference.SeekBarPreference
11+
import androidx.preference.children
12+
import com.programminghoch10.VolumeStepsIncrease.Common.SHARED_PREFERENCES_NAME
13+
import com.programminghoch10.VolumeStepsIncrease.Common.STREAMS
14+
import com.programminghoch10.VolumeStepsIncrease.Common.getModuleDefaultVolumeSteps
15+
import com.programminghoch10.VolumeStepsIncrease.Common.getModuleMaxVolumeSteps
16+
import com.programminghoch10.VolumeStepsIncrease.Common.getModuleMinVolumeSteps
17+
import com.programminghoch10.VolumeStepsIncrease.Common.getPreferenceKey
18+
import com.programminghoch10.VolumeStepsIncrease.Common.getSystemMaxVolumeSteps
19+
20+
class SettingsActivity : FragmentActivity() {
21+
22+
override fun onCreate(savedInstanceState: Bundle?) {
23+
super.onCreate(savedInstanceState)
24+
setContentView(R.layout.settings_activity)
25+
if (savedInstanceState == null) {
26+
supportFragmentManager.beginTransaction().replace(R.id.settings, SettingsFragment()).commit()
27+
}
28+
actionBar?.setDisplayHomeAsUpEnabled(true)
29+
}
30+
31+
override fun onNavigateUp(): Boolean {
32+
finish()
33+
return super.onNavigateUp()
34+
}
35+
36+
class SettingsFragment : PreferenceFragmentCompat() {
37+
@SuppressLint("WorldReadableFiles")
38+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
39+
setPreferencesFromResource(R.xml.root_preferences, rootKey)
40+
preferenceManager.sharedPreferencesName = SHARED_PREFERENCES_NAME
41+
preferenceManager.sharedPreferencesMode = MODE_WORLD_READABLE
42+
43+
for (stream in STREAMS) {
44+
val preference = SeekBarPreference(requireContext())
45+
preference.key = getPreferenceKey(stream.value)
46+
preference.title = stream.key.replace("STREAM_", "")
47+
preference.min = getModuleMinVolumeSteps(stream.value)
48+
preference.max = getModuleMaxVolumeSteps(stream.value)
49+
preference.setDefaultValue(getModuleDefaultVolumeSteps(stream.value))
50+
preference.showSeekBarValue = true
51+
preference.updatesContinuously = true
52+
preference.setOnPreferenceChangeListener { preference, newValue ->
53+
val factor = (newValue as Int).toDouble() / getSystemMaxVolumeSteps(stream.value)
54+
preference.summary = arrayOf(
55+
"factor=${factor.round(1)}x ",
56+
//"systemMin=${getSystemMinVolumeSteps(stream.value)} ",
57+
"systemMax=${getSystemMaxVolumeSteps(stream.value)} ",
58+
).joinToString(" ")
59+
true
60+
}
61+
preferenceScreen.addPreference(preference)
62+
preference.onPreferenceChangeListener?.onPreferenceChange(preference, preference.value)
63+
}
64+
preferenceScreen.setIconSpaceReservedRecursive(false)
65+
}
66+
67+
fun Preference.setIconSpaceReservedRecursive(iconSpaceReserved: Boolean) {
68+
this.isIconSpaceReserved = iconSpaceReserved
69+
if (this is PreferenceGroup) children.forEach { it.setIconSpaceReservedRecursive(iconSpaceReserved) }
70+
}
71+
72+
fun Double.round(decimals: Int = 0): Double {
73+
var multiplier = 1.0
74+
repeat(decimals) { multiplier *= 10 }
75+
return round(this * multiplier) / multiplier
76+
}
77+
}
78+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.programminghoch10.VolumeStepsIncrease
2+
3+
import android.media.AudioManager
4+
import androidx.annotation.Keep
5+
6+
@Keep
7+
object StockValues {
8+
9+
// from com.android.server.media.AudioService
10+
11+
/** Maximum volume index values for audio streams */
12+
var MAX_STREAM_VOLUME: IntArray = intArrayOf(
13+
5, // STREAM_VOICE_CALL
14+
7, // STREAM_SYSTEM
15+
7, // STREAM_RING
16+
15, // STREAM_MUSIC
17+
7, // STREAM_ALARM
18+
7, // STREAM_NOTIFICATION
19+
15, // STREAM_BLUETOOTH_SCO
20+
7, // STREAM_SYSTEM_ENFORCED
21+
15, // STREAM_DTMF
22+
15, // STREAM_TTS
23+
15, // STREAM_ACCESSIBILITY
24+
15, // STREAM_ASSISTANT
25+
)
26+
27+
/** Minimum volume index values for audio streams */
28+
var MIN_STREAM_VOLUME: IntArray = intArrayOf(
29+
1, // STREAM_VOICE_CALL
30+
0, // STREAM_SYSTEM
31+
0, // STREAM_RING
32+
0, // STREAM_MUSIC
33+
1, // STREAM_ALARM
34+
0, // STREAM_NOTIFICATION
35+
0, // STREAM_BLUETOOTH_SCO
36+
0, // STREAM_SYSTEM_ENFORCED
37+
0, // STREAM_DTMF
38+
0, // STREAM_TTS
39+
1, // STREAM_ACCESSIBILITY
40+
0, // STREAM_ASSISTANT
41+
)
42+
43+
// from android.media.AudioSystem
44+
45+
const val STREAM_VOICE_CALL: Int = AudioManager.STREAM_VOICE_CALL
46+
47+
/** @hide Used to identify the volume of audio streams for system sounds
48+
*/
49+
const val STREAM_SYSTEM: Int = AudioManager.STREAM_SYSTEM
50+
51+
/** @hide Used to identify the volume of audio streams for the phone ring and message alerts
52+
*/
53+
const val STREAM_RING: Int = AudioManager.STREAM_RING
54+
55+
/** @hide Used to identify the volume of audio streams for music playback
56+
*/
57+
const val STREAM_MUSIC: Int = AudioManager.STREAM_MUSIC
58+
59+
/** @hide Used to identify the volume of audio streams for alarms
60+
*/
61+
const val STREAM_ALARM: Int = AudioManager.STREAM_ALARM
62+
63+
/** @hide Used to identify the volume of audio streams for notifications
64+
*/
65+
const val STREAM_NOTIFICATION: Int = AudioManager.STREAM_NOTIFICATION
66+
67+
/** @hide
68+
* Used to identify the volume of audio streams for phone calls when connected on bluetooth
69+
*/
70+
@Deprecated("use {@link #STREAM_VOICE_CALL} instead ")
71+
const val STREAM_BLUETOOTH_SCO: Int = 6
72+
73+
/** @hide Used to identify the volume of audio streams for enforced system sounds in certain
74+
* countries (e.g camera in Japan)
75+
*/
76+
const val STREAM_SYSTEM_ENFORCED: Int = 7
77+
78+
/** @hide Used to identify the volume of audio streams for DTMF tones
79+
*/
80+
const val STREAM_DTMF: Int = AudioManager.STREAM_DTMF
81+
82+
/** @hide Used to identify the volume of audio streams exclusively transmitted through the
83+
* speaker (TTS) of the device
84+
*/
85+
const val STREAM_TTS: Int = 9
86+
87+
/** @hide Used to identify the volume of audio streams for accessibility prompts
88+
*/
89+
const val STREAM_ACCESSIBILITY: Int = AudioManager.STREAM_ACCESSIBILITY
90+
91+
/** @hide Used to identify the volume of audio streams for virtual assistant
92+
*/
93+
const val STREAM_ASSISTANT: Int = 11
94+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
style="@style/AppTheme.Edge2EdgeFix"
7+
>
8+
9+
<FrameLayout
10+
android:id="@+id/settings"
11+
android:layout_width="match_parent"
12+
android:layout_height="match_parent"
13+
/>
14+
</LinearLayout>

0 commit comments

Comments
 (0)