Skip to content
3 changes: 3 additions & 0 deletions core/api/system-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ package android {
field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
field public static final String RECOVERY = "android.permission.RECOVERY";
field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
field public static final String RECOVER_KEYSTORE_GMSCORE = "android.permission.RECOVER_KEYSTORE_GMSCORE";
field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
field @FlaggedApi("android.net.platform.flags.register_nsd_offload_engine") public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
Expand Down Expand Up @@ -5821,6 +5822,7 @@ package android.ext {
}

public interface KnownSystemPackage {
field public static final int GMS_COMPAT = 4; // 0x4
field public static final int SETTINGS = 0; // 0x0
field public static final int SETUP_WIZARD = 3; // 0x3
field public static final int SHELL = 1; // 0x1
Expand All @@ -5831,6 +5833,7 @@ package android.ext {
method @NonNull public static android.ext.KnownSystemPackages get(@NonNull android.content.Context);
method @NonNull public String getById(int);
field @NonNull public final String contactsProvider;
field @NonNull public final String gmsCompat;
field @NonNull public final String launcher;
field @NonNull public final String mediaProvider;
field @NonNull public final String permissionController;
Expand Down
1 change: 1 addition & 0 deletions core/java/android/app/compat/gms/GmsCorePackageFlag.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
/** @hide */
public interface GmsCorePackageFlag {
int GRANT_PERMS_FOR_ICC_AUTHENTICATION = 0;
int GRANT_PERMS_FOR_RECOVER_KEYSTORE_GMSCORE = 1;
}
2 changes: 2 additions & 0 deletions core/java/android/ext/KnownSystemPackage.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ public interface KnownSystemPackage {
int SHELL = 1;
int SYSTEM_UI = 2;
int SETUP_WIZARD = 3;
int GMS_COMPAT = 4;

/** @hide */
@IntDef(value = {
SETTINGS,
SHELL,
SYSTEM_UI,
SETUP_WIZARD,
GMS_COMPAT,
})
@Retention(RetentionPolicy.SOURCE)
@interface Enum {}
Expand Down
3 changes: 3 additions & 0 deletions core/java/android/ext/KnownSystemPackages.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static KnownSystemPackages get(@NonNull Context ctx) {
}

@NonNull public final String contactsProvider;
@NonNull public final String gmsCompat;
@NonNull public final String launcher;
@NonNull public final String mediaProvider;
@NonNull public final String permissionController;
Expand All @@ -33,6 +34,7 @@ public static KnownSystemPackages get(@NonNull Context ctx) {
private KnownSystemPackages(Context ctx) {
Resources res = ctx.getResources();
contactsProvider = "com.android.providers.contacts";
gmsCompat = "app.grapheneos.gmscompat";
launcher = "com.android.launcher3";
mediaProvider = "com.android.providers.media.module";
permissionController = "com.android.permissioncontroller";
Expand All @@ -49,6 +51,7 @@ public String getById(@KnownSystemPackage.Enum int id) {
case KnownSystemPackage.SHELL -> shell;
case KnownSystemPackage.SYSTEM_UI -> systemUi;
case KnownSystemPackage.SETUP_WIZARD -> setupWizard;
case KnownSystemPackage.GMS_COMPAT -> gmsCompat;
default -> throw new IllegalArgumentException();
};
}
Expand Down
2 changes: 1 addition & 1 deletion core/java/android/provider/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -14470,7 +14470,7 @@ public static final class Global extends NameValueTable {
public static final String WIFI_AUTO_OFF = "wifi_off_timeout";

/** @hide */
@Protected(readWrite = KnownSystemPackage.SETTINGS)
@Protected(read = KnownSystemPackage.GMS_COMPAT, readWrite = KnownSystemPackage.SETTINGS)
public static final String BLUETOOTH_AUTO_OFF = "bluetooth_off_timeout";

/** @hide */
Expand Down
149 changes: 149 additions & 0 deletions core/java/com/android/internal/gmscompat/GmsHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.android.internal.gmscompat;

import android.Manifest;
import android.accounts.AccountManager;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.Activity;
Expand All @@ -34,11 +35,13 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.credentials.CredentialOption;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.ext.PackageId;
import android.net.Uri;
import android.os.BadParcelableException;
import android.os.Binder;
import android.os.Bundle;
import android.os.DeadSystemRuntimeException;
Expand All @@ -51,6 +54,9 @@
import android.os.UserHandle;
import android.provider.Downloads;
import android.provider.Settings;
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialRequest;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
Expand Down Expand Up @@ -489,6 +495,149 @@ public static void activityOnCreate(Activity activity) {
Log.i(TAG, "calling setTranslucent(false) for " + activity.getClass().getName());
activity.setTranslucent(false);
}

maybeShowRecoverableKeystorePermissionNotification(activity.getIntent());
}

private static void maybeShowRecoverableKeystorePermissionNotification(
@Nullable Intent intent) {
if (!GmsCompat.isGmsCore()
|| GmsCompat.hasPermission(Manifest.permission.RECOVER_KEYSTORE_GMSCORE)) {
return;
}

if (isPublicKeyCredentialRequest(intent)) {
if (!hasGoogleAccount()) {
return;
}
try {
GmsCompatApp.iGms2Gca()
.maybeShowGmsCoreMissingRecoverableKeystorePermissionNotification();
} catch (RemoteException e) {
Log.w(TAG, "failed to show passkey account keychain notification", e);
}
return;
}

if (!isFindHubProvisioningIntent(intent)) {
return;
}

try {
GmsCompatApp.iGms2Gca()
.maybeShowGmsCoreMissingFindHubAccountKeychainPermissionNotification();
} catch (RemoteException e) {
Log.w(TAG, "failed to show Find Hub account keychain notification", e);
}
}

private static final String TYPE_PUBLIC_KEY_CREDENTIAL =
"androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL";
private static final String FIDO_REGISTRATION_ACTIVITY =
"com.google.android.gms.auth.api.credentials.fido.registration.ui."
+ "RegistrationActivity";
private static final String FIDO_AUTHENTICATION_ACTIVITY =
"com.google.android.gms.auth.api.credentials.fido.authentication.ui."
+ "AuthenticationActivity";
private static final String ACTION_FIDO_REGISTRATION =
"com.google.android.gms.auth.api.credentials.FIDO_REGISTRATION";
private static final String ACTION_FIDO_AUTHENTICATION =
"com.google.android.gms.auth.api.credentials.FIDO_AUTHENTICATION";
private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
private static final String FIND_HUB_PROVISION_ACTIVITY =
"com.google.android.gms.findmydevice.spot.fastpair.halfsheet.ProvisionActivity";
private static final String FAST_PAIR_HALF_SHEET_ACTIVITY =
"com.google.android.gms.nearby.discovery.fastpair.HalfSheetActivity";
private static final String EXTRA_HALF_SHEET_TYPE =
"com.google.android.gms.nearby.discovery.fastpair.EXTRA_HALF_SHEET_TYPE";
private static final String HALF_SHEET_TYPE_SPOT = "SPOT";

private static boolean hasGoogleAccount() {
try {
return AccountManager.get(GmsCompat.appContext())
.getAccountsByType(GOOGLE_ACCOUNT_TYPE).length != 0;
} catch (RuntimeException e) {
// Skip the optional notification if the account query fails.
Log.w(TAG, "failed to query Google accounts", e);
return false;
}
}

private static boolean isPublicKeyCredentialRequest(@Nullable Intent intent) {
if (intent == null) {
return false;
}

if (isGmsCoreFidoIntent(intent)) {
return true;
}

try {
CreateCredentialRequest createRequest = intent.getParcelableExtra(
CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
CreateCredentialRequest.class);
if (createRequest != null
&& TYPE_PUBLIC_KEY_CREDENTIAL.equals(createRequest.getType())) {
return true;
}

GetCredentialRequest getRequest = intent.getParcelableExtra(
CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
GetCredentialRequest.class);
if (getRequest != null) {
for (CredentialOption option : getRequest.getCredentialOptions()) {
if (option != null
&& TYPE_PUBLIC_KEY_CREDENTIAL.equals(option.getType())) {
return true;
}
}
}
} catch (BadParcelableException e) {
Log.w(TAG, "failed to inspect Credential Manager request", e);
}

return false;
}

private static boolean isGmsCoreFidoIntent(Intent intent) {
ComponentName component = intent.getComponent();
if (component == null || !PACKAGE_GMS_CORE.equals(component.getPackageName())) {
return false;
}

String className = component.getClassName();
String action = intent.getAction();
return (FIDO_REGISTRATION_ACTIVITY.equals(className)
&& ACTION_FIDO_REGISTRATION.equals(action))
|| (FIDO_AUTHENTICATION_ACTIVITY.equals(className)
&& ACTION_FIDO_AUTHENTICATION.equals(action));
}

private static boolean isFindHubProvisioningIntent(@Nullable Intent intent) {
if (intent == null) {
return false;
}

ComponentName component = intent.getComponent();
if (component == null || !PACKAGE_GMS_CORE.equals(component.getPackageName())) {
return false;
}

String className = component.getClassName();
if (FIND_HUB_PROVISION_ACTIVITY.equals(className)) {
return true;
}

if (!FAST_PAIR_HALF_SHEET_ACTIVITY.equals(className)) {
return false;
}

try {
return HALF_SHEET_TYPE_SPOT.equals(intent.getStringExtra(EXTRA_HALF_SHEET_TYPE));
} catch (BadParcelableException e) {
Log.w(TAG, "failed to inspect Fast Pair half sheet type", e);
return false;
}
}

// ContentResolver#insert(Uri, ContentValues, Bundle)
Expand Down
4 changes: 4 additions & 0 deletions core/java/com/android/internal/gmscompat/IGms2Gca.aidl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ interface IGms2Gca {

oneway void showGmsCoreMissingNearbyDevicesPermissionGeneric();

oneway void maybeShowGmsCoreMissingRecoverableKeystorePermissionNotification();

oneway void maybeShowGmsCoreMissingFindHubAccountKeychainPermissionNotification();

oneway void showMissingPostNotifsPermissionNotification(String callerPkg);

oneway void maybeShowContactsSyncNotification();
Expand Down
4 changes: 4 additions & 0 deletions core/res/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7959,6 +7959,10 @@
<permission android:name="android.permission.RECOVER_KEYSTORE"
android:protectionLevel="signature|privileged" />

<!-- @SystemApi @hide -->
<permission android:name="android.permission.RECOVER_KEYSTORE_GMSCORE"
android:protectionLevel="internal" />

<!-- Allows a package to launch the secure full-backup confirmation UI.
ONLY the system process may hold this permission.
@hide -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ public abstract class LockSettingsInternal {
*/
public abstract void removeUser(@UserIdInt int userId);

/**
* Removes recoverable keystore state for the given recovery agent.
*
* @param userId the ID of the user whose state is being removed
* @param uid the UID of the recovery agent
*/
public abstract void removeRecoverableKeystoreStateForRecoveryAgent(
@UserIdInt int userId, int uid);

/**
* Create an escrow token for the current user, which can later be used to unlock FBE
* or change user password.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4358,6 +4358,13 @@ public void removeUser(@UserIdInt int userId) {
LockSettingsService.this.removeUser(userId);
}

@Override
public void removeRecoverableKeystoreStateForRecoveryAgent(
@UserIdInt int userId, int uid) {
mRecoverableKeyStoreManager.removeRecoverableKeystoreStateForRecoveryAgent(
userId, uid);
}

@Override
public long addEscrowToken(byte[] token, int userId,
EscrowTokenStateChangeCallback callback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.Enumeration;

/**
* Proxies {@link java.security.KeyStore}. As all of its methods are final, it cannot otherwise be
Expand All @@ -43,4 +44,7 @@ void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter p

/** @see KeyStore#deleteEntry(String) */
void deleteEntry(String alias) throws KeyStoreException;

/** @see KeyStore#aliases() */
Enumeration<String> aliases() throws KeyStoreException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Enumeration;

/**
* Implementation of {@link KeyStoreProxy} that delegates all method calls to the {@link KeyStore}.
Expand Down Expand Up @@ -62,6 +63,11 @@ public void deleteEntry(String alias) throws KeyStoreException {
mKeyStore.deleteEntry(alias);
}

@Override
public Enumeration<String> aliases() throws KeyStoreException {
return mKeyStore.aliases();
}

/**
* Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked
* {@link KeyStore#load(KeyStore.LoadStoreParameter)}.
Expand Down
Loading