From c3621e6012aee4b3a838a4148a5f6ddf2ab0b511 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 3 Jul 2024 17:48:50 +0900 Subject: [PATCH 1/2] Add a CallbackQueue class The class will be used to build memory-efficient objects (int arrays) used to queue ConnectivityService callbacks while an app is frozen. This is very similar to an ArrayList>, but avoids object boxing which would use a lot more memory. This is intended to be used by ConnectivityService both as a builder, and as a utility to read lists of callbacks that it stores. Long-lived references to the CallbackQueue would not be kept to minimize memory usage; only the array returned by toArray needs to be stored. Test: atest Bug: 327038794 Bug: 279392981 Change-Id: I187ea15d14dfd160e577e9e9e87b1aded27b1938 --- .../src/android/net/ConnectivityManager.java | 1 + .../src/com/android/server/CallbackQueue.java | 126 ++++++++++++ .../net/module/util/GrowingIntArray.java | 190 ++++++++++++++++++ .../net/module/util/GrowingIntArrayTest.kt | 129 ++++++++++++ .../com/android/server/CallbackQueueTest.kt | 181 +++++++++++++++++ 5 files changed, 627 insertions(+) create mode 100644 service/src/com/android/server/CallbackQueue.java create mode 100644 staticlibs/device/com/android/net/module/util/GrowingIntArray.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt create mode 100644 tests/unit/java/com/android/server/CallbackQueueTest.kt diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 5e41dd9c4f..a6a967b1a1 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -4489,6 +4489,7 @@ public class ConnectivityManager { public static final int CALLBACK_BLK_CHANGED = 11; /** @hide */ public static final int CALLBACK_LOCAL_NETWORK_INFO_CHANGED = 12; + // When adding new IDs, note CallbackQueue assumes callback IDs are at most 16 bits. /** @hide */ diff --git a/service/src/com/android/server/CallbackQueue.java b/service/src/com/android/server/CallbackQueue.java new file mode 100644 index 0000000000..060a984437 --- /dev/null +++ b/service/src/com/android/server/CallbackQueue.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.annotation.NonNull; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; + +import com.android.net.module.util.GrowingIntArray; + +/** + * A utility class to add/remove {@link NetworkCallback}s from a queue. + * + *

This is intended to be used as a temporary builder to create/modify callbacks stored in an int + * array for memory efficiency. + * + *

Intended usage: + *

+ *     final CallbackQueue queue = new CallbackQueue(storedCallbacks);
+ *     queue.forEach(netId, callbackId -> { [...] });
+ *     queue.addCallback(netId, callbackId);
+ *     [...]
+ *     queue.shrinkToLength();
+ *     storedCallbacks = queue.getBackingArray();
+ * 
+ * + *

This class is not thread-safe. + */ +public class CallbackQueue extends GrowingIntArray { + public CallbackQueue(int[] initialCallbacks) { + super(initialCallbacks); + } + + /** + * Get a callback int from netId and callbackId. + * + *

The first 16 bits of each int is the netId; the last 16 bits are the callback index. + */ + private static int getCallbackInt(int netId, int callbackId) { + return (netId << 16) | (callbackId & 0xffff); + } + + private static int getNetId(int callbackInt) { + return callbackInt >>> 16; + } + + private static int getCallbackId(int callbackInt) { + return callbackInt & 0xffff; + } + + /** + * A consumer interface for {@link #forEach(CallbackConsumer)}. + * + *

This is similar to a BiConsumer<Integer, Integer>, but avoids the boxing cost. + */ + public interface CallbackConsumer { + /** + * Method called on each callback in the queue. + */ + void accept(int netId, int callbackId); + } + + /** + * Iterate over all callbacks in the queue. + */ + public void forEach(@NonNull CallbackConsumer consumer) { + forEach(value -> { + final int netId = getNetId(value); + final int callbackId = getCallbackId(value); + consumer.accept(netId, callbackId); + }); + } + + /** + * Indicates whether the queue contains a callback for the given (netId, callbackId). + */ + public boolean hasCallback(int netId, int callbackId) { + return contains(getCallbackInt(netId, callbackId)); + } + + /** + * Remove all callbacks for the given netId. + * + * @return true if at least one callback was removed. + */ + public boolean removeCallbacksForNetId(int netId) { + return removeValues(cb -> getNetId(cb) == netId); + } + + /** + * Remove all callbacks for the given netId and callbackId. + * @return true if at least one callback was removed. + */ + public boolean removeCallbacks(int netId, int callbackId) { + final int cbInt = getCallbackInt(netId, callbackId); + return removeValues(cb -> cb == cbInt); + } + + /** + * Add a callback at the end of the queue. + */ + public void addCallback(int netId, int callbackId) { + add(getCallbackInt(netId, callbackId)); + } + + @Override + protected String valueToString(int item) { + final int callbackId = getCallbackId(item); + final int netId = getNetId(item); + return ConnectivityManager.getCallbackName(callbackId) + "(" + netId + ")"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/GrowingIntArray.java b/staticlibs/device/com/android/net/module/util/GrowingIntArray.java new file mode 100644 index 0000000000..4a81c10f82 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/GrowingIntArray.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util; + +import android.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Arrays; +import java.util.StringJoiner; +import java.util.function.IntConsumer; +import java.util.function.IntPredicate; + +/** + * A growing array of primitive ints. + * + *

This is similar to ArrayList<Integer>, but avoids the cost of boxing (each Integer costs + * 16 bytes) and creation / garbage collection of individual Integer objects. + * + *

This class does not use any heuristic for growing capacity, so every call to + * {@link #add(int)} may reallocate the backing array. Callers should use + * {@link #ensureHasCapacity(int)} to minimize this behavior when they plan to add several values. + */ +public class GrowingIntArray { + private int[] mValues; + private int mLength; + + /** + * Create an empty GrowingIntArray with the given capacity. + */ + public GrowingIntArray(int initialCapacity) { + mValues = new int[initialCapacity]; + mLength = 0; + } + + /** + * Create a GrowingIntArray with an initial array of values. + * + *

The array will be used as-is and may be modified, so callers must stop using it after + * calling this constructor. + */ + protected GrowingIntArray(int[] initialValues) { + mValues = initialValues; + mLength = initialValues.length; + } + + /** + * Add a value to the array. + */ + public void add(int value) { + ensureHasCapacity(1); + mValues[mLength] = value; + mLength++; + } + + /** + * Get the current number of values in the array. + */ + public int length() { + return mLength; + } + + /** + * Get the value at a given index. + * + * @throws ArrayIndexOutOfBoundsException if the index is out of bounds. + */ + public int get(int index) { + if (index < 0 || index >= mLength) { + throw new ArrayIndexOutOfBoundsException(index); + } + return mValues[index]; + } + + /** + * Iterate over all values in the array. + */ + public void forEach(@NonNull IntConsumer consumer) { + for (int i = 0; i < mLength; i++) { + consumer.accept(mValues[i]); + } + } + + /** + * Remove all values matching a predicate. + * + * @return true if at least one value was removed. + */ + public boolean removeValues(@NonNull IntPredicate predicate) { + int newQueueLength = 0; + for (int i = 0; i < mLength; i++) { + final int cb = mValues[i]; + if (!predicate.test(cb)) { + mValues[newQueueLength] = cb; + newQueueLength++; + } + } + if (mLength != newQueueLength) { + mLength = newQueueLength; + return true; + } + return false; + } + + /** + * Indicates whether the array contains the given value. + */ + public boolean contains(int value) { + for (int i = 0; i < mLength; i++) { + if (mValues[i] == value) { + return true; + } + } + return false; + } + + /** + * Remove all values from the array. + */ + public void clear() { + mLength = 0; + } + + /** + * Ensure at least the given number of values can be added to the array without reallocating. + * + * @param capacity The minimum number of additional values the array must be able to hold. + */ + public void ensureHasCapacity(int capacity) { + if (mValues.length >= mLength + capacity) { + return; + } + mValues = Arrays.copyOf(mValues, mLength + capacity); + } + + @VisibleForTesting + int getBackingArrayLength() { + return mValues.length; + } + + /** + * Shrink the array backing this class to the minimum required length. + */ + public void shrinkToLength() { + if (mValues.length != mLength) { + mValues = Arrays.copyOf(mValues, mLength); + } + } + + /** + * Get values as array by shrinking the internal array to length and returning it. + * + *

This avoids reallocations if the array is already the correct length, but callers should + * stop using this instance of {@link GrowingIntArray} if they use the array returned by this + * method. + */ + public int[] getShrinkedBackingArray() { + shrinkToLength(); + return mValues; + } + + /** + * Get the String representation of an item in the array, for use by {@link #toString()}. + */ + protected String valueToString(int item) { + return String.valueOf(item); + } + + @NonNull + @Override + public String toString() { + final StringJoiner joiner = new StringJoiner(",", "[", "]"); + forEach(item -> joiner.add(valueToString(item))); + return joiner.toString(); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt new file mode 100644 index 0000000000..bdcb8c0b59 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.fail +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class GrowingIntArrayTest { + @Test + fun testAddAndGet() { + val array = GrowingIntArray(1) + array.add(-1) + array.add(0) + array.add(2) + + assertEquals(-1, array.get(0)) + assertEquals(0, array.get(1)) + assertEquals(2, array.get(2)) + assertEquals(3, array.length()) + } + + @Test + fun testForEach() { + val array = GrowingIntArray(10) + array.add(-1) + array.add(0) + array.add(2) + + val actual = mutableListOf() + array.forEach { actual.add(it) } + + val expected = listOf(-1, 0, 2) + assertEquals(expected, actual) + } + + @Test + fun testForEach_EmptyArray() { + val array = GrowingIntArray(10) + array.forEach { + fail("This should not be called") + } + } + + @Test + fun testRemoveValues() { + val array = GrowingIntArray(10) + array.add(-1) + array.add(0) + array.add(2) + + array.removeValues { it <= 0 } + assertEquals(1, array.length()) + assertEquals(2, array.get(0)) + } + + @Test + fun testContains() { + val array = GrowingIntArray(10) + array.add(-1) + array.add(2) + + assertTrue(array.contains(-1)) + assertTrue(array.contains(2)) + + assertFalse(array.contains(0)) + assertFalse(array.contains(3)) + } + + @Test + fun testClear() { + val array = GrowingIntArray(10) + array.add(-1) + array.add(2) + array.clear() + + assertEquals(0, array.length()) + } + + @Test + fun testEnsureHasCapacity() { + val array = GrowingIntArray(0) + array.add(42) + array.ensureHasCapacity(2) + + assertEquals(3, array.backingArrayLength) + } + + @Test + fun testGetShrinkedBackingArray() { + val array = GrowingIntArray(10) + array.add(-1) + array.add(2) + + assertContentEquals(intArrayOf(-1, 2), array.shrinkedBackingArray) + } + + @Test + fun testToString() { + assertEquals("[]", GrowingIntArray(10).toString()) + assertEquals("[1,2,3]", GrowingIntArray(3).apply { + add(1) + add(2) + add(3) + }.toString()) + } +} diff --git a/tests/unit/java/com/android/server/CallbackQueueTest.kt b/tests/unit/java/com/android/server/CallbackQueueTest.kt new file mode 100644 index 0000000000..a6dd5c3561 --- /dev/null +++ b/tests/unit/java/com/android/server/CallbackQueueTest.kt @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server + +import android.net.ConnectivityManager +import android.net.ConnectivityManager.CALLBACK_AVAILABLE +import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED +import android.net.ConnectivityManager.CALLBACK_IP_CHANGED +import android.os.Build +import androidx.test.filters.SmallTest +import com.android.testutils.DevSdkIgnoreRule +import com.android.testutils.DevSdkIgnoreRunner +import java.lang.reflect.Modifier +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +private const val TEST_NETID_1 = 123 + +// Maximum 16 bits unsigned value +private const val TEST_NETID_2 = 0xffff + +@RunWith(DevSdkIgnoreRunner::class) +@SmallTest +@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) +class CallbackQueueTest { + @Test + fun testAddCallback() { + val cbs = listOf( + TEST_NETID_1 to CALLBACK_AVAILABLE, + TEST_NETID_2 to CALLBACK_AVAILABLE, + TEST_NETID_1 to CALLBACK_CAP_CHANGED, + TEST_NETID_1 to CALLBACK_CAP_CHANGED + ) + val queue = CallbackQueue(intArrayOf()).apply { + cbs.forEach { addCallback(it.first, it.second) } + } + + assertQueueEquals(cbs, queue) + } + + @Test + fun testHasCallback() { + val queue = CallbackQueue(intArrayOf()).apply { + addCallback(TEST_NETID_1, CALLBACK_AVAILABLE) + addCallback(TEST_NETID_2, CALLBACK_AVAILABLE) + addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED) + addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED) + } + + assertTrue(queue.hasCallback(TEST_NETID_1, CALLBACK_AVAILABLE)) + assertTrue(queue.hasCallback(TEST_NETID_2, CALLBACK_AVAILABLE)) + assertTrue(queue.hasCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED)) + + assertFalse(queue.hasCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED)) + assertFalse(queue.hasCallback(1234, CALLBACK_AVAILABLE)) + assertFalse(queue.hasCallback(TEST_NETID_1, 5678)) + assertFalse(queue.hasCallback(1234, 5678)) + } + + @Test + fun testRemoveCallbacks() { + val queue = CallbackQueue(intArrayOf()).apply { + assertFalse(removeCallbacks(TEST_NETID_1, CALLBACK_AVAILABLE)) + addCallback(TEST_NETID_1, CALLBACK_AVAILABLE) + addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED) + addCallback(TEST_NETID_2, CALLBACK_AVAILABLE) + addCallback(TEST_NETID_1, CALLBACK_AVAILABLE) + assertTrue(removeCallbacks(TEST_NETID_1, CALLBACK_AVAILABLE)) + } + assertQueueEquals(listOf( + TEST_NETID_1 to CALLBACK_CAP_CHANGED, + TEST_NETID_2 to CALLBACK_AVAILABLE + ), queue) + } + + @Test + fun testRemoveCallbacksForNetId() { + val queue = CallbackQueue(intArrayOf()).apply { + assertFalse(removeCallbacksForNetId(TEST_NETID_2)) + addCallback(TEST_NETID_2, CALLBACK_AVAILABLE) + assertFalse(removeCallbacksForNetId(TEST_NETID_1)) + addCallback(TEST_NETID_1, CALLBACK_AVAILABLE) + addCallback(TEST_NETID_1, CALLBACK_CAP_CHANGED) + addCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED) + addCallback(TEST_NETID_1, CALLBACK_AVAILABLE) + addCallback(TEST_NETID_2, CALLBACK_IP_CHANGED) + assertTrue(removeCallbacksForNetId(TEST_NETID_2)) + } + assertQueueEquals(listOf( + TEST_NETID_1 to CALLBACK_AVAILABLE, + TEST_NETID_1 to CALLBACK_CAP_CHANGED, + TEST_NETID_1 to CALLBACK_AVAILABLE, + ), queue) + } + + @Test + fun testConstructorFromExistingArray() { + val queue1 = CallbackQueue(intArrayOf()).apply { + addCallback(TEST_NETID_1, CALLBACK_AVAILABLE) + addCallback(TEST_NETID_2, CALLBACK_AVAILABLE) + } + val queue2 = CallbackQueue(queue1.shrinkedBackingArray) + assertQueueEquals(listOf( + TEST_NETID_1 to CALLBACK_AVAILABLE, + TEST_NETID_2 to CALLBACK_AVAILABLE + ), queue2) + } + + @Test + fun testToString() { + assertEquals("[]", CallbackQueue(intArrayOf()).toString()) + assertEquals( + "[CALLBACK_AVAILABLE($TEST_NETID_1)]", + CallbackQueue(intArrayOf()).apply { + addCallback(TEST_NETID_1, CALLBACK_AVAILABLE) + }.toString() + ) + assertEquals( + "[CALLBACK_AVAILABLE($TEST_NETID_1),CALLBACK_CAP_CHANGED($TEST_NETID_2)]", + CallbackQueue(intArrayOf()).apply { + addCallback(TEST_NETID_1, CALLBACK_AVAILABLE) + addCallback(TEST_NETID_2, CALLBACK_CAP_CHANGED) + }.toString() + ) + } + + @Test + fun testMaxNetId() { + // CallbackQueue assumes netIds are at most 16 bits + assertTrue(NetIdManager.MAX_NET_ID <= 0xffff) + } + + @Test + fun testMaxCallbackId() { + // CallbackQueue assumes callback IDs are at most 16 bits. + val constants = ConnectivityManager::class.java.declaredFields.filter { + Modifier.isStatic(it.modifiers) && Modifier.isFinal(it.modifiers) && + it.name.startsWith("CALLBACK_") + } + constants.forEach { + it.isAccessible = true + assertTrue(it.get(null) as Int <= 0xffff) + } + } +} + +private fun assertQueueEquals(expected: List>, actual: CallbackQueue) { + assertEquals( + expected.size, + actual.length(), + "Size mismatch between expected: $expected and actual: $actual" + ) + + var nextIndex = 0 + actual.forEach { netId, cbId -> + val (expNetId, expCbId) = expected[nextIndex] + val msg = "$actual does not match $expected at index $nextIndex" + assertEquals(expNetId, netId, msg) + assertEquals(expCbId, cbId, msg) + nextIndex++ + } + // Ensure forEach iterations and size are consistent + assertEquals(expected.size, nextIndex) +} From 48d2527e435600d18d409879f1fd65c3d1444c0b Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 3 Jul 2024 18:50:03 +0900 Subject: [PATCH 2/2] Add flag for QUEUE_CALLBACKS_FOR_FROZEN_APPS Add a flag to be used for an optimization to queue callbacks while apps are frozen, instead of filling the binder buffer. Bug: 327038794 Bug: 279392981 Test: atest Change-Id: I6cc2507de88fae3d0866f9831e1e9026200297e4 --- .../com/android/server/ConnectivityService.java | 17 ++++++++++++++++- .../server/connectivity/ConnectivityFlags.java | 3 +++ .../android/server/ConnectivityServiceTest.java | 1 + .../server/connectivityservice/base/CSTest.kt | 1 + 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index f01574237b..5167570fc0 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -145,6 +145,7 @@ import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPer import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf; import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE; import static com.android.server.connectivity.ConnectivityFlags.DELAY_DESTROY_SOCKETS; +import static com.android.server.connectivity.ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS; import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI; import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING; @@ -510,6 +511,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private final boolean mUseDeclaredMethodsForCallbacksEnabled; + // Flag to delay callbacks for frozen apps, suppressing duplicate and stale callbacks. + private final boolean mQueueCallbacksForFrozenApps; + /** * Uids ConnectivityService tracks blocked status of to send blocked status callbacks. * Key is uid based on mAsUid of registered networkRequestInfo @@ -1873,6 +1877,9 @@ public class ConnectivityService extends IConnectivityManager.Stub context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN); mUseDeclaredMethodsForCallbacksEnabled = mDeps.isFeatureEnabled(context, ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS); + // registerUidFrozenStateChangedCallback is only available on U+ + mQueueCallbacksForFrozenApps = mDeps.isAtLeastU() + && mDeps.isFeatureEnabled(context, QUEUE_CALLBACKS_FOR_FROZEN_APPS); mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator( mContext, mTelephonyManager, mRequestRestrictedWifiEnabled, this::handleUidCarrierPrivilegesLost, mHandler); @@ -2028,7 +2035,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mDelayDestroySockets = mDeps.isFeatureNotChickenedOut(context, DELAY_DESTROY_SOCKETS); mAllowSysUiConnectivityReports = mDeps.isFeatureNotChickenedOut( mContext, ALLOW_SYSUI_CONNECTIVITY_REPORTS); - if (mDestroyFrozenSockets) { + if (mDestroyFrozenSockets || mQueueCallbacksForFrozenApps) { final UidFrozenStateChangedCallback frozenStateChangedCallback = new UidFrozenStateChangedCallback() { @Override @@ -3464,6 +3471,14 @@ public class ConnectivityService extends IConnectivityManager.Stub private void handleFrozenUids(int[] uids, int[] frozenStates) { ensureRunningOnConnectivityServiceThread(); + handleDestroyFrozenSockets(uids, frozenStates); + // TODO: handle freezing NetworkCallbacks + } + + private void handleDestroyFrozenSockets(int[] uids, int[] frozenStates) { + if (!mDestroyFrozenSockets) { + return; + } for (int i = 0; i < uids.length; i++) { final int uid = uids[i]; final boolean addReason = frozenStates[i] == UID_FROZEN_STATE_FROZEN; diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java index 1ee1ed7432..df8731619a 100644 --- a/service/src/com/android/server/connectivity/ConnectivityFlags.java +++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java @@ -49,6 +49,9 @@ public final class ConnectivityFlags { public static final String USE_DECLARED_METHODS_FOR_CALLBACKS = "use_declared_methods_for_callbacks"; + public static final String QUEUE_CALLBACKS_FOR_FROZEN_APPS = + "queue_callbacks_for_frozen_apps"; + private boolean mNoRematchAllRequestsOnRegister; /** diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index be7f2a3a01..999d17d7b0 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -2180,6 +2180,7 @@ public class ConnectivityServiceTest { case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK: case ConnectivityFlags.REQUEST_RESTRICTED_WIFI: case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS: + case ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS: case KEY_DESTROY_FROZEN_SOCKETS_VERSION: return true; default: diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt index ed72fd2b59..de56ae5c36 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt @@ -165,6 +165,7 @@ open class CSTest { it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true + it[ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS] = true } fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)