Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@
package org.microg.gms.cast;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Base64;
import android.util.Log;

import com.google.android.gms.cast.ApplicationMetadata;
Expand All @@ -37,10 +34,8 @@
import com.google.android.gms.cast.internal.ICastDeviceController;
import com.google.android.gms.cast.internal.ICastDeviceControllerListener;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.common.images.WebImage;
import com.google.android.gms.common.internal.BinderWrapper;
import com.google.android.gms.common.internal.GetServiceRequest;

import su.litvak.chromecast.api.v2.Application;
import su.litvak.chromecast.api.v2.ChromeCast;
Expand All @@ -51,7 +46,6 @@
import su.litvak.chromecast.api.v2.ChromeCastConnectionEvent;
import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEvent;
import su.litvak.chromecast.api.v2.ChromeCastRawMessage;
import su.litvak.chromecast.api.v2.AppEvent;

public class CastDeviceControllerImpl extends ICastDeviceController.Stub implements
ChromeCastConnectionEventListener,
Expand Down Expand Up @@ -91,6 +85,22 @@ public CastDeviceControllerImpl(Context context, String packageName, Bundle extr
this.chromecast.registerConnectionListener(this);
}

/**
* Ensures a TCP/TLS connection to the Cast device is established.
* Must be called before any operation that communicates with the device.
*
* @throws IOException if the connection cannot be established
*/
private void ensureConnected() throws IOException {
if (!this.chromecast.isConnected()) {
try {
this.chromecast.connect();
} catch (GeneralSecurityException e) {
throw new IOException("SSL error connecting to cast device", e);
}
}
}

