Skip to content
Open
1 change: 0 additions & 1 deletion core/api/system-current.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12845,7 +12845,6 @@ package android.permission {
method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, long, int, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void stopOneTimePermissionSession(@NonNull String);
method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public void updatePermissionFlags(@NonNull String, @NonNull String, @NonNull String, int, int);
method public void updatePermissionState(@NonNull String, int);
field @RequiresPermission(android.Manifest.permission.START_REVIEW_PERMISSION_DECISIONS) public static final String ACTION_REVIEW_PERMISSION_DECISIONS = "android.permission.action.REVIEW_PERMISSION_DECISIONS";
field public static final String EXTRA_PERMISSION_USAGES = "android.permission.extra.PERMISSION_USAGES";
field public static final int PERMISSION_GRANTED = 0; // 0x0
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: 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
2 changes: 1 addition & 1 deletion core/java/android/permission/IPermissionManager.aidl
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ interface IPermissionManager {

int getPermissionRequestState(String packageName, String permissionName, int deviceId);

void updatePermissionState(String packageName, int userId);
void updatePermissionStateAndInvalidateCache(String packageName, int userId);
}

/**
Expand Down
5 changes: 3 additions & 2 deletions core/java/android/permission/PermissionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -2283,9 +2283,10 @@ public String toString() {
}
}

public void updatePermissionState(@NonNull String packageName, int userId) {
/** @hide */
public void updatePermissionStateAndInvalidateCache(@NonNull String packageName, int userId) {
try {
mPermissionManager.updatePermissionState(packageName, userId);
mPermissionManager.updatePermissionStateAndInvalidateCache(packageName, userId);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
Expand Down
52 changes: 52 additions & 0 deletions core/java/com/android/internal/gmscompat/GmsHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,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 Down Expand Up @@ -672,6 +674,56 @@ public static void onEndGmsServiceBrokerCall() {
GmcPackageManager.notifyPermissionsChangeListeners();
}

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

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
IGmsCompatLib lib = GmsCompatLib.get();
final String policyId = lib.parseVerifyPhoneNumberRequestForPolicy(data);
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. However, this is set as an "API param", so it's not as backwards
// compatible or guaranteed as the String property in the VerifyPhoneNumberRequest
// class
GmsCompatApp.iGms2Gca()
.maybeShowRcsRequirementsNotification(isTs43Verification);
}
} 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);
}
20 changes: 20 additions & 0 deletions core/java/com/android/internal/gmscompat/IGmsCompatLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import android.content.ServiceConnection;
import android.os.BinderProxy;
import android.os.IInterface;
import android.os.Parcel;
import android.os.UserHandle;

import java.io.FileDescriptor;
import java.text.ParseException;
import java.util.concurrent.Executor;

