Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ada62eb2 authored by Jonathan Klee's avatar Jonathan Klee
Browse files

Squash gael/youtube-freeze commits on top of 41d01cbd

parent dc7b9cea
Loading
Loading
Loading
Loading
+11 −5
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
 * SPDX-License-Identifier: Apache-2.0
 */

// 2026 Murena SAS
buildscript {
    ext.cronetVersion = '102.5005.125'
    ext.wearableVersion = '0.1.1'
@@ -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"
@@ -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 {
+8 −1
Original line number Diff line number Diff line
// 2026 Murena SAS
/*
 * Copyright (C) 2013-2017 microG Project Team
 *
@@ -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
+4 −2
Original line number Diff line number Diff line
// 2026 Murena SAS
/*
 * Copyright (C) 2013-2017 microG Project Team
 *
@@ -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;
    }

+19 −3
Original line number Diff line number Diff line
// 2026 Murena SAS
/*
 * Copyright (C) 2013-2017 microG Project Team
 *
@@ -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;
    }

    /**
+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
 *
@@ -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;
@@ -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
@@ -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) {
@@ -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");
@@ -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