Loading core/java/android/telephony/PhoneStateListener.java +38 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.ChangeId; import android.compat.annotation.UnsupportedAppUsage; import android.os.Binder; import android.os.Build; Loading Loading @@ -64,6 +65,43 @@ public class PhoneStateListener { private static final String LOG_TAG = "PhoneStateListener"; private static final boolean DBG = false; // STOPSHIP if true /** * Experiment flag to set the per-pid registration limit for PhoneStateListeners * * Limit on registrations of {@link PhoneStateListener}s on a per-pid * basis. When this limit is exceeded, any calls to {@link TelephonyManager#listen} will fail * with an {@link IllegalStateException}. * * {@link android.os.Process#PHONE_UID}, {@link android.os.Process#SYSTEM_UID}, and the uid that * TelephonyRegistry runs under are exempt from this limit. * * If the value of the flag is less than 1, enforcement of the limit will be disabled. * @hide */ public static final String FLAG_PER_PID_REGISTRATION_LIMIT = "phone_state_listener_per_pid_registration_limit"; /** * Default value for the per-pid registation limit. * See {@link #FLAG_PER_PID_REGISTRATION_LIMIT}. * @hide */ public static final int DEFAULT_PER_PID_REGISTRATION_LIMIT = 50; /** * This change enables a limit on the number of {@link PhoneStateListener} objects any process * may register via {@link TelephonyManager#listen}. The default limit is 50, which may change * via remote device config updates. * * This limit is enforced via an {@link IllegalStateException} thrown from * {@link TelephonyManager#listen} when the offending process attempts to register one too many * listeners. * * @hide */ @ChangeId public static final long PHONE_STATE_LISTENER_LIMIT_CHANGE_ID = 150880553L; /** * Stop listening for updates. * Loading services/core/java/com/android/server/TelephonyRegistry.java +69 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading @@ -39,8 +40,10 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.telephony.Annotation; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.DataFailureCause; Loading Loading @@ -177,8 +180,38 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } /** * Wrapper class to facilitate testing -- encapsulates bits of configuration that are * normally fetched from static methods with many dependencies. */ public static class ConfigurationProvider { /** * @return The per-pid registration limit for PhoneStateListeners, as set from DeviceConfig * @noinspection ConstantConditions */ public int getRegistrationLimit() { return Binder.withCleanCallingIdentity(() -> DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY, PhoneStateListener.FLAG_PER_PID_REGISTRATION_LIMIT, PhoneStateListener.DEFAULT_PER_PID_REGISTRATION_LIMIT)); } /** * @param uid uid to check * @return Whether enforcement of the per-pid registation limit for PhoneStateListeners is * enabled in PlatformCompat for the given uid. * @noinspection ConstantConditions */ public boolean isRegistrationLimitEnabledInPlatformCompat(int uid) { return Binder.withCleanCallingIdentity(() -> CompatChanges.isChangeEnabled( PhoneStateListener.PHONE_STATE_LISTENER_LIMIT_CHANGE_ID, uid)); } } private final Context mContext; private ConfigurationProvider mConfigurationProvider; // access should be inside synchronized (mRecords) for these two fields private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>(); private final ArrayList<Record> mRecords = new ArrayList<Record>(); Loading Loading @@ -506,10 +539,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { // handler before they get to app code. @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public TelephonyRegistry(Context context) { public TelephonyRegistry(Context context, ConfigurationProvider configurationProvider) { CellLocation location = CellLocation.getEmpty(); mContext = context; mConfigurationProvider = configurationProvider; mBatteryStats = BatteryStatsService.getService(); int numPhones = getTelephonyManager().getActiveModemCount(); Loading Loading @@ -605,7 +639,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { // register IBinder b = callback.asBinder(); Record r = add(b); Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), false); if (r == null) { return; Loading Loading @@ -659,7 +693,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { // register IBinder b = callback.asBinder(); Record r = add(b); Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), false); if (r == null) { return; Loading Loading @@ -789,7 +823,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { // register IBinder b = callback.asBinder(); Record r = add(b); boolean doesLimitApply = Binder.getCallingUid() != Process.SYSTEM_UID && Binder.getCallingUid() != Process.PHONE_UID && Binder.getCallingUid() != Process.myUid(); Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply); if (r == null) { return; Loading Loading @@ -1084,18 +1122,44 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return record.canReadCallLog() ? mCallIncomingNumber[phoneId] : ""; } private Record add(IBinder binder) { private Record add(IBinder binder, int callingUid, int callingPid, boolean doesLimitApply) { Record r; synchronized (mRecords) { final int N = mRecords.size(); // While iterating through the records, keep track of how many we have from this pid. int numRecordsForPid = 0; for (int i = 0; i < N; i++) { r = mRecords.get(i); if (binder == r.binder) { // Already existed. return r; } if (r.callerPid == callingPid) { numRecordsForPid++; } } // If we've exceeded the limit for registrations, log an error and quit. int registrationLimit = mConfigurationProvider.getRegistrationLimit(); if (doesLimitApply && registrationLimit >= 1 && numRecordsForPid >= registrationLimit) { String errorMsg = "Pid " + callingPid + " has exceeded the number of permissible" + "registered listeners. Ignoring request to add."; loge(errorMsg); if (mConfigurationProvider .isRegistrationLimitEnabledInPlatformCompat(callingUid)) { throw new IllegalStateException(errorMsg); } } else if (doesLimitApply && numRecordsForPid >= PhoneStateListener.DEFAULT_PER_PID_REGISTRATION_LIMIT / 2) { // Log the warning independently of the dynamically set limit -- apps shouldn't be // doing this regardless of whether we're throwing them an exception for it. Rlog.w(TAG, "Pid " + callingPid + " has exceeded half the number of permissible" + "registered listeners. Now at " + numRecordsForPid); } r = new Record(); r.binder = binder; r.deathRecipient = new TelephonyRegistryDeathRecipient(binder); Loading services/java/com/android/server/SystemServer.java +2 −1 Original line number Diff line number Diff line Loading @@ -1069,7 +1069,8 @@ public final class SystemServer { t.traceEnd(); t.traceBegin("StartTelephonyRegistry"); telephonyRegistry = new TelephonyRegistry(context); telephonyRegistry = new TelephonyRegistry( context, new TelephonyRegistry.ConfigurationProvider()); ServiceManager.addService("telephony.registry", telephonyRegistry); t.traceEnd(); Loading telephony/java/android/telephony/TelephonyManager.java +4 −0 Original line number Diff line number Diff line Loading @@ -5577,6 +5577,10 @@ public class TelephonyManager { * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A * {@link SecurityException} will be thrown otherwise. * * This API should be used sparingly -- large numbers of listeners will cause system * instability. If a process has registered too many listeners without unregistering them, it * may encounter an {@link IllegalStateException} when trying to register more listeners. * * @param listener The {@link PhoneStateListener} object to register * (or unregister) * @param events The telephony state(s) of interest to the listener, Loading tests/net/java/com/android/server/ConnectivityServiceTest.java +3 −0 Original line number Diff line number Diff line Loading @@ -205,6 +205,7 @@ import android.os.UserManager; import android.provider.Settings; import android.security.KeyStore; import android.system.Os; import android.telephony.TelephonyManager; import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; Loading Loading @@ -347,6 +348,7 @@ public class ConnectivityServiceTest { @Mock IBinder mIBinder; @Mock LocationManager mLocationManager; @Mock AppOpsManager mAppOpsManager; @Mock TelephonyManager mTelephonyManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); Loading Loading @@ -433,6 +435,7 @@ public class ConnectivityServiceTest { if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager; if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager; if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager; if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; return super.getSystemService(name); } Loading Loading
core/java/android/telephony/PhoneStateListener.java +38 −0 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.ChangeId; import android.compat.annotation.UnsupportedAppUsage; import android.os.Binder; import android.os.Build; Loading Loading @@ -64,6 +65,43 @@ public class PhoneStateListener { private static final String LOG_TAG = "PhoneStateListener"; private static final boolean DBG = false; // STOPSHIP if true /** * Experiment flag to set the per-pid registration limit for PhoneStateListeners * * Limit on registrations of {@link PhoneStateListener}s on a per-pid * basis. When this limit is exceeded, any calls to {@link TelephonyManager#listen} will fail * with an {@link IllegalStateException}. * * {@link android.os.Process#PHONE_UID}, {@link android.os.Process#SYSTEM_UID}, and the uid that * TelephonyRegistry runs under are exempt from this limit. * * If the value of the flag is less than 1, enforcement of the limit will be disabled. * @hide */ public static final String FLAG_PER_PID_REGISTRATION_LIMIT = "phone_state_listener_per_pid_registration_limit"; /** * Default value for the per-pid registation limit. * See {@link #FLAG_PER_PID_REGISTRATION_LIMIT}. * @hide */ public static final int DEFAULT_PER_PID_REGISTRATION_LIMIT = 50; /** * This change enables a limit on the number of {@link PhoneStateListener} objects any process * may register via {@link TelephonyManager#listen}. The default limit is 50, which may change * via remote device config updates. * * This limit is enforced via an {@link IllegalStateException} thrown from * {@link TelephonyManager#listen} when the offending process attempts to register one too many * listeners. * * @hide */ @ChangeId public static final long PHONE_STATE_LISTENER_LIMIT_CHANGE_ID = 150880553L; /** * Stop listening for updates. * Loading
services/core/java/com/android/server/TelephonyRegistry.java +69 −5 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading @@ -39,8 +40,10 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; import android.telephony.Annotation; import android.telephony.Annotation.ApnType; import android.telephony.Annotation.DataFailureCause; Loading Loading @@ -177,8 +180,38 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } } /** * Wrapper class to facilitate testing -- encapsulates bits of configuration that are * normally fetched from static methods with many dependencies. */ public static class ConfigurationProvider { /** * @return The per-pid registration limit for PhoneStateListeners, as set from DeviceConfig * @noinspection ConstantConditions */ public int getRegistrationLimit() { return Binder.withCleanCallingIdentity(() -> DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY, PhoneStateListener.FLAG_PER_PID_REGISTRATION_LIMIT, PhoneStateListener.DEFAULT_PER_PID_REGISTRATION_LIMIT)); } /** * @param uid uid to check * @return Whether enforcement of the per-pid registation limit for PhoneStateListeners is * enabled in PlatformCompat for the given uid. * @noinspection ConstantConditions */ public boolean isRegistrationLimitEnabledInPlatformCompat(int uid) { return Binder.withCleanCallingIdentity(() -> CompatChanges.isChangeEnabled( PhoneStateListener.PHONE_STATE_LISTENER_LIMIT_CHANGE_ID, uid)); } } private final Context mContext; private ConfigurationProvider mConfigurationProvider; // access should be inside synchronized (mRecords) for these two fields private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>(); private final ArrayList<Record> mRecords = new ArrayList<Record>(); Loading Loading @@ -506,10 +539,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { // handler before they get to app code. @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public TelephonyRegistry(Context context) { public TelephonyRegistry(Context context, ConfigurationProvider configurationProvider) { CellLocation location = CellLocation.getEmpty(); mContext = context; mConfigurationProvider = configurationProvider; mBatteryStats = BatteryStatsService.getService(); int numPhones = getTelephonyManager().getActiveModemCount(); Loading Loading @@ -605,7 +639,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { // register IBinder b = callback.asBinder(); Record r = add(b); Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), false); if (r == null) { return; Loading Loading @@ -659,7 +693,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { // register IBinder b = callback.asBinder(); Record r = add(b); Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), false); if (r == null) { return; Loading Loading @@ -789,7 +823,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { // register IBinder b = callback.asBinder(); Record r = add(b); boolean doesLimitApply = Binder.getCallingUid() != Process.SYSTEM_UID && Binder.getCallingUid() != Process.PHONE_UID && Binder.getCallingUid() != Process.myUid(); Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply); if (r == null) { return; Loading Loading @@ -1084,18 +1122,44 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return record.canReadCallLog() ? mCallIncomingNumber[phoneId] : ""; } private Record add(IBinder binder) { private Record add(IBinder binder, int callingUid, int callingPid, boolean doesLimitApply) { Record r; synchronized (mRecords) { final int N = mRecords.size(); // While iterating through the records, keep track of how many we have from this pid. int numRecordsForPid = 0; for (int i = 0; i < N; i++) { r = mRecords.get(i); if (binder == r.binder) { // Already existed. return r; } if (r.callerPid == callingPid) { numRecordsForPid++; } } // If we've exceeded the limit for registrations, log an error and quit. int registrationLimit = mConfigurationProvider.getRegistrationLimit(); if (doesLimitApply && registrationLimit >= 1 && numRecordsForPid >= registrationLimit) { String errorMsg = "Pid " + callingPid + " has exceeded the number of permissible" + "registered listeners. Ignoring request to add."; loge(errorMsg); if (mConfigurationProvider .isRegistrationLimitEnabledInPlatformCompat(callingUid)) { throw new IllegalStateException(errorMsg); } } else if (doesLimitApply && numRecordsForPid >= PhoneStateListener.DEFAULT_PER_PID_REGISTRATION_LIMIT / 2) { // Log the warning independently of the dynamically set limit -- apps shouldn't be // doing this regardless of whether we're throwing them an exception for it. Rlog.w(TAG, "Pid " + callingPid + " has exceeded half the number of permissible" + "registered listeners. Now at " + numRecordsForPid); } r = new Record(); r.binder = binder; r.deathRecipient = new TelephonyRegistryDeathRecipient(binder); Loading
services/java/com/android/server/SystemServer.java +2 −1 Original line number Diff line number Diff line Loading @@ -1069,7 +1069,8 @@ public final class SystemServer { t.traceEnd(); t.traceBegin("StartTelephonyRegistry"); telephonyRegistry = new TelephonyRegistry(context); telephonyRegistry = new TelephonyRegistry( context, new TelephonyRegistry.ConfigurationProvider()); ServiceManager.addService("telephony.registry", telephonyRegistry); t.traceEnd(); Loading
telephony/java/android/telephony/TelephonyManager.java +4 −0 Original line number Diff line number Diff line Loading @@ -5577,6 +5577,10 @@ public class TelephonyManager { * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A * {@link SecurityException} will be thrown otherwise. * * This API should be used sparingly -- large numbers of listeners will cause system * instability. If a process has registered too many listeners without unregistering them, it * may encounter an {@link IllegalStateException} when trying to register more listeners. * * @param listener The {@link PhoneStateListener} object to register * (or unregister) * @param events The telephony state(s) of interest to the listener, Loading
tests/net/java/com/android/server/ConnectivityServiceTest.java +3 −0 Original line number Diff line number Diff line Loading @@ -205,6 +205,7 @@ import android.os.UserManager; import android.provider.Settings; import android.security.KeyStore; import android.system.Os; import android.telephony.TelephonyManager; import android.test.mock.MockContentResolver; import android.text.TextUtils; import android.util.ArraySet; Loading Loading @@ -347,6 +348,7 @@ public class ConnectivityServiceTest { @Mock IBinder mIBinder; @Mock LocationManager mLocationManager; @Mock AppOpsManager mAppOpsManager; @Mock TelephonyManager mTelephonyManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); Loading Loading @@ -433,6 +435,7 @@ public class ConnectivityServiceTest { if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager; if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager; if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager; if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; return super.getSystemService(name); } Loading