App 2.45.0: now playing on the Overview

NowPlayingWatcher follows the head unit's media sessions (listening
only, never connecting to a player); NowPlaying picks what plays and
cleans the text. Tiles shows source, title, artist, cover and progress
under the battery; Halloween carves title and artist into the hill.
Settings: "Show what's playing", on by default. Nothing is logged or
sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-11-02 14:03:26 +01:00
co-authored by Claude Opus 5.5
parent bdb3f5e765
commit 91283093f5
15 changed files with 619 additions and 8 deletions
@@ -78,6 +78,7 @@ public class MainActivity extends AppCompatActivity {
/** The pane of each Overview style, by its {@link OverviewStyle} ordinal. */
private View[] stylePanes;
private MusicTap musicTap;
private NowPlayingWatcher nowPlaying;
private View consumptionPane;
private View chargingPane;
/** Display, battery, charging price and Wi-Fi. */
@@ -197,6 +198,7 @@ public class MainActivity extends AppCompatActivity {
markCurrentPage(position);
applyOverviewFullScreen(position);
updateMusicTap();
updateNowPlaying();
}
});
findViewById(R.id.edge_tab).setOnClickListener(v -> leaveOverviewFullScreen());
@@ -215,6 +217,8 @@ public class MainActivity extends AppCompatActivity {
/** Whether the Overview hides the navigation (the button in the navigation's header). */
static final String PREF_OVERVIEW_FULLSCREEN = "overview_fullscreen";
/** The Overview shows what plays (Tiles, Halloween); on by default. */
static final String PREF_NOW_PLAYING = "now_playing";
/** The Halloween pumpkin's light follows the music; off by default, so the analyser stays off. */
static final String PREF_MUSIC_LIGHT = "music_light";
@@ -359,6 +363,7 @@ public class MainActivity extends AppCompatActivity {
visorView = new VisorView(this);
HalloweenView halloweenView = new HalloweenView(this);
musicTap = new MusicTap(this);
nowPlaying = new NowPlayingWatcher(this);
halloweenView.setMusic(musicTap);
java.util.Collections.addAll(canvasStyles, new MinimalView(this), terminalView,
new NixieView(this), new ChronographView(this), gridView, gridMapView, new TimeCircuitsView(this),
@@ -514,6 +519,7 @@ public class MainActivity extends AppCompatActivity {
inFront = true;
updateMapLocation();
updateMusicTap();
updateNowPlaying();
}
@Override
@@ -564,6 +570,7 @@ public class MainActivity extends AppCompatActivity {
inFront = false;
updateMapLocation();
updateMusicTap();
updateNowPlaying();
}
@Override
@@ -1225,6 +1232,12 @@ public class MainActivity extends AppCompatActivity {
bindDriveMode();
bindOverviewStyle();
bindAccentColor();
SwitchMaterial showPlaying = settingsPane.findViewById(R.id.now_playing_switch);
showPlaying.setChecked(prefs.getBoolean(PREF_NOW_PLAYING, true));
showPlaying.setOnCheckedChangeListener((btn, checked) -> {
prefs.edit().putBoolean(PREF_NOW_PLAYING, checked).apply();
updateNowPlaying();
});
SwitchMaterial musicLight = settingsPane.findViewById(R.id.music_light_switch);
musicLight.setChecked(prefs.getBoolean(PREF_MUSIC_LIGHT, false));
musicLight.setOnCheckedChangeListener((btn, checked) -> {
@@ -1767,6 +1780,57 @@ public class MainActivity extends AppCompatActivity {
if (settingsPane != null) bindAccentColor();
updateMapLocation();
updateMusicTap();
updateNowPlaying();
}
/** The styles that show what plays. */
private static boolean showsNowPlaying(OverviewStyle style) {
return style == OverviewStyle.TILES || style == OverviewStyle.HALLOWEEN;
}
/** The media sessions are watched only while what plays can be seen, and the switch is on. */
private void updateNowPlaying() {
ViewPager2 pager = findViewById(R.id.content);
nowPlaying.setRunning(inFront && showsNowPlaying(overviewStyle())
&& prefs.getBoolean(PREF_NOW_PLAYING, true)
&& pager != null && pager.getCurrentItem() == 0);
if (!nowPlaying.isRunning()) showNowPlaying(null);
}
/** The Tiles tile: source, title, artist, a cover when there is one, and how far the song is. */
private void showNowPlaying(NowPlaying.Track t) {
if (vehiclePane == null) return;
View tile = vehiclePane.findViewById(R.id.now_playing_tile);
if (tile == null) return; // the narrow layout has no room for it
if (t == null) {
tile.setVisibility(View.INVISIBLE);
return;
}
tile.setVisibility(View.VISIBLE);
String source = getString(t.source == NowPlaying.Source.BLUETOOTH ? R.string.now_playing_bluetooth
: t.source == NowPlaying.Source.CARPLAY ? R.string.now_playing_carplay
: t.source == NowPlaying.Source.RADIO ? R.string.now_playing_radio : R.string.now_playing_other);
setIfChanged(vehiclePane.findViewById(R.id.now_playing_source),
t.playing ? source : getString(R.string.now_playing_paused, source));
setIfChanged(vehiclePane.findViewById(R.id.now_playing_title), t.title);
TextView artist = vehiclePane.findViewById(R.id.now_playing_artist);
setIfChanged(artist, t.subtitle == null ? "" : t.subtitle);
artist.setVisibility(t.subtitle == null ? View.GONE : View.VISIBLE);
android.widget.ImageView cover = vehiclePane.findViewById(R.id.now_playing_cover);
android.graphics.Bitmap art = nowPlaying.cover(t);
if (cover.getTag() != art) {
cover.setTag(art);
cover.setImageBitmap(art);
cover.setVisibility(art == null ? View.GONE : View.VISIBLE);
}
android.widget.ProgressBar bar = vehiclePane.findViewById(R.id.now_playing_progress);
float progress = t.progressAt(android.os.SystemClock.elapsedRealtime());
bar.setVisibility(progress < 0f ? View.INVISIBLE : View.VISIBLE);
if (progress >= 0f) bar.setProgress(Math.round(progress * bar.getMax()));
}
private static void setIfChanged(TextView view, CharSequence text) {
if (!android.text.TextUtils.equals(view.getText(), text)) view.setText(text);
}
/**
@@ -1840,6 +1904,9 @@ public class MainActivity extends AppCompatActivity {
TextView note = settingsPane.findViewById(R.id.accent_note);
note.setVisibility(style.usesAccent ? View.GONE : View.VISIBLE);
note.setText(getString(R.string.accent_not_used, styleLabels()[style.ordinal()]));
int playing = showsNowPlaying(style) ? View.VISIBLE : View.GONE;
settingsPane.findViewById(R.id.now_playing_switch).setVisibility(playing);
settingsPane.findViewById(R.id.now_playing_hint).setVisibility(playing);
int music = style == OverviewStyle.HALLOWEEN ? View.VISIBLE : View.GONE;
settingsPane.findViewById(R.id.music_light_switch).setVisibility(music);
settingsPane.findViewById(R.id.music_light_hint).setVisibility(music);
@@ -1925,6 +1992,10 @@ public class MainActivity extends AppCompatActivity {
v.tyreRl = unit.format(s.getTirePressures().getRearLeftKpa());
v.tyreRr = unit.format(s.getTirePressures().getRearRightKpa());
v.powerTrail = cockpitTrace.snapshot();
NowPlaying.Track track = nowPlaying.isRunning() ? nowPlaying.current() : null;
v.songTitle = track == null ? null : track.title;
v.songArtist = track == null ? null : track.subtitle;
showNowPlaying(track);
retroView.setValues(v);
flowView.setValues(v);
sciFiView.setValues(v);