@Override
public void connectionEventReceived(ChromeCastConnectionEvent event) {
if (!event.isConnected()) {
Expand All @@ -109,7 +119,7 @@ protected ApplicationMetadata createMetadataFromApplication(Application app) {
Log.d(TAG, "unimplemented: ApplicationMetadata.senderAppLaunchUri");
metadata.images = new ArrayList<WebImage>();
metadata.namespaces = new ArrayList<String>();
for(Namespace namespace : app.namespaces) {
for (Namespace namespace : app.namespaces) {
metadata.namespaces.add(namespace.name);
}
metadata.senderAppIdentifier = this.context.getPackageName();
Expand All @@ -122,7 +132,7 @@ public void spontaneousEventReceived(ChromeCastSpontaneousEvent event) {
case MEDIA_STATUS:
break;
case STATUS:
su.litvak.chromecast.api.v2.Status status = (su.litvak.chromecast.api.v2.Status)event.getData();
su.litvak.chromecast.api.v2.Status status = (su.litvak.chromecast.api.v2.Status) event.getData();
Application app = status.getRunningApp();
ApplicationMetadata metadata = this.createMetadataFromApplication(app);
if (app != null) {
Expand Down Expand Up @@ -167,27 +177,27 @@ public void disconnect() {
this.chromecast.disconnect();
} catch (IOException e) {
Log.e(TAG, "Error disconnecting chromecast: " + e.getMessage());
return;
}
}

@Override
public void sendMessage(String namespace, String message, long requestId) {
try {
ensureConnected();
this.chromecast.sendRawRequest(namespace, message, requestId);
} catch (IOException e) {
Log.w(TAG, "Error sending cast message: " + e.getMessage());
this.onSendMessageFailure("", requestId, CommonStatusCodes.NETWORK_ERROR);
return;
}
}

@Override
public void stopApplication(String sessionId) {
try {
ensureConnected();
this.chromecast.stopSession(sessionId);
} catch (IOException e) {
Log.w(TAG, "Error sending cast message: " + e.getMessage());
Log.w(TAG, "Error stopping cast session: " + e.getMessage());
return;
}
this.sessionId = null;
Expand All @@ -205,6 +215,14 @@ public void unregisterNamespace(String namespace) {

@Override
public void launchApplication(String applicationId, LaunchOptions launchOptions) {
try {
ensureConnected();
} catch (IOException e) {
Log.w(TAG, "Error connecting to cast device: " + e.getMessage());
this.onApplicationConnectionFailure(CommonStatusCodes.NETWORK_ERROR);
return;
}

Application app = null;
try {
app = this.chromecast.launchApp(applicationId);
Expand All @@ -213,16 +231,39 @@ public void launchApplication(String applicationId, LaunchOptions launchOptions)
this.onApplicationConnectionFailure(CommonStatusCodes.NETWORK_ERROR);
return;
}
this.sessionId = app.sessionId;

if (app == null) {
Log.w(TAG, "launchApplication returned null for id: " + applicationId);
this.onApplicationConnectionFailure(CommonStatusCodes.ERROR);
return;
}

this.sessionId = app.sessionId;
ApplicationMetadata metadata = this.createMetadataFromApplication(app);
this.onApplicationConnectionSuccess(metadata, app.statusText, app.sessionId, true);
}

@Override
public void joinApplication(String applicationId, String sessionId, JoinOptions joinOptions) {
Log.d(TAG, "unimplemented Method: joinApplication");
this.launchApplication(applicationId, new LaunchOptions());
try {
ensureConnected();
su.litvak.chromecast.api.v2.Status status = this.chromecast.getStatus();
Application runningApp = (status != null) ? status.getRunningApp() : null;

if (runningApp != null && runningApp.id.equals(applicationId)
&& (sessionId == null || runningApp.sessionId.equals(sessionId))) {
// The requested app is already running — join it without relaunching.
this.sessionId = runningApp.sessionId;
ApplicationMetadata metadata = this.createMetadataFromApplication(runningApp);
this.onApplicationConnectionSuccess(metadata, runningApp.statusText, runningApp.sessionId, false);
} else {
// App not running or session mismatch — fall back to launching.
this.launchApplication(applicationId, new LaunchOptions());
}
} catch (IOException e) {
Log.w(TAG, "Error joining cast application: " + e.getMessage());
this.onApplicationConnectionFailure(CommonStatusCodes.NETWORK_ERROR);
}
}

public void onDisconnected(int reason) {
Expand Down Expand Up @@ -276,7 +317,6 @@ public void onBinaryMessageReceived(String namespace, byte[] data) {
}

public void onApplicationDisconnected(int paramInt) {
Log.d(TAG, "unimplemented Method: onApplicationDisconnected");
if (this.listener != null) {
try {
this.listener.onApplicationDisconnected(paramInt);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,16 @@

package org.microg.gms.cast;

import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;

import androidx.mediarouter.media.MediaRouteProvider;
import androidx.mediarouter.media.MediaRouter;

import com.google.android.gms.common.images.WebImage;
import com.google.android.gms.cast.CastDevice;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.io.IOException;
import java.lang.Thread;
import java.lang.Runnable;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.security.GeneralSecurityException;

import su.litvak.chromecast.api.v2.ChromeCast;
import su.litvak.chromecast.api.v2.ChromeCasts;
import su.litvak.chromecast.api.v2.Status;
import su.litvak.chromecast.api.v2.ChromeCastsListener;

public class CastMediaRouteController extends MediaRouteProvider.RouteController {
private static final String TAG = CastMediaRouteController.class.getSimpleName();
Expand All @@ -56,37 +36,69 @@ public class CastMediaRouteController extends MediaRouteProvider.RouteController

public CastMediaRouteController(CastMediaRouteProvider provider, String routeId, String address) {
super();

this.provider = provider;
this.routeId = routeId;
this.chromecast = new ChromeCast(address);
}

@Override
public boolean onControlRequest(Intent intent, MediaRouter.ControlRequestCallback callback) {
Log.d(TAG, "unimplemented Method: onControlRequest: " + this.routeId);
return false;
}

@Override
public void onRelease() {
Log.d(TAG, "unimplemented Method: onRelease: " + this.routeId);
try {
if (this.chromecast.isConnected()) {
this.chromecast.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error releasing cast route controller: " + e.getMessage());
}
}

/**
* Called when the user selects this route. Opens the TCP/TLS connection
* to the Cast device so subsequent operations succeed immediately.
*/
@Override
public void onSelect() {
Log.d(TAG, "unimplemented Method: onSelect: " + this.routeId);
try {
if (!this.chromecast.isConnected()) {
this.chromecast.connect();
}
} catch (IOException | GeneralSecurityException e) {
Log.e(TAG, "Error connecting to cast device on route select: " + e.getMessage());
}
}

@Override
public void onSetVolume(int volume) {
Log.d(TAG, "unimplemented Method: onSetVolume: " + this.routeId);
}

/**
* Called when the user deselects or disconnects from this route.
* Closes the TCP/TLS connection to the Cast device.
*/
@Override
public void onUnselect() {
Log.d(TAG, "unimplemented Method: onUnselect: " + this.routeId);
try {
if (this.chromecast.isConnected()) {
this.chromecast.disconnect();
}
} catch (IOException e) {
Log.e(TAG, "Error disconnecting from cast device on route unselect: " + e.getMessage());
}
}

@Override
public void onUnselect(int reason) {
Log.d(TAG, "unimplemented Method: onUnselect: " + this.routeId);
onUnselect();
}

@Override
public void onUpdateVolume(int delta) {
Log.d(TAG, "unimplemented Method: onUpdateVolume: " + this.routeId);
}
Expand Down