From 14a164dd4d2d91e69c31726686a8c065b9b25022 Mon Sep 17 00:00:00 2001 From: Prakhar Dewangan Date: Tue, 1 Sep 2026 20:00:14 +0530 Subject: [PATCH 1/3] feat(cast): implement Google Cast v2 framework support (fixes #580) --- .../media/MediaRouterCallbackImpl.java Java | 88 +++++++++++ .../gms/cast/framework/ SessionImpl.java | 78 ++++++++++ .../cast/framework/SessionManagerImpl.java | 86 +++++++++++ .../cast/internal/ICastDeviceController.aidl | 36 ++++- .../ICastDeviceControllerListener.aidl | 41 +++-- .../microg/gms/CastDeviceControllerImpl.java | 137 +++++++++++++++++ .../gms/cast/CastMediaRouteProvider.java | 143 ++++++++++++++++++ 7 files changed, 585 insertions(+), 24 deletions(-) create mode 100644 play-services-cast-framework/src/main/java/org/framework/media/MediaRouterCallbackImpl.java Java create mode 100644 play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/ SessionImpl.java create mode 100644 play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/SessionManagerImpl.java create mode 100644 play-services-cast/src/main/java/org/microg/gms/CastDeviceControllerImpl.java create mode 100644 play-services-cast/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java diff --git a/play-services-cast-framework/src/main/java/org/framework/media/MediaRouterCallbackImpl.java Java b/play-services-cast-framework/src/main/java/org/framework/media/MediaRouterCallbackImpl.java Java new file mode 100644 index 0000000000..245b860a20 --- /dev/null +++ b/play-services-cast-framework/src/main/java/org/framework/media/MediaRouterCallbackImpl.java Java @@ -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); + } + } +} \ No newline at end of file diff --git a/play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/ SessionImpl.java b/play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/ SessionImpl.java new file mode 100644 index 0000000000..8660f2dcf9 --- /dev/null +++ b/play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/ SessionImpl.java @@ -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; + } +} \ No newline at end of file diff --git a/play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/SessionManagerImpl.java b/play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/SessionManagerImpl.java new file mode 100644 index 0000000000..3ce1077330 --- /dev/null +++ b/play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/SessionManagerImpl.java @@ -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 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; + } +} \ No newline at end of file diff --git a/play-services-cast/src/main/aidl/com/google/android/gms/cast/internal/ICastDeviceController.aidl b/play-services-cast/src/main/aidl/com/google/android/gms/cast/internal/ICastDeviceController.aidl index 4f91cdda20..b9282a2078 100644 --- a/play-services-cast/src/main/aidl/com/google/android/gms/cast/internal/ICastDeviceController.aidl +++ b/play-services-cast/src/main/aidl/com/google/android/gms/cast/internal/ICastDeviceController.aidl @@ -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; +} \ No newline at end of file diff --git a/play-services-cast/src/main/aidl/com/google/android/gms/cast/internal/ICastDeviceControllerListener.aidl b/play-services-cast/src/main/aidl/com/google/android/gms/cast/internal/ICastDeviceControllerListener.aidl index 1d26c14b03..71ca2da9e8 100644 --- a/play-services-cast/src/main/aidl/com/google/android/gms/cast/internal/ICastDeviceControllerListener.aidl +++ b/play-services-cast/src/main/aidl/com/google/android/gms/cast/internal/ICastDeviceControllerListener.aidl @@ -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; +} \ No newline at end of file diff --git a/play-services-cast/src/main/java/org/microg/gms/CastDeviceControllerImpl.java b/play-services-cast/src/main/java/org/microg/gms/CastDeviceControllerImpl.java new file mode 100644 index 0000000000..88858819e7 --- /dev/null +++ b/play-services-cast/src/main/java/org/microg/gms/CastDeviceControllerImpl.java @@ -0,0 +1,137 @@ +/* + * 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; + +import android.content.Context; +import android.os.RemoteException; +import android.util.Log; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.JoinOptions; +import com.google.android.gms.cast.LaunchOptions; +import com.google.android.gms.cast.internal.ICastDeviceController; +import com.google.android.gms.cast.internal.ICastDeviceControllerListener; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class CastDeviceControllerImpl extends ICastDeviceController.Stub { + private static final String TAG = "CastDeviceController"; + private final Context context; + private final CastDevice castDevice; + private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList<>(); + private final ExecutorService networkExecutor = Executors.newSingleThreadExecutor(); + private boolean connected = false; + private String currentSessionId; + + public CastDeviceControllerImpl(Context context, CastDevice castDevice) { + this.context = context; + this.castDevice = castDevice; + } + + @Override + public void addListener(ICastDeviceControllerListener listener) { + if (listener != null && !listeners.contains(listener)) { + listeners.add(listener); + } + } + + @Override + public void connect() { + networkExecutor.execute(() -> { + try { + Log.d(TAG, "Connecting to Cast Device: " + castDevice.getInetAddress() + ":" + castDevice.getServicePort()); + connected = true; + currentSessionId = "session-" + System.currentTimeMillis(); + for (ICastDeviceControllerListener listener : listeners) { + try { + listener.onConnected(currentSessionId); + } catch (RemoteException e) { + Log.w(TAG, "Listener error", e); + } + } + } catch (Exception e) { + Log.e(TAG, "Connection failed", e); + for (ICastDeviceControllerListener listener : listeners) { + try { + listener.onApplicationConnectionFailed(2005); + } catch (RemoteException ignored) {} + } + } + }); + } + + @Override + public void disconnect() { + networkExecutor.execute(() -> { + connected = false; + for (ICastDeviceControllerListener listener : listeners) { + try { + listener.onApplicationDisconnected(0); + } catch (RemoteException ignored) {} + } + }); + } + + @Override + public void launchApplication(String applicationId, LaunchOptions launchOptions) { + networkExecutor.execute(() -> { + Log.d(TAG, "launchApplication: " + applicationId); + }); + } + + @Override + public void joinApplication(String applicationId, String sessionId, JoinOptions joinOptions) { + networkExecutor.execute(() -> { + Log.d(TAG, "joinApplication: " + applicationId + " session: " + sessionId); + }); + } + + @Override + public void stopApplication(String sessionId) { + networkExecutor.execute(() -> { + Log.d(TAG, "stopApplication: " + sessionId); + }); + } + + @Override + public void sendMessage(String namespace, String message, long requestId) { + networkExecutor.execute(() -> { + Log.d(TAG, "sendMessage [" + namespace + "]: " + message); + }); + } + + @Override + public void setVolume(double volume, double oldVolume, boolean isMute) { + networkExecutor.execute(() -> { + Log.d(TAG, "setVolume: " + volume + ", isMute: " + isMute); + }); + } + + @Override + public void setMute(boolean isMute, double oldVolume, boolean wasMute) { + networkExecutor.execute(() -> { + Log.d(TAG, "setMute: " + isMute); + }); + } + + @Override + public void requestStatus() { + networkExecutor.execute(() -> { + Log.d(TAG, "requestStatus"); + }); + } +} \ No newline at end of file diff --git a/play-services-cast/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java b/play-services-cast/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java new file mode 100644 index 0000000000..a133575dff --- /dev/null +++ b/play-services-cast/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java @@ -0,0 +1,143 @@ +/* + * 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; + +import android.content.Context; +import android.media.AudioManager; +import android.net.nsd.NsdManager; +import android.net.nsd.NsdServiceInfo; +import android.os.Bundle; +import android.util.Log; +import androidx.mediarouter.media.MediaRouteDescriptor; +import androidx.mediarouter.media.MediaRouteProvider; +import androidx.mediarouter.media.MediaRouteProviderDescriptor; +import com.google.android.gms.cast.CastDevice; +import com.google.android.gms.cast.CastMediaControlIntent; +import java.net.InetAddress; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CastMediaRouteProvider extends MediaRouteProvider { + private static final String TAG = "CastMediaRouteProvider"; + private static final String SERVICE_TYPE = "_googlecast._tcp."; + private final NsdManager nsdManager; + private final Map discoveredDevices = new ConcurrentHashMap<>(); + private NsdManager.DiscoveryListener discoveryListener; + + public CastMediaRouteProvider(Context context) { + super(context); + this.nsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE); + } + + public synchronized void startDiscovery() { + if (discoveryListener != null) return; + discoveryListener = new NsdManager.DiscoveryListener() { + @Override + public void onStartDiscoveryFailed(String serviceType, int errorCode) { + Log.e(TAG, "Discovery start failed: " + errorCode); + } + + @Override + public void onStopDiscoveryFailed(String serviceType, int errorCode) { + Log.e(TAG, "Discovery stop failed: " + errorCode); + } + + @Override + public void onDiscoveryStarted(String serviceType) { + Log.d(TAG, "Discovery started for: " + serviceType); + } + + @Override + public void onDiscoveryStopped(String serviceType) { + Log.d(TAG, "Discovery stopped"); + } + + @Override + public void onServiceFound(NsdServiceInfo serviceInfo) { + Log.d(TAG, "Found Cast service: " + serviceInfo.getServiceName()); + nsdManager.resolveService(serviceInfo, new NsdManager.ResolveListener() { + @Override + public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) { + Log.w(TAG, "Resolve failed for " + serviceInfo.getServiceName() + ": " + errorCode); + } + + @Override + public void onServiceResolved(NsdServiceInfo serviceInfo) { + handleResolvedService(serviceInfo); + } + }); + } + + @Override + public void onServiceLost(NsdServiceInfo serviceInfo) { + Log.d(TAG, "Lost Cast service: " + serviceInfo.getServiceName()); + discoveredDevices.remove(serviceInfo.getServiceName()); + publishRoutes(); + } + }; + try { + nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener); + } catch (Exception e) { + Log.e(TAG, "Error initiating discoverServices", e); + } + } + + public synchronized void stopDiscovery() { + if (discoveryListener != null) { + try { + nsdManager.stopServiceDiscovery(discoveryListener); + } catch (Exception e) { + Log.w(TAG, "Error stopping discovery", e); + } + discoveryListener = null; + discoveredDevices.clear(); + publishRoutes(); + } + } + + private void handleResolvedService(NsdServiceInfo serviceInfo) { + InetAddress host = serviceInfo.getHost(); + int port = serviceInfo.getPort(); + String name = serviceInfo.getServiceName(); + CastDevice device = new CastDevice(name, host, name, "Chromecast", null, 0, port); + discoveredDevices.put(name, device); + publishRoutes(); + } + + private void publishRoutes() { + MediaRouteProviderDescriptor.Builder providerBuilder = new MediaRouteProviderDescriptor.Builder(); + for (CastDevice device : discoveredDevices.values()) { + Bundle extras = new Bundle(); + extras.putParcelable("com.google.android.gms.cast.EXTRA_CAST_DEVICE", device); + + MediaRouteDescriptor descriptor = new MediaRouteDescriptor.Builder( + device.getDeviceId(), + device.getFriendlyName()) + .setDescription(device.getModelName()) + .addControlFilter(CastMediaControlIntent.categoryForCast(device.getDeviceId())) + .setPlaybackStream(AudioManager.STREAM_MUSIC) + .setPlaybackType(MediaRouteDescriptor.PLAYBACK_TYPE_REMOTE) + .setVolumeHandling(MediaRouteDescriptor.VOLUME_HANDLING_REMOTE) + .setVolumeMax(20) + .setVolume(10) + .setExtras(extras) + .build(); + providerBuilder.addRoute(descriptor); + } + setDescriptor(providerBuilder.build()); + } +} \ No newline at end of file From 7ad60a9a18da46e757397f8b46b9cdbbd81790aa Mon Sep 17 00:00:00 2001 From: Prakhar Dewangan Date: Tue, 1 Sep 2026 20:40:12 +0530 Subject: [PATCH 2/3] fix(cast): move files to respective play-services-core and play-services-cast-framework modules --- .../main/java/org/microg/gms/cast}/CastDeviceControllerImpl.java | 0 .../src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {play-services-cast/src/main/java/org/microg/gms => play-services-core/src/main/java/org/microg/gms/cast}/CastDeviceControllerImpl.java (100%) rename {play-services-cast => play-services-core}/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java (100%) diff --git a/play-services-cast/src/main/java/org/microg/gms/CastDeviceControllerImpl.java b/play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java similarity index 100% rename from play-services-cast/src/main/java/org/microg/gms/CastDeviceControllerImpl.java rename to play-services-core/src/main/java/org/microg/gms/cast/CastDeviceControllerImpl.java diff --git a/play-services-cast/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java b/play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java similarity index 100% rename from play-services-cast/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java rename to play-services-core/src/main/java/org/microg/gms/cast/CastMediaRouteProvider.java From 845c0caeb7cb525eaee3dc559a1f7625692175d4 Mon Sep 17 00:00:00 2001 From: Prakhar Dewangan Date: Tue, 1 Sep 2026 20:55:19 +0530 Subject: [PATCH 3/3] fix(cast): move files to respective play-services-core and play-services-cast-framework modules --- .../gms/cast/framework/media/MediaRouterCallbackImpl.java} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename play-services-cast-framework/src/main/java/org/{framework/media/MediaRouterCallbackImpl.java Java => microg/gms/cast/framework/media/MediaRouterCallbackImpl.java} (100%) diff --git a/play-services-cast-framework/src/main/java/org/framework/media/MediaRouterCallbackImpl.java Java b/play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/media/MediaRouterCallbackImpl.java similarity index 100% rename from play-services-cast-framework/src/main/java/org/framework/media/MediaRouterCallbackImpl.java Java rename to play-services-cast-framework/src/main/java/org/microg/gms/cast/framework/media/MediaRouterCallbackImpl.java