Skip to content

Commit

Permalink
feat(app): add user setting to gradually ramp up alarm ringer volume
Browse files Browse the repository at this point in the history
resolves #1095
  • Loading branch information
ashutoshgngwr committed Nov 20, 2022
1 parent 84aeb6c commit de02a61
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ class SettingsRepository @Inject constructor(
).toDuration(DurationUnit.MINUTES)
}

fun getAlarmVolumeRampDuration(): Duration {
return prefs.getInt(
context.getString(R.string.alarm_gradually_increase_volume_key),
context.resources.getInteger(R.integer.default_alarm_gradually_increase_volume_minutes),
).toDuration(DurationUnit.MINUTES)
}

private fun keyFlow(@StringRes keyStrRes: Int): Flow<String> {
return prefs.keyFlow(context.getString(keyStrRes))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@ import com.github.ashutoshgngwr.noice.repository.PresetRepository
import com.github.ashutoshgngwr.noice.repository.SettingsRepository
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.lastOrNull
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.math.min
import kotlin.random.Random
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

@AndroidEntryPoint
Expand All @@ -66,6 +70,7 @@ class AlarmRingerService : LifecycleService(), AudioFocusManager.Listener {
internal lateinit var uiController: UiController

private var defaultRingtonePlayer: MediaPlayer? = null
private var masterVolumeFadeJob: Job? = null
private var autoDismissJob: Job? = null

private val notificationManager: NotificationManager by lazy { requireNotNull(getSystemService()) }
Expand Down Expand Up @@ -181,17 +186,45 @@ class AlarmRingerService : LifecycleService(), AudioFocusManager.Listener {
?: presetRepository.generate(emptySet(), Random.nextInt(2, 6))
.lastOrNull()?.data // or attempt to generate one

val fadeDurationMillis = min(
settingsRepository.getAlarmVolumeRampDuration().inWholeMilliseconds,
(settingsRepository.getAlarmRingerMaxDuration() - 1.minutes).inWholeMilliseconds,
)

if (preset != null) {
Log.d(LOG_TAG, "startRinger: starting preset: $preset")
playbackController.setAudioUsage(AudioAttributesCompat.USAGE_ALARM)
if (fadeDurationMillis > 0) playbackController.setMasterVolume(5)
playbackController.play(preset)
} else {
Log.d(LOG_TAG, "startRinger: starting default ringtone")
playDefaultRingtone()
playDefaultRingtone(if (fadeDurationMillis > 0) 0.2f else 1f)
buildPresetLoadFailedNotification(alarmTriggerTime)
.also { notificationManager.notify(NOTIFICATION_ID_PRIMING, it) }
}

masterVolumeFadeJob?.cancelAndJoin()
masterVolumeFadeJob = if (fadeDurationMillis > 0) {
lifecycleScope.launch {
val fadeStepMillis = fadeDurationMillis / (PlaybackController.MAX_MASTER_VOLUME - 5)
var volume = 5
while (isActive) {
volume++
playbackController.setMasterVolume(volume)
(volume.toFloat() / PlaybackController.MAX_MASTER_VOLUME)
.also { defaultRingtonePlayer?.setVolume(it, it) }

if (volume >= PlaybackController.MAX_MASTER_VOLUME) {
break
}

delay(fadeStepMillis)
}
}
} else {
null
}

if (alarm.vibrate) {
startVibrating()
}
Expand Down Expand Up @@ -296,8 +329,10 @@ class AlarmRingerService : LifecycleService(), AudioFocusManager.Listener {
alarmRepository.reportTrigger(alarmId, isSnoozed)

vibrator.cancel()
masterVolumeFadeJob?.cancelAndJoin()
playbackController.pause(true)
playbackController.setAudioUsage(AudioAttributesCompat.USAGE_MEDIA)
playbackController.setMasterVolume(PlaybackController.MAX_MASTER_VOLUME)
audioFocusManager.abandonFocus()
defaultRingtonePlayer?.release()

Expand All @@ -321,7 +356,7 @@ class AlarmRingerService : LifecycleService(), AudioFocusManager.Listener {
stopSelf()
}

private fun playDefaultRingtone() {
private fun playDefaultRingtone(initialVolume: Float) {
defaultRingtonePlayer?.release()
val ringtoneUri = getDefaultAlarmRingtoneUri() ?: return
defaultRingtonePlayer = MediaPlayer().apply {
Expand All @@ -333,6 +368,7 @@ class AlarmRingerService : LifecycleService(), AudioFocusManager.Listener {
.setLegacyStreamType(AudioManager.STREAM_ALARM)
.build()
)
setVolume(initialVolume, initialVolume)
prepare()
}

Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/donottranslate.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<string name="use_material_you_colors_key">use_material_you_colors</string>
<string name="alarm_snooze_length_key">alarm_snooze_length</string>
<string name="alarm_ringer_max_duration_key">alarm_ringer_max_duration</string>
<string name="alarm_gradually_increase_volume_key">alarm_gradually_increase_volume</string>
<string name="open_collective_url">https://opencollective.com/noice</string>
<string name="play_store_url">https://play.google.com/store/apps/details?id=com.github.ashutoshgngwr.noice</string>
<string name="fdroid_url">https://f-droid.org/app/com.github.ashutoshgngwr.noice</string>
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/integers.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
<integer name="default_fade_out_duration_seconds">5</integer>
<integer name="default_alarm_ringer_max_duration_minutes">18</integer>
<integer name="default_alarm_snooze_length_minutes">9</integer>
<integer name="default_alarm_gradually_increase_volume_minutes">1</integer>
</resources>
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,6 @@
<string name="alarm_missed">Alarm missed</string>
<string name="no_presets">No Presets</string>
<string name="master_volume">Master Volume</string>
<string name="alarm_gradually_increase_volume">Gradually increase alarm volume</string>
<string name="alarm_gradually_increase_volume_summary">Duration in minutes for gradually increasing the volume when an alarm fires</string>
</resources>
11 changes: 11 additions & 0 deletions app/src/main/res/xml/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@
app:seekBarIncrement="1"
app:showSeekBarValue="true" />

<SeekBarPreference
android:defaultValue="@integer/default_alarm_gradually_increase_volume_minutes"
android:key="@string/alarm_gradually_increase_volume_key"
android:max="30"
android:summary="@string/alarm_gradually_increase_volume_summary"
android:title="@string/alarm_gradually_increase_volume"
app:iconSpaceReserved="false"
app:min="0"
app:seekBarIncrement="1"
app:showSeekBarValue="true" />

</PreferenceCategory>

<PreferenceCategory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,22 @@ class SettingsRepositoryTest {
)
}
}

@Test
fun getAlarmVolumeRampDuration() {
val inputs = arrayOf(null, 2, 4, 7)
for (input in inputs) {
every {
prefs.getInt(context.getString(R.string.alarm_gradually_increase_volume_key), any())
} answers { input ?: secondArg() }

val defaultMinutes = context.resources
.getInteger(R.integer.default_alarm_gradually_increase_volume_minutes)

assertEquals(
(input ?: defaultMinutes).minutes,
settingsRepository.getAlarmVolumeRampDuration(),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class AlarmRingerServiceTest {
presetRepositoryMock = mockk(relaxed = true)
settingsRepositoryMock = mockk {
every { getAlarmRingerMaxDuration() } returns 2.minutes
every { getAlarmVolumeRampDuration() } returns 1.minutes
}

playbackControllerMock = mockk(relaxed = true)
Expand Down Expand Up @@ -163,6 +164,10 @@ class AlarmRingerServiceTest {
.filter { it.id == AlarmRingerService.NOTIFICATION_ID_ALARM }
assertEquals(1, notifications.size)
assertTrue(notifications.firstOrNull()?.isOngoing ?: false)

// assert master volume fade
advanceUntilIdle()
verify(atLeast = 20) { playbackControllerMock.setMasterVolume(any()) }
}
}

Expand Down

0 comments on commit de02a61

Please sign in to comment.