From 9551cb1e40bc39c305c34292789d70131055788c Mon Sep 17 00:00:00 2001 From: marazmarci Date: Wed, 1 Jul 2026 12:34:23 +0200 Subject: [PATCH 1/6] Simplify signature of processStreamVolume and adjustVolumeStream by only getting AudioManager at its usage site --- .../android/notifications/MessagingManager.kt | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt index 0619db41d18..5d3aa782e2c 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt @@ -731,14 +731,11 @@ class MessagingManager @Inject constructor( } COMMAND_VOLUME_LEVEL -> { - val audioManager = - context.getSystemService() val notificationManager = context.getSystemService() if (notificationManager?.isNotificationPolicyAccessGranted == false) { notifyMissingPermission(message, serverId) } else { processStreamVolume( - audioManager!!, data[NotificationData.MEDIA_STREAM].toString(), command!!.toInt(), serverId, @@ -1903,27 +1900,22 @@ class MessagingManager @Inject constructor( } } - private suspend fun processStreamVolume(audioManager: AudioManager, stream: String, volume: Int, serverId: String) { + private suspend fun processStreamVolume(stream: String, volume: Int, serverId: String) { when (stream) { - NotificationData.ALARM_STREAM -> adjustVolumeStream(AudioManager.STREAM_ALARM, volume, audioManager) - NotificationData.MUSIC_STREAM -> adjustVolumeStream(AudioManager.STREAM_MUSIC, volume, audioManager) - NotificationData.NOTIFICATION_STREAM -> adjustVolumeStream( - AudioManager.STREAM_NOTIFICATION, - volume, - audioManager, - ) - - NotificationData.RING_STREAM -> adjustVolumeStream(AudioManager.STREAM_RING, volume, audioManager) - NotificationData.CALL_STREAM -> adjustVolumeStream(AudioManager.STREAM_VOICE_CALL, volume, audioManager) - NotificationData.SYSTEM_STREAM -> adjustVolumeStream(AudioManager.STREAM_SYSTEM, volume, audioManager) - NotificationData.DTMF_STREAM -> adjustVolumeStream(AudioManager.STREAM_DTMF, volume, audioManager) + NotificationData.ALARM_STREAM -> adjustVolumeStream(AudioManager.STREAM_ALARM, volume) + NotificationData.MUSIC_STREAM -> adjustVolumeStream(AudioManager.STREAM_MUSIC, volume) + NotificationData.NOTIFICATION_STREAM -> adjustVolumeStream(AudioManager.STREAM_NOTIFICATION, volume) + NotificationData.RING_STREAM -> adjustVolumeStream(AudioManager.STREAM_RING, volume) + NotificationData.CALL_STREAM -> adjustVolumeStream(AudioManager.STREAM_VOICE_CALL, volume) + NotificationData.SYSTEM_STREAM -> adjustVolumeStream(AudioManager.STREAM_SYSTEM, volume) + NotificationData.DTMF_STREAM -> adjustVolumeStream(AudioManager.STREAM_DTMF, volume) NotificationData.ASSISTANT_STREAM -> if (SdkVersion.isAtLeast(Build.VERSION_CODES.CINNAMON_BUN)) { if (!defaultAssistantManager.isDefaultAssistant()) { Timber.w("Cannot control assistant volume: app is not the default assistant") notifyDefaultAssistant(command = "$COMMAND_VOLUME_LEVEL($stream)", serverId = serverId) return } - adjustVolumeStream(AudioManager.STREAM_ASSISTANT, volume, audioManager) + adjustVolumeStream(AudioManager.STREAM_ASSISTANT, volume) } else { Timber.w("Cannot control assistant volume: Not supported by the current version of Android") } @@ -1932,7 +1924,8 @@ class MessagingManager @Inject constructor( } } - private fun adjustVolumeStream(stream: Int, volume: Int, audioManager: AudioManager) { + private fun adjustVolumeStream(stream: Int, volume: Int) { + val audioManager = context.getSystemService()!! var volumeLevel = volume if (volumeLevel > audioManager.getStreamMaxVolume(stream)) { volumeLevel = audioManager.getStreamMaxVolume(stream) From 90315beb953fed46bc4a25e63c94f75111e01528 Mon Sep 17 00:00:00 2001 From: marazmarci Date: Wed, 1 Jul 2026 12:44:44 +0200 Subject: [PATCH 2/6] Extract streamType out of when block to reduce code repetition in MessagingManager::processStreamVolume --- .../android/notifications/MessagingManager.kt | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt index 5d3aa782e2c..9d7d128f594 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt @@ -1901,27 +1901,32 @@ class MessagingManager @Inject constructor( } private suspend fun processStreamVolume(stream: String, volume: Int, serverId: String) { - when (stream) { - NotificationData.ALARM_STREAM -> adjustVolumeStream(AudioManager.STREAM_ALARM, volume) - NotificationData.MUSIC_STREAM -> adjustVolumeStream(AudioManager.STREAM_MUSIC, volume) - NotificationData.NOTIFICATION_STREAM -> adjustVolumeStream(AudioManager.STREAM_NOTIFICATION, volume) - NotificationData.RING_STREAM -> adjustVolumeStream(AudioManager.STREAM_RING, volume) - NotificationData.CALL_STREAM -> adjustVolumeStream(AudioManager.STREAM_VOICE_CALL, volume) - NotificationData.SYSTEM_STREAM -> adjustVolumeStream(AudioManager.STREAM_SYSTEM, volume) - NotificationData.DTMF_STREAM -> adjustVolumeStream(AudioManager.STREAM_DTMF, volume) + val streamType = when (stream) { + NotificationData.ALARM_STREAM -> AudioManager.STREAM_ALARM + NotificationData.MUSIC_STREAM -> AudioManager.STREAM_MUSIC + NotificationData.NOTIFICATION_STREAM -> AudioManager.STREAM_NOTIFICATION + NotificationData.RING_STREAM -> AudioManager.STREAM_RING + NotificationData.CALL_STREAM -> AudioManager.STREAM_VOICE_CALL + NotificationData.SYSTEM_STREAM -> AudioManager.STREAM_SYSTEM + NotificationData.DTMF_STREAM -> AudioManager.STREAM_DTMF NotificationData.ASSISTANT_STREAM -> if (SdkVersion.isAtLeast(Build.VERSION_CODES.CINNAMON_BUN)) { if (!defaultAssistantManager.isDefaultAssistant()) { Timber.w("Cannot control assistant volume: app is not the default assistant") notifyDefaultAssistant(command = "$COMMAND_VOLUME_LEVEL($stream)", serverId = serverId) return } - adjustVolumeStream(AudioManager.STREAM_ASSISTANT, volume) + AudioManager.STREAM_ASSISTANT } else { Timber.w("Cannot control assistant volume: Not supported by the current version of Android") + return + } + else -> { + Timber.d("Skipping command due to invalid channel stream ($stream)") + return } - - else -> Timber.d("Skipping command due to invalid channel stream ($stream)") } + + adjustVolumeStream(streamType, volume) } private fun adjustVolumeStream(stream: Int, volume: Int) { From 51f81c8eff1224a7a5e1c7a6941d1b8758940226 Mon Sep 17 00:00:00 2001 From: marazmarci Date: Wed, 1 Jul 2026 12:59:10 +0200 Subject: [PATCH 3/6] Implement command_volume_level_step notification command for relative volume adjustments --- .../android/notifications/MessagingManager.kt | 59 +++++++++++++------ 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt index 9d7d128f594..150fc18db6a 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt @@ -178,6 +178,7 @@ class MessagingManager @Inject constructor( const val COMMAND_RINGER_MODE = "command_ringer_mode" const val COMMAND_BROADCAST_INTENT = "command_broadcast_intent" const val COMMAND_VOLUME_LEVEL = "command_volume_level" + const val COMMAND_VOLUME_LEVEL_STEP = "command_volume_level_step" const val COMMAND_BLUETOOTH = "command_bluetooth" const val COMMAND_SCREEN_ON = "command_screen_on" const val COMMAND_MEDIA = "command_media" @@ -234,6 +235,7 @@ class MessagingManager @Inject constructor( COMMAND_RINGER_MODE, COMMAND_BROADCAST_INTENT, COMMAND_VOLUME_LEVEL, + COMMAND_VOLUME_LEVEL_STEP, COMMAND_BLUETOOTH, DeviceCommandData.COMMAND_BLE_TRANSMITTER, DeviceCommandData.COMMAND_BEACON_MONITOR, @@ -418,7 +420,8 @@ class MessagingManager @Inject constructor( } } - COMMAND_VOLUME_LEVEL -> { + COMMAND_VOLUME_LEVEL, + COMMAND_VOLUME_LEVEL_STEP -> { if (!jsonData[NotificationData.MEDIA_STREAM].isNullOrEmpty() && jsonData[NotificationData.MEDIA_STREAM] in CHANNEL_VOLUME_STREAM && !jsonData[NotificationData.COMMAND].isNullOrEmpty() && @@ -730,15 +733,18 @@ class MessagingManager @Inject constructor( } } - COMMAND_VOLUME_LEVEL -> { + COMMAND_VOLUME_LEVEL, + COMMAND_VOLUME_LEVEL_STEP -> { val notificationManager = context.getSystemService() if (notificationManager?.isNotificationPolicyAccessGranted == false) { notifyMissingPermission(message, serverId) } else { processStreamVolume( - data[NotificationData.MEDIA_STREAM].toString(), - command!!.toInt(), - serverId, + stream = data[NotificationData.MEDIA_STREAM].toString(), + volume = command!!.toInt(), + step = message == COMMAND_VOLUME_LEVEL_STEP, + serverId = serverId , + message = message, ) } } @@ -1900,7 +1906,13 @@ class MessagingManager @Inject constructor( } } - private suspend fun processStreamVolume(stream: String, volume: Int, serverId: String) { + private suspend fun processStreamVolume( + stream: String, + volume: Int, + step: Boolean, + serverId: String, + message: String, + ) { val streamType = when (stream) { NotificationData.ALARM_STREAM -> AudioManager.STREAM_ALARM NotificationData.MUSIC_STREAM -> AudioManager.STREAM_MUSIC @@ -1912,7 +1924,7 @@ class MessagingManager @Inject constructor( NotificationData.ASSISTANT_STREAM -> if (SdkVersion.isAtLeast(Build.VERSION_CODES.CINNAMON_BUN)) { if (!defaultAssistantManager.isDefaultAssistant()) { Timber.w("Cannot control assistant volume: app is not the default assistant") - notifyDefaultAssistant(command = "$COMMAND_VOLUME_LEVEL($stream)", serverId = serverId) + notifyDefaultAssistant(command = "$message($stream)", serverId = serverId) return } AudioManager.STREAM_ASSISTANT @@ -1926,20 +1938,31 @@ class MessagingManager @Inject constructor( } } - adjustVolumeStream(streamType, volume) + if (step) { + stepStreamVolume(streamType = streamType, volumeDelta = volume) + } else { + setStreamVolume(streamType = streamType, volume = volume) + } } - private fun adjustVolumeStream(stream: Int, volume: Int) { + private fun stepStreamVolume(streamType: Int, volumeDelta: Int) { val audioManager = context.getSystemService()!! - var volumeLevel = volume - if (volumeLevel > audioManager.getStreamMaxVolume(stream)) { - volumeLevel = audioManager.getStreamMaxVolume(stream) - } else if (volumeLevel < 0) { - volumeLevel = 0 - } + + val currentVolume = audioManager.getStreamVolume(streamType) + val newVolume = currentVolume + volumeDelta + + setStreamVolume(streamType = streamType, volume = newVolume) + } + + private fun setStreamVolume(streamType: Int, volume: Int) { + val audioManager = context.getSystemService()!! + + val maxVolume = audioManager.getStreamMaxVolume(streamType) + val clampedVolume = volume.coerceIn(0, maxVolume) + audioManager.setStreamVolume( - stream, - volumeLevel, + streamType, + clampedVolume, AudioManager.FLAG_SHOW_UI, ) } @@ -2125,7 +2148,7 @@ class MessagingManager @Inject constructor( when (type) { COMMAND_WEBVIEW, COMMAND_ACTIVITY, COMMAND_LAUNCH_APP -> requestSystemAlertPermission() - COMMAND_RINGER_MODE, COMMAND_DND, COMMAND_VOLUME_LEVEL -> requestDNDPermission() + COMMAND_RINGER_MODE, COMMAND_DND, COMMAND_VOLUME_LEVEL, COMMAND_VOLUME_LEVEL_STEP -> requestDNDPermission() COMMAND_MEDIA -> requestNotificationPermission() From 15899916e06d3252b1d4a9c907e2de9511a5e605 Mon Sep 17 00:00:00 2001 From: marazmarci Date: Wed, 1 Jul 2026 13:01:36 +0200 Subject: [PATCH 4/6] Inject AudioManager into MessagingManager --- .../companion/android/ApplicationModule.kt | 7 +++++++ .../android/notifications/MessagingManager.kt | 10 +++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/ApplicationModule.kt b/app/src/main/kotlin/io/homeassistant/companion/android/ApplicationModule.kt index a508e18391b..d97d068b466 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/ApplicationModule.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/ApplicationModule.kt @@ -2,6 +2,7 @@ package io.homeassistant.companion.android import android.app.DownloadManager import android.content.Context +import android.media.AudioManager import androidx.core.content.getSystemService import androidx.work.WorkManager import dagger.Module @@ -74,4 +75,10 @@ object ApplicationModule { fun providesDownloadManager(@ApplicationContext context: Context): DownloadManager? { return context.getSystemService() } + + @Provides + @Singleton + fun providesAudioManager(@ApplicationContext context: Context): AudioManager? { + return context.getSystemService() + } } diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt index 150fc18db6a..6365fea6d91 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt @@ -129,6 +129,7 @@ class MessagingManager @Inject constructor( private val permissionRequestMediator: PermissionRequestMediator, private val assistConfigManager: AssistConfigManager, private val defaultAssistantManager: DefaultAssistantManager, + private val audioManager: AudioManager?, ) { companion object { const val APP_PREFIX = "app://" @@ -696,7 +697,6 @@ class MessagingManager @Inject constructor( } COMMAND_RINGER_MODE -> { - val audioManager = context.getSystemService() val notificationManager = context.getSystemService() if (notificationManager?.isNotificationPolicyAccessGranted == false) { @@ -1946,18 +1946,14 @@ class MessagingManager @Inject constructor( } private fun stepStreamVolume(streamType: Int, volumeDelta: Int) { - val audioManager = context.getSystemService()!! - - val currentVolume = audioManager.getStreamVolume(streamType) + val currentVolume = audioManager!!.getStreamVolume(streamType) val newVolume = currentVolume + volumeDelta setStreamVolume(streamType = streamType, volume = newVolume) } private fun setStreamVolume(streamType: Int, volume: Int) { - val audioManager = context.getSystemService()!! - - val maxVolume = audioManager.getStreamMaxVolume(streamType) + val maxVolume = audioManager!!.getStreamMaxVolume(streamType) val clampedVolume = volume.coerceIn(0, maxVolume) audioManager.setStreamVolume( From a5af15f0921ea855bfe2ae21dc71d25060f0e727 Mon Sep 17 00:00:00 2001 From: marazmarci Date: Wed, 1 Jul 2026 16:05:35 +0200 Subject: [PATCH 5/6] Add changelog entry for command_volume_level_step --- app/src/main/res/xml/changelog_master.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml index 431ba30d84f..c3664c3ed85 100755 --- a/app/src/main/res/xml/changelog_master.xml +++ b/app/src/main/res/xml/changelog_master.xml @@ -2,6 +2,7 @@ + Added command_volume_level_step notification command for relative volume adjustments Bug fixes and dependency updates From 93332f92a19fbb503b6b40e982193fcb11253254 Mon Sep 17 00:00:00 2001 From: marazmarci Date: Wed, 1 Jul 2026 16:25:53 +0200 Subject: [PATCH 6/6] Fix ktlint formatting in MessagingManager --- .../android/notifications/MessagingManager.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt index 6365fea6d91..0eaea8d6807 100644 --- a/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt +++ b/app/src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt @@ -422,7 +422,8 @@ class MessagingManager @Inject constructor( } COMMAND_VOLUME_LEVEL, - COMMAND_VOLUME_LEVEL_STEP -> { + COMMAND_VOLUME_LEVEL_STEP, + -> { if (!jsonData[NotificationData.MEDIA_STREAM].isNullOrEmpty() && jsonData[NotificationData.MEDIA_STREAM] in CHANNEL_VOLUME_STREAM && !jsonData[NotificationData.COMMAND].isNullOrEmpty() && @@ -734,7 +735,8 @@ class MessagingManager @Inject constructor( } COMMAND_VOLUME_LEVEL, - COMMAND_VOLUME_LEVEL_STEP -> { + COMMAND_VOLUME_LEVEL_STEP, + -> { val notificationManager = context.getSystemService() if (notificationManager?.isNotificationPolicyAccessGranted == false) { notifyMissingPermission(message, serverId) @@ -743,7 +745,7 @@ class MessagingManager @Inject constructor( stream = data[NotificationData.MEDIA_STREAM].toString(), volume = command!!.toInt(), step = message == COMMAND_VOLUME_LEVEL_STEP, - serverId = serverId , + serverId = serverId, message = message, ) } @@ -1932,6 +1934,7 @@ class MessagingManager @Inject constructor( Timber.w("Cannot control assistant volume: Not supported by the current version of Android") return } + else -> { Timber.d("Skipping command due to invalid channel stream ($stream)") return @@ -2144,7 +2147,11 @@ class MessagingManager @Inject constructor( when (type) { COMMAND_WEBVIEW, COMMAND_ACTIVITY, COMMAND_LAUNCH_APP -> requestSystemAlertPermission() - COMMAND_RINGER_MODE, COMMAND_DND, COMMAND_VOLUME_LEVEL, COMMAND_VOLUME_LEVEL_STEP -> requestDNDPermission() + COMMAND_RINGER_MODE, + COMMAND_DND, + COMMAND_VOLUME_LEVEL, + COMMAND_VOLUME_LEVEL_STEP, + -> requestDNDPermission() COMMAND_MEDIA -> requestNotificationPermission()