Skip to content

Cast: return a no-op ReconnectionService instead of null#3577

Open
peterhel wants to merge 1 commit into
microg:masterfrom
peterhel:fix/cast-reconnection-service
Open

Cast: return a no-op ReconnectionService instead of null#3577
peterhel wants to merge 1 commit into
microg:masterfrom
peterhel:fix/cast-reconnection-service

Conversation

@peterhel

Copy link
Copy Markdown

Cast: return a no-op ReconnectionService instead of null

Summary

CastDynamiteModuleImpl.newReconnectionServiceImpl currently returns null. But the
Cast SDK's ReconnectionService calls onCreate() on the returned delegate without a
null check
, so any client that starts the ReconnectionService crashes with:

java.lang.RuntimeException: Unable to create service
  com.google.android.gms.cast.framework.ReconnectionService:
  java.lang.NullPointerException: Attempt to invoke interface method
  'void com.google.android.gms.cast.framework.zzq.onCreate()' on a null object reference
    at com.google.android.gms.cast.framework.ReconnectionService.onCreate(...play-services-cast-framework@@19.0.0:8)

The ReconnectionService is started on the connectionless (Cast.API_CXLESS) path that
modern first-party apps (Prime Video, YouTube, …) use, so in practice this crash-loops the
sender process during a cast session. The churn also surfaces downstream as a stream of
RemoteExceptions on the device-controller callbacks (onSendMessageSuccess /
onTextMessageReceived) — those are just callbacks landing on the listener of a process
that's being killed and restarted.

Fix

Return a minimal no-op IReconnectionService instead of null:

return new IReconnectionService.Stub() {
    @Override public void onCreate() {}
    @Override public int onStartCommand(Intent intent, int flags, int startId) {
        return android.app.Service.START_NOT_STICKY;
    }
    @Override public IBinder onBind(Intent intent) { return null; }
    @Override public void onDestroy() {}
};

microG doesn't implement Cast reconnection itself, so a no-op delegate is sufficient: it
satisfies the SDK's unconditional onCreate()/onStartCommand() calls without doing
anything, which keeps the session stable. (START_NOT_STICKY so the empty service isn't
re-created after being killed.)

Testing

On a Pixel 2 + a real Chromecast, casting a clip through the connectionless path:

  • Before: the sender NPE-loops on ReconnectionService.onCreate; the device-controller
    callbacks throw RemoteException (dead listener); the session is unstable.
  • After: no crash, no RemoteExceptions, the cast plays cleanly and the sender stays
    alive.

Notes

This is a microG-level gap independent of any single Cast PR — it affects every
connectionless Cast flow that wires up the ReconnectionService. Surfaced while
hardware-testing the open Cast PRs (#3377 in particular); with this fix in place those
connectionless implementations cast cleanly rather than crash-looping.

CastDynamiteModuleImpl.newReconnectionServiceImpl returned null, but the Cast
SDK's ReconnectionService calls onCreate() on the returned delegate without a
null check. So any client that starts the ReconnectionService (common on the
connectionless Cast.API_CXLESS path used by Prime/YouTube/etc.) crashes with an
NPE in ReconnectionService.onCreate, which destabilises the whole cast session.

Return a minimal no-op IReconnectionService (no-op onCreate/onDestroy,
onStartCommand=START_NOT_STICKY, onBind=null). microG doesn't drive reconnection
itself, so the no-op is sufficient and keeps connectionless sessions stable.

Tested on-device (Pixel 2 + Chromecast): with this in place, casting a clip via
the connectionless path no longer crash-loops the sender and plays cleanly;
without it the sender NPE-loops and the cast callbacks throw RemoteException.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant