diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index b9076e95dd..9d318cd941 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -79,6 +79,8 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY; import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired; import static android.os.Process.INVALID_UID; import static android.os.Process.VPN_UID; @@ -2622,6 +2624,12 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private void enforceManageTestNetworksPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MANAGE_TEST_NETWORKS, + "ConnectivityService"); + } + private boolean checkNetworkStackPermission() { return checkAnyPermissionOf( android.Manifest.permission.NETWORK_STACK, @@ -9953,8 +9961,15 @@ public class ConnectivityService extends IConnectivityManager.Stub @NonNull final OemNetworkPreferences preference, @Nullable final IOnCompleteListener listener) { - enforceAutomotiveDevice(); - enforceOemNetworkPreferencesPermission(); + Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); + // Only bypass the permission/device checks if this is a valid test request. + if (isValidTestOemNetworkPreference(preference)) { + enforceManageTestNetworksPermission(); + } else { + enforceAutomotiveDevice(); + enforceOemNetworkPreferencesPermission(); + validateOemNetworkPreferences(preference); + } // TODO: Have a priority for each preference. if (!mProfileNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) { @@ -9966,18 +9981,41 @@ public class ConnectivityService extends IConnectivityManager.Stub throwConcurrentPreferenceException(); } - Objects.requireNonNull(preference, "OemNetworkPreferences must be non-null"); - validateOemNetworkPreferences(preference); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_OEM_NETWORK_PREFERENCE, new Pair<>(preference, listener))); } + /** + * Check the validity of an OEM network preference to be used for testing purposes. + * @param preference the preference to validate + * @return true if this is a valid OEM network preference test request. + */ + private boolean isValidTestOemNetworkPreference( + @NonNull final OemNetworkPreferences preference) { + // Allow for clearing of an existing OemNetworkPreference used for testing. + // This isn't called on the handler thread so it is possible that mOemNetworkPreferences + // changes after this check is complete. This is an unlikely scenario as calling of this API + // is controlled by the OEM therefore the added complexity is not worth adding given those + // circumstances. That said, it is an edge case to be aware of hence this comment. + final boolean isValidTestClearPref = preference.getNetworkPreferences().size() == 0 + && isTestOemNetworkPreference(mOemNetworkPreferences); + return isTestOemNetworkPreference(preference) || isValidTestClearPref; + } + + private boolean isTestOemNetworkPreference(@NonNull final OemNetworkPreferences preference) { + final Map prefMap = preference.getNetworkPreferences(); + return prefMap.size() == 1 + && (prefMap.containsValue(OEM_NETWORK_PREFERENCE_TEST) + || prefMap.containsValue(OEM_NETWORK_PREFERENCE_TEST_ONLY)); + } + private void validateOemNetworkPreferences(@NonNull OemNetworkPreferences preference) { for (@OemNetworkPreferences.OemNetworkPreference final int pref : preference.getNetworkPreferences().values()) { - if (OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED == pref) { - final String msg = "OEM_NETWORK_PREFERENCE_UNINITIALIZED is an invalid value."; - throw new IllegalArgumentException(msg); + if (pref <= 0 || OemNetworkPreferences.OEM_NETWORK_PREFERENCE_MAX < pref) { + throw new IllegalArgumentException( + OemNetworkPreferences.oemNetworkPreferenceToString(pref) + + " is an invalid value."); } } } @@ -10201,13 +10239,21 @@ public class ConnectivityService extends IConnectivityManager.Stub case OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY: requests.add(createOemPrivateNetworkRequest()); break; + case OEM_NETWORK_PREFERENCE_TEST: + requests.add(createUnmeteredNetworkRequest()); + requests.add(createTestNetworkRequest()); + requests.add(createDefaultRequest()); + break; + case OEM_NETWORK_PREFERENCE_TEST_ONLY: + requests.add(createTestNetworkRequest()); + break; default: // This should never happen. throw new IllegalArgumentException("createNriFromOemNetworkPreferences()" + " called with invalid preference of " + preference); } - final ArraySet ranges = new ArraySet(); + final ArraySet ranges = new ArraySet<>(); for (final int uid : uids) { ranges.add(new UidRange(uid, uid)); } @@ -10240,10 +10286,17 @@ public class ConnectivityService extends IConnectivityManager.Stub } private NetworkCapabilities createDefaultPerAppNetCap() { - final NetworkCapabilities netCap = new NetworkCapabilities(); - netCap.addCapability(NET_CAPABILITY_INTERNET); - netCap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); - return netCap; + final NetworkCapabilities netcap = new NetworkCapabilities(); + netcap.addCapability(NET_CAPABILITY_INTERNET); + netcap.setRequestorUidAndPackageName(Process.myUid(), mContext.getPackageName()); + return netcap; + } + + private NetworkRequest createTestNetworkRequest() { + final NetworkCapabilities netcap = new NetworkCapabilities(); + netcap.clearAll(); + netcap.addTransportType(TRANSPORT_TEST); + return createNetworkRequest(NetworkRequest.Type.REQUEST, netcap); } } } diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java index 70dd993a49..86c319d791 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java @@ -65,11 +65,13 @@ import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; import static android.system.OsConstants.AF_UNSPEC; +import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; import static com.android.compatibility.common.util.SystemUtil.runShellCommand; import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN; import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE; import static com.android.testutils.MiscAsserts.assertThrows; +import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork; import static com.android.testutils.TestPermissionUtil.runAsShell; import static org.junit.Assert.assertEquals; @@ -112,6 +114,7 @@ import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.NetworkStateSnapshot; import android.net.NetworkUtils; +import android.net.OemNetworkPreferences; import android.net.ProxyInfo; import android.net.SocketKeepalive; import android.net.TelephonyNetworkSpecifier; @@ -155,6 +158,7 @@ import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRuleKt; import com.android.testutils.RecorderCallback.CallbackEntry; import com.android.testutils.SkipPresubmit; +import com.android.testutils.TestNetworkTracker; import com.android.testutils.TestableNetworkCallback; import junit.framework.AssertionFailedError; @@ -191,6 +195,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -518,13 +523,8 @@ public class ConnectivityManagerTest { @Test @SkipPresubmit(reason = "Virtual devices use a single internet connection for all networks") public void testOpenConnection() throws Exception { - boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI) - && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY); - if (!canRunTest) { - Log.i(TAG,"testOpenConnection cannot execute unless device supports both WiFi " - + "and a cellular connection"); - return; - } + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI)); + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)); Network wifiNetwork = mCtsNetUtils.connectToWifi(); Network cellNetwork = mCtsNetUtils.connectToCell(); @@ -699,10 +699,7 @@ public class ConnectivityManagerTest { @AppModeFull(reason = "Cannot get WifiManager in instant app mode") @Test public void testRegisterNetworkCallback() { - if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) { - Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi"); - return; - } + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI)); // We will register for a WIFI network being available or lost. final TestNetworkCallback callback = new TestNetworkCallback(); @@ -766,10 +763,7 @@ public class ConnectivityManagerTest { @AppModeFull(reason = "Cannot get WifiManager in instant app mode") @Test public void testRegisterNetworkCallback_withPendingIntent() { - if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) { - Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi"); - return; - } + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI)); // Create a ConnectivityActionReceiver that has an IntentFilter for our locally defined // action, NETWORK_CALLBACK_ACTION. @@ -940,7 +934,8 @@ public class ConnectivityManagerTest { } } - private void waitForActiveNetworkMetered(int targetTransportType, boolean requestedMeteredness) + private void waitForActiveNetworkMetered(final int targetTransportType, + final boolean requestedMeteredness, final boolean useSystemDefault) throws Exception { final CountDownLatch latch = new CountDownLatch(1); final NetworkCallback networkCallback = new NetworkCallback() { @@ -954,17 +949,36 @@ public class ConnectivityManagerTest { } } }; - // Registering a callback here guarantees onCapabilitiesChanged is called immediately - // with the current setting. Therefore, if the setting has already been changed, - // this method will return right away, and if not it will wait for the setting to change. - mCm.registerDefaultNetworkCallback(networkCallback); - // Changing meteredness on wifi involves reconnecting, which can take several seconds - // (involves re-associating, DHCP...). - if (!latch.await(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { - fail("Timed out waiting for active network metered status to change to " - + requestedMeteredness + " ; network = " + mCm.getActiveNetwork()); + + try { + // Registering a callback here guarantees onCapabilitiesChanged is called immediately + // with the current setting. Therefore, if the setting has already been changed, + // this method will return right away, and if not, it'll wait for the setting to change. + if (useSystemDefault) { + runWithShellPermissionIdentity(() -> + mCmShim.registerSystemDefaultNetworkCallback(networkCallback, + new Handler(Looper.getMainLooper())), + NETWORK_SETTINGS); + } else { + mCm.registerDefaultNetworkCallback(networkCallback); + } + + // Changing meteredness on wifi involves reconnecting, which can take several seconds + // (involves re-associating, DHCP...). + if (!latch.await(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + fail("Timed out waiting for active network metered status to change to " + + requestedMeteredness + " ; network = " + mCm.getActiveNetwork()); + } + } finally { + mCm.unregisterNetworkCallback(networkCallback); } - mCm.unregisterNetworkCallback(networkCallback); + } + + private void setWifiMeteredStatusAndWait(String ssid, boolean isMetered) throws Exception { + setWifiMeteredStatus(ssid, Boolean.toString(isMetered) /* metered */); + waitForActiveNetworkMetered(TRANSPORT_WIFI, + isMetered /* requestedMeteredness */, + true /* useSystemDefault */); } private void assertMultipathPreferenceIsEventually(Network network, int oldValue, @@ -1026,10 +1040,9 @@ public class ConnectivityManagerTest { int newMeteredPreference = findNextPrefValue(resolver); Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE, Integer.toString(newMeteredPreference)); - setWifiMeteredStatus(ssid, "true"); - waitForActiveNetworkMetered(TRANSPORT_WIFI, true); // Wifi meterness changes from unmetered to metered will disconnect and reconnect since // R. + setWifiMeteredStatusAndWait(ssid, true); final Network network = mCtsNetUtils.ensureWifiConnected(); assertEquals(ssid, unquoteSSID(mWifiManager.getConnectionInfo().getSSID())); assertEquals(mCm.getNetworkCapabilities(network).hasCapability( @@ -1046,9 +1059,8 @@ public class ConnectivityManagerTest { assertMultipathPreferenceIsEventually(network, oldMeteredPreference, newMeteredPreference); - setWifiMeteredStatus(ssid, "false"); // No disconnect from unmetered to metered. - waitForActiveNetworkMetered(TRANSPORT_WIFI, false); + setWifiMeteredStatusAndWait(ssid, false); assertEquals(mCm.getNetworkCapabilities(network).hasCapability( NET_CAPABILITY_NOT_METERED), true); assertMultipathPreferenceIsEventually(network, newMeteredPreference, @@ -1238,11 +1250,7 @@ public class ConnectivityManagerTest { @AppModeFull(reason = "Cannot get WifiManager in instant app mode") @Test public void testKeepaliveWifiUnsupported() throws Exception { - if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) { - Log.i(TAG, "testKeepaliveUnsupported cannot execute unless device" - + " supports WiFi"); - return; - } + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI)); final Network network = mCtsNetUtils.ensureWifiConnected(); if (getSupportedKeepalivesForNet(network) != 0) return; @@ -1259,10 +1267,7 @@ public class ConnectivityManagerTest { @Test @SkipPresubmit(reason = "Keepalive is not supported on virtual hardware") public void testCreateTcpKeepalive() throws Exception { - if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) { - Log.i(TAG, "testCreateTcpKeepalive cannot execute unless device supports WiFi"); - return; - } + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI)); final Network network = mCtsNetUtils.ensureWifiConnected(); if (getSupportedKeepalivesForNet(network) == 0) return; @@ -1469,11 +1474,7 @@ public class ConnectivityManagerTest { @Test @SkipPresubmit(reason = "Keepalive is not supported on virtual hardware") public void testSocketKeepaliveLimitWifi() throws Exception { - if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) { - Log.i(TAG, "testSocketKeepaliveLimitWifi cannot execute unless device" - + " supports WiFi"); - return; - } + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI)); final Network network = mCtsNetUtils.ensureWifiConnected(); final int supported = getSupportedKeepalivesForNet(network); @@ -1566,11 +1567,7 @@ public class ConnectivityManagerTest { @Test @SkipPresubmit(reason = "Keepalive is not supported on virtual hardware") public void testSocketKeepaliveUnprivileged() throws Exception { - if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) { - Log.i(TAG, "testSocketKeepaliveUnprivileged cannot execute unless device" - + " supports WiFi"); - return; - } + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI)); final Network network = mCtsNetUtils.ensureWifiConnected(); final int supported = getSupportedKeepalivesForNet(network); @@ -1717,6 +1714,24 @@ public class ConnectivityManagerTest { c -> c instanceof CallbackEntry.Available); } + private void waitForAvailable( + @NonNull final TestableNetworkCallback cb, final int expectedTransport) { + cb.eventuallyExpect( + CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS, + entry -> { + final NetworkCapabilities nc = mCm.getNetworkCapabilities(entry.getNetwork()); + return nc.hasTransport(expectedTransport); + } + ); + } + + private void waitForAvailable( + @NonNull final TestableNetworkCallback cb, @NonNull final Network expectedNetwork) { + cb.expectAvailableCallbacks(expectedNetwork, false /* suspended */, + true /* validated */, + false /* blocked */, NETWORK_CALLBACK_TIMEOUT_MS); + } + private void waitForLost(@NonNull final TestableNetworkCallback cb) { cb.eventuallyExpect(CallbackEntry.LOST, NETWORK_CALLBACK_TIMEOUT_MS, c -> c instanceof CallbackEntry.Lost); @@ -2102,4 +2117,172 @@ public class ConnectivityManagerTest { startTetheringCallback.verifyTetheringStarted(); callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI); } + + /** + * Verify that per-app OEM network preference functions as expected for network preference TEST. + * For specified apps, validate networks are prioritized in order: unmetered, TEST transport, + * default network. + */ + @AppModeFull(reason = "Cannot get WifiManager in instant app mode") + @Test + public void testSetOemNetworkPreferenceForTestPref() throws Exception { + // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31 + // shims, and @IgnoreUpTo does not check that. + assumeTrue(TestUtils.shouldTestSApis()); + assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI)); + + final TestNetworkTracker tnt = callWithShellPermissionIdentity( + () -> initTestNetwork(mContext, TEST_LINKADDR, NETWORK_CALLBACK_TIMEOUT_MS)); + final TestableNetworkCallback defaultCallback = new TestableNetworkCallback(); + final TestableNetworkCallback systemDefaultCallback = new TestableNetworkCallback(); + + final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected(); + final NetworkCapabilities wifiNetworkCapabilities = callWithShellPermissionIdentity( + () -> mCm.getNetworkCapabilities(wifiNetwork)); + final String ssid = unquoteSSID(wifiNetworkCapabilities.getSsid()); + final boolean oldMeteredValue = wifiNetworkCapabilities.isMetered(); + + try { + // This network will be used for unmetered. + setWifiMeteredStatusAndWait(ssid, false /* isMetered */); + + setOemNetworkPreferenceForMyPackage(OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST); + registerTestOemNetworkPreferenceCallbacks(defaultCallback, systemDefaultCallback); + + // Validate that an unmetered network is used over other networks. + waitForAvailable(defaultCallback, wifiNetwork); + waitForAvailable(systemDefaultCallback, wifiNetwork); + + // Validate when setting unmetered to metered, unmetered is lost and replaced by the + // network with the TEST transport. + setWifiMeteredStatusAndWait(ssid, true /* isMetered */); + defaultCallback.expectCallback(CallbackEntry.LOST, wifiNetwork); + waitForAvailable(defaultCallback, tnt.getNetwork()); + // Depending on if this device has cellular connectivity or not, multiple available + // callbacks may be received. Eventually, metered Wi-Fi should be the final available + // callback in any case therefore confirm its receipt before continuing to assure the + // system is in the expected state. + waitForAvailable(systemDefaultCallback, TRANSPORT_WIFI); + } finally { + // Validate that removing the test network will fallback to the default network. + runWithShellPermissionIdentity(tnt::teardown); + defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork()); + waitForAvailable(defaultCallback); + + setWifiMeteredStatusAndWait(ssid, oldMeteredValue); + + // Cleanup any prior test state from setOemNetworkPreference + clearOemNetworkPreference(); + unregisterTestOemNetworkPreferenceCallbacks(defaultCallback, systemDefaultCallback); + } + } + + /** + * Verify that per-app OEM network preference functions as expected for network pref TEST_ONLY. + * For specified apps, validate that only TEST transport type networks are used. + */ + @Test + public void testSetOemNetworkPreferenceForTestOnlyPref() throws Exception { + // Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31 + // shims, and @IgnoreUpTo does not check that. + assumeTrue(TestUtils.shouldTestSApis()); + + final TestNetworkTracker tnt = callWithShellPermissionIdentity( + () -> initTestNetwork(mContext, TEST_LINKADDR, NETWORK_CALLBACK_TIMEOUT_MS)); + final TestableNetworkCallback defaultCallback = new TestableNetworkCallback(); + final TestableNetworkCallback systemDefaultCallback = new TestableNetworkCallback(); + + final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected(); + + try { + setOemNetworkPreferenceForMyPackage( + OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY); + registerTestOemNetworkPreferenceCallbacks(defaultCallback, systemDefaultCallback); + waitForAvailable(defaultCallback, tnt.getNetwork()); + waitForAvailable(systemDefaultCallback, wifiNetwork); + } finally { + runWithShellPermissionIdentity(tnt::teardown); + defaultCallback.expectCallback(CallbackEntry.LOST, tnt.getNetwork()); + + // This network preference should only ever use the test network therefore available + // should not trigger when the test network goes down (e.g. switch to cellular). + defaultCallback.assertNoCallback(); + // The system default should still be connected to Wi-fi + assertEquals(wifiNetwork, systemDefaultCallback.getLastAvailableNetwork()); + + // Cleanup any prior test state from setOemNetworkPreference + clearOemNetworkPreference(); + + // The default (non-test) network should be available as the network pref was cleared. + waitForAvailable(defaultCallback); + unregisterTestOemNetworkPreferenceCallbacks(defaultCallback, systemDefaultCallback); + } + } + + private void unregisterTestOemNetworkPreferenceCallbacks( + @NonNull final TestableNetworkCallback defaultCallback, + @NonNull final TestableNetworkCallback systemDefaultCallback) { + mCm.unregisterNetworkCallback(defaultCallback); + mCm.unregisterNetworkCallback(systemDefaultCallback); + } + + private void registerTestOemNetworkPreferenceCallbacks( + @NonNull final TestableNetworkCallback defaultCallback, + @NonNull final TestableNetworkCallback systemDefaultCallback) { + mCm.registerDefaultNetworkCallback(defaultCallback); + runWithShellPermissionIdentity(() -> + mCmShim.registerSystemDefaultNetworkCallback(systemDefaultCallback, + new Handler(Looper.getMainLooper())), NETWORK_SETTINGS); + } + + private static final class OnCompleteListenerCallback { + final CompletableFuture mDone = new CompletableFuture<>(); + + public void onComplete() { + mDone.complete(new Object()); + } + + void expectOnComplete() throws Exception { + try { + mDone.get(NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + fail("Expected onComplete() not received after " + + NETWORK_CALLBACK_TIMEOUT_MS + " ms"); + } + } + } + + private void setOemNetworkPreferenceForMyPackage(final int networkPref) throws Exception { + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(mContext.getPackageName(), networkPref) + .build(); + final OnCompleteListenerCallback oemPrefListener = new OnCompleteListenerCallback(); + mUiAutomation.adoptShellPermissionIdentity(); + try { + mCm.setOemNetworkPreference( + pref, mContext.getMainExecutor(), oemPrefListener::onComplete); + } finally { + mUiAutomation.dropShellPermissionIdentity(); + } + oemPrefListener.expectOnComplete(); + } + + /** + * This will clear the OEM network preference on the device. As there is currently no way of + * getting the existing preference, if this is executed while an existing preference is in + * place, that preference will need to be reapplied after executing this test. + * @throws Exception + */ + private void clearOemNetworkPreference() throws Exception { + final OemNetworkPreferences clearPref = new OemNetworkPreferences.Builder().build(); + final OnCompleteListenerCallback oemPrefListener = new OnCompleteListenerCallback(); + mUiAutomation.adoptShellPermissionIdentity(); + try { + mCm.setOemNetworkPreference( + clearPref, mContext.getMainExecutor(), oemPrefListener::onComplete); + } finally { + mUiAutomation.dropShellPermissionIdentity(); + } + oemPrefListener.expectOnComplete(); + } } diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index b0d849a353..e8f249e07a 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -109,6 +109,8 @@ import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST; +import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY; import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED; @@ -10704,8 +10706,7 @@ public class ConnectivityServiceTest { } @Test - public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError() - throws PackageManager.NameNotFoundException { + public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError() { @OemNetworkPreferences.OemNetworkPreference final int prefToTest = OEM_NETWORK_PREFERENCE_UNINITIALIZED; @@ -10972,7 +10973,48 @@ public class ConnectivityServiceTest { assertThrows(UnsupportedOperationException.class, () -> mService.setOemNetworkPreference( createDefaultOemNetworkPreferences(networkPref), - new TestOemListenerCallback())); + null)); + } + + @Test + public void testSetOemNetworkPreferenceFailsForTestRequestWithoutPermission() { + // Calling setOemNetworkPreference() with a test pref requires the permission + // MANAGE_TEST_NETWORKS. + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); + @OemNetworkPreferences.OemNetworkPreference final int networkPref = + OEM_NETWORK_PREFERENCE_TEST; + + // Act on ConnectivityService.setOemNetworkPreference() + assertThrows(SecurityException.class, + () -> mService.setOemNetworkPreference( + createDefaultOemNetworkPreferences(networkPref), + null)); + } + + @Test + public void testSetOemNetworkPreferenceFailsForInvalidTestRequest() { + assertSetOemNetworkPreferenceFailsForInvalidTestRequest(OEM_NETWORK_PREFERENCE_TEST); + } + + @Test + public void testSetOemNetworkPreferenceFailsForInvalidTestOnlyRequest() { + assertSetOemNetworkPreferenceFailsForInvalidTestRequest(OEM_NETWORK_PREFERENCE_TEST_ONLY); + } + + private void assertSetOemNetworkPreferenceFailsForInvalidTestRequest( + @OemNetworkPreferences.OemNetworkPreference final int oemNetworkPreferenceForTest) { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); + final String secondPackage = "does.not.matter"; + + // A valid test request would only have a single mapping. + final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() + .addNetworkPreference(TEST_PACKAGE_NAME, oemNetworkPreferenceForTest) + .addNetworkPreference(secondPackage, oemNetworkPreferenceForTest) + .build(); + + // Act on ConnectivityService.setOemNetworkPreference() + assertThrows(IllegalArgumentException.class, + () -> mService.setOemNetworkPreference(pref, null)); } private void setOemNetworkPreferenceAgentConnected(final int transportType, @@ -11149,8 +11191,18 @@ public class ConnectivityServiceTest { private void setupSetOemNetworkPreferenceForPreferenceTest( @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, @NonNull final UidRangeParcel[] uidRanges, - @NonNull final String testPackageName) - throws Exception { + @NonNull final String testPackageName) throws Exception { + setupSetOemNetworkPreferenceForPreferenceTest( + networkPrefToSetup, uidRanges, testPackageName, true); + } + + private void setupSetOemNetworkPreferenceForPreferenceTest( + @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, + @NonNull final UidRangeParcel[] uidRanges, + @NonNull final String testPackageName, + final boolean hasAutomotiveFeature) throws Exception { + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, hasAutomotiveFeature); + // These tests work off a single UID therefore using 'start' is valid. mockGetApplicationInfo(testPackageName, uidRanges[0].start); @@ -11454,6 +11506,55 @@ public class ConnectivityServiceTest { reset(mMockNetd); } + /** + * Test the tracked default requests allows test requests without standard setup. + */ + @Test + public void testSetOemNetworkPreferenceAllowsValidTestRequestWithoutChecks() throws Exception { + @OemNetworkPreferences.OemNetworkPreference int networkPref = + OEM_NETWORK_PREFERENCE_TEST; + validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(networkPref); + } + + /** + * Test the tracked default requests allows test only requests without standard setup. + */ + @Test + public void testSetOemNetworkPreferenceAllowsValidTestOnlyRequestWithoutChecks() + throws Exception { + @OemNetworkPreferences.OemNetworkPreference int networkPref = + OEM_NETWORK_PREFERENCE_TEST_ONLY; + validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(networkPref); + } + + private void validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(int networkPref) + throws Exception { + // The caller must have the MANAGE_TEST_NETWORKS permission. + final int testPackageUid = 123; + final String validTestPackageName = "does.not.matter"; + final UidRangeParcel[] uidRanges = + toUidRangeStableParcels(uidRangesForUids(testPackageUid)); + mServiceContext.setPermission( + Manifest.permission.MANAGE_TEST_NETWORKS, PERMISSION_GRANTED); + + // Put the system into a state in which setOemNetworkPreference() would normally fail. This + // will confirm that a valid test request can bypass these checks. + mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); + mServiceContext.setPermission( + Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE, PERMISSION_DENIED); + + // Validate the starting requests only includes the system default request. + assertEquals(1, mService.mDefaultNetworkRequests.size()); + + // Add an OEM default network request to track. + setupSetOemNetworkPreferenceForPreferenceTest( + networkPref, uidRanges, validTestPackageName, + false /* hasAutomotiveFeature */); + + // Two requests should now exist; the system default and the test request. + assertEquals(2, mService.mDefaultNetworkRequests.size()); + } + /** * Test the tracked default requests clear previous OEM requests on setOemNetworkPreference(). */ @@ -11466,7 +11567,7 @@ public class ConnectivityServiceTest { final UidRangeParcel[] uidRanges = toUidRangeStableParcels(uidRangesForUids(testPackageUid)); - // Validate the starting requests only includes the fallback request. + // Validate the starting requests only includes the system default request. assertEquals(1, mService.mDefaultNetworkRequests.size()); // Add an OEM default network request to track.