diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index aa1648f9e7..3771bbc4a6 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -616,6 +616,11 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_UNREGISTER_NETWORK_OFFER = 53; + /** + * Used internally when MOBILE_DATA_PREFERRED_UIDS setting changed. + */ + private static final int EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED = 54; + /** * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification * should be shown. @@ -1460,6 +1465,11 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED); } + @VisibleForTesting + void updateMobileDataPreferredUids() { + mHandler.sendEmptyMessage(EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED); + } + private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, int id) { final boolean enable = mContext.getResources().getBoolean(id); handleAlwaysOnNetworkRequest(networkRequest, enable); @@ -1504,6 +1514,8 @@ public class ConnectivityService extends IConnectivityManager.Stub vehicleAlwaysRequested || legacyAlwaysRequested); } + // Note that registering observer for setting do not get initial callback when registering, + // callers might have self-initialization to update status if need. private void registerSettingsCallbacks() { // Watch for global HTTP proxy changes. mSettingsObserver.observe( @@ -1519,6 +1531,11 @@ public class ConnectivityService extends IConnectivityManager.Stub mSettingsObserver.observe( Settings.Global.getUriFor(ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED), EVENT_CONFIGURE_ALWAYS_ON_NETWORKS); + + // Watch for mobile data preferred uids changes. + mSettingsObserver.observe( + Settings.Secure.getUriFor(ConnectivitySettingsManager.MOBILE_DATA_PREFERRED_UIDS), + EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED); } private void registerPrivateDnsSettingsCallbacks() { @@ -2717,6 +2734,13 @@ public class ConnectivityService extends IConnectivityManager.Stub // Create network requests for always-on networks. mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS)); + + // Update mobile data preference if necessary. + // Note that empty uid list can be skip here only because no uid rules applied before system + // ready. Normally, the empty uid list means to clear the uids rules on netd. + if (!ConnectivitySettingsManager.getMobileDataPreferredUids(mContext).isEmpty()) { + updateMobileDataPreferredUids(); + } } /** @@ -4801,6 +4825,9 @@ public class ConnectivityService extends IConnectivityManager.Stub case EVENT_REPORT_NETWORK_ACTIVITY: mNetworkActivityTracker.handleReportNetworkActivity(); break; + case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED: + handleMobileDataPreferredUidsChanged(); + break; } } } @@ -5605,7 +5632,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * Get the list of UIDs this nri applies to. */ @NonNull - private Set getUids() { + Set getUids() { // networkCapabilities.getUids() returns a defensive copy. // multilayer requests will all have the same uids so return the first one. final Set uids = mRequests.get(0).networkCapabilities.getUidRanges(); @@ -6319,6 +6346,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences(); + // A set of UIDs that should use mobile data preferentially if available. This object follows + // the same threading rules as the OEM network preferences above. + @NonNull + private Set mMobileDataPreferredUids = new ArraySet<>(); + // OemNetworkPreferences activity String log entries. private static final int MAX_OEM_NETWORK_PREFERENCE_LOGS = 20; @NonNull @@ -9686,7 +9718,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // safe - it's just possible the value is slightly outdated. For the final check, // see #handleSetProfileNetworkPreference. But if this can be caught here it is a // lot easier to understand, so opportunistically check it. - if (!mOemNetworkPreferences.isEmpty()) { + // TODO: Have a priority for each preference. + if (!mOemNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) { throwConcurrentPreferenceException(); } final NetworkCapabilities nc; @@ -9745,7 +9778,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // The binder call has already checked this, but as mOemNetworkPreferences is only // touched on the handler thread, it's theoretically not impossible that it has changed // since. - if (!mOemNetworkPreferences.isEmpty()) { + // TODO: Have a priority for each preference. + if (!mOemNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) { // This may happen on a device with an OEM preference set when a user is removed. // In this case, it's safe to ignore. In particular this happens in the tests. loge("handleSetProfileNetworkPreference, but OEM network preferences not empty"); @@ -9774,6 +9808,56 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @VisibleForTesting + @NonNull + ArraySet createNrisFromMobileDataPreferredUids( + @NonNull final Set uids) { + final ArraySet nris = new ArraySet<>(); + if (uids.size() == 0) { + // Should not create NetworkRequestInfo if no preferences. Without uid range in + // NetworkRequestInfo, makeDefaultForApps() would treat it as a illegal NRI. + if (DBG) log("Don't create NetworkRequestInfo because no preferences"); + return nris; + } + + final List requests = new ArrayList<>(); + // The NRI should be comprised of two layers: + // - The request for the mobile network preferred. + // - The request for the default network, for fallback. + requests.add(createDefaultInternetRequestForTransport( + TRANSPORT_CELLULAR, NetworkRequest.Type.LISTEN)); + requests.add(createDefaultInternetRequestForTransport( + TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT)); + final Set ranges = new ArraySet<>(); + for (final int uid : uids) { + ranges.add(new UidRange(uid, uid)); + } + setNetworkRequestUids(requests, ranges); + nris.add(new NetworkRequestInfo(Process.myUid(), requests)); + return nris; + } + + private void handleMobileDataPreferredUidsChanged() { + // Ignore update preference because it's not clear what preference should win in case both + // apply to the same app. + // TODO: Have a priority for each preference. + if (!mOemNetworkPreferences.isEmpty() || !mProfileNetworkPreferences.isEmpty()) { + loge("Ignore mobile data preference change because other preferences are not empty"); + return; + } + + mMobileDataPreferredUids = ConnectivitySettingsManager.getMobileDataPreferredUids(mContext); + mSystemNetworkRequestCounter.transact( + mDeps.getCallingUid(), 1 /* numOfNewRequests */, + () -> { + final ArraySet nris = + createNrisFromMobileDataPreferredUids(mMobileDataPreferredUids); + replaceDefaultNetworkRequestsForPreference(nris); + }); + // Finally, rematch. + rematchAllNetworksAndRequests(); + } + private void enforceAutomotiveDevice() { final boolean isAutomotiveDevice = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); @@ -9803,7 +9887,8 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceAutomotiveDevice(); enforceOemNetworkPreferencesPermission(); - if (!mProfileNetworkPreferences.isEmpty()) { + // TODO: Have a priority for each preference. + if (!mProfileNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) { // Strictly speaking, mProfileNetworkPreferences should only be touched on the // handler thread. However it is an immutable object, so reading the reference is // safe - it's just possible the value is slightly outdated. For the final check, @@ -9841,7 +9926,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // The binder call has already checked this, but as mOemNetworkPreferences is only // touched on the handler thread, it's theoretically not impossible that it has changed // since. - if (!mProfileNetworkPreferences.isEmpty()) { + // TODO: Have a priority for each preference. + if (!mProfileNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) { logwtf("handleSetOemPreference, but per-profile network preferences not empty"); return; } diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 04e63fc626..0d3cd6146f 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -299,7 +299,9 @@ import com.android.internal.util.WakeupMessage; import com.android.internal.util.test.BroadcastInterceptingContext; import com.android.internal.util.test.FakeSettingsProvider; import com.android.net.module.util.ArrayTrackRecord; +import com.android.net.module.util.CollectionUtils; import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo; +import com.android.server.ConnectivityService.NetworkRequestInfo; import com.android.server.connectivity.ConnectivityConstants; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; @@ -415,6 +417,7 @@ public class ConnectivityServiceTest { private static final String VPN_IFNAME = "tun10042"; private static final String TEST_PACKAGE_NAME = "com.android.test.package"; private static final int TEST_PACKAGE_UID = 123; + private static final int TEST_PACKAGE_UID2 = 321; private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn"; private static final String INTERFACE_NAME = "interface"; @@ -1170,6 +1173,10 @@ public class ConnectivityServiceTest { return ranges; } + private Set uidRangesForUids(Collection uids) { + return uidRangesForUids(CollectionUtils.toIntArray(uids)); + } + private static Looper startHandlerThreadAndReturnLooper() { final HandlerThread handlerThread = new HandlerThread("MockVpnThread"); handlerThread.start(); @@ -1536,6 +1543,8 @@ public class ConnectivityServiceTest { private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER, "", UserInfo.FLAG_PRIMARY); private static final UserHandle PRIMARY_USER_HANDLE = new UserHandle(PRIMARY_USER); + private static final int SECONDARY_USER = 10; + private static final UserHandle SECONDARY_USER_HANDLE = new UserHandle(SECONDARY_USER); private static final int RESTRICTED_USER = 1; private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "", @@ -10050,7 +10059,7 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); waitForIdle(); - final ConnectivityService.NetworkRequestInfo[] nriOutput = mService.requestsSortedById(); + final NetworkRequestInfo[] nriOutput = mService.requestsSortedById(); assertTrue(nriOutput.length > 1); for (int i = 0; i < nriOutput.length - 1; i++) { @@ -10333,8 +10342,7 @@ public class ConnectivityServiceTest { .thenReturn(hasFeature); } - private Range getNriFirstUidRange( - @NonNull final ConnectivityService.NetworkRequestInfo nri) { + private Range getNriFirstUidRange(@NonNull final NetworkRequestInfo nri) { return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next(); } @@ -10373,7 +10381,7 @@ public class ConnectivityServiceTest { OEM_NETWORK_PREFERENCE_OEM_PAID; // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() - final ArraySet nris = + final ArraySet nris = mService.new OemNetworkRequestFactory() .createNrisFromOemNetworkPreferences( createDefaultOemNetworkPreferences(prefToTest)); @@ -10402,7 +10410,7 @@ public class ConnectivityServiceTest { OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() - final ArraySet nris = + final ArraySet nris = mService.new OemNetworkRequestFactory() .createNrisFromOemNetworkPreferences( createDefaultOemNetworkPreferences(prefToTest)); @@ -10428,7 +10436,7 @@ public class ConnectivityServiceTest { OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() - final ArraySet nris = + final ArraySet nris = mService.new OemNetworkRequestFactory() .createNrisFromOemNetworkPreferences( createDefaultOemNetworkPreferences(prefToTest)); @@ -10451,7 +10459,7 @@ public class ConnectivityServiceTest { OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() - final ArraySet nris = + final ArraySet nris = mService.new OemNetworkRequestFactory() .createNrisFromOemNetworkPreferences( createDefaultOemNetworkPreferences(prefToTest)); @@ -10484,7 +10492,7 @@ public class ConnectivityServiceTest { .build(); // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() - final ArraySet nris = + final ArraySet nris = mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref); assertNotNull(nris); @@ -10509,7 +10517,7 @@ public class ConnectivityServiceTest { .build(); // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() - final List nris = + final List nris = new ArrayList<>( mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences( pref)); @@ -10541,7 +10549,7 @@ public class ConnectivityServiceTest { .build(); // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() - final List nris = + final List nris = new ArrayList<>( mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences( pref)); @@ -10583,7 +10591,7 @@ public class ConnectivityServiceTest { .build(); // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() - final ArraySet nris = + final ArraySet nris = mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref); assertEquals(expectedNumOfNris, nris.size()); @@ -10676,8 +10684,7 @@ public class ConnectivityServiceTest { // each time to confirm it doesn't change under test. final int expectedDefaultNetworkRequestsSize = 2; assertEquals(expectedDefaultNetworkRequestsSize, mService.mDefaultNetworkRequests.size()); - for (final ConnectivityService.NetworkRequestInfo defaultRequest - : mService.mDefaultNetworkRequests) { + for (final NetworkRequestInfo defaultRequest : mService.mDefaultNetworkRequests) { final Network defaultNetwork = defaultRequest.getSatisfier() == null ? null : defaultRequest.getSatisfier().network(); // If this is the default request. @@ -12506,4 +12513,73 @@ public class ConnectivityServiceTest { } } } + + private void assertCreateNrisFromMobileDataPreferredUids(Set uids) { + final Set nris = + mService.createNrisFromMobileDataPreferredUids(uids); + final NetworkRequestInfo nri = nris.iterator().next(); + // Verify that one NRI is created with multilayer requests. Because one NRI can contain + // multiple uid ranges, so it only need create one NRI here. + assertEquals(1, nris.size()); + assertTrue(nri.isMultilayerRequest()); + assertEquals(nri.getUids(), uidRangesForUids(uids)); + } + + /** + * Test createNrisFromMobileDataPreferredUids returns correct NetworkRequestInfo. + */ + @Test + public void testCreateNrisFromMobileDataPreferredUids() { + // Verify that empty uid set should not create any NRI for it. + final Set nrisNoUid = + mService.createNrisFromMobileDataPreferredUids(new ArraySet<>()); + assertEquals(0, nrisNoUid.size()); + + final int uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID); + final int uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2); + final int uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID); + assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1)); + assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1, uid3)); + assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1, uid2)); + } + + private void setAndUpdateMobileDataPreferredUids(Set uids) { + ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext, uids); + mService.updateMobileDataPreferredUids(); + waitForIdle(); + } + + /** + * Test that MOBILE_DATA_PREFERRED_UIDS changes will send correct net id and uid ranges to netd. + */ + @Test + public void testMobileDataPreferredUidsChanged() throws Exception { + final InOrder inorder = inOrder(mMockNetd); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + waitForIdle(); + + final int cellNetId = mCellNetworkAgent.getNetwork().netId; + inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical( + cellNetId, INetd.PERMISSION_NONE)); + + // Initial mobile data preferred uids status. + setAndUpdateMobileDataPreferredUids(Set.of()); + inorder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any()); + inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any()); + + final Set uids1 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)); + final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1)); + setAndUpdateMobileDataPreferredUids(uids1); + inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges1); + inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any()); + + final Set uids2 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID), + PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2), + SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)); + final UidRangeParcel[] uidRanges2 = toUidRangeStableParcels(uidRangesForUids(uids2)); + setAndUpdateMobileDataPreferredUids(uids2); + inorder.verify(mMockNetd, times(1)).networkRemoveUidRanges(cellNetId, uidRanges1); + inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges2); + } }