From c8bfbb1c78f9079855c9d522445c34f5be3372ba Mon Sep 17 00:00:00 2001 From: Alex9001 Date: Thu, 20 Aug 2026 22:00:26 -0700 Subject: [PATCH] gmscompat: synthesize GSF package presence when GmsCore is installed Apps that probe com.google.android.gsf via PackageManager fail to enable FCM on fresh GrapheneOS installs where GSF is absent. When verified Play Services is visible to the caller, synthesize a minimal PackageInfo/ApplicationInfo for GSF so presence checks succeed. Real GSF installation remains blocked. Bug: https://github.com/GrapheneOS/os-issue-tracker/issues/8567 Co-authored-by: Cursor --- .../app/ApplicationPackageManager.java | 33 +++-- .../internal/gmscompat/GsfPresenceCompat.java | 115 ++++++++++++++++++ 2 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 core/java/com/android/internal/gmscompat/GsfPresenceCompat.java diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index b1f0fc042391b..557690b9b6f7d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -141,6 +141,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.gmscompat.GsfPresenceCompat; import com.android.internal.gmscompat.sysservice.GmcPackageManager; import com.android.internal.os.ApplicationSharedMemory; import com.android.internal.os.SomeArgs; @@ -265,15 +266,20 @@ public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags) public PackageInfo getPackageInfo(VersionedPackage versionedPackage, PackageInfoFlags flags) throws NameNotFoundException { final int userId = getUserId(); + final long updatedFlags = updateFlagsForPackage(flags.getValue(), userId); try { - PackageInfo pi = mPM.getPackageInfoVersioned(versionedPackage, - updateFlagsForPackage(flags.getValue(), userId), userId); + PackageInfo pi = mPM.getPackageInfoVersioned(versionedPackage, updatedFlags, userId); if (pi != null) { return pi; } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + PackageInfo synth = GsfPresenceCompat.maybeSynthesizePackageInfo( + versionedPackage.getPackageName(), updatedFlags, userId); + if (synth != null) { + return synth; + } throw new NameNotFoundException(versionedPackage.toString()); } @@ -286,12 +292,14 @@ public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userI @Override public PackageInfo getPackageInfoAsUser(String packageName, PackageInfoFlags flags, int userId) throws NameNotFoundException { - PackageInfo pi = - getPackageInfoAsUserCached( - packageName, - updateFlagsForPackage(flags.getValue(), userId), - userId); + final long updatedFlags = updateFlagsForPackage(flags.getValue(), userId); + PackageInfo pi = getPackageInfoAsUserCached(packageName, updatedFlags, userId); if (pi == null) { + PackageInfo synth = GsfPresenceCompat.maybeSynthesizePackageInfo( + packageName, updatedFlags, userId); + if (synth != null) { + return synth; + } throw new NameNotFoundException(packageName); } GmcPackageManager.maybeAdjustPackageInfo(pi); @@ -549,11 +557,14 @@ public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, i @Override public ApplicationInfo getApplicationInfoAsUser(String packageName, ApplicationInfoFlags flags, int userId) throws NameNotFoundException { - ApplicationInfo ai = getApplicationInfoAsUserCached( - packageName, - updateFlagsForApplication(flags.getValue(), userId), - userId); + final long updatedFlags = updateFlagsForApplication(flags.getValue(), userId); + ApplicationInfo ai = getApplicationInfoAsUserCached(packageName, updatedFlags, userId); if (ai == null) { + ApplicationInfo synth = GsfPresenceCompat.maybeSynthesizeApplicationInfo( + packageName, updatedFlags, userId); + if (synth != null) { + return synth; + } throw new NameNotFoundException(packageName); } diff --git a/core/java/com/android/internal/gmscompat/GsfPresenceCompat.java b/core/java/com/android/internal/gmscompat/GsfPresenceCompat.java new file mode 100644 index 0000000000000..794b426eb7b0a --- /dev/null +++ b/core/java/com/android/internal/gmscompat/GsfPresenceCompat.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * 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.android.internal.gmscompat; + +import android.annotation.Nullable; +import android.app.ActivityThread; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.ext.PackageId; +import android.os.RemoteException; +import android.util.Log; + +/** + * Synthesizes {@link PackageId#GSF_NAME} package presence for third-party apps when + * Play Services is installed but GSF itself is absent (fresh GrapheneOS installs). + *

+ * Some apps (e.g. Hikvision Hik-Connect / Guarding Vision) gate FCM on + * {@code getPackageInfo("com.google.android.gsf")} even though FCM works through GmsCore. + * This helper answers that presence probe only; it does not allow installing real GSF. + *

+ * The GmsCore presence check is subject to the caller's package visibility. That matches + * when FCM is viable for the app. + * + * @hide + */ +public final class GsfPresenceCompat { + private static final String TAG = "GsfPresenceCompat"; + + private GsfPresenceCompat() {} + + /** + * @param flags unused; kept so call sites can pass the original query flags unchanged + * @return a synthetic {@link PackageInfo} for GSF, or null if this request should + * still throw {@link PackageManager.NameNotFoundException} + */ + @Nullable + public static PackageInfo maybeSynthesizePackageInfo(String packageName, long flags, + int userId) { + if (!PackageId.GSF_NAME.equals(packageName)) { + return null; + } + if (!isVerifiedGmsCoreInstalled(userId)) { + return null; + } + // Minimal presence-only object. Do not copy GmsCore fields (version, signatures, + // sharedUserId, etc.) under the GSF name. + PackageInfo pi = new PackageInfo(); + pi.packageName = PackageId.GSF_NAME; + pi.applicationInfo = synthesizeApplicationInfo(); + Log.d(TAG, "synthesized PackageInfo for " + PackageId.GSF_NAME + + " (GmsCore present, userId=" + userId + ")"); + return pi; + } + + /** + * @param flags unused; kept so call sites can pass the original query flags unchanged + * @return a synthetic {@link ApplicationInfo} for GSF, or null if this request should + * still throw {@link PackageManager.NameNotFoundException} + */ + @Nullable + public static ApplicationInfo maybeSynthesizeApplicationInfo(String packageName, long flags, + int userId) { + if (!PackageId.GSF_NAME.equals(packageName)) { + return null; + } + if (!isVerifiedGmsCoreInstalled(userId)) { + return null; + } + ApplicationInfo ai = synthesizeApplicationInfo(); + Log.d(TAG, "synthesized ApplicationInfo for " + PackageId.GSF_NAME + + " (GmsCore present, userId=" + userId + ")"); + return ai; + } + + private static ApplicationInfo synthesizeApplicationInfo() { + ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = PackageId.GSF_NAME; + ai.enabled = true; + return ai; + } + + /** + * True if verified Play Services is installed and visible to the caller for {@code userId}. + */ + private static boolean isVerifiedGmsCoreInstalled(int userId) { + IPackageManager pm = ActivityThread.getPackageManager(); + ApplicationInfo ai; + try { + ai = pm.getApplicationInfo(PackageId.GMS_CORE_NAME, /* flags */ 0L, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (ai == null || !ai.enabled) { + return false; + } + // Reject lookalikes (wrong signing key / no GrapheneOS PackageId assignment). + return ai.ext().getPackageId() == PackageId.GMS_CORE; + } +}