Skip to content
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
75e9d8b
Add native Android media controls for HA media_player entities
FletcherD Mar 25, 2026
20bb0b1
1. Dispatchers.Main → Dispatchers.Default in service scope
FletcherD Mar 26, 2026
3b3178d
Add volume control to native media controls
FletcherD Mar 26, 2026
ba45502
Decouple session logic from HaMediaSessionService into HaMediaSession
FletcherD Mar 26, 2026
45a112b
Stop media session service when its configured server is deleted
FletcherD Mar 26, 2026
133c278
Support multiple media_player entities as native media controls
FletcherD Mar 26, 2026
3bd574e
add COMMAND_SET_DEVICE_VOLUME and COMMAND_ADJUST_DEVICE_VOLUME back a…
FletcherD Mar 26, 2026
c83de10
Put the player into STATE_BUFFERING on disconnect when the WebSocket …
FletcherD Mar 26, 2026
7028496
Address PR review feedback on media session code
FletcherD Mar 26, 2026
4ee48ed
Use URL for artwork path
FletcherD Mar 26, 2026
2b1cbf2
Refactor Media Control settings UI to use reorderable picker similar …
FletcherD Mar 26, 2026
bbad9c2
Refactor to avoid ComposeUnstableCollections issues, remove stale lin…
FletcherD Mar 27, 2026
f484849
Store media control configuration in the database instead of PrefsRep…
FletcherD Mar 27, 2026
c434c30
Fix crash when adding media control entity; Fix artwork disappearing …
FletcherD Mar 27, 2026
e75df17
Fetch media information and artwork immediately on startObservingState
FletcherD Mar 27, 2026
16805bd
Remove Save and Clear All buttons from media control settings page, s…
FletcherD Mar 27, 2026
eea9e85
Minor cleanup
FletcherD Mar 27, 2026
cf2db98
Fix multi-entity session routing; add @AssistedInject and unit tests
FletcherD Mar 27, 2026
f11db4c
ktlint fixes
FletcherD Mar 27, 2026
2b3209d
Reactively manage sessions via observeConfiguredEntities() flow
FletcherD Mar 28, 2026
52ba3b7
Fix media controls not appearing on cold start
FletcherD Mar 28, 2026
b14c1e5
Fix media controls not appearing on cold start on V2 frontend
FletcherD Mar 29, 2026
1ad0c4e
Expose stop, mute, shuffle, and repeat functions to media controls
FletcherD Mar 29, 2026
5abd769
Merge branch 'main' into feature/native-media-controls
FletcherD Mar 29, 2026
8375610
Expose more metadata fields from media player entity
FletcherD Mar 29, 2026
045ede1
Make media controls settings page more consistent with other settings
FletcherD Mar 29, 2026
1290d3e
Remove setContentBufferedPositionMs() call which is useless
FletcherD Mar 29, 2026
d36ba76
Tapping media controls notification body opens the media player entit…
FletcherD Apr 1, 2026
5563c41
Address mechanical PR review comments from TimoPtr
FletcherD Apr 2, 2026
c5089c0
Address coroutine architecture PR review comments
FletcherD Apr 2, 2026
b65ddb0
Split up HaRemoteMediaPlayer.kt getState(); use entityFriendlyName in…
FletcherD Apr 2, 2026
23c5d38
→ use ; Remove the four fields.
FletcherD Apr 2, 2026
8702016
Address MediaControlSettings PR review comments
FletcherD Apr 2, 2026
70080ba
Fix showing entity name instead of track title; Add small app icon to…
FletcherD Apr 2, 2026
4a4f113
When disconnected, retry only once when opening app instead of in a l…
FletcherD Apr 3, 2026
ebe2801
Simplify startObservingState by moving initial fetch into observeEnti…
FletcherD Apr 3, 2026
9a2cec1
FrontendViewModel.kt: Removed appContext: Context, mediaControlReposi…
FletcherD Apr 3, 2026
09ac27e
ktlint fix
FletcherD Apr 3, 2026
06172da
loadArtworkAndUpdatePlayer now uses state.entityPictureUrl as the cac…
FletcherD Apr 5, 2026
fb0612c
Don't include .size() when requesting artwork image
FletcherD Apr 8, 2026
612a1f8
Remove ServerRegistries in MediaControlSettingsViewModel.kt; Remove '…
FletcherD Apr 8, 2026
5867fe5
HaMediaSessionTest.kt: Remove Thread.sleep
FletcherD Apr 8, 2026
b6af049
HaMediaSessionServiceTest.kt: Don't call reconcileSessions() directly…
FletcherD Apr 9, 2026
5c1251a
Remove fake 3-item playlist from HaRemoteMediaPlayer
FletcherD Apr 9, 2026
2a28179
Remove automotive and Meya Quest support for media controls
FletcherD Apr 9, 2026
0d1bd25
Remove Meta Quest support for media controls
FletcherD Apr 9, 2026
b7d9e0b
Add screenshot tests for MediaControlSettingsScreen
FletcherD Apr 9, 2026
1c9b63f
Merge origin/main into feature/native-media-controls
FletcherD Apr 9, 2026
c3641e6
Remove the reconnect() call from onStartCommand so we don't cancel We…
FletcherD Apr 14, 2026
08b8d6f
Remove retry loop from startObservingState()
FletcherD Apr 14, 2026
4278fb2
Refactor HaMediaSession.kt: Remove scope creation from this class. In…
FletcherD Apr 14, 2026
26a8a83
Code clarity in HaRemoteMediaPlayer.kt
FletcherD Apr 14, 2026
6b823b1
HaRemoteMediaPlayer.kt
FletcherD Apr 14, 2026
7de40bb
Address PR review comments (batch 1)
FletcherD Apr 14, 2026
9d48643
HaMediaSessionService: Document why onTaskRemoved skips stopSelf when…
FletcherD Apr 14, 2026
eda99bf
HaMediaSessionService: Remove repository param from startIfConfigured
FletcherD Apr 14, 2026
5766948
WebViewActivity: Revert unrelated deep-link handling change
FletcherD Apr 14, 2026
c491d11
MediaControlSettings: Move HATheme from Screen composable into Fragme…
FletcherD Apr 14, 2026
1f607e8
HaMediaSessionServiceTest: Remove redundant unmockkAll() — instance m…
FletcherD Apr 14, 2026
3b85483
MediaControlConfig: Rename position column to index
FletcherD Apr 14, 2026
5d53fe6
Revert changes to ServerSettingsPresenterImpl.kt; Revert log message …
FletcherD Apr 15, 2026
20757c4
Fix crash when removing/readding entity in settings
FletcherD Apr 15, 2026
e2c2120
Workaround for notification disappearing after adding two entities an…
FletcherD Apr 15, 2026
fd69a5d
Refactor to fix multiple entitites: Build MediaStyle notifications ou…
FletcherD Apr 15, 2026
a695d13
Set notification when playing so it can't be swiped away (dismissibl…
FletcherD Apr 15, 2026
e75dea8
Remove entity reordering from media controls settings screen
FletcherD Apr 15, 2026
1b38f33
Fix MediaControlRepositoryImplTest tests
FletcherD Apr 15, 2026
3575cfe
Fix subtitle in media control settings entity row; Fix defunct notifi…
FletcherD Apr 16, 2026
a3ffa0a
Improve media control settings screen: Don't show anything until load…
FletcherD Apr 16, 2026
0ad3588
Make activeSessions, reconcileSessions, and startObservingEntities pr…
FletcherD Apr 16, 2026
82895ff
MediaControlSettingsViewModel.kt: inject the Default dispatcher into …
FletcherD Apr 16, 2026
99b6b10
Media controls tests: Clean up properly after tests
FletcherD Apr 17, 2026
3c65412
Update screenshot tests for media control settings changes and regene…
FletcherD Apr 17, 2026
1eda4e4
Merge origin/main into feature/native-media-controls
FletcherD Apr 17, 2026
ad135f5
All `handle*()` methods return a pending `SettableFuture<Void>` (stor…
FletcherD Apr 17, 2026
e0e43d6
Cleanup test structure
FletcherD Apr 23, 2026
3b32629
Merge origin/main into feature/native-media-controls
FletcherD May 1, 2026
adee5e9
Remove unnecessary SDK_INT version check
FletcherD May 5, 2026
cb0f9d5
Merge branch 'main' into feature/native-media-controls
FletcherD May 5, 2026
304ed6c
Move media control service start to LaunchActivity
FletcherD May 5, 2026
30073b4
Use @ApplicationContext in HaMediaSession constructor
FletcherD May 5, 2026
275b4ec
Encapsulate MediaSession inside HaMediaSession, expose id and notific…
FletcherD May 5, 2026
c4b1784
Refactor CommandCallback to return Job, tie ListenableFuture to corou…
FletcherD May 5, 2026
c222e81
Remove actionScope field, pass scope to getCommandCallback, make call…
FletcherD May 5, 2026
d3794a2
Add FailFast guard in HaMediaSession.observe() for double-call
FletcherD May 5, 2026
535cb96
Remove verbose log in observe(), make startObservingState private
FletcherD May 5, 2026
9baa5b1
Use LaunchActivity for media notification tap intent
FletcherD May 5, 2026
c6c4314
Simplify loadArtworkAndUpdatePlayer in HaMediaSession
FletcherD May 5, 2026
486a859
Fix log message in resolveArtworkUrl to reference server ID
FletcherD May 5, 2026
71bb517
Split reconcileSessions into smaller helper functions
FletcherD May 5, 2026
f5a47c4
Remove unnecessary onStartCommand override in HaMediaSessionService
FletcherD May 5, 2026
4f5be54
Confine activeSessions map access to Main thread
FletcherD May 5, 2026
6f6fa04
Pre-scale album art to notification icon size to avoid main thread do…
FletcherD May 5, 2026
fddbcd1
Revert "Encapsulate MediaSession inside HaMediaSession, expose id and…
FletcherD May 6, 2026
623bb57
Restore tests: observe() stays alive after flow ends, restarts on com…
FletcherD May 6, 2026
eaf71d6
Encapsulate MediaSession inside HaMediaSession, expose id and notific…
FletcherD May 6, 2026
7c4b248
Unify session ID format with map key; use key string in promoteForegr…
FletcherD May 6, 2026
f6cd168
Merge remote-tracking branch 'fork/feature/native-media-controls' int…
FletcherD May 6, 2026
b416ec7
Fix HaMediaSessionService constructor and test
FletcherD May 10, 2026
6336c81
add commandScope in HaMediaSession so thrown exceptions are handled g…
FletcherD May 10, 2026
168fd9c
remove restartObservationIfNeeded; remove unregisterFrom
FletcherD May 10, 2026
09e456d
Add test to verify HaMediaSessionService.start is called in LaunchAct…
FletcherD May 10, 2026
cf42a8f
Address trivial PR review comments from @TimoPtr
FletcherD May 12, 2026
e85b81a
Address more PR review comments from @TimoPtr
FletcherD May 13, 2026
f4ff7c5
Address threading PR review comments (threads 8, 9, 10)
FletcherD May 13, 2026
ea35aed
Address PR thread 6: move startObservingState after player creation
FletcherD May 13, 2026
9727ee4
Return failed future for unsupported commands in handleCommand
FletcherD May 13, 2026
aa94487
Fix HaMediaSession coroutineScope hanging on natural flow completion
FletcherD May 13, 2026
f608103
Clarify comment on MediaPlaybackState.Off -> STATE_IDLE mapping
FletcherD May 13, 2026
86d0420
Address trivial PR review comments (threads 33, 35, 48, 51)
FletcherD May 15, 2026
5c19e07
Address PR review comments (threads 29, 30, 32, 36, 52)
FletcherD May 15, 2026
2b399b3
Pre-scale notification album art bitmap on IO to fix StrictMode viola…
FletcherD May 15, 2026
2e620a9
Address PR review comments (threads 34, 39, 40, 41)
FletcherD May 15, 2026
32ff7da
Address PR review comments (threads 42–47): refactor MediaControlSett…
FletcherD May 15, 2026
7068ddd
Remove index column from MediaControlConfig (thread 53)
FletcherD May 15, 2026
a8c57a1
Fix seek-bar regression after media commands by completing pendingCom…
FletcherD May 15, 2026
4fc1f1b
Update screenshot reference images for MediaControlSettingsScreen
FletcherD May 15, 2026
38935d8
Merge origin/main into feature/native-media-controls
FletcherD May 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions app/lint-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
errorLine2=" ^">
<location
file="src/main/kotlin/io/homeassistant/companion/android/util/DataUriDownloadManager.kt"
line="64"
line="65"
column="9"/>
</issue>

Expand All @@ -85,7 +85,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/io/homeassistant/companion/android/notifications/MessagingManager.kt"
line="1095"
line="1097"
column="17"/>
</issue>

Expand Down Expand Up @@ -404,7 +404,7 @@
errorLine2=" ~~~~~~~~~~~">
<location
file="src/main/AndroidManifest.xml"
line="89"
line="90"
column="6"/>
</issue>

Expand Down Expand Up @@ -646,7 +646,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/io/homeassistant/companion/android/settings/SettingsFragment.kt"
line="539"
line="558"
column="17"/>
</issue>

Expand All @@ -657,7 +657,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/io/homeassistant/companion/android/settings/SettingsFragment.kt"
line="539"
line="558"
column="17"/>
</issue>

Expand Down Expand Up @@ -1341,7 +1341,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/kotlin/io/homeassistant/companion/android/settings/assist/AssistSettingsScreen.kt"
line="381"
line="342"
column="22"/>
</issue>

Expand Down
13 changes: 13 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
Expand Down Expand Up @@ -322,6 +323,18 @@
</intent-filter>
</service>

<service
android:name=".mediacontrol.HaMediaSessionService"
android:exported="true"
android:foregroundServiceType="mediaPlayback"
tools:ignore="ExportedService">
<!-- Exported so system media controllers can bind to the session.
Connections are filtered in MediaSession.Callback#onConnect. -->
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>

<activity android:name=".controls.HaControlsPanelActivity"
android:permission="android.permission.BIND_CONTROLS"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.LocalContext
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
Expand All @@ -18,6 +22,7 @@ import io.homeassistant.companion.android.frontend.FrontendScreen
import io.homeassistant.companion.android.frontend.FrontendViewModel
import io.homeassistant.companion.android.frontend.FrontendViewState
import io.homeassistant.companion.android.launch.HAStartDestinationRoute
import io.homeassistant.companion.android.mediacontrol.MediaControlStarterViewModel
import io.homeassistant.companion.android.settings.SettingsActivity
import io.homeassistant.companion.android.util.getActivity
import io.homeassistant.companion.android.webview.WebViewActivity
Expand Down Expand Up @@ -70,6 +75,15 @@ internal fun NavGraphBuilder.frontendScreen(
if (WIPFeature.USE_FRONTEND_V2) {
composable<FrontendRoute> {
val viewModel: FrontendViewModel = hiltViewModel()
val mediaControlStarter: MediaControlStarterViewModel = hiltViewModel()
val context = LocalContext.current

val lifecycle = LocalLifecycleOwner.current.lifecycle
LaunchedEffect(lifecycle) {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
mediaControlStarter.startIfConfigured(context)
}
}
Comment thread
FletcherD marked this conversation as resolved.
Outdated

FrontendNavigationHandler(
navigationEvents = viewModel.navigationEvents,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.homeassistant.companion.android.mediacontrol

import android.content.Context
import androidx.core.app.NotificationCompat
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.CommandButton
import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.MediaNotification
import androidx.media3.session.MediaSession
import com.google.common.collect.ImmutableList
import io.homeassistant.companion.android.common.R as commonR

/**
* Extends [DefaultMediaNotificationProvider] to display the Home Assistant notification icon
* and the media player entity's friendly name as the notification sub-text.
*
* @param sessionEntityName Maps a session ID to the friendly name of its media_player entity.
* May return null if the entity name is not yet known.
*/
@UnstableApi
class HaMediaNotificationProvider(context: Context, private val sessionEntityName: (sessionId: String) -> String?) :
Comment thread
FletcherD marked this conversation as resolved.
Outdated
DefaultMediaNotificationProvider(context) {

init {
setSmallIcon(commonR.drawable.ic_stat_ic_notification)
}

override fun addNotificationActions(
mediaSession: MediaSession,
mediaButtons: ImmutableList<CommandButton>,
builder: NotificationCompat.Builder,
actionFactory: MediaNotification.ActionFactory,
): IntArray {
sessionEntityName(mediaSession.id)?.let { builder.setSubText(it) }
return super.addNotificationActions(mediaSession, mediaButtons, builder, actionFactory)
}
}
Loading
Loading