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

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

chore: add debug notifications when a method or a service is not implemented

parent 36e0ef2d
Loading
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.google.android.gms.common.internal.IGmsCallbacks;
import com.google.android.gms.common.internal.IGmsServiceBroker;

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

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -57,6 +58,12 @@ public abstract class BaseService extends LifecycleService {
        };
    }

    @Override
    public void onCreate() {
        super.onCreate();
        UnimplementedMethodNotifier.setAppContext(this);
    }

    @Override
    public IBinder onBind(Intent intent) {
        super.onBind(intent);
+64 −1
Original line number Diff line number Diff line
@@ -16,22 +16,85 @@

package org.microg.gms;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.os.RemoteException;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.IGmsCallbacks;

import org.microg.gms.base.core.R;
import org.microg.gms.common.GmsService;

public class DummyService extends BaseService {
    private static final String NOTIFICATION_CHANNEL_ID = "unimplemented-service";
    private static final String NOTIFICATION_PREFS = "unimplemented_service_notifications";
    private static final long NOTIFICATION_COOLDOWN_MS = 60 * 60 * 1000L;
    private static final String PREF_KEY_ENABLED = "pref_unimplemented_method_notifications";

    public DummyService() {
        super("GmsDummySvc", GmsService.ANY);
    }

    @Override
    public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
        maybeNotifyUnimplementedService(request, service);
        callback.onPostInitComplete(ConnectionResult.API_DISABLED, null, null);
    }

    private void maybeNotifyUnimplementedService(GetServiceRequest request, GmsService service) {
        if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean(PREF_KEY_ENABLED, true)) return;
        String packageName = request.packageName == null ? "unknown" : request.packageName;
        String serviceName = service == null ? "UNKNOWN" : service.name();
        String appLabel = packageName;
        try {
            appLabel = getPackageManager()
                    .getApplicationLabel(getPackageManager().getApplicationInfo(packageName, 0))
                    .toString();
        } catch (Exception ignored) {
        }

        String key = request.serviceId + ":" + packageName;
        SharedPreferences prefs = getSharedPreferences(NOTIFICATION_PREFS, MODE_PRIVATE);
        long now = System.currentTimeMillis();
        long last = prefs.getLong(key, 0L);
        if (now - last < NOTIFICATION_COOLDOWN_MS) return;
        prefs.edit().putLong(key, now).apply();

        ensureNotificationChannel();

        String title = getString(R.string.unimplemented_service_notification_title);
        String text = getString(R.string.unimplemented_service_notification_text, appLabel, serviceName, request.serviceId);
        String bigText = getString(R.string.unimplemented_service_notification_big_text, appLabel, packageName, serviceName, request.serviceId);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.drawable.ic_info_outline)
                .setContentTitle(title)
                .setContentText(text)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
                .setAutoCancel(true);

        int notificationId = key.hashCode();
        NotificationManagerCompat.from(this).notify(notificationId, builder.build());
    }

    private void ensureNotificationChannel() {
        if (Build.VERSION.SDK_INT < 26) return;
        NotificationManager manager = getSystemService(NotificationManager.class);
        if (manager == null) return;
        NotificationChannel channel = new NotificationChannel(
                NOTIFICATION_CHANNEL_ID,
                getString(R.string.unimplemented_service_channel_name),
                NotificationManager.IMPORTANCE_DEFAULT
        );
        channel.setDescription(getString(R.string.unimplemented_service_channel_description));
        manager.createNotificationChannel(channel);
    }
}
+6 −0
Original line number Diff line number Diff line
@@ -9,6 +9,12 @@
    <string name="foreground_service_notification_text"><xliff:g example="Exposure Notification">%1$s</xliff:g> is running in background.</string>
    <string name="foreground_service_notification_big_text">Exclude <xliff:g example="microG Services">%1$s</xliff:g> from battery optimizations or change notification settings to hide this notification.</string>

    <string name="unimplemented_service_channel_name">Missing microG services</string>
    <string name="unimplemented_service_channel_description">Notifications for apps requesting services that microG does not implement yet.</string>
    <string name="unimplemented_service_notification_title">Service not implemented</string>
    <string name="unimplemented_service_notification_text"><xliff:g example="Example App">%1$s</xliff:g> requested <xliff:g example="FIREBASE_AUTH">%2$s</xliff:g> (ID <xliff:g example="112">%3$d</xliff:g>).</string>
    <string name="unimplemented_service_notification_big_text"><xliff:g example="Example App">%1$s</xliff:g> (<xliff:g example="com.example.app">%2$s</xliff:g>) requested <xliff:g example="FIREBASE_AUTH">%3$s</xliff:g> (service ID <xliff:g example="112">%4$d</xliff:g>), which microG does not implement yet.</string>

    <string name="menu_advanced">Advanced</string>
    <string name="menu_game_managed">Game Accounts Managed</string>

+128 −0
Original line number Diff line number Diff line
/*
 * SPDX-FileCopyrightText: 2026, microG Project Team
 * SPDX-License-Identifier: Apache-2.0
 */

package org.microg.gms.common;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Binder;
import android.os.Build;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

public final class UnimplementedMethodNotifier {
    private static final String TAG = "UnimplementedMethod";
    private static final String CHANNEL_ID = "unimplemented-method";
    private static final String PREFS = "unimplemented_method_notifications";
    private static final long COOLDOWN_MS = 30 * 60 * 1000L;
    public static final String PREF_KEY_ENABLED = "pref_unimplemented_method_notifications";

    private static volatile Context appContext;

