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
2 changes: 2 additions & 0 deletions core/api/system-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5300,6 +5300,8 @@ package android.ext {
public interface PackageId {
field public static final int ANDROID_AUTO = 10; // 0xa
field public static final String ANDROID_AUTO_NAME = "com.google.android.projection.gearhead";
field public static final int BUGLE = 14; // 0xe
field public static final String BUGLE_NAME = "com.google.android.apps.messaging";
field public static final int EUICC_SUPPORT_PIXEL = 5; // 0x5
field public static final String EUICC_SUPPORT_PIXEL_NAME = "com.google.euiccpixel";
field public static final int GMS_CORE = 2; // 0x2
Expand Down
5 changes: 5 additions & 0 deletions core/java/android/app/ContextImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2610,6 +2610,11 @@ public int checkPermission(String permission, int pid, int uid) {
throw new IllegalArgumentException("permission is null");
}

// TODO: In Android 17, READ_PRIVILEGED_PHONE_STATE will no longer be accepted for
// TelephonyManager#getIccAuthentication. However, Manifest.permission.USE_ICC_AUTH was
// introduced. getIccAuthentication can accept this new permission along with preexisting
// Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER. Need to pay attention to how
// GmsCore uses USE_ICC_AUTH or USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER.
if (GmsCompat.isGmsCore() &&
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE.equals(permission)) {
if (Log.isLoggable(TAG_SPOOF, Log.VERBOSE)) {
Expand Down
4 changes: 2 additions & 2 deletions core/java/android/ext/PackageId.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ public interface PackageId {
String PIXEL_HEALTH_NAME = "com.google.android.apps.pixel.health";
int PIXEL_HEALTH = 13;

/** @hide */ String BUGLE_NAME = "com.google.android.apps.messaging";
/** @hide */ int BUGLE = 14;
String BUGLE_NAME = "com.google.android.apps.messaging";
int BUGLE = 14;

/** @hide */ String SYSTEM_KEYBOARD = "com.android.inputmethod.latin";
}
4 changes: 4 additions & 0 deletions core/java/android/os/Binder.java
Original file line number Diff line number Diff line change
Expand Up @@ -769,11 +769,13 @@ public void attachInterface(@Nullable IInterface owner, @Nullable String descrip

if (GmsCompat.isGmsCore()) {
mIsGmsServiceBroker = GmsHooks.GMS_SERVICE_BROKER_INTERFACE_DESCRIPTOR.equals(descriptor);
mIsGmsConstellationService = GmsHooks.GMS_CONSTELLATION_SERVICE_INTERFACE_DESCRIPTOR.equals(descriptor);
}
}

private boolean mIsIGmsCallbacks;
private boolean mIsGmsServiceBroker;
private boolean mIsGmsConstellationService;

/**
* Default implementation returns an empty interface name.
Expand Down Expand Up @@ -1496,6 +1498,8 @@ private boolean execTransactInternal(int code, Parcel data, Parcel reply, int fl
try {
if (mIsGmsServiceBroker) {
onBeginGmsServiceBrokerCallRet = GmsHooks.onBeginGmsServiceBrokerCall(code, data);
} else if (mIsGmsConstellationService) {
GmsHooks.onBeginGmsConstellationServiceCall(code, data);
}
// TODO(b/299356201) - this logic should not be in Java - it should be in native
// code in libbinder so that it works for all binder users.
Expand Down
127 changes: 127 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.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.Activity;
Expand All @@ -37,7 +38,9 @@
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteOpenHelper;
import android.ext.PackageId;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.DeadSystemRuntimeException;
import android.os.IBinder;
Expand All @@ -62,6 +65,7 @@
import com.android.internal.gmscompat.gcarriersettings.TestCarrierConfigService;
import com.android.internal.gmscompat.sysservice.GmcPackageManager;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -672,6 +676,129 @@ public static void onEndGmsServiceBrokerCall() {
GmcPackageManager.notifyPermissionsChangeListeners();
}

public static final String GMS_CONSTELLATION_SERVICE_INTERFACE_DESCRIPTOR =
"com.google.android.gms.constellation.internal.IConstellationApiService";

/** Imported from android.app.appsearch.safeparcel.SafeParcelReader */
private static class SafeParcelReader {
private static void throwParseException(
@NonNull String message, @NonNull Parcel p) throws ParseException {
throw new ParseException(message
+ " Parcel: pos=" + p.dataPosition() + " size=" + p.dataSize(), p.dataPosition());
}

private static final int OBJECT_HEADER = 0x00004f45;

public static int readHeader(@NonNull Parcel p) {
return p.readInt();
}

public static int getFieldId(int header) {
return header & 0x0000ffff;
}

public static int readSize(@NonNull Parcel p, int header) {
if ((header & 0xffff0000) != 0xffff0000) {
return (header >> 16) & 0x0000ffff;
} else {
return p.readInt();
}
}

/** Skips the unknown field. */
public static void skipUnknownField(@NonNull Parcel p, int header) {
int size = readSize(p, header);
p.setDataPosition(p.dataPosition() + size);
}

/**
* Returns the end position of the object in the parcel.
*/
public static int validateObjectHeader(@NonNull Parcel p) throws ParseException {
final int header = readHeader(p);
final int size = readSize(p, header);
final int start = p.dataPosition();
if (getFieldId(header) != OBJECT_HEADER) {
throwParseException(
"Expected object header. Got 0x" + Integer.toHexString(header), p);
}
final int end = start + size;
if (end < start || end > p.dataSize()) {
throwParseException("Size read is invalid start=" + start + " end=" + end, p);
}
return end;
}

@Nullable
public static String createString(@NonNull Parcel p, int header) {
final int size = readSize(p, header);
final int pos = p.dataPosition();
if (size == 0) {
return null;
}
final String result = p.readString();
p.setDataPosition(pos + size);
return result;
}
}

public static void onBeginGmsConstellationServiceCall(int transactionCode, Parcel data) {
if (transactionCode != 3) { // verifyPhoneNumber V2 method
return;
}

try {
final var ctx = GmsCompat.appContext();
if (ctx == null) return;
final var callingPkg = ctx.getPackageManager().getNameForUid(Binder.getCallingUid());
// Ensure we're only sending RCS permission notifications for Bugle phone number
// verification attempts.
//
// GmsServiceBroker also does validation of allowed packages, but it doesn't seem it's
// restricted to only Bugle. For the Constellation service (155), the
// VerifyPhoneNumberApi__packages_allowed_to_call flag (proto list) also includes other
// apps like com.google.android.dialer, etc. in its default value.
if (!PackageId.BUGLE_NAME.equals(callingPkg)) {
Log.d(TAG, "onBeginGmsConstellationServiceCall code " + transactionCode + ", unexpected callingPkg " + callingPkg);
return;
}

data.enforceInterface(GMS_CONSTELLATION_SERVICE_INTERFACE_DESCRIPTOR);
// IConstellationCallbacks binder
data.readStrongBinder();

if (data.readInt() == 1) { // VerifyPhoneNumberRequest is present
// Note: VerifyPhoneNumberRequest may contain sensitive information (IMSIs), but
// we're only reading the policy ID.
final int end = SafeParcelReader.validateObjectHeader(data);
while (data.dataPosition() < end) {
final int fieldHeader = SafeParcelReader.readHeader(data);
if (SafeParcelReader.getFieldId(fieldHeader) == 0x1) { // policyId field
// Known TS43 UPI policies as of 20250119:
// upi-carrier-tos-ts43 and upi-ts43-only
final String policyId = SafeParcelReader.createString(data, fieldHeader);
final boolean isTs43Verification = policyId != null &&
policyId.startsWith("upi-") &&
policyId.contains("ts43");
Log.d(TAG, "onBeginGmsConstellationServiceCall: policyId " + policyId);
// We could also decode the Bundle field in VerifyPhoneNumberRequest which
// stores the key-value "required_consumer_consent" -> "RCS", and check
// for this.
GmsCompatApp.iGms2Gca()
.maybeShowRcsRequirementsNotification(isTs43Verification);
return;
} else {
SafeParcelReader.skipUnknownField(data, fieldHeader);
}
}
}
} catch (Exception e) {
Log.e(TAG, "onBeginGmsConstellationServiceCall: failed", e);
} finally {
data.setDataPosition(0);
}
}

public static IBinder maybeOverrideBinder(IBinder binder) {
boolean proceed = GmsCompat.isEnabled() || GmsCompat.isClientOfGmsCore();
if (!proceed) {
Expand Down
2 changes: 2 additions & 0 deletions core/java/com/android/internal/gmscompat/IGms2Gca.aidl
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,6 @@ interface IGms2Gca {
Notification getMediaProjectionNotification();

void raisePackageToForeground(String targetPkg, long durationMs, @nullable String reason, int reasonCode);

oneway void maybeShowRcsRequirementsNotification(boolean isTs43Verification);
}