feat: configurable ring volume

Adds a volume slider (1-100% of the alarm stream) to Settings. RingService
plays at the configured percentage instead of always maxing STREAM_ALARM.
This commit is contained in:
avi 2026-08-07 13:46:20 -05:00
commit 4a04052845
6 changed files with 71 additions and 3 deletions

View file

@ -30,6 +30,9 @@ Choose between:
Playback runs for the configured number of seconds (`5300`, default **30**), then
the previous volume is restored.
### Volume
The ring plays at a configurable percentage (`1100%`) of the alarm-stream volume.
---
## How Do Not Disturb Override Works (read this)
@ -50,7 +53,7 @@ Only after the user flips that toggle will a trigger actually ring.
When access **is** granted, `RingService`:
- saves the current **alarm-stream** volume,
- forces `AudioManager.STREAM_ALARM` to max,
- sets `AudioManager.STREAM_ALARM` to the configured volume percentage,
- requests `AUDIOFOCUS_GAIN_TRANSIENT` using `AudioAttributes.USAGE_ALARM`,
- plays on the **alarm stream** — the one stream Do Not Disturb does **not**
silence by default (media/music *is* silenced), which is what lets the ring

View file

@ -23,6 +23,7 @@ object RingConfig {
private const val KEY_RINGTONE_URI = "ringtone_uri"
private const val KEY_SONG_URI = "song_uri"
private const val KEY_DURATION_SEC = "duration_sec"
private const val KEY_VOLUME_PERCENT = "volume_percent"
const val SOUND_RINGTONE = "ringtone"
const val SOUND_SONG = "song"
@ -82,6 +83,12 @@ object RingConfig {
fun setDurationSeconds(context: Context, seconds: Int): Boolean =
prefs(context).edit().putInt(KEY_DURATION_SEC, seconds.coerceIn(MIN_DURATION_SEC, MAX_DURATION_SEC)).commit()
fun volumePercent(context: Context): Int =
prefs(context).getInt(KEY_VOLUME_PERCENT, 100).coerceIn(1, 100)
fun setVolumePercent(context: Context, percent: Int): Boolean =
prefs(context).edit().putInt(KEY_VOLUME_PERCENT, percent.coerceIn(1, 100)).commit()
/**
* The Uri the RingService should actually play.
* - ringtone mode -> the system default ringtone,

View file

@ -158,11 +158,12 @@ class RingService : Service() {
// Step 1: save current alarm-stream volume so we can restore it later.
originalVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM)
// Step 2: force the alarm stream to max volume.
// Step 2: force the alarm stream to the configured volume percentage.
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM)
val targetVolume = (maxVolume * RingConfig.volumePercent(this) / 100).coerceAtLeast(1)
audioManager.setStreamVolume(
AudioManager.STREAM_ALARM,
maxVolume,
targetVolume,
0 // flags = 0 so we do NOT show the system volume UI.
)

View file

@ -36,6 +36,8 @@ class SettingsActivity : AppCompatActivity() {
private lateinit var tvSelectedSound: TextView
private lateinit var sbDuration: SeekBar
private lateinit var tvDurationValue: TextView
private lateinit var sbVolume: SeekBar
private lateinit var tvVolumeValue: TextView
private lateinit var btnSave: Button
private var pickedRingtoneUri: Uri? = null
@ -88,10 +90,13 @@ class SettingsActivity : AppCompatActivity() {
tvSelectedSound = findViewById(R.id.tvSelectedSound)
sbDuration = findViewById(R.id.sbDuration)
tvDurationValue = findViewById(R.id.tvDurationValue)
sbVolume = findViewById(R.id.sbVolume)
tvVolumeValue = findViewById(R.id.tvVolumeValue)
btnSave = findViewById(R.id.btnSave)
loadCurrentValues()
setupDurationSeekBar()
setupVolumeSeekBar()
setupPickers()
btnSave.setOnClickListener { saveSettings() }
@ -108,6 +113,9 @@ class SettingsActivity : AppCompatActivity() {
sbDuration.progress = RingConfig.durationSeconds(this) - RingConfig.MIN_DURATION_SEC
updateDurationLabel()
sbVolume.progress = RingConfig.volumePercent(this) - 1
updateVolumeLabel()
updateSoundSummary()
}
@ -129,6 +137,22 @@ class SettingsActivity : AppCompatActivity() {
tvDurationValue.text = getString(R.string.duration_value, seconds)
}
private fun setupVolumeSeekBar() {
sbVolume.max = 99 // 1..100
sbVolume.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
updateVolumeLabel()
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
}
private fun updateVolumeLabel() {
tvVolumeValue.text = getString(R.string.volume_value, sbVolume.progress + 1)
}
private fun setupPickers() {
btnPickRingtone.setOnClickListener {
val intent = Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
@ -186,12 +210,14 @@ class SettingsActivity : AppCompatActivity() {
val mode = if (rdbSong.isChecked) RingConfig.SOUND_SONG else RingConfig.SOUND_RINGTONE
val seconds = sbDuration.progress + RingConfig.MIN_DURATION_SEC
val volume = sbVolume.progress + 1
RingConfig.setTriggerText(this, trigger)
RingConfig.setSoundMode(this, mode)
RingConfig.setRingtoneUri(this, pickedRingtoneUri)
RingConfig.setSongUri(this, pickedSongUri)
RingConfig.setDurationSeconds(this, seconds)
RingConfig.setVolumePercent(this, volume)
Toast.makeText(this, R.string.toast_saved, Toast.LENGTH_SHORT).show()
finish()

View file

@ -138,6 +138,33 @@
android:text="@string/label_duration_range"
android:textSize="12sp" />
<!-- Volume -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="6dp"
android:text="@string/label_volume"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvVolumeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<SeekBar
android:id="@+id/sbVolume"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_volume_range"
android:textSize="12sp" />
<Button
android:id="@+id/btnSave"
android:layout_width="match_parent"

View file

@ -57,6 +57,10 @@
<string name="duration_value">%1$d seconds</string>
<string name="label_duration_range">Between 5 and 300 seconds.</string>
<string name="label_volume">Ring volume</string>
<string name="volume_value">%1$d%% of the alarm volume</string>
<string name="label_volume_range">Between 1 and 100%.</string>
<string name="btn_save_settings">Save Settings</string>
<string name="toast_ringtone_default">Using default system ringtone.</string>