diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 1e6f13b34a0fb..5aec7caaa5ae6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -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 diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 395fe57904ac1..af1dc485695a6 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -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)) { diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 92e4b8f3e209a..615364d2e66ff 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -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. @@ -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. diff --git a/core/java/android/permission/IPermissionManager.aidl b/core/java/android/permission/IPermissionManager.aidl index fd7b3522d028f..c37c4e0a739d6 100644 --- a/core/java/android/permission/IPermissionManager.aidl +++ b/core/java/android/permission/IPermissionManager.aidl @@ -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); } /** diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index f13720c699eb5..30719c31c8aa4 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -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(); } diff --git a/core/java/com/android/internal/gmscompat/GmsHooks.java b/core/java/com/android/internal/gmscompat/GmsHooks.java index 98681d0299e64..3fce37b1116cb 100644 --- a/core/java/com/android/internal/gmscompat/GmsHooks.java +++ b/core/java/com/android/internal/gmscompat/GmsHooks.java @@ -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; @@ -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) { diff --git a/core/java/com/android/internal/gmscompat/IGms2Gca.aidl b/core/java/com/android/internal/gmscompat/IGms2Gca.aidl index a6aa9f35aea3b..c4a30840c1f48 100644 --- a/core/java/com/android/internal/gmscompat/IGms2Gca.aidl +++ b/core/java/com/android/internal/gmscompat/IGms2Gca.aidl @@ -47,4 +47,6 @@ interface IGms2Gca { Notification getMediaProjectionNotification(); void raisePackageToForeground(String targetPkg, long durationMs, @nullable String reason, int reasonCode); + + oneway void maybeShowRcsRequirementsNotification(boolean isTs43Verification); } diff --git a/core/java/com/android/internal/gmscompat/IGmsCompatLib.java b/core/java/com/android/internal/gmscompat/IGmsCompatLib.java index 556eaeaefc63c..837189b72aafd 100644 --- a/core/java/com/android/internal/gmscompat/IGmsCompatLib.java +++ b/core/java/com/android/internal/gmscompat/IGmsCompatLib.java @@ -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 { @@ -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. + *

+ * 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. + *

+ * 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; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 89a74baa9ed6b..0c55f4a0629d1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -6620,6 +6620,10 @@ + + + + + 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 */