Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions core/java/android/app/ApplicationPackageManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}

Expand All @@ -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);
Expand Down Expand Up @@ -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);
}

Expand Down
115 changes: 115 additions & 0 deletions core/java/com/android/internal/gmscompat/GsfPresenceCompat.java
Original file line number Diff line number Diff line change
@@ -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).
* <p>
* 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.
* <p>
* 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;
}
}