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
@@ -0,0 +1,78 @@
/*
* Copyright (C) 2013-2026 microG Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.microg.gms.cast.framework;

import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.framework.ISessionProxy;

public class SessionImpl {
private static final String TAG = "SessionImpl";
private final Context context;
private final ISessionProxy proxy;
private final CastDevice device;
private boolean connected = false;

public SessionImpl(Context context, ISessionProxy proxy, CastDevice device) {
this.context = context;
this.proxy = proxy;
this.device = device;
}

public void start(Bundle extras) {
try {
Log.d(TAG, "Starting Cast session for device: " + (device != null ? device.getFriendlyName() : "unknown"));
if (proxy != null) {
// Must trigger onSessionStarting before establishing channel
proxy.onSessionStarting();
connected = true;
proxy.onSessionStarted(device != null ? device.getDeviceId() : "");
}
} catch (RemoteException e) {
Log.e(TAG, "Error starting session", e);
if (proxy != null) {
try {
proxy.onSessionStartFailed(2005);
} catch (RemoteException ignored) {}
}
}
}

public void end(boolean stopCasting) {
if (!connected) return;
try {
if (proxy != null) {
proxy.onSessionEnding();
connected = false;
proxy.onSessionEnded(0);
}
} catch (RemoteException e) {
Log.w(TAG, "Error ending session", e);
}
}

public boolean isConnected() {
return connected;
}

public CastDevice getDevice() {
return device;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2013-2026 microG Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.microg.gms.cast.framework;

import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.framework.ISessionManagerListener;
import java.util.concurrent.CopyOnWriteArrayList;

public class SessionManagerImpl extends ISessionManager.Stub {
private static final String TAG = "SessionManagerImpl";
private final Context context;
private final CopyOnWriteArrayList<ISessionManagerListener> listeners = new CopyOnWriteArrayList<>();
private SessionImpl currentSession;
private boolean deviceAvailable = false;

public SessionManagerImpl(Context context) {
this.context = context;
}

public void addSessionManagerListener(ISessionManagerListener listener) {
if (listener != null && !listeners.contains(listener)) {
listeners.add(listener);
}
}

public void removeSessionManagerListener(ISessionManagerListener listener) {
if (listener != null) {
listeners.remove(listener);
}
}

@Override
public void onRouteSelected(Bundle extras) {
if (extras == null) return;
CastDevice device = extras.getParcelable("com.google.android.gms.cast.EXTRA_CAST_DEVICE");
if (device == null) return;

Log.d(TAG, "Selected Cast route for: " + device.getFriendlyName());
if (currentSession != null) {
currentSession.end(true);
}
currentSession = new SessionImpl(context, null, device);
currentSession.start(extras);
}

@Override
public void onRouteUnselected() {
Log.d(TAG, "Unselected Cast route");
if (currentSession != null) {
currentSession.end(true);
currentSession = null;
}
}

@Override
public void onDeviceAvailabilityChanged(boolean available) {
this.deviceAvailable = available;
Log.d(TAG, "Device availability changed: " + available);
}

public boolean isDeviceAvailable() {
return deviceAvailable;
}

public SessionImpl getCurrentSession() {
return currentSession;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (C) 2013-2026 microG Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.microg.gms.cast.framework.media;

import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.mediarouter.media.MediaRouter;
import org.microg.gms.cast.framework.ISessionManager;

public class MediaRouterCallbackImpl extends MediaRouter.Callback {
private static final String TAG = "MediaRouterCallbackImpl";
private final ISessionManager sessionManager;

public MediaRouterCallbackImpl(ISessionManager sessionManager) {
this.sessionManager = sessionManager;
}

@Override
public void onRouteSelected(@NonNull MediaRouter router, @NonNull MediaRouter.RouteInfo route, int reason) {
Log.d(TAG, "onRouteSelected: " + route.getName() + " (reason: " + reason + ")");
try {
if (sessionManager != null && route.getExtras() != null) {
sessionManager.onRouteSelected(route.getExtras());
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to forward onRouteSelected", e);
}
}

@Override
public void onRouteUnselected(@NonNull MediaRouter router, @NonNull MediaRouter.RouteInfo route, int reason) {
Log.d(TAG, "onRouteUnselected: " + route.getName() + " (reason: " + reason + ")");
try {
if (sessionManager != null) {
sessionManager.onRouteUnselected();
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to forward onRouteUnselected", e);
}
}

@Override
public void onRouteAdded(@NonNull MediaRouter router, @NonNull MediaRouter.RouteInfo route) {
checkAvailability(router);
}

@Override
public void onRouteRemoved(@NonNull MediaRouter router, @NonNull MediaRouter.RouteInfo route) {
checkAvailability(router);
}

@Override
public void onRouteChanged(@NonNull MediaRouter router, @NonNull MediaRouter.RouteInfo route) {
checkAvailability(router);
}

private void checkAvailability(MediaRouter router) {
boolean hasCastDevice = false;
for (MediaRouter.RouteInfo route : router.getRoutes()) {
if (!route.isDefault() && route.getExtras() != null && route.getExtras().containsKey("com.google.android.gms.cast.EXTRA_CAST_DEVICE")) {
hasCastDevice = true;
break;
}
}
try {
if (sessionManager != null) {
sessionManager.onDeviceAvailabilityChanged(hasCastDevice);
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to notify device availability", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
/*
* Copyright (C) 2013-2026 microG Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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;

interface ICastDeviceController {
oneway void disconnect() = 0;
oneway void stopApplication(String sessionId) = 4;
oneway void sendMessage(String namespace, String message, long requestId) = 8;
oneway void registerNamespace(String namespace) = 10;
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;
}
void disconnect() = 0;
void sendMessage(String namespace, String message, long requestId) = 1;
void launchApplication(String applicationId, in LaunchOptions launchOptions) = 2;
void joinApplication(String applicationId, String sessionId, in JoinOptions joinOptions) = 3;
void stopApplication(String sessionId) = 4;
void setVolume(double volume, double oldVolume, boolean isMute) = 5;
void setMute(boolean isMute, double oldVolume, boolean wasMute) = 6;
void requestStatus() = 7;
void connect() = 15;
void addListener(ICastDeviceControllerListener listener) = 16;
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
/*
* Copyright (C) 2013-2026 microG Project Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.gms.cast.internal;

import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.ApplicationStatus;
import com.google.android.gms.cast.CastDeviceStatus;
import com.google.android.gms.cast.internal.ApplicationStatusDetails;
import com.google.android.gms.cast.internal.DeviceStatusDetails;

interface ICastDeviceControllerListener {
void onDisconnected(int reason) = 0;
void onApplicationConnectionSuccess(in ApplicationMetadata applicationMetadata, String applicationStatus, String sessionId, boolean wasLaunched) = 1;
void onApplicationConnectionFailure(int statusCode) = 2;
// Deprecated: void onStatusReceived(String string1, double double1, boolean boolean1) = 3;
void onTextMessageReceived(String namespace, String message) = 4;
void onBinaryMessageReceived(String namespace, in byte[] data) = 5;
// void onStatusChanged(int status) = 6; // TODO
// void onStatusChanged2(int status) = 7; // TODO
void onApplicationDisconnected(int paramInt) = 8;
void onSendMessageFailure(String response, long requestId, int statusCode) = 9;
void onSendMessageSuccess(String response, long requestId) = 10;
void onApplicationStatusChanged(in ApplicationStatus applicationStatus) = 11;
void onDeviceStatusChanged(in CastDeviceStatus deviceStatus) = 12;
}
void onApplicationStatusChanged(in ApplicationStatusDetails applicationStatusDetails) = 0;
void onDeviceStatusChanged(in DeviceStatusDetails deviceStatusDetails) = 1;
void onApplicationDisconnected(int statusCode) = 2;
void onApplicationConnectionFailed(int statusCode) = 3;
void onMessageReceived(String namespace, String message) = 4;
void onConnected(String sessionId) = 12;
}
Loading