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
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ android.useAndroidX=true
org.gradle.configuration-cache=true
org.gradle.caching=true
org.gradle.jvmargs=-Xmx4096m -XX:+UseParallelGC --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
kotlin.daemon.jvmargs=-Xmx4g
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import androidx.mediarouter.media.MediaControlIntent;
import androidx.mediarouter.media.MediaRouteSelector;
import androidx.mediarouter.media.MediaRouter;

import com.google.android.gms.cast.CastMediaControlIntent;
import com.google.android.gms.cast.framework.CastOptions;
Expand All @@ -35,8 +36,8 @@
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper;

import java.util.Map;
import java.util.HashMap;
import java.util.Map;

public class CastContextImpl extends ICastContext.Stub {
private static final String TAG = CastContextImpl.class.getSimpleName();
Expand All @@ -56,21 +57,51 @@ public CastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter
this.context = (Context) ObjectWrapper.unwrap(context);
this.options = options;
this.router = router;

for (Map.Entry<String, IBinder> entry : sessionProviders.entrySet()) {
this.sessionProviders.put(entry.getKey(), ISessionProvider.Stub.asInterface(entry.getValue()));
this.sessionProviders.put(
entry.getKey(),
ISessionProvider.Stub.asInterface(entry.getValue())
);
}

String receiverApplicationId = options.getReceiverApplicationId();
String defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId);

// Direct lookup
this.defaultSessionProvider = this.sessionProviders.get(defaultCategory);

// Prefix fallback
if (this.defaultSessionProvider == null) {
for (Map.Entry<String, ISessionProvider> entry : this.sessionProviders.entrySet()) {
if (entry.getKey() != null && entry.getKey().startsWith(defaultCategory)) {
this.defaultSessionProvider = entry.getValue();
break;
}
}
}

// TODO: This should incorporate passed options
this.mergedSelector = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.addControlCategory(defaultCategory)
.build();
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
.addControlCategory(defaultCategory)
.build();

// Always register the media router callback
try {
this.router.registerMediaRouterCallbackImpl(
this.mergedSelector.asBundle(),
new MediaRouterCallbackImpl(this)
);

this.router.addCallback(
this.mergedSelector.asBundle(),
MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY
);
} catch (RemoteException e) {
Log.w(TAG, "Failed to register media router callback: " + e.getMessage());
}
}

@Override
Expand Down Expand Up @@ -118,7 +149,6 @@ public void destroy() throws RemoteException {
@Override
public void onActivityResumed(IObjectWrapper activity) throws RemoteException {
Log.d(TAG, "unimplemented Method: onActivityResumed");

}

@Override
Expand Down Expand Up @@ -151,4 +181,4 @@ public CastOptions getOptions() {
public IObjectWrapper getWrappedThis() throws RemoteException {
return ObjectWrapper.wrap(this);
}
}
}
21 changes: 13 additions & 8 deletions play-services-cast-framework/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,28 @@

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>

<queries>
<package android:name="com.google.android.gms.policy_cast_dynamite"/>
</queries>

<application>
<receiver
android:name="com.google.android.gms.cast.framework.media.MediaIntentReceiver"
android:exported="false"/>
android:name="com.google.android.gms.cast.framework.media.MediaIntentReceiver"
android:exported="false"/>

<service
android:name="com.google.android.gms.cast.framework.media.MediaNotificationService"
android:exported="false"
android:foregroundServiceType="mediaPlayback"/>
android:name="com.google.android.gms.cast.framework.media.MediaNotificationService"
android:exported="false"
android:foregroundServiceType="mediaPlayback"/>

<service
android:name="com.google.android.gms.cast.framework.ReconnectionService"
android:exported="false"/>
android:name="com.google.android.gms.cast.framework.ReconnectionService"
android:exported="false"/>
</application>

</manifest>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEvent;
import su.litvak.chromecast.api.v2.ChromeCastRawMessage;
import su.litvak.chromecast.api.v2.AppEvent;
import android.os.IBinder;

