diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index fa770a9583..5bab8e358d 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -17,6 +17,7 @@ package com.android.server; import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE; +import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN; import static android.content.pm.PackageManager.FEATURE_BLUETOOTH; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.FEATURE_WIFI; @@ -110,6 +111,8 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.app.ActivityManager.UidFrozenStateChangedCallback; import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.PendingIntent; @@ -786,6 +789,11 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_SET_LOW_TCP_POLLING_UNTIL = 60; + /** + * Event to inform the ConnectivityService handler when a uid has been frozen or unfrozen. + */ + private static final int EVENT_UID_FROZEN_STATE_CHANGED = 61; + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. @@ -1691,6 +1699,32 @@ public class ConnectivityService extends IConnectivityManager.Stub } else { mCdmps = null; } + + if (SdkLevel.isAtLeastU() + && mDeps.isFeatureEnabled(context, KEY_DESTROY_FROZEN_SOCKETS_VERSION)) { + final UidFrozenStateChangedCallback frozenStateChangedCallback = + new UidFrozenStateChangedCallback() { + @Override + public void onUidFrozenStateChanged(int[] uids, int[] frozenStates) { + if (uids.length != frozenStates.length) { + Log.wtf(TAG, "uids has length " + uids.length + + " but frozenStates has length " + frozenStates.length); + return; + } + + final UidFrozenStateChangedArgs args = + new UidFrozenStateChangedArgs(uids, frozenStates); + + mHandler.sendMessage( + mHandler.obtainMessage(EVENT_UID_FROZEN_STATE_CHANGED, args)); + } + }; + + final ActivityManager activityManager = + mContext.getSystemService(ActivityManager.class); + activityManager.registerUidFrozenStateChangedCallback( + (Runnable r) -> r.run(), frozenStateChangedCallback); + } } /** @@ -2859,6 +2893,39 @@ public class ConnectivityService extends IConnectivityManager.Stub setUidBlockedReasons(uid, blockedReasons); } + static final class UidFrozenStateChangedArgs { + final int[] mUids; + final int[] mFrozenStates; + + UidFrozenStateChangedArgs(int[] uids, int[] frozenStates) { + mUids = uids; + mFrozenStates = frozenStates; + } + } + + private void handleFrozenUids(int[] uids, int[] frozenStates) { + final ArraySet> ranges = new ArraySet<>(); + + for (int i = 0; i < uids.length; i++) { + if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) { + Integer uidAsInteger = Integer.valueOf(uids[i]); + ranges.add(new Range(uidAsInteger, uidAsInteger)); + } + } + + if (!ranges.isEmpty()) { + final Set exemptUids = new ArraySet<>(); + try { + mDeps.destroyLiveTcpSockets(ranges, exemptUids); + } catch (Exception e) { + loge("Exception in socket destroy: " + e); + } + } + } + + @VisibleForTesting + static final String KEY_DESTROY_FROZEN_SOCKETS_VERSION = "destroy_frozen_sockets_version"; + private void enforceInternetPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERNET, @@ -5722,6 +5789,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeepaliveTracker.handleSetTestLowTcpPollingTimer(time); break; } + case EVENT_UID_FROZEN_STATE_CHANGED: + UidFrozenStateChangedArgs args = (UidFrozenStateChangedArgs) msg.obj; + handleFrozenUids(args.mUids, args.mFrozenStates); + break; } } } diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index d1869208c5..a90aa0d4d3 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -30,6 +30,8 @@ import static android.Manifest.permission.NETWORK_SETTINGS; import static android.Manifest.permission.NETWORK_SETUP_WIZARD; import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD; +import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN; +import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; @@ -148,6 +150,7 @@ import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALID import static android.os.Process.INVALID_UID; import static android.system.OsConstants.IPPROTO_TCP; +import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION; import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID; import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED; import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM; @@ -224,6 +227,8 @@ import static java.util.Arrays.asList; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityManager.UidFrozenStateChangedCallback; import android.app.AlarmManager; import android.app.AppOpsManager; import android.app.BroadcastOptions; @@ -609,6 +614,7 @@ public class ConnectivityServiceTest { @Mock CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator; @Mock TetheringManager mTetheringManager; @Mock BroadcastOptionsShim mBroadcastOptionsShim; + @Mock ActivityManager mActivityManager; // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the // underlying binder calls. @@ -732,6 +738,7 @@ public class ConnectivityServiceTest { if (Context.BATTERY_STATS_SERVICE.equals(name)) return mBatteryStatsManager; if (Context.PAC_PROXY_SERVICE.equals(name)) return mPacProxyManager; if (Context.TETHERING_SERVICE.equals(name)) return mTetheringManager; + if (Context.ACTIVITY_SERVICE.equals(name)) return mActivityManager; return super.getSystemService(name); } @@ -2081,6 +2088,8 @@ public class ConnectivityServiceTest { switch (name) { case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER: return true; + case KEY_DESTROY_FROZEN_SOCKETS_VERSION: + return true; default: return super.isFeatureEnabled(context, name); } @@ -17833,4 +17842,35 @@ public class ConnectivityServiceTest { verify(mMockNetd, never()).wakeupAddInterface(eq(ethernetIface), anyString(), anyInt(), anyInt()); } + + private static final int TEST_FROZEN_UID = 1000; + private static final int TEST_UNFROZEN_UID = 2000; + + /** + * Send a UidFrozenStateChanged message to ConnectivityService. Verify that only the frozen UID + * gets passed to socketDestroy(). + */ + @Test + @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) + public void testFrozenUidSocketDestroy() throws Exception { + ArgumentCaptor callbackArg = + ArgumentCaptor.forClass(UidFrozenStateChangedCallback.class); + + verify(mActivityManager).registerUidFrozenStateChangedCallback(any(), + callbackArg.capture()); + + final int[] uids = {TEST_FROZEN_UID, TEST_UNFROZEN_UID}; + final int[] frozenStates = {UID_FROZEN_STATE_FROZEN, UID_FROZEN_STATE_UNFROZEN}; + + callbackArg.getValue().onUidFrozenStateChanged(uids, frozenStates); + + waitForIdle(); + + final Set exemptUids = new ArraySet(); + final UidRange frozenUidRange = new UidRange(TEST_FROZEN_UID, TEST_FROZEN_UID); + final Set ranges = Collections.singleton(frozenUidRange); + + verify(mDeps).destroyLiveTcpSockets(eq(UidRange.toIntRanges(ranges)), + eq(exemptUids)); + } }