Loading build.gradle +11 −5 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ // 2026 Murena SAS buildscript { ext.cronetVersion = '102.5005.125' ext.wearableVersion = '0.1.1' Loading Loading @@ -77,8 +78,11 @@ def execResult(... args) { } def ignoreGit = providers.environmentVariable('GRADLE_MICROG_VERSION_WITHOUT_GIT').getOrElse('0') == '1' def gmsVersion = "25.09.32" // Bump version code to avoid install downgrades while validating YouTube-freeze builds. def gmsVersion = "26.04.39" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) // 2026-03-03 18c51c9: bump version code offset to avoid install downgrades. def versionCodeOffset = 60 // 2026-03-06T23:58:30Z LKG=ed00be7: bump offset above installed builds to avoid downgrade blocks. def vendingVersion = "40.2.26" def vendingVersionCode = Integer.parseInt(vendingVersion.replaceAll('\\.', '')) def gitVersionBase = !ignoreGit ? execResult('git', 'describe', '--tags', '--abbrev=0', '--match=v[0-9]*').trim().substring(1) : "v0.0.0.$gmsVersionCode" Loading @@ -104,10 +108,12 @@ if (!ignoreGit) { } def ourVersionBase = gitVersionBase.substring(0, gitVersionBase.lastIndexOf('.')) def ourVersionMinor = Integer.parseInt(ourVersionBase.substring(ourVersionBase.lastIndexOf('.') + 1)) def ourGmsVersionCode = gmsVersionCode * 1000 + ourVersionMinor * 2 + (gitCommitCount > 0 || gitDirty ? 1 : 0) def ourGmsVersionName = "$ourVersionBase.$gmsVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") def ourVendingVersionCode = 80000000 + vendingVersionCode * 100 + ourVersionMinor * 2 + (gitCommitCount > 0 || gitDirty ? 1 : 0) def ourVendingVersionName = "$ourVersionBase.$vendingVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") // 2026-03-03 18c51c9: apply version code offset for repeated test installs. def ourGmsVersionCode = gmsVersionCode * 1000 + ourVersionMinor * 2 + versionCodeOffset + (gitCommitCount > 0 || gitDirty ? 1 : 0) def ourGmsVersionName = "$ourVersionBase.$gmsVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") + (gitCommitCount > 0 && !gitDirty ? " ($gitCommitId)" : "") // 2026-03-03 18c51c9: keep vending version offset aligned with GmsCore. def ourVendingVersionCode = 80000000 + vendingVersionCode * 100 + ourVersionMinor * 2 + versionCodeOffset + (gitCommitCount > 0 || gitDirty ? 1 : 0) def ourVendingVersionName = "$ourVersionBase.$vendingVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") + (gitCommitCount > 0 && !gitDirty ? " ($gitCommitId)" : "") logger.lifecycle('Starting build for GMS version {} ({})...', ourGmsVersionName, ourGmsVersionCode) allprojects { Loading play-services-base/core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java +8 −1 Original line number Diff line number Diff line // 2026 Murena SAS /* * Copyright (C) 2013-2017 microG Project Team * Loading Loading @@ -177,7 +178,13 @@ public abstract class AbstractGmsServiceBroker extends IGmsServiceBroker.Stub { @Override public void getCastService(IGmsCallbacks callback, int versionCode, String packageName, IBinder binder, Bundle params) throws RemoteException { callGetService(GmsService.CAST, callback, versionCode, packageName, params); // 2026-03-02T14:35Z last_good=7429998 intent=attach cast listener binder to extras so CastDeviceControllerImpl can bind callbacks; validated=TODO Bundle mergedParams = params != null ? new Bundle(params) : new Bundle(); mergedParams.setClassLoader(BinderWrapper.class.getClassLoader()); mergedParams.putParcelable("listener", new BinderWrapper(binder)); // 2026-03-02T15:05Z last_good=7429998 intent=log getCastService binder to confirm caller path; validated=TODO Log.d(TAG, "getCastService binder=" + binder + " params=" + (params != null ? params.keySet() : "null")); callGetService(GmsService.CAST, callback, versionCode, packageName, mergedParams); } @Deprecated Loading play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java +4 −2 Original line number Diff line number Diff line // 2026 Murena SAS /* * Copyright (C) 2013-2017 microG Project Team * Loading Loading @@ -158,8 +159,9 @@ public class GoogleApiAvailability { * SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID */ public int isGooglePlayServicesAvailable(Context context) { Log.d(TAG, "As we can't know right now if the later desired feature is available, " + "we just pretend it to be."); // Log caller package to diagnose app-side Play Services update prompts. String pkg = context != null ? context.getPackageName() : "null"; Log.d(TAG, "isGooglePlayServicesAvailable pkg=" + pkg + ": pretending SUCCESS"); return SUCCESS; } Loading play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java +19 −3 Original line number Diff line number Diff line // 2026 Murena SAS /* * Copyright (C) 2013-2017 microG Project Team * Loading Loading @@ -166,14 +167,29 @@ public class GooglePlayServicesUtil { */ @Deprecated public static int isGooglePlayServicesAvailable(Context context) { Log.d(TAG, "As we can't know right now if the later desired feature is available, " + "we just pretend it to be."); // Log caller package to diagnose YouTube "update Google Play services" prompts. String pkg = context != null ? context.getPackageName() : "null"; Log.d(TAG, "isGooglePlayServicesAvailable pkg=" + pkg + ": pretending SUCCESS"); return ConnectionResult.SUCCESS; } @Deprecated public static boolean isGoogleSignedUid(PackageManager packageManager, int uid) { return false; // TODO // Treat microG's GmsCore UID as Google-signed to avoid false update prompts in clients. try { String[] packages = packageManager.getPackagesForUid(uid); if (packages != null) { for (String pkg : packages) { if (Constants.GMS_PACKAGE_NAME.equals(pkg)) { Log.d(TAG, "isGoogleSignedUid: trusted uid=" + uid + " pkg=" + pkg); return true; } } } } catch (RuntimeException e) { Log.w(TAG, "isGoogleSignedUid: lookup failed for uid=" + uid, e); } return false; } /** Loading play-services-cast-framework/core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java +175 −6 Original line number Diff line number Diff line // 2026 Murena SAS // [2026-03-02 20:15] base=86bde618: Restore fix1099 YouTube cast behavior in this file. Validated: pending. /* * Copyright (C) 2013-2017 microG Project Team * Loading @@ -16,10 +18,14 @@ package com.google.android.gms.cast.framework.internal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.Build; import android.util.Log; import androidx.mediarouter.media.MediaControlIntent; Loading @@ -34,43 +40,144 @@ import com.google.android.gms.cast.framework.ISessionManager; import com.google.android.gms.cast.framework.ISessionProvider; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; import com.google.android.gms.cast.framework.internal.MediaRouterImpl; import java.util.Map; import java.util.HashMap; public class CastContextImpl extends ICastContext.Stub { private static final String TAG = CastContextImpl.class.getSimpleName(); // [2026-03-02 21:41] base=86bde618: Track last instance for cross-process route handoff. Validated: pending. private static volatile CastContextImpl lastInstance; // [2026-03-02 21:41] base=86bde618: Cache pending route selection for delayed SessionManager hookup. Validated: pending. private static String pendingRouteId; // [2026-03-02 21:41] base=86bde618: Cache pending route extras for delayed SessionManager hookup. Validated: pending. private static Bundle pendingRouteExtras; // [2026-03-02 14:42] base=86bde618: Track ROUTE_SELECTED broadcasts from cast-core to restore YouTube session start. Validated: pending. private static final String ROUTE_SELECTED_ACTION = "com.google.android.gms.cast.framework.ROUTE_SELECTED"; private SessionManagerImpl sessionManager; private DiscoveryManagerImpl discoveryManager; // [2026-03-02 14:42] base=86bde618: Receiver to bridge route selections across processes. Validated: pending. private BroadcastReceiver routeSelectionReceiver; private Context context; private CastOptions options; private IMediaRouter router; private Map<String, ISessionProvider> sessionProviders = new HashMap<String, ISessionProvider>(); public ISessionProvider defaultSessionProvider; // [2026-03-02 20:19] base=86bde618: Cache default category for SessionManager compile path. Validated: pending. private String defaultCategory; private MediaRouteSelector mergedSelector; private MediaRouterCallbackImpl mediaRouterCallback; public CastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter router, Map<String, IBinder> sessionProviders) throws RemoteException { this.context = (Context) ObjectWrapper.unwrap(context); this.options = options; this.router = router; this.router = router != null ? router : new MediaRouterImpl(this.context); // [2026-03-02 21:41] base=86bde618: Keep last instance to allow CastMediaRouteProvider to deliver pending route. Validated: pending. lastInstance = this; for (Map.Entry<String, IBinder> entry : sessionProviders.entrySet()) { this.sessionProviders.put(entry.getKey(), ISessionProvider.Stub.asInterface(entry.getValue())); // [2026-03-02 14:58] base=86bde618: Register providers under normalized keys to avoid category suffix mismatches. Validated: pending. String key = entry.getKey(); ISessionProvider provider = ISessionProvider.Stub.asInterface(entry.getValue()); this.sessionProviders.put(key, provider); String normalized = normalizeCategoryKey(key); if (!normalized.equals(key) && !this.sessionProviders.containsKey(normalized)) { this.sessionProviders.put(normalized, provider); } } String receiverApplicationId = options.getReceiverApplicationId(); String defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId); this.defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId); this.defaultSessionProvider = this.sessionProviders.get(defaultCategory); // [2026-03-02 14:58] base=86bde618: Resolve default provider with normalized category fallback. Validated: pending. this.defaultSessionProvider = resolveSessionProviderForCategory(this.defaultCategory); // TODO: This should incorporate passed options this.mergedSelector = new MediaRouteSelector.Builder() .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .addControlCategory(defaultCategory) .addControlCategory(this.defaultCategory) .build(); this.mediaRouterCallback = new MediaRouterCallbackImpl(this); this.router.registerMediaRouterCallbackImpl(this.mergedSelector.asBundle(), this.mediaRouterCallback); // [2026-03-02 14:42] base=86bde618: Register route selection receiver in cast-framework process. Validated: pending. registerRouteSelectionReceiver(); } // [2026-03-02 21:41] base=86bde618: Expose last instance for reflective route selection bridge. Validated: pending. public static CastContextImpl getLastInstance() { return lastInstance; } // [2026-03-02 21:41] base=86bde618: Record pending route selection so SessionManager can replay it. Validated: pending. public static void recordPendingRouteSelection(String routeId, Bundle extras) { pendingRouteId = routeId; pendingRouteExtras = extras; } // [2026-03-02 21:41] base=86bde618: Clear pending route once consumed. Validated: pending. public static void clearPendingRouteSelection() { pendingRouteId = null; pendingRouteExtras = null; } // [2026-03-02 21:41] base=86bde618: Access pending route id for debugging. Validated: pending. public static String getPendingRouteId() { return pendingRouteId; } // [2026-03-02 21:41] base=86bde618: Access pending route extras for debugging. Validated: pending. public static Bundle getPendingRouteExtras() { return pendingRouteExtras; } // [2026-03-02 14:42] base=86bde618: Ensure broadcast receiver is registered to handle cross-process route selection. Validated: pending. private void registerRouteSelectionReceiver() { if (routeSelectionReceiver != null) { return; } routeSelectionReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent == null || !ROUTE_SELECTED_ACTION.equals(intent.getAction())) { return; } String routeId = intent.getStringExtra("route_id"); Bundle extras = intent.getBundleExtra("route_extras"); Log.d(TAG, "route selected broadcast routeId=" + routeId + " extras=" + summarizeExtras(extras)); recordPendingRouteSelection(routeId, extras); // [2026-03-02 14:42] base=86bde618: Trigger SessionManager init to consume pending route. Validated: pending. getSessionManagerImpl(); } }; try { IntentFilter filter = new IntentFilter(ROUTE_SELECTED_ACTION); if (Build.VERSION.SDK_INT >= 33) { context.registerReceiver(routeSelectionReceiver, filter, Context.RECEIVER_EXPORTED); } else { context.registerReceiver(routeSelectionReceiver, filter); } Log.d(TAG, "route selection receiver registered"); } catch (Exception e) { Log.d(TAG, "route selection receiver register failed: " + e.getMessage()); } } // [2026-03-02 14:42] base=86bde618: Compact extras logging for route selection debugging. Validated: pending. private static String summarizeExtras(Bundle extras) { if (extras == null) { return "null"; } try { return "keys=" + extras.keySet(); } catch (Exception e) { return "error:" + e.getMessage(); } } @Override Loading @@ -96,12 +203,26 @@ public class CastContextImpl extends ICastContext.Stub { @Override public SessionManagerImpl getSessionManagerImpl() { // 2026-04-03T14:35Z LKG=68ba637: Trace SessionManager creation and pending route replay in cast-framework process. Fix: diagnose missing YouTube session start (pending validation). Log.d(TAG, "getSessionManagerImpl pendingRoute=" + pendingRouteId + " defaultCategory=" + defaultCategory); if (this.sessionManager == null) { this.sessionManager = new SessionManagerImpl(this); } // [2026-03-02 22:15] base=86bde618: Replay pending route selection once SessionManager is available. Validated: pending. if (pendingRouteId != null) { this.sessionManager.onRouteSelected(pendingRouteId, pendingRouteExtras); clearPendingRouteSelection(); // 2026-04-03T14:35Z LKG=68ba637: Confirm pending route replay to SessionManager. Fix: trace YouTube route handoff (pending validation). Log.d(TAG, "getSessionManagerImpl replayed pending route"); } return this.sessionManager; } // 2026-04-03T14:35Z LKG=68ba637: Dump session provider keys for debugging provider resolution failures. Fix: surface missing provider categories (pending validation). public String dumpSessionProviderKeys() { return "providers=" + sessionProviders.keySet(); } @Override public IDiscoveryManager getDiscoveryManagerImpl() throws RemoteException { if (this.discoveryManager == null) { Loading @@ -110,6 +231,45 @@ public class CastContextImpl extends ICastContext.Stub { return this.discoveryManager; } // [2026-03-02 20:19] base=86bde618: Accessor used by SessionManagerImpl fallback path. Validated: pending. public String getDefaultCategory() { return this.defaultCategory; } // [2026-03-02 20:19] base=86bde618: Minimal resolver for SessionManagerImpl compile path. Validated: pending. public ISessionProvider resolveSessionProviderForCategory(String category) { // [2026-03-02 14:58] base=86bde618: Normalize and prefix-match category keys to recover app providers. Validated: pending. if (category == null) { return null; } ISessionProvider provider = this.sessionProviders.get(category); if (provider != null) { return provider; } String normalized = normalizeCategoryKey(category); provider = this.sessionProviders.get(normalized); if (provider != null) { return provider; } String prefix = normalized + "///"; for (Map.Entry<String, ISessionProvider> entry : this.sessionProviders.entrySet()) { String key = entry.getKey(); if (key != null && (key.startsWith(prefix) || key.startsWith(category + "///"))) { return entry.getValue(); } } return null; } // [2026-03-02 14:58] base=86bde618: Strip suffixes (e.g., ///ALLOW_IPV6) from category keys. Validated: pending. private static String normalizeCategoryKey(String key) { if (key == null) { return null; } int idx = key.indexOf("///"); return idx >= 0 ? key.substring(0, idx) : key; } @Override public void destroy() throws RemoteException { Log.d(TAG, "unimplemented Method: destroy"); Loading @@ -128,7 +288,16 @@ public class CastContextImpl extends ICastContext.Stub { @Override public void setReceiverApplicationId(String receiverApplicationId, Map sessionProvidersByCategory) throws RemoteException { Log.d(TAG, "unimplemented Method: setReceiverApplicationId"); // [2026-03-02 20:19] base=86bde618: Keep cached category in sync with receiver changes. Validated: pending. this.defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId); // [2026-03-02 14:58] base=86bde618: Resolve provider via normalized key on receiver app change. Validated: pending. this.defaultSessionProvider = resolveSessionProviderForCategory(this.defaultCategory); this.mergedSelector = new MediaRouteSelector.Builder() .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .addControlCategory(this.defaultCategory) .build(); this.router.registerMediaRouterCallbackImpl(this.mergedSelector.asBundle(), this.mediaRouterCallback); } public Context getContext() { Loading Loading
build.gradle +11 −5 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ // 2026 Murena SAS buildscript { ext.cronetVersion = '102.5005.125' ext.wearableVersion = '0.1.1' Loading Loading @@ -77,8 +78,11 @@ def execResult(... args) { } def ignoreGit = providers.environmentVariable('GRADLE_MICROG_VERSION_WITHOUT_GIT').getOrElse('0') == '1' def gmsVersion = "25.09.32" // Bump version code to avoid install downgrades while validating YouTube-freeze builds. def gmsVersion = "26.04.39" def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', '')) // 2026-03-03 18c51c9: bump version code offset to avoid install downgrades. def versionCodeOffset = 60 // 2026-03-06T23:58:30Z LKG=ed00be7: bump offset above installed builds to avoid downgrade blocks. def vendingVersion = "40.2.26" def vendingVersionCode = Integer.parseInt(vendingVersion.replaceAll('\\.', '')) def gitVersionBase = !ignoreGit ? execResult('git', 'describe', '--tags', '--abbrev=0', '--match=v[0-9]*').trim().substring(1) : "v0.0.0.$gmsVersionCode" Loading @@ -104,10 +108,12 @@ if (!ignoreGit) { } def ourVersionBase = gitVersionBase.substring(0, gitVersionBase.lastIndexOf('.')) def ourVersionMinor = Integer.parseInt(ourVersionBase.substring(ourVersionBase.lastIndexOf('.') + 1)) def ourGmsVersionCode = gmsVersionCode * 1000 + ourVersionMinor * 2 + (gitCommitCount > 0 || gitDirty ? 1 : 0) def ourGmsVersionName = "$ourVersionBase.$gmsVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") def ourVendingVersionCode = 80000000 + vendingVersionCode * 100 + ourVersionMinor * 2 + (gitCommitCount > 0 || gitDirty ? 1 : 0) def ourVendingVersionName = "$ourVersionBase.$vendingVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") // 2026-03-03 18c51c9: apply version code offset for repeated test installs. def ourGmsVersionCode = gmsVersionCode * 1000 + ourVersionMinor * 2 + versionCodeOffset + (gitCommitCount > 0 || gitDirty ? 1 : 0) def ourGmsVersionName = "$ourVersionBase.$gmsVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") + (gitCommitCount > 0 && !gitDirty ? " ($gitCommitId)" : "") // 2026-03-03 18c51c9: keep vending version offset aligned with GmsCore. def ourVendingVersionCode = 80000000 + vendingVersionCode * 100 + ourVersionMinor * 2 + versionCodeOffset + (gitCommitCount > 0 || gitDirty ? 1 : 0) def ourVendingVersionName = "$ourVersionBase.$vendingVersionCode" + (gitCommitCount > 0 && !gitDirty ? "-$gitCommitCount" : "") + (gitDirty ? "-dirty" : "") + (gitCommitCount > 0 && !gitDirty ? " ($gitCommitId)" : "") logger.lifecycle('Starting build for GMS version {} ({})...', ourGmsVersionName, ourGmsVersionCode) allprojects { Loading
play-services-base/core/src/main/java/org/microg/gms/AbstractGmsServiceBroker.java +8 −1 Original line number Diff line number Diff line // 2026 Murena SAS /* * Copyright (C) 2013-2017 microG Project Team * Loading Loading @@ -177,7 +178,13 @@ public abstract class AbstractGmsServiceBroker extends IGmsServiceBroker.Stub { @Override public void getCastService(IGmsCallbacks callback, int versionCode, String packageName, IBinder binder, Bundle params) throws RemoteException { callGetService(GmsService.CAST, callback, versionCode, packageName, params); // 2026-03-02T14:35Z last_good=7429998 intent=attach cast listener binder to extras so CastDeviceControllerImpl can bind callbacks; validated=TODO Bundle mergedParams = params != null ? new Bundle(params) : new Bundle(); mergedParams.setClassLoader(BinderWrapper.class.getClassLoader()); mergedParams.putParcelable("listener", new BinderWrapper(binder)); // 2026-03-02T15:05Z last_good=7429998 intent=log getCastService binder to confirm caller path; validated=TODO Log.d(TAG, "getCastService binder=" + binder + " params=" + (params != null ? params.keySet() : "null")); callGetService(GmsService.CAST, callback, versionCode, packageName, mergedParams); } @Deprecated Loading
play-services-base/src/main/java/com/google/android/gms/common/GoogleApiAvailability.java +4 −2 Original line number Diff line number Diff line // 2026 Murena SAS /* * Copyright (C) 2013-2017 microG Project Team * Loading Loading @@ -158,8 +159,9 @@ public class GoogleApiAvailability { * SERVICE_VERSION_UPDATE_REQUIRED, SERVICE_DISABLED, SERVICE_INVALID */ public int isGooglePlayServicesAvailable(Context context) { Log.d(TAG, "As we can't know right now if the later desired feature is available, " + "we just pretend it to be."); // Log caller package to diagnose app-side Play Services update prompts. String pkg = context != null ? context.getPackageName() : "null"; Log.d(TAG, "isGooglePlayServicesAvailable pkg=" + pkg + ": pretending SUCCESS"); return SUCCESS; } Loading
play-services-base/src/main/java/com/google/android/gms/common/GooglePlayServicesUtil.java +19 −3 Original line number Diff line number Diff line // 2026 Murena SAS /* * Copyright (C) 2013-2017 microG Project Team * Loading Loading @@ -166,14 +167,29 @@ public class GooglePlayServicesUtil { */ @Deprecated public static int isGooglePlayServicesAvailable(Context context) { Log.d(TAG, "As we can't know right now if the later desired feature is available, " + "we just pretend it to be."); // Log caller package to diagnose YouTube "update Google Play services" prompts. String pkg = context != null ? context.getPackageName() : "null"; Log.d(TAG, "isGooglePlayServicesAvailable pkg=" + pkg + ": pretending SUCCESS"); return ConnectionResult.SUCCESS; } @Deprecated public static boolean isGoogleSignedUid(PackageManager packageManager, int uid) { return false; // TODO // Treat microG's GmsCore UID as Google-signed to avoid false update prompts in clients. try { String[] packages = packageManager.getPackagesForUid(uid); if (packages != null) { for (String pkg : packages) { if (Constants.GMS_PACKAGE_NAME.equals(pkg)) { Log.d(TAG, "isGoogleSignedUid: trusted uid=" + uid + " pkg=" + pkg); return true; } } } } catch (RuntimeException e) { Log.w(TAG, "isGoogleSignedUid: lookup failed for uid=" + uid, e); } return false; } /** Loading
play-services-cast-framework/core/src/main/java/com/google/android/gms/cast/framework/internal/CastContextImpl.java +175 −6 Original line number Diff line number Diff line // 2026 Murena SAS // [2026-03-02 20:15] base=86bde618: Restore fix1099 YouTube cast behavior in this file. Validated: pending. /* * Copyright (C) 2013-2017 microG Project Team * Loading @@ -16,10 +18,14 @@ package com.google.android.gms.cast.framework.internal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.Build; import android.util.Log; import androidx.mediarouter.media.MediaControlIntent; Loading @@ -34,43 +40,144 @@ import com.google.android.gms.cast.framework.ISessionManager; import com.google.android.gms.cast.framework.ISessionProvider; import com.google.android.gms.dynamic.IObjectWrapper; import com.google.android.gms.dynamic.ObjectWrapper; import com.google.android.gms.cast.framework.internal.MediaRouterImpl; import java.util.Map; import java.util.HashMap; public class CastContextImpl extends ICastContext.Stub { private static final String TAG = CastContextImpl.class.getSimpleName(); // [2026-03-02 21:41] base=86bde618: Track last instance for cross-process route handoff. Validated: pending. private static volatile CastContextImpl lastInstance; // [2026-03-02 21:41] base=86bde618: Cache pending route selection for delayed SessionManager hookup. Validated: pending. private static String pendingRouteId; // [2026-03-02 21:41] base=86bde618: Cache pending route extras for delayed SessionManager hookup. Validated: pending. private static Bundle pendingRouteExtras; // [2026-03-02 14:42] base=86bde618: Track ROUTE_SELECTED broadcasts from cast-core to restore YouTube session start. Validated: pending. private static final String ROUTE_SELECTED_ACTION = "com.google.android.gms.cast.framework.ROUTE_SELECTED"; private SessionManagerImpl sessionManager; private DiscoveryManagerImpl discoveryManager; // [2026-03-02 14:42] base=86bde618: Receiver to bridge route selections across processes. Validated: pending. private BroadcastReceiver routeSelectionReceiver; private Context context; private CastOptions options; private IMediaRouter router; private Map<String, ISessionProvider> sessionProviders = new HashMap<String, ISessionProvider>(); public ISessionProvider defaultSessionProvider; // [2026-03-02 20:19] base=86bde618: Cache default category for SessionManager compile path. Validated: pending. private String defaultCategory; private MediaRouteSelector mergedSelector; private MediaRouterCallbackImpl mediaRouterCallback; public CastContextImpl(IObjectWrapper context, CastOptions options, IMediaRouter router, Map<String, IBinder> sessionProviders) throws RemoteException { this.context = (Context) ObjectWrapper.unwrap(context); this.options = options; this.router = router; this.router = router != null ? router : new MediaRouterImpl(this.context); // [2026-03-02 21:41] base=86bde618: Keep last instance to allow CastMediaRouteProvider to deliver pending route. Validated: pending. lastInstance = this; for (Map.Entry<String, IBinder> entry : sessionProviders.entrySet()) { this.sessionProviders.put(entry.getKey(), ISessionProvider.Stub.asInterface(entry.getValue())); // [2026-03-02 14:58] base=86bde618: Register providers under normalized keys to avoid category suffix mismatches. Validated: pending. String key = entry.getKey(); ISessionProvider provider = ISessionProvider.Stub.asInterface(entry.getValue()); this.sessionProviders.put(key, provider); String normalized = normalizeCategoryKey(key); if (!normalized.equals(key) && !this.sessionProviders.containsKey(normalized)) { this.sessionProviders.put(normalized, provider); } } String receiverApplicationId = options.getReceiverApplicationId(); String defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId); this.defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId); this.defaultSessionProvider = this.sessionProviders.get(defaultCategory); // [2026-03-02 14:58] base=86bde618: Resolve default provider with normalized category fallback. Validated: pending. this.defaultSessionProvider = resolveSessionProviderForCategory(this.defaultCategory); // TODO: This should incorporate passed options this.mergedSelector = new MediaRouteSelector.Builder() .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .addControlCategory(defaultCategory) .addControlCategory(this.defaultCategory) .build(); this.mediaRouterCallback = new MediaRouterCallbackImpl(this); this.router.registerMediaRouterCallbackImpl(this.mergedSelector.asBundle(), this.mediaRouterCallback); // [2026-03-02 14:42] base=86bde618: Register route selection receiver in cast-framework process. Validated: pending. registerRouteSelectionReceiver(); } // [2026-03-02 21:41] base=86bde618: Expose last instance for reflective route selection bridge. Validated: pending. public static CastContextImpl getLastInstance() { return lastInstance; } // [2026-03-02 21:41] base=86bde618: Record pending route selection so SessionManager can replay it. Validated: pending. public static void recordPendingRouteSelection(String routeId, Bundle extras) { pendingRouteId = routeId; pendingRouteExtras = extras; } // [2026-03-02 21:41] base=86bde618: Clear pending route once consumed. Validated: pending. public static void clearPendingRouteSelection() { pendingRouteId = null; pendingRouteExtras = null; } // [2026-03-02 21:41] base=86bde618: Access pending route id for debugging. Validated: pending. public static String getPendingRouteId() { return pendingRouteId; } // [2026-03-02 21:41] base=86bde618: Access pending route extras for debugging. Validated: pending. public static Bundle getPendingRouteExtras() { return pendingRouteExtras; } // [2026-03-02 14:42] base=86bde618: Ensure broadcast receiver is registered to handle cross-process route selection. Validated: pending. private void registerRouteSelectionReceiver() { if (routeSelectionReceiver != null) { return; } routeSelectionReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent == null || !ROUTE_SELECTED_ACTION.equals(intent.getAction())) { return; } String routeId = intent.getStringExtra("route_id"); Bundle extras = intent.getBundleExtra("route_extras"); Log.d(TAG, "route selected broadcast routeId=" + routeId + " extras=" + summarizeExtras(extras)); recordPendingRouteSelection(routeId, extras); // [2026-03-02 14:42] base=86bde618: Trigger SessionManager init to consume pending route. Validated: pending. getSessionManagerImpl(); } }; try { IntentFilter filter = new IntentFilter(ROUTE_SELECTED_ACTION); if (Build.VERSION.SDK_INT >= 33) { context.registerReceiver(routeSelectionReceiver, filter, Context.RECEIVER_EXPORTED); } else { context.registerReceiver(routeSelectionReceiver, filter); } Log.d(TAG, "route selection receiver registered"); } catch (Exception e) { Log.d(TAG, "route selection receiver register failed: " + e.getMessage()); } } // [2026-03-02 14:42] base=86bde618: Compact extras logging for route selection debugging. Validated: pending. private static String summarizeExtras(Bundle extras) { if (extras == null) { return "null"; } try { return "keys=" + extras.keySet(); } catch (Exception e) { return "error:" + e.getMessage(); } } @Override Loading @@ -96,12 +203,26 @@ public class CastContextImpl extends ICastContext.Stub { @Override public SessionManagerImpl getSessionManagerImpl() { // 2026-04-03T14:35Z LKG=68ba637: Trace SessionManager creation and pending route replay in cast-framework process. Fix: diagnose missing YouTube session start (pending validation). Log.d(TAG, "getSessionManagerImpl pendingRoute=" + pendingRouteId + " defaultCategory=" + defaultCategory); if (this.sessionManager == null) { this.sessionManager = new SessionManagerImpl(this); } // [2026-03-02 22:15] base=86bde618: Replay pending route selection once SessionManager is available. Validated: pending. if (pendingRouteId != null) { this.sessionManager.onRouteSelected(pendingRouteId, pendingRouteExtras); clearPendingRouteSelection(); // 2026-04-03T14:35Z LKG=68ba637: Confirm pending route replay to SessionManager. Fix: trace YouTube route handoff (pending validation). Log.d(TAG, "getSessionManagerImpl replayed pending route"); } return this.sessionManager; } // 2026-04-03T14:35Z LKG=68ba637: Dump session provider keys for debugging provider resolution failures. Fix: surface missing provider categories (pending validation). public String dumpSessionProviderKeys() { return "providers=" + sessionProviders.keySet(); } @Override public IDiscoveryManager getDiscoveryManagerImpl() throws RemoteException { if (this.discoveryManager == null) { Loading @@ -110,6 +231,45 @@ public class CastContextImpl extends ICastContext.Stub { return this.discoveryManager; } // [2026-03-02 20:19] base=86bde618: Accessor used by SessionManagerImpl fallback path. Validated: pending. public String getDefaultCategory() { return this.defaultCategory; } // [2026-03-02 20:19] base=86bde618: Minimal resolver for SessionManagerImpl compile path. Validated: pending. public ISessionProvider resolveSessionProviderForCategory(String category) { // [2026-03-02 14:58] base=86bde618: Normalize and prefix-match category keys to recover app providers. Validated: pending. if (category == null) { return null; } ISessionProvider provider = this.sessionProviders.get(category); if (provider != null) { return provider; } String normalized = normalizeCategoryKey(category); provider = this.sessionProviders.get(normalized); if (provider != null) { return provider; } String prefix = normalized + "///"; for (Map.Entry<String, ISessionProvider> entry : this.sessionProviders.entrySet()) { String key = entry.getKey(); if (key != null && (key.startsWith(prefix) || key.startsWith(category + "///"))) { return entry.getValue(); } } return null; } // [2026-03-02 14:58] base=86bde618: Strip suffixes (e.g., ///ALLOW_IPV6) from category keys. Validated: pending. private static String normalizeCategoryKey(String key) { if (key == null) { return null; } int idx = key.indexOf("///"); return idx >= 0 ? key.substring(0, idx) : key; } @Override public void destroy() throws RemoteException { Log.d(TAG, "unimplemented Method: destroy"); Loading @@ -128,7 +288,16 @@ public class CastContextImpl extends ICastContext.Stub { @Override public void setReceiverApplicationId(String receiverApplicationId, Map sessionProvidersByCategory) throws RemoteException { Log.d(TAG, "unimplemented Method: setReceiverApplicationId"); // [2026-03-02 20:19] base=86bde618: Keep cached category in sync with receiver changes. Validated: pending. this.defaultCategory = CastMediaControlIntent.categoryForCast(receiverApplicationId); // [2026-03-02 14:58] base=86bde618: Resolve provider via normalized key on receiver app change. Validated: pending. this.defaultSessionProvider = resolveSessionProviderForCategory(this.defaultCategory); this.mergedSelector = new MediaRouteSelector.Builder() .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .addControlCategory(this.defaultCategory) .build(); this.router.registerMediaRouterCallbackImpl(this.mergedSelector.asBundle(), this.mediaRouterCallback); } public Context getContext() { Loading