public class CastDeviceControllerImpl extends ICastDeviceController.Stub implements
ChromeCastConnectionEventListener,
Expand All @@ -66,7 +67,9 @@ public class CastDeviceControllerImpl extends ICastDeviceController.Stub impleme
private CastDevice castDevice;
boolean notificationEnabled;
long castFlags;
ICastDeviceControllerListener listener;
volatile ICastDeviceControllerListener listener;
private IBinder listenerBinder;
private IBinder.DeathRecipient listenerDeathRecipient;

ChromeCast chromecast;

Expand All @@ -82,7 +85,7 @@ public CastDeviceControllerImpl(Context context, String packageName, Bundle extr
this.castFlags = extras.getLong("com.google.android.gms.cast.EXTRA_CAST_FLAGS");
BinderWrapper listenerWrapper = (BinderWrapper)extras.get("listener");
if (listenerWrapper != null) {
this.listener = ICastDeviceControllerListener.Stub.asInterface(listenerWrapper.binder);
replaceListener(ICastDeviceControllerListener.Stub.asInterface(listenerWrapper.binder));
}

this.chromecast = new ChromeCast(this.castDevice.getAddress());
Expand All @@ -91,6 +94,55 @@ public CastDeviceControllerImpl(Context context, String packageName, Bundle extr
this.chromecast.registerConnectionListener(this);
}

private synchronized void replaceListener(ICastDeviceControllerListener newListener) {
// Unlink old death recipient before replacing
if (this.listenerBinder != null && this.listenerDeathRecipient != null) {
try {
this.listenerBinder.unlinkToDeath(this.listenerDeathRecipient, 0);
} catch (Exception ignored) {}
}
this.listener = null;
this.listenerBinder = null;
this.listenerDeathRecipient = null;

if (newListener == null) return;

IBinder binder = newListener.asBinder();
IBinder.DeathRecipient dr = () -> handleListenerDeath(binder);
this.listener = newListener;
this.listenerBinder = binder;
this.listenerDeathRecipient = dr;
try {
binder.linkToDeath(dr, 0);
} catch (RemoteException e) {
Log.w(TAG, "Failed to link Cast client death: " + e.getMessage());
handleListenerDeath(binder);
}
}

private synchronized void clearListener() {
replaceListener(null);
}

private void handleListenerDeath(IBinder deadBinder) {
synchronized (this) {
if (deadBinder != this.listenerBinder) return;
this.listener = null;
this.listenerBinder = null;
this.listenerDeathRecipient = null;
}
Log.d(TAG, "Cast client died; disconnecting");
disconnectChromecast();
}

private void disconnectChromecast() {
try {
this.chromecast.disconnect();
} catch (IOException e) {
Log.e(TAG, "Error disconnecting chromecast: " + e.getMessage());
}
}

@Override
public void connectionEventReceived(ChromeCastConnectionEvent event) {
if (!event.isConnected()) {
Expand Down Expand Up @@ -147,12 +199,7 @@ public void rawMessageReceived(ChromeCastRawMessage message, Long requestId) {
switch (message.getPayloadType()) {
case STRING:
String response = message.getPayloadUtf8();
if (requestId == null) {
this.onTextMessageReceived(message.getNamespace(), response);
} else {
this.onSendMessageSuccess(response, requestId);
this.onTextMessageReceived(message.getNamespace(), response);
}
this.onTextMessageReceived(message.getNamespace(), response);
break;
case BINARY:
byte[] payload = message.getPayloadBinary();
Expand All @@ -163,14 +210,36 @@ public void rawMessageReceived(ChromeCastRawMessage message, Long requestId) {

@Override
public void disconnect() {
disconnectChromecast();
clearListener();
}

@Override
public void connect() {
Log.d(TAG, "connect()");
try {
this.chromecast.disconnect();
} catch (IOException e) {
Log.e(TAG, "Error disconnecting chromecast: " + e.getMessage());
return;
if (!this.chromecast.isConnected()) {
this.chromecast.connect();
}
this.onConnectedWithResult(CommonStatusCodes.SUCCESS);
} catch (Exception e) {
Log.w(TAG, "Error connecting to chromecast: " + e.getMessage());
this.onConnectedWithResult(CommonStatusCodes.NETWORK_ERROR);
}
}

@Override
public void setListener(ICastDeviceControllerListener listener) {
Log.d(TAG, "setListener()");
replaceListener(listener);
}

@Override
public void unregisterListener() {
Log.d(TAG, "unregisterListener()");
clearListener();
}

@Override
public void sendMessage(String namespace, String message, long requestId) {
try {
Expand Down Expand Up @@ -325,4 +394,15 @@ public void onDeviceStatusChanged(CastDeviceStatus deviceStatus) {
}
}
}

public void onConnectedWithResult(int statusCode) {
ICastDeviceControllerListener l = this.listener;
if (l != null) {
try {
l.onConnectedWithResult(statusCode);
} catch (RemoteException ex) {
Log.e(TAG, "Error calling onConnectedWithResult: " + ex.getMessage());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,38 @@

package org.microg.gms.cast;

import android.os.IBinder;
import android.os.RemoteException;
import android.os.Parcel;
import android.util.ArrayMap;
import android.util.Log;

import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.internal.ICastDeviceControllerListener;
import com.google.android.gms.common.Feature;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.internal.ConnectionInfo;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.BinderWrapper;
import com.google.android.gms.common.internal.IGmsCallbacks;

import org.microg.gms.BaseService;
import org.microg.gms.common.GmsService;

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 CastDeviceControllerService extends BaseService {
private static final String TAG = CastDeviceControllerService.class.getSimpleName();

private static final Feature[] FEATURES = new Feature[] {
new Feature("cxless_connect", 1L),
new Feature("cxless_set_listener", 1L)
};

public CastDeviceControllerService() {
super("GmsCastDeviceControllerSvc", GmsService.CAST);
}

@Override
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
callback.onPostInitComplete(0, new CastDeviceControllerImpl(this, request.packageName, request.extras), null);
ConnectionInfo connectionInfo = new ConnectionInfo();
connectionInfo.features = FEATURES;

callback.onPostInitCompleteWithConnectionInfo(
CommonStatusCodes.SUCCESS,
new CastDeviceControllerImpl(this, request.packageName, request.extras),
connectionInfo
);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.google.android.gms.cast.internal;

import com.google.android.gms.cast.internal.ICastDeviceControllerListener;
import com.google.android.gms.cast.LaunchOptions;
import com.google.android.gms.cast.JoinOptions;

Expand All @@ -11,4 +12,7 @@ interface ICastDeviceController {
oneway void unregisterNamespace(String namespace) = 11;
oneway void launchApplication(String applicationId, in LaunchOptions launchOptions) = 12;
oneway void joinApplication(String applicationId, String sessionId, in JoinOptions joinOptions) = 13;
}
oneway void connect() = 16;
oneway void setListener(ICastDeviceControllerListener listener) = 17;
oneway void unregisterListener() = 18;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ interface ICastDeviceControllerListener {
void onSendMessageSuccess(String response, long requestId) = 10;
void onApplicationStatusChanged(in ApplicationStatus applicationStatus) = 11;
void onDeviceStatusChanged(in CastDeviceStatus deviceStatus) = 12;
void onConnectedWithResult(int statusCode) = 13;
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ public CastDevice (
this.deviceVersion = deviceVersion;
this.friendlyName = friendlyName;
this.icons = new ArrayList<WebImage>();
this.icons.add(new WebImage(Uri.parse(String.format("http://%s:8008%s", this.address, iconPath))));
this.icons.add(new WebImage(
Uri.parse(String.format("http://%s:8008%s", this.address, iconPath))
));
this.modelName = modelName;
this.capabilities = capabilities;
}
Expand Down