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:
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user