From 616ec5f7ff01d754f79544ba4f06f3ca2e36b8f3 Mon Sep 17 00:00:00 2001 From: DaVinci9196 Date: Mon, 5 Jan 2026 15:21:42 +0800 Subject: [PATCH 1/2] fix: prevent infinite request loop in Google 2-step verification --- .../src/main/java/org/microg/gms/gcm/McsService.java | 9 +++++++++ .../main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt | 4 ++++ .../src/main/kotlin/org/microg/gms/gcm/extensions.kt | 1 + 3 files changed, 14 insertions(+) diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 5c298ae418..6b973cccb8 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -81,6 +81,7 @@ import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.common.PackageUtils.warnIfNotPersistentProcess; +import static org.microg.gms.gcm.ExtensionsKt.ACTION_RETRY_REGISTER_GCM_IN_GMS; import static org.microg.gms.gcm.GcmConstants.*; import static org.microg.gms.gcm.ExtensionsKt.ACTION_GCM_REGISTERED; import static org.microg.gms.gcm.McsConstants.*; @@ -116,6 +117,7 @@ public class McsService extends Service implements Handler.Callback { private static McsOutputStream outputStream; private PendingIntent heartbeatIntent; + private PendingIntent retryRegisterGcmInGmsIntent; private static HandlerThread handlerThread; private static Handler rootHandler; @@ -178,6 +180,7 @@ public void onCreate() { TriggerReceiver.register(this); database = new GcmDatabase(this); heartbeatIntent = PendingIntentCompat.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), 0, false); + retryRegisterGcmInGmsIntent = PendingIntentCompat.getService(this, 0, new Intent(ACTION_RETRY_REGISTER_GCM_IN_GMS, null, this, GcmInGmsService.class), 0, false); alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); powerManager = (PowerManager) getSystemService(POWER_SERVICE); if (SDK_INT >= 23 && checkSelfPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") == PackageManager.PERMISSION_GRANTED) { @@ -225,6 +228,7 @@ public static void stop(Context context) { public void onDestroy() { Log.d(TAG, "onDestroy"); alarmManager.cancel(heartbeatIntent); + alarmManager.cancel(retryRegisterGcmInGmsIntent); closeAll(); database.close(); super.onDestroy(); @@ -287,13 +291,17 @@ public void scheduleHeartbeat(Context context) { if (SDK_INT >= 23) { // This is supposed to work even when running in idle and without battery optimization disabled alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); + alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, retryRegisterGcmInGmsIntent); } else if (SDK_INT >= 19) { // With KitKat, the alarms become inexact by default, but with the newly available setWindow we can get inexact alarms with guarantees. // Schedule the alarm to fire within the interval [heartbeatMs/3*4, heartbeatMs] alarmManager.setWindow(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs / 4 * 3, heartbeatMs / 4, heartbeatIntent); + alarmManager.setWindow(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs / 4 * 3, heartbeatMs / 4, + retryRegisterGcmInGmsIntent); } else { alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); + alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, retryRegisterGcmInGmsIntent); } } @@ -823,6 +831,7 @@ private void handleTeardown(android.os.Message msg) { scheduleReconnect(this); alarmManager.cancel(heartbeatIntent); + alarmManager.cancel(retryRegisterGcmInGmsIntent); if (wakeLock != null) { try { wakeLock.release(); diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt index 8304c24463..79f5249891 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt @@ -370,6 +370,10 @@ class GcmInGmsService : LifecycleService() { completeRegisterRequest(context, gcmDatabase, request).getString(GcmConstants.EXTRA_REGISTRATION_ID) } Log.d(TAG, "GCM IN GMS regId: $regId") + if (regId == null) { + Log.w(TAG, "registerGcmInGms reg id is null") + return + } val sharedPreferencesEditor = sp?.edit() sharedPreferencesEditor?.putLong(KEY_GCM_ANDROID_ID, LastCheckinInfo.read(context).androidId) sharedPreferencesEditor?.putString(KEY_GCM_REG_ID, regId) diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt index 428a9bdd7a..9bad63fa08 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt @@ -17,6 +17,7 @@ const val ACTION_GCM_REGISTER_ACCOUNT = "org.microg.gms.gcm.REGISTER_ACCOUNT" const val ACTION_GCM_NOTIFY_COMPLETE = "org.microg.gms.gcm.NOTIFY_COMPLETE" const val KEY_GCM_REGISTER_ACCOUNT_NAME = "register_account_name" const val EXTRA_NOTIFICATION_ACCOUNT = "notification_account" +const val ACTION_RETRY_REGISTER_GCM_IN_GMS = "org.microg.gms.gcm.RETRY_REGISTER_GCM_IN_GMS" const val GMS_NOTS_OAUTH_SERVICE = "oauth2:https://www.googleapis.com/auth/notifications" const val GMS_NOTS_BASE_URL = "https://notifications-pa.googleapis.com" From 52d6d1e0a7e872ed7b2ebf5bc7a64b4cb6afd9b3 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 8 Jan 2026 10:36:28 +0100 Subject: [PATCH 2/2] GCM: No need to update registration with every heartbeat, only do it after connection --- play-services-core/src/main/AndroidManifest.xml | 2 +- .../java/org/microg/gms/gcm/McsService.java | 17 ++++------------- .../org/microg/gms/gcm/GcmInGmsService.kt | 5 +++-- .../kotlin/org/microg/gms/gcm/extensions.kt | 6 +++--- .../org/microg/gms/ui/AccountsFragment.kt | 5 +++-- 5 files changed, 14 insertions(+), 21 deletions(-) diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 6f593efdf8..8e4affa4f4 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -368,7 +368,7 @@ android:exported="false" android:process=":persistent"> - + diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java index 6b973cccb8..7bce08dbd3 100644 --- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java +++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java @@ -81,9 +81,8 @@ import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.os.Build.VERSION.SDK_INT; import static org.microg.gms.common.PackageUtils.warnIfNotPersistentProcess; -import static org.microg.gms.gcm.ExtensionsKt.ACTION_RETRY_REGISTER_GCM_IN_GMS; +import static org.microg.gms.gcm.ExtensionsKt.ACTION_GCM_CONNECTED; import static org.microg.gms.gcm.GcmConstants.*; -import static org.microg.gms.gcm.ExtensionsKt.ACTION_GCM_REGISTERED; import static org.microg.gms.gcm.McsConstants.*; @ForegroundServiceInfo(value = "Cloud messaging", resName = "service_name_mcs", resPackage = "com.google.android.gms") @@ -117,7 +116,6 @@ public class McsService extends Service implements Handler.Callback { private static McsOutputStream outputStream; private PendingIntent heartbeatIntent; - private PendingIntent retryRegisterGcmInGmsIntent; private static HandlerThread handlerThread; private static Handler rootHandler; @@ -180,7 +178,6 @@ public void onCreate() { TriggerReceiver.register(this); database = new GcmDatabase(this); heartbeatIntent = PendingIntentCompat.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), 0, false); - retryRegisterGcmInGmsIntent = PendingIntentCompat.getService(this, 0, new Intent(ACTION_RETRY_REGISTER_GCM_IN_GMS, null, this, GcmInGmsService.class), 0, false); alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); powerManager = (PowerManager) getSystemService(POWER_SERVICE); if (SDK_INT >= 23 && checkSelfPermission("android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST") == PackageManager.PERMISSION_GRANTED) { @@ -228,7 +225,6 @@ public static void stop(Context context) { public void onDestroy() { Log.d(TAG, "onDestroy"); alarmManager.cancel(heartbeatIntent); - alarmManager.cancel(retryRegisterGcmInGmsIntent); closeAll(); database.close(); super.onDestroy(); @@ -291,17 +287,13 @@ public void scheduleHeartbeat(Context context) { if (SDK_INT >= 23) { // This is supposed to work even when running in idle and without battery optimization disabled alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); - alarmManager.setExactAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, retryRegisterGcmInGmsIntent); } else if (SDK_INT >= 19) { // With KitKat, the alarms become inexact by default, but with the newly available setWindow we can get inexact alarms with guarantees. // Schedule the alarm to fire within the interval [heartbeatMs/3*4, heartbeatMs] alarmManager.setWindow(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs / 4 * 3, heartbeatMs / 4, heartbeatIntent); - alarmManager.setWindow(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs / 4 * 3, heartbeatMs / 4, - retryRegisterGcmInGmsIntent); } else { alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, heartbeatIntent); - alarmManager.set(ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + heartbeatMs, retryRegisterGcmInGmsIntent); } } @@ -505,15 +497,15 @@ private void handleLoginResponse(LoginResponse loginResponse) { if (loginResponse.error == null) { GcmPrefs.clearLastPersistedId(this); logd(this, "Logged in"); - notifyGcmRegistered(); + notifyGcmConnected(); wakeLock.release(); } else { throw new RuntimeException("Could not login: " + loginResponse.error); } } - private void notifyGcmRegistered() { - Intent intent = new Intent(ACTION_GCM_REGISTERED); + private void notifyGcmConnected() { + Intent intent = new Intent(ACTION_GCM_CONNECTED); intent.setPackage(Constants.GMS_PACKAGE_NAME); sendBroadcast(intent); } @@ -831,7 +823,6 @@ private void handleTeardown(android.os.Message msg) { scheduleReconnect(this); alarmManager.cancel(heartbeatIntent); - alarmManager.cancel(retryRegisterGcmInGmsIntent); if (wakeLock != null) { try { wakeLock.release(); diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt index 79f5249891..36b0c36994 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/GcmInGmsService.kt @@ -155,7 +155,8 @@ class GcmInGmsService : LifecycleService() { Log.d(TAG, "start handle gcm message") intent.extras?.let { notifyVerificationInfo(it) } } - ACTION_GCM_REGISTERED -> { + ACTION_GCM_REGISTER_ALL_ACCOUNTS, + ACTION_GCM_CONNECTED -> { updateLocalAccountGroups() } ACTION_GCM_REGISTER_ACCOUNT -> { @@ -514,4 +515,4 @@ class GcmRegistrationReceiver : WakefulBroadcastReceiver() { } ForegroundServiceContext(context).startService(callIntent) } -} \ No newline at end of file +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt b/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt index 9bad63fa08..8491604554 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/gcm/extensions.kt @@ -12,12 +12,12 @@ import okhttp3.OkHttpClient import okhttp3.Response const val ACTION_GCM_RECONNECT = "org.microg.gms.gcm.RECONNECT" -const val ACTION_GCM_REGISTERED = "org.microg.gms.gcm.REGISTERED" +const val ACTION_GCM_CONNECTED = "org.microg.gms.gcm.CONNECTED" const val ACTION_GCM_REGISTER_ACCOUNT = "org.microg.gms.gcm.REGISTER_ACCOUNT" +const val ACTION_GCM_REGISTER_ALL_ACCOUNTS = "org.microg.gms.gcm.REGISTER_ALL_ACCOUNTS" const val ACTION_GCM_NOTIFY_COMPLETE = "org.microg.gms.gcm.NOTIFY_COMPLETE" const val KEY_GCM_REGISTER_ACCOUNT_NAME = "register_account_name" const val EXTRA_NOTIFICATION_ACCOUNT = "notification_account" -const val ACTION_RETRY_REGISTER_GCM_IN_GMS = "org.microg.gms.gcm.RETRY_REGISTER_GCM_IN_GMS" const val GMS_NOTS_OAUTH_SERVICE = "oauth2:https://www.googleapis.com/auth/notifications" const val GMS_NOTS_BASE_URL = "https://notifications-pa.googleapis.com" @@ -45,4 +45,4 @@ inline fun createGrpcClient( .minMessageToCompress(minMessageToCompress) .build() return grpcClient.create(S::class) -} \ No newline at end of file +} diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt index 4102e2c1c7..2be044bd99 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/AccountsFragment.kt @@ -26,7 +26,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.auth.AuthConstants import org.microg.gms.common.Constants -import org.microg.gms.gcm.ACTION_GCM_REGISTERED +import org.microg.gms.gcm.ACTION_GCM_CONNECTED +import org.microg.gms.gcm.ACTION_GCM_REGISTER_ALL_ACCOUNTS import org.microg.gms.people.DatabaseHelper import org.microg.gms.people.PeopleManager import org.microg.gms.settings.SettingsContract @@ -65,7 +66,7 @@ class AccountsFragment : PreferenceFragmentCompat() { }).also { it.isCircular = true } else null private fun registerGcmInGms() { - Intent(ACTION_GCM_REGISTERED).apply { + Intent(ACTION_GCM_REGISTER_ALL_ACCOUNTS).apply { `package` = Constants.GMS_PACKAGE_NAME }.let { requireContext().sendBroadcast(it) } }