public interface IGmsCompatLib {
Expand All @@ -26,4 +28,22 @@ public interface IGmsCompatLib {
/** @see BinderProxy#dump(FileDescriptor, String[])
* @see BinderProxy#dumpAsync(FileDescriptor, String[]) */
boolean maybeInterceptBinderProxyDump(BinderProxy binderProxy, FileDescriptor fd, String[] args, boolean async);

/**
* Parses a {@code com.google.android.gms.constellation.VerifyPhoneNumberRequest} and returns
* its policy id.
* <p>
* This is not a replacement for {@link Parcel#readTypedObject}, so a
* {@link Parcel#readInt} == 1 check should be done beforehand if {@code data} is from a
* transaction. This is more of a replacement for direct {@code
* VerifyPhoneNumberRequest.CREATOR} usage.
* <p>
* If any exceptions are thrown (exceptions from {@link Parcel} reading), the data position will
* be in an undefined position.
*
* @param data The data where the {@code VerifyPhoneNumberRequest} is in. Initial data position
* should be at the header of the {@code VerifyPhoneNumberRequest}.
*/
@Nullable
String parseVerifyPhoneNumberRequestForPolicy(Parcel data) throws ParseException;
}
4 changes: 4 additions & 0 deletions core/res/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6620,6 +6620,10 @@
<permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS"
android:protectionLevel="signature|installer|verifier" />

<!--- @hide -->
<permission android:name="android.permission.UPDATE_AND_INVALIDATE_PERMISSION_STATE"
android:protectionLevel="internal|preinstalled" />

<!-- @SystemApi Allows an application to revoke specific permissions.
@hide -->
<permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS"
Expand Down
2 changes: 2 additions & 0 deletions packages/Shell/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,8 @@
<!-- Permission required for CTS test - CtsMediaProjectionTestCases -->
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />

<!-- Permission required for GrapheneOS package state updates -->
<uses-permission android:name="android.permission.UPDATE_AND_INVALIDATE_PERMISSION_STATE" />

<application
android:label="@string/app_label"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ static void init(PackageManagerService pm) {
builder()
.readFlags(playIntegrityFlags)
.readWriteFlag(SUPPRESS_PLAY_INTEGRITY_API_NOTIF)
.readWriteFields(FIELD_PACKAGE_FLAGS)
.apply(GmsCompatApp.PKG_NAME, computer);

@GosPackageStateFlag.Enum int[] settingsReadWriteFlags = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ static int runShellCommand(PackageManagerShellCommand cmd) {
return 1;
}
if (updatePermissionState) {
cmd.mPermissionManager.updatePermissionState(packageName, userId);
cmd.mPermissionManager.updatePermissionStateAndInvalidateCache(
packageName, userId);
}
return 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import android.annotation.SpecialUsers.CanBeALL;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.AttributionFlags;
import android.app.IActivityManager;
Expand Down Expand Up @@ -496,18 +497,67 @@ private List<String> getPackagesWithAutoRevokePolicy(int autoRevokePolicy, int u
}

@Override
public void updatePermissionState(String packageName, int userId) {
public void updatePermissionStateAndInvalidateCache(String packageName, int userId) {
mContext.enforceCallingPermission(
android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
"updatePermissionState");
android.Manifest.permission.UPDATE_AND_INVALIDATE_PERMISSION_STATE,
"updatePermissionStateAndInvalidateCache");

final int callingUid = Binder.getCallingUid();
if (UserHandle.getUserId(callingUid) != userId) {
final boolean isCallerAllowed = mContext.checkCallingPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL)
== PackageManager.PERMISSION_GRANTED;
if (!isCallerAllowed) {
throw new SecurityException(
"uid " + callingUid + " can't update state for " + userId);
}
Slog.w(LOG_TAG, "updatePermissionStateAndInvalidateCache: cross user granted for " + callingUid);
}

final String callingPackage;
{
final AndroidPackage callingAndroidPackage = mPackageManagerInt.getPackage(callingUid);
if (callingAndroidPackage == null) {
Slog.w(LOG_TAG, "updatePermissionStateAndInvalidateCache: no callingPackage "
+ "for uid " + callingUid);
return;
}
callingPackage = callingAndroidPackage.getPackageName();
}

PackageState packageState = mPackageManagerInt.getPackageStateInternal(packageName);
if (packageState == null) {
Slog.w(LOG_TAG, "updatePermissionState: no PackageState for " + packageName);
Slog.w(LOG_TAG, "updatePermissionStateAndInvalidateCache: no PackageState "
+ "for " + packageName);
return;
}

mPermissionManagerServiceImpl.updatePermissions(packageState, userId);

final var pm = AppGlobals.getPackageManager();

final long identity = Binder.clearCallingIdentity();
try {
final var appInfo = pm.getApplicationInfo(packageName, 0, userId);
if (appInfo == null) {
Slog.w(LOG_TAG, "updatePermissionStateAndInvalidateCache: no "
+ "appInfo for " + packageName);
return;
}

if (appInfo.enabled) {
// Invalidate cached system_server state; clearing identity, since this requires
// Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE
pm.setApplicationEnabledSetting(packageName,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0, userId, callingPackage);
pm.setApplicationEnabledSetting(packageName,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userId, callingPackage);
}
} catch (RemoteException e) {
Slog.e(LOG_TAG, "updatePermissionStateAndInvalidateCache: failed "
+ "for " + packageName, e);
} finally {
Binder.restoreCallingIdentity(identity);
}
}

/* Start of delegate methods to PermissionManagerServiceInterface */
Expand Down