    private UnimplementedMethodNotifier() {
    }

    public static void setAppContext(Context context) {
        if (context == null) return;
        if (appContext == null) {
            appContext = context.getApplicationContext();
        }
    }

    public static void notify(String logTag, String message) {
        Log.d(logTag, message);
        Context context = appContext;
        if (context == null) return;
        if (!isEnabled(context)) return;

        String packageName = resolveCallingPackage(context);
        String appLabel = resolveAppLabel(context, packageName);
        String methodKey = methodKeyFromMessage(message);

        long now = System.currentTimeMillis();
        SharedPreferences prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
        String key = packageName + ":" + logTag + ":" + methodKey;
        long last = prefs.getLong(key, 0L);
        if (now - last < COOLDOWN_MS) return;
        prefs.edit().putLong(key, now).apply();

        ensureNotificationChannel(context);

        String title = "Service method not implemented";
        String text = appLabel + " called " + methodKey + " (not implemented).";
        String bigText = appLabel + " (" + packageName + ") called " + methodKey + " in " + logTag + ".\n" +
                "This method is not implemented in microG yet.\n" +
                message;

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
                .setSmallIcon(android.R.drawable.stat_sys_warning)
                .setContentTitle(title)
                .setContentText(text)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(bigText))
                .setAutoCancel(true);

        NotificationManagerCompat.from(context).notify(key.hashCode(), builder.build());
    }

    private static void ensureNotificationChannel(Context context) {
        if (Build.VERSION.SDK_INT < 26) return;
        NotificationManager manager = context.getSystemService(NotificationManager.class);
        if (manager == null) return;
        NotificationChannel channel = new NotificationChannel(
                CHANNEL_ID,
                "Unimplemented microG methods",
                NotificationManager.IMPORTANCE_DEFAULT
        );
        channel.setDescription("Notifications for apps calling methods not implemented in microG.");
        manager.createNotificationChannel(channel);
    }

    private static String resolveCallingPackage(Context context) {
        try {
            int uid = Binder.getCallingUid();
            String[] packages = context.getPackageManager().getPackagesForUid(uid);
            if (packages != null && packages.length > 0) {
                return packages[0];
            }
        } catch (Exception e) {
            Log.w(TAG, "Failed to resolve calling package", e);
        }
        return "unknown";
    }

    private static boolean isEnabled(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREF_KEY_ENABLED, true);
    }

    private static String resolveAppLabel(Context context, String packageName) {
        if (TextUtils.isEmpty(packageName) || "unknown".equals(packageName)) return "An app";
        try {
            return context.getPackageManager()
                    .getApplicationLabel(context.getPackageManager().getApplicationInfo(packageName, 0))
                    .toString();
        } catch (Exception e) {
            return packageName;
        }
    }

    private static String methodKeyFromMessage(String message) {
        if (message == null) return "unknown";
        String prefix = "unimplemented Method:";
        int index = message.indexOf(prefix);
        if (index < 0) return message;
        String rest = message.substring(index + prefix.length()).trim();
        int colon = rest.indexOf(':');
        if (colon > 0) {
            rest = rest.substring(0, colon).trim();
        }
        return rest.isEmpty() ? "unknown" : rest;
    }
}
+7 −7
Original line number Diff line number Diff line
@@ -80,17 +80,17 @@ public class CastContextImpl extends ICastContext.Stub {

    @Override
    public void addVisibilityChangeListener(IAppVisibilityListener listener) {
        Log.d(TAG, "unimplemented Method: addVisibilityChangeListener");
        org.microg.gms.common.UnimplementedMethodNotifier.notify(TAG, "unimplemented Method: addVisibilityChangeListener");
    }

    @Override
    public void removeVisibilityChangeListener(IAppVisibilityListener listener) {
        Log.d(TAG, "unimplemented Method: removeVisibilityChangeListener");
        org.microg.gms.common.UnimplementedMethodNotifier.notify(TAG, "unimplemented Method: removeVisibilityChangeListener");
    }

    @Override
    public boolean isApplicationVisible() throws RemoteException {
        Log.d(TAG, "unimplemented Method: isApplicationVisible");
        org.microg.gms.common.UnimplementedMethodNotifier.notify(TAG, "unimplemented Method: isApplicationVisible");
        return true;
    }

@@ -112,23 +112,23 @@ public class CastContextImpl extends ICastContext.Stub {

    @Override
    public void destroy() throws RemoteException {
        Log.d(TAG, "unimplemented Method: destroy");
        org.microg.gms.common.UnimplementedMethodNotifier.notify(TAG, "unimplemented Method: destroy");
    }

    @Override
    public void onActivityResumed(IObjectWrapper activity) throws RemoteException {
        Log.d(TAG, "unimplemented Method: onActivityResumed");
        org.microg.gms.common.UnimplementedMethodNotifier.notify(TAG, "unimplemented Method: onActivityResumed");

    }

    @Override
    public void onActivityPaused(IObjectWrapper activity) throws RemoteException {
        Log.d(TAG, "unimplemented Method: onActivityPaused");
        org.microg.gms.common.UnimplementedMethodNotifier.notify(TAG, "unimplemented Method: onActivityPaused");
    }

    @Override
    public void setReceiverApplicationId(String receiverApplicationId, Map sessionProvidersByCategory) throws RemoteException {
        Log.d(TAG, "unimplemented Method: setReceiverApplicationId");
        org.microg.gms.common.UnimplementedMethodNotifier.notify(TAG, "unimplemented Method: setReceiverApplicationId");
    }

    public Context getContext() {
Loading