App 2.42.1: the music check connects to no other app
In the car, 2.42.0's check stopped the Bluetooth music and raised a media-access prompt: binding every media browser service started the car radio and the Local Media Player. The services are now only listed; the active media sessions already carry the song of Bluetooth and CarPlay without any connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
+2
-2
@@ -34,8 +34,8 @@ android {
|
||||
applicationId "com.stinnesbeck.nilsmg4"
|
||||
minSdk 27
|
||||
targetSdk 36
|
||||
versionCode 151
|
||||
versionName "2.42.0"
|
||||
versionCode 152
|
||||
versionName "2.42.1"
|
||||
|
||||
// Nothing about the server ships in the APK: its URL and ingest token are entered in
|
||||
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
|
||||
|
||||
@@ -9,25 +9,17 @@ import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioPlaybackConfiguration;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.browse.MediaBrowser;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.service.media.MediaBrowserService;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* The music-sources check on the Logs tab: where the head unit tells what is playing, before
|
||||
@@ -37,23 +29,15 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
* <li>the active media sessions ({@code MediaSessionManager.getActiveSessions}, which needs
|
||||
* MEDIA_CONTENT_CONTROL: the car build has it as the system user), with their song and
|
||||
* state;</li>
|
||||
* <li>every media browser service on the head unit, CarPlay's
|
||||
* ({@link #CARPLAY}, where the car's own media library reads CarPlay's song) among them:
|
||||
* connected to as the car's media library does, its session's song and state read, and
|
||||
* disconnected.</li>
|
||||
* <li>every media browser service on the head unit, listed but not connected to (2.42.1:
|
||||
* a connection is not harmless, see {@link #browsers}).</li>
|
||||
* </ol>
|
||||
* Nothing is played, paused or skipped. The report goes where a signal recording goes
|
||||
* ({@link SignalRecorder#saveCopies}): the app, a USB stick, the server.
|
||||
* No other app's service is bound; nothing is played, paused or skipped. The report goes where
|
||||
* a signal recording goes ({@link SignalRecorder#saveCopies}): the app, a USB stick, the server.
|
||||
*/
|
||||
final class MediaProbe {
|
||||
|
||||
static final String FILE = "media_probe.txt";
|
||||
/** CarPlay's song, as {@code com.saicmotor.sdk.media…MediaPlayControlManager} reads it. */
|
||||
static final ComponentName CARPLAY = new ComponentName("com.allgo.remoteui.mediabrowserservice",
|
||||
"com.allgo.remoteui.mediabrowserservice.RemoteUIMediaBrowserService");
|
||||
static final long CONNECT_TIMEOUT_MS = 8_000L;
|
||||
/** More browser services than this are listed but not connected to. */
|
||||
static final int MAX_BROWSERS = 12;
|
||||
|
||||
private static volatile boolean running;
|
||||
private static long startedMs;
|
||||
@@ -182,98 +166,31 @@ final class MediaProbe {
|
||||
}
|
||||
}
|
||||
|
||||
/** Every media browser service, each connected to, read and disconnected. */
|
||||
/**
|
||||
* Every media browser service, listed only. 2.42.0 connected to each, as the car's media
|
||||
* library does; on the car that was not harmless: the Bluetooth music stopped (the AOSP car
|
||||
* radio's service, `com.android.car.radio/.RadioService`, answered "connecting", most likely
|
||||
* taking the audio) and the Local Media Player asked for access to media. The active sessions
|
||||
* above already carry the song of whatever plays, CarPlay's and Bluetooth's included, so no
|
||||
* service is bound any more.
|
||||
*/
|
||||
private static void browsers(Context app, List<String> out) {
|
||||
out.add("Media browser services (" + MediaBrowserService.SERVICE_INTERFACE + "):");
|
||||
Set<ComponentName> services = new LinkedHashSet<>();
|
||||
out.add("Media browser services (" + MediaBrowserService.SERVICE_INTERFACE + "), listed, not connected to:");
|
||||
try {
|
||||
List<ResolveInfo> found = app.getPackageManager()
|
||||
.queryIntentServices(new Intent(MediaBrowserService.SERVICE_INTERFACE), 0);
|
||||
for (ResolveInfo ri : found == null ? new ArrayList<ResolveInfo>() : found) {
|
||||
if (ri.serviceInfo != null) {
|
||||
services.add(new ComponentName(ri.serviceInfo.packageName, ri.serviceInfo.name));
|
||||
}
|
||||
if (found == null || found.isEmpty()) {
|
||||
out.add(" none");
|
||||
return;
|
||||
}
|
||||
for (ResolveInfo ri : found) {
|
||||
if (ri.serviceInfo == null) continue;
|
||||
out.add(" " + new ComponentName(ri.serviceInfo.packageName, ri.serviceInfo.name)
|
||||
.flattenToShortString());
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
out.add(" list not readable: " + e.getClass().getSimpleName());
|
||||
}
|
||||
if (!services.contains(CARPLAY)) out.add(" CarPlay's service is not listed; tried all the same");
|
||||
services.add(CARPLAY);
|
||||
HandlerThread thread = new HandlerThread("media-probe-browser");
|
||||
thread.start();
|
||||
try {
|
||||
Handler handler = new Handler(thread.getLooper());
|
||||
int n = 0;
|
||||
for (ComponentName service : services) {
|
||||
if (++n > MAX_BROWSERS) {
|
||||
out.add(" " + service.flattenToShortString() + ": not tried (limit)");
|
||||
continue;
|
||||
}
|
||||
browse(app, handler, service, out);
|
||||
}
|
||||
} finally {
|
||||
thread.quitSafely();
|
||||
}
|
||||
}
|
||||
|
||||
/** Connects to one browser service on {@code handler}'s thread and reads its session. */
|
||||
private static void browse(Context app, Handler handler, ComponentName service, List<String> out) {
|
||||
out.add(" " + service.flattenToShortString() + ":");
|
||||
CountDownLatch answered = new CountDownLatch(1);
|
||||
AtomicReference<String> outcome = new AtomicReference<>();
|
||||
AtomicReference<MediaBrowser> browser = new AtomicReference<>();
|
||||
boolean posted = handler.post(() -> {
|
||||
try {
|
||||
MediaBrowser b = new MediaBrowser(app, service, new MediaBrowser.ConnectionCallback() {
|
||||
@Override public void onConnected() {
|
||||
outcome.compareAndSet(null, "connected");
|
||||
answered.countDown();
|
||||
}
|
||||
|
||||
@Override public void onConnectionFailed() {
|
||||
outcome.compareAndSet(null, "refused or failed");
|
||||
answered.countDown();
|
||||
}
|
||||
|
||||
@Override public void onConnectionSuspended() {
|
||||
outcome.compareAndSet(null, "suspended");
|
||||
answered.countDown();
|
||||
}
|
||||
}, null);
|
||||
browser.set(b);
|
||||
b.connect();
|
||||
} catch (Throwable t) {
|
||||
outcome.compareAndSet(null, "could not try: " + t.getClass().getSimpleName());
|
||||
answered.countDown();
|
||||
}
|
||||
});
|
||||
try {
|
||||
if (!posted || !answered.await(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
|
||||
out.add(" no answer in " + CONNECT_TIMEOUT_MS / 1000 + " s");
|
||||
return;
|
||||
}
|
||||
out.add(" " + outcome.get());
|
||||
MediaBrowser b = browser.get();
|
||||
if (!"connected".equals(outcome.get()) || b == null) return;
|
||||
try {
|
||||
out.add(" root: " + MediaProbeText.clean(b.getRoot()));
|
||||
describe(new MediaController(app, b.getSessionToken()), out, " ");
|
||||
} catch (Throwable t) {
|
||||
out.add(" session not readable: " + t.getClass().getSimpleName());
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
out.add(" interrupted");
|
||||
} finally {
|
||||
handler.post(() -> {
|
||||
try {
|
||||
MediaBrowser b = browser.get();
|
||||
if (b != null) b.disconnect();
|
||||
} catch (Throwable ignored) {
|
||||
// already gone
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** A session's state and everything its song's metadata holds. */
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
<string name="signal_recorder_running">Recording: %1$d:%2$02d of 3:00, %3$d captures, %4$d change events heard. Keys reported, then assistance properties heard, then values changed so far, the rarest first:</string>
|
||||
<string name="signal_recorder_done">Recording from %1$s: %2$d captures, %3$d change events heard, %4$s. Keys reported, then assistance properties heard, then values changed, the rarest first:</string>
|
||||
<string name="label_media_probe">Music sources</string>
|
||||
<string name="media_probe_hint">Finds where the head unit tells what is playing: the playback Android reports, the media sessions apps publish, and every media service of the head unit, CarPlay\'s among them, each with its song. Play something first (CarPlay, then the radio, Bluetooth or USB) and check once for each. Like a recording, the result goes to your server and to a USB stick. It only reads: nothing is played, paused or skipped.</string>
|
||||
<string name="media_probe_hint">Finds where the head unit tells what is playing: the playback Android reports and the media sessions apps publish (CarPlay, Bluetooth, radio, USB), each with its song, and the head unit\'s media services by name. Play something first and check once for each source. Like a recording, the result goes to your server and to a USB stick. It only reads and connects to no other app: nothing is played, paused or skipped.</string>
|
||||
<string name="btn_media_probe">Check music sources</string>
|
||||
<string name="media_probe_running">Checking… (up to a minute)</string>
|
||||
<string name="media_probe_done">Check from %1$s, %2$s:</string>
|
||||
|
||||
Reference in New Issue
Block a user