Merge "Partially enforce permission when calling getNetworkInfoForUid"
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index 8083cbf..1844334 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -1,7 +1,7 @@
lorenzo@google.com
satk@google.com #{LAST_RESORT_SUGGESTION}
-# For cherry-picks of CLs that are already merged in aosp/master.
+# For cherry-picks of CLs that are already merged in aosp/master, or flaky test fixes.
jchalard@google.com #{LAST_RESORT_SUGGESTION}
maze@google.com #{LAST_RESORT_SUGGESTION}
reminv@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 6e30fd1..700a085 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -135,6 +135,37 @@
}
]
},
+ // Test with APK modules only, in cases where APEX is not supported, or the other modules
+ // were simply not updated
+ {
+ "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ },
+ {
+ "exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
+ }
+ ]
+ },
+ // Test with connectivity/tethering module only, to catch integration issues with older versions
+ // of other modules. "new tethering + old NetworkStack" is not a configuration that should
+ // really exist in the field, but there is no strong guarantee, and it is required by MTS
+ // testing for module qualification, where modules are tested independently.
+ {
+ "name": "CtsNetTestCasesLatestSdk[com.google.android.tethering.apex]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.RequiresDevice"
+ }
+ ]
+ },
{
"name": "bpf_existence_test[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]"
},
@@ -159,38 +190,6 @@
{
"name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"keywords": ["sim"]
- },
- // TODO: move to mainline-presubmit when known green.
- // Test with APK modules only, in cases where APEX is not supported, or the other modules were simply not updated
- {
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.RequiresDevice"
- },
- {
- "exclude-annotation": "com.android.testutils.ConnectivityModuleTest"
- }
- ]
- },
- // TODO: move to mainline-presubmit when known green.
- // Test with connectivity/tethering module only, to catch integration issues with older versions of other modules.
- // "new tethering + old NetworkStack" is not a configuration that should really exist in the field, but
- // there is no strong guarantee, and it is required by MTS testing for module qualification, where modules
- // are tested independently.
- {
- "name": "CtsNetTestCasesLatestSdk[com.google.android.tethering.apex]",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.RequiresDevice"
- }
- ]
}
],
"imports": [
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 142a0b9..74ba209 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -536,6 +536,13 @@
// TODO: Wrap conntrackMonitor stopping function into mBpfCoordinatorShim.
if (!isUsingBpf() || !mDeps.isAtLeastS()) return;
+ // Ignore stopping monitoring if the monitor has never started for a given IpServer.
+ if (!mMonitoringIpServers.contains(ipServer)) {
+ mLog.e("Ignore stopping monitoring because monitoring has never started for "
+ + ipServer.interfaceName());
+ return;
+ }
+
mMonitoringIpServers.remove(ipServer);
if (!mMonitoringIpServers.isEmpty()) return;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 758b533..ac92b43 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -1413,7 +1413,7 @@
// [1] Don't stop monitoring if it has never started.
coordinator.stopMonitoring(mIpServer);
- verify(mConntrackMonitor, never()).start();
+ verify(mConntrackMonitor, never()).stop();
// [2] Start monitoring.
coordinator.startMonitoring(mIpServer);
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index f1298ce..8b35197 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -433,6 +433,7 @@
public abstract class QosFilter {
method @NonNull public abstract android.net.Network getNetwork();
method public abstract boolean matchesLocalAddress(@NonNull java.net.InetAddress, int, int);
+ method public boolean matchesProtocol(int);
method public abstract boolean matchesRemoteAddress(@NonNull java.net.InetAddress, int, int);
}
@@ -453,6 +454,7 @@
public final class QosSocketInfo implements android.os.Parcelable {
ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.Socket) throws java.io.IOException;
+ ctor public QosSocketInfo(@NonNull android.net.Network, @NonNull java.net.DatagramSocket) throws java.io.IOException;
method public int describeContents();
method @NonNull public java.net.InetSocketAddress getLocalSocketAddress();
method @NonNull public android.net.Network getNetwork();
@@ -480,6 +482,14 @@
ctor public SocketNotBoundException();
}
+ public class SocketNotConnectedException extends java.lang.Exception {
+ ctor public SocketNotConnectedException();
+ }
+
+ public class SocketRemoteAddressChangedException extends java.lang.Exception {
+ ctor public SocketRemoteAddressChangedException();
+ }
+
public final class StaticIpConfiguration implements android.os.Parcelable {
ctor public StaticIpConfiguration();
ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index ea8a3df..d0cbbe5 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -185,10 +185,18 @@
NET_ENTERPRISE_ID_4,
NET_ENTERPRISE_ID_5,
})
-
public @interface EnterpriseId {
}
+ private static final int ALL_VALID_ENTERPRISE_IDS;
+ static {
+ int enterpriseIds = 0;
+ for (int i = NET_ENTERPRISE_ID_1; i <= NET_ENTERPRISE_ID_5; ++i) {
+ enterpriseIds |= 1 << i;
+ }
+ ALL_VALID_ENTERPRISE_IDS = enterpriseIds;
+ }
+
/**
* Bitfield representing the network's enterprise capability identifier. If any are specified
* they will be satisfied by any Network that matches all of them.
@@ -622,6 +630,15 @@
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+ private static final int ALL_VALID_CAPABILITIES;
+ static {
+ int caps = 0;
+ for (int i = MIN_NET_CAPABILITY; i <= MAX_NET_CAPABILITY; ++i) {
+ caps |= 1 << i;
+ }
+ ALL_VALID_CAPABILITIES = caps;
+ }
+
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
* network is connected.
@@ -1146,6 +1163,15 @@
/** @hide */
public static final int MAX_TRANSPORT = TRANSPORT_USB;
+ private static final int ALL_VALID_TRANSPORTS;
+ static {
+ int transports = 0;
+ for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; ++i) {
+ transports |= 1 << i;
+ }
+ ALL_VALID_TRANSPORTS = transports;
+ }
+
/** @hide */
public static boolean isValidTransport(@Transport int transportType) {
return (MIN_TRANSPORT <= transportType) && (transportType <= MAX_TRANSPORT);
@@ -2114,9 +2140,9 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeLong(mNetworkCapabilities);
- dest.writeLong(mForbiddenNetworkCapabilities);
- dest.writeLong(mTransportTypes);
+ dest.writeLong(mNetworkCapabilities & ALL_VALID_CAPABILITIES);
+ dest.writeLong(mForbiddenNetworkCapabilities & ALL_VALID_CAPABILITIES);
+ dest.writeLong(mTransportTypes & ALL_VALID_TRANSPORTS);
dest.writeInt(mLinkUpBandwidthKbps);
dest.writeInt(mLinkDownBandwidthKbps);
dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
@@ -2132,7 +2158,7 @@
dest.writeString(mRequestorPackageName);
dest.writeIntArray(CollectionUtils.toIntArray(mSubIds));
dest.writeTypedList(mUnderlyingNetworks);
- dest.writeInt(mEnterpriseId);
+ dest.writeInt(mEnterpriseId & ALL_VALID_ENTERPRISE_IDS);
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -2140,10 +2166,10 @@
@Override
public NetworkCapabilities createFromParcel(Parcel in) {
NetworkCapabilities netCap = new NetworkCapabilities();
-
- netCap.mNetworkCapabilities = in.readLong();
- netCap.mForbiddenNetworkCapabilities = in.readLong();
- netCap.mTransportTypes = in.readLong();
+ // Validate the unparceled data, in case the parceling party was malicious.
+ netCap.mNetworkCapabilities = in.readLong() & ALL_VALID_CAPABILITIES;
+ netCap.mForbiddenNetworkCapabilities = in.readLong() & ALL_VALID_CAPABILITIES;
+ netCap.mTransportTypes = in.readLong() & ALL_VALID_TRANSPORTS;
netCap.mLinkUpBandwidthKbps = in.readInt();
netCap.mLinkDownBandwidthKbps = in.readInt();
netCap.mNetworkSpecifier = in.readParcelable(null);
@@ -2167,7 +2193,7 @@
netCap.mSubIds.add(subIdInts[i]);
}
netCap.setUnderlyingNetworks(in.createTypedArrayList(Network.CREATOR));
- netCap.mEnterpriseId = in.readInt();
+ netCap.mEnterpriseId = in.readInt() & ALL_VALID_ENTERPRISE_IDS;
return netCap;
}
@Override
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
index 01dc4bb..a731b23 100644
--- a/framework/src/android/net/QosFilter.java
+++ b/framework/src/android/net/QosFilter.java
@@ -97,10 +97,15 @@
* Determines whether or not the parameter will be matched with this filter.
*
* @param protocol the protocol such as TCP or UDP included in IP packet filter set of a QoS
- * flow assigned on {@link Network}.
+ * flow assigned on {@link Network}. Only {@code IPPROTO_TCP} and {@code
+ * IPPROTO_UDP} currently supported.
* @return whether the parameters match the socket type of the filter
- * @hide
*/
- public abstract boolean matchesProtocol(int protocol);
+ // Since this method is added in U, it's required to be default method for binary compatibility
+ // with existing @SystemApi.
+ // IPPROTO_* are not compile-time constants, so they are not annotated with @IntDef.
+ public boolean matchesProtocol(int protocol) {
+ return false;
+ }
}
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
index da9b356..1c3db23 100644
--- a/framework/src/android/net/QosSocketInfo.java
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -144,7 +144,6 @@
*
* @param network the network
* @param socket the bound {@link DatagramSocket}
- * @hide
*/
public QosSocketInfo(@NonNull final Network network, @NonNull final DatagramSocket socket)
throws IOException {
diff --git a/framework/src/android/net/SocketNotConnectedException.java b/framework/src/android/net/SocketNotConnectedException.java
index fa2a615..a6357c7 100644
--- a/framework/src/android/net/SocketNotConnectedException.java
+++ b/framework/src/android/net/SocketNotConnectedException.java
@@ -16,13 +16,18 @@
package android.net;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Thrown when a previously bound socket becomes unbound.
*
* @hide
*/
+@SystemApi
public class SocketNotConnectedException extends Exception {
- /** @hide */
+ @VisibleForTesting
public SocketNotConnectedException() {
super("The socket is not connected");
}
diff --git a/framework/src/android/net/SocketRemoteAddressChangedException.java b/framework/src/android/net/SocketRemoteAddressChangedException.java
index ecaeebc..e13d5ed 100644
--- a/framework/src/android/net/SocketRemoteAddressChangedException.java
+++ b/framework/src/android/net/SocketRemoteAddressChangedException.java
@@ -16,13 +16,18 @@
package android.net;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Thrown when the local address of the socket has changed.
*
* @hide
*/
+@SystemApi
public class SocketRemoteAddressChangedException extends Exception {
- /** @hide */
+ @VisibleForTesting
public SocketRemoteAddressChangedException() {
super("The remote address of the socket changed");
}
diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java
deleted file mode 100644
index 2dcf932..0000000
--- a/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2016 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 android.net.util;
-
-import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
-import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
-
-import android.annotation.NonNull;
-import android.annotation.TargetApi;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.net.ConnectivityResources;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.provider.Settings;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyCallback;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-
-/**
- * A class to encapsulate management of the "Smart Networking" capability of
- * avoiding bad Wi-Fi when, for example upstream connectivity is lost or
- * certain critical link failures occur.
- *
- * This enables the device to switch to another form of connectivity, like
- * mobile, if it's available and working.
- *
- * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied
- * Handler' whenever the computed "avoid bad wifi" value changes.
- *
- * Disabling this reverts the device to a level of networking sophistication
- * circa 2012-13 by disabling disparate code paths each of which contribute to
- * maintaining continuous, working Internet connectivity.
- *
- * @hide
- */
-public class MultinetworkPolicyTracker {
- private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
-
- private final Context mContext;
- private final ConnectivityResources mResources;
- private final Handler mHandler;
- private final Runnable mAvoidBadWifiCallback;
- private final List<Uri> mSettingsUris;
- private final ContentResolver mResolver;
- private final SettingObserver mSettingObserver;
- private final BroadcastReceiver mBroadcastReceiver;
-
- private volatile boolean mAvoidBadWifi = true;
- private volatile int mMeteredMultipathPreference;
- private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
- private volatile long mTestAllowBadWifiUntilMs = 0;
-
- // Mainline module can't use internal HandlerExecutor, so add an identical executor here.
- private static class HandlerExecutor implements Executor {
- @NonNull
- private final Handler mHandler;
-
- HandlerExecutor(@NonNull Handler handler) {
- mHandler = handler;
- }
- @Override
- public void execute(Runnable command) {
- if (!mHandler.post(command)) {
- throw new RejectedExecutionException(mHandler + " is shutting down");
- }
- }
- }
- // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
- @VisibleForTesting @TargetApi(Build.VERSION_CODES.S)
- protected class ActiveDataSubscriptionIdListener extends TelephonyCallback
- implements TelephonyCallback.ActiveDataSubscriptionIdListener {
- @Override
- public void onActiveDataSubscriptionIdChanged(int subId) {
- mActiveSubId = subId;
- reevaluateInternal();
- }
- }
-
- public MultinetworkPolicyTracker(Context ctx, Handler handler) {
- this(ctx, handler, null);
- }
-
- public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
- mContext = ctx;
- mResources = new ConnectivityResources(ctx);
- mHandler = handler;
- mAvoidBadWifiCallback = avoidBadWifiCallback;
- mSettingsUris = Arrays.asList(
- Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
- Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
- mResolver = mContext.getContentResolver();
- mSettingObserver = new SettingObserver();
- mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- reevaluateInternal();
- }
- };
-
- updateAvoidBadWifi();
- updateMeteredMultipathPreference();
- }
-
- // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
- @TargetApi(Build.VERSION_CODES.S)
- public void start() {
- for (Uri uri : mSettingsUris) {
- mResolver.registerContentObserver(uri, false, mSettingObserver);
- }
-
- final IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
- mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter,
- null /* broadcastPermission */, mHandler);
-
- mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback(
- new HandlerExecutor(mHandler), new ActiveDataSubscriptionIdListener());
-
- reevaluate();
- }
-
- public void shutdown() {
- mResolver.unregisterContentObserver(mSettingObserver);
-
- mContext.unregisterReceiver(mBroadcastReceiver);
- }
-
- public boolean getAvoidBadWifi() {
- return mAvoidBadWifi;
- }
-
- // TODO: move this to MultipathPolicyTracker.
- public int getMeteredMultipathPreference() {
- return mMeteredMultipathPreference;
- }
-
- /**
- * Whether the device or carrier configuration disables avoiding bad wifi by default.
- */
- public boolean configRestrictsAvoidBadWifi() {
- final boolean allowBadWifi = mTestAllowBadWifiUntilMs > 0
- && mTestAllowBadWifiUntilMs > System.currentTimeMillis();
- // If the config returns true, then avoid bad wifi design can be controlled by the
- // NETWORK_AVOID_BAD_WIFI setting.
- if (allowBadWifi) return true;
-
- // TODO: use R.integer.config_networkAvoidBadWifi directly
- final int id = mResources.get().getIdentifier("config_networkAvoidBadWifi",
- "integer", mResources.getResourcesContext().getPackageName());
- return (getResourcesForActiveSubId().getInteger(id) == 0);
- }
-
- /**
- * Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration.
- * The value works when the time set is more than {@link System.currentTimeMillis()}.
- */
- public void setTestAllowBadWifiUntil(long timeMs) {
- Log.d(TAG, "setTestAllowBadWifiUntil: " + timeMs);
- mTestAllowBadWifiUntilMs = timeMs;
- reevaluateInternal();
- }
-
- @VisibleForTesting
- @NonNull
- protected Resources getResourcesForActiveSubId() {
- return SubscriptionManager.getResourcesForSubId(
- mResources.getResourcesContext(), mActiveSubId);
- }
-
- /**
- * Whether we should display a notification when wifi becomes unvalidated.
- */
- public boolean shouldNotifyWifiUnvalidated() {
- return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
- }
-
- public String getAvoidBadWifiSetting() {
- return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
- }
-
- @VisibleForTesting
- public void reevaluate() {
- mHandler.post(this::reevaluateInternal);
- }
-
- /**
- * Reevaluate the settings. Must be called on the handler thread.
- */
- private void reevaluateInternal() {
- if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) {
- mAvoidBadWifiCallback.run();
- }
- updateMeteredMultipathPreference();
- }
-
- public boolean updateAvoidBadWifi() {
- final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
- final boolean prev = mAvoidBadWifi;
- mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
- return mAvoidBadWifi != prev;
- }
-
- /**
- * The default (device and carrier-dependent) value for metered multipath preference.
- */
- public int configMeteredMultipathPreference() {
- // TODO: use R.integer.config_networkMeteredMultipathPreference directly
- final int id = mResources.get().getIdentifier("config_networkMeteredMultipathPreference",
- "integer", mResources.getResourcesContext().getPackageName());
- return mResources.get().getInteger(id);
- }
-
- public void updateMeteredMultipathPreference() {
- String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE);
- try {
- mMeteredMultipathPreference = Integer.parseInt(setting);
- } catch (NumberFormatException e) {
- mMeteredMultipathPreference = configMeteredMultipathPreference();
- }
- }
-
- private class SettingObserver extends ContentObserver {
- public SettingObserver() {
- super(null);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- Log.wtf(TAG, "Should never be reached.");
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (!mSettingsUris.contains(uri)) {
- Log.wtf(TAG, "Unexpected settings observation: " + uri);
- }
- reevaluate();
- }
- }
-}
diff --git a/nearby/halfsheet/res/values-ro/strings.xml b/nearby/halfsheet/res/values-ro/strings.xml
index 5b50f15..189f698 100644
--- a/nearby/halfsheet/res/values-ro/strings.xml
+++ b/nearby/halfsheet/res/values-ro/strings.xml
@@ -18,12 +18,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="fast_pair_setup_in_progress" msgid="4158762239172829807">"Începe configurarea…"</string>
- <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurați dispozitivul"</string>
+ <string name="fast_pair_title_setup" msgid="2894360355540593246">"Configurează dispozitivul"</string>
<string name="fast_pair_device_ready" msgid="2903490346082833101">"Dispozitivul s-a conectat"</string>
<string name="fast_pair_title_fail" msgid="5677174346601290232">"Nu s-a putut conecta"</string>
<string name="paring_action_done" msgid="6888875159174470731">"Gata"</string>
- <string name="paring_action_save" msgid="6259357442067880136">"Salvați"</string>
- <string name="paring_action_connect" msgid="4801102939608129181">"Conectați"</string>
- <string name="paring_action_launch" msgid="8940808384126591230">"Configurați"</string>
+ <string name="paring_action_save" msgid="6259357442067880136">"Salvează"</string>
+ <string name="paring_action_connect" msgid="4801102939608129181">"Conectează"</string>
+ <string name="paring_action_launch" msgid="8940808384126591230">"Configurează"</string>
<string name="paring_action_settings" msgid="424875657242864302">"Setări"</string>
</resources>
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
index 1daa410..eaa5ca1 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
@@ -54,7 +54,7 @@
private static final byte[] SECRETE_ID = new byte[]{1, 2, 3, 4};
private static final byte[] AUTHENTICITY_KEY = new byte[]{0, 1, 1, 1};
private static final byte[] METADATA_ENCRYPTION_KEY = new byte[]{1, 1, 3, 4, 5};
- private static final int KEY = 1234;
+ private static final int KEY = 3;
private static final byte[] VALUE = new byte[]{1, 1, 1, 1};
private static final String DEVICE_NAME = "test_device";
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
index 5fefc68..94f8fe7 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
@@ -45,7 +45,7 @@
private static final int RSSI = -40;
private static final int MEDIUM = NearbyDevice.Medium.BLE;
private static final String DEVICE_NAME = "testDevice";
- private static final int KEY = 1234;
+ private static final int KEY = 3;
private static final byte[] VALUE = new byte[]{1, 1, 1, 1};
private static final byte[] SALT = new byte[]{2, 3};
private static final byte[] SECRET_ID = new byte[]{11, 13};
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
index b7fe40a..cecdfd2 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
@@ -48,7 +48,7 @@
private static final byte[] PUBLIC_KEY = new byte[]{1, 1, 2, 2};
private static final byte[] ENCRYPTED_METADATA = new byte[]{1, 2, 3, 4, 5};
private static final byte[] METADATA_ENCRYPTION_KEY_TAG = new byte[]{1, 1, 3, 4, 5};
- private static final int KEY = 1234;
+ private static final int KEY = 3;
private static final byte[] VALUE = new byte[]{1, 1, 1, 1};
diff --git a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
index 56c21eb..2196bf8 100644
--- a/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
+++ b/service-t/src/com/android/server/ethernet/EthernetNetworkFactory.java
@@ -586,6 +586,14 @@
}
private void stop() {
+ // Unregister NetworkAgent before stopping IpClient, so destroyNativeNetwork (which
+ // deletes routes) hopefully happens before stop() finishes execution. Otherwise, it may
+ // delete the new routes when IpClient gets restarted.
+ if (mNetworkAgent != null) {
+ mNetworkAgent.unregister();
+ mNetworkAgent = null;
+ }
+
// Invalidate all previous start requests
if (mIpClient != null) {
mIpClient.shutdown();
@@ -595,10 +603,6 @@
mIpClientCallback = null;
- if (mNetworkAgent != null) {
- mNetworkAgent.unregister();
- mNetworkAgent = null;
- }
mLinkProperties.clear();
}
diff --git a/service/Android.bp b/service/Android.bp
index 7d3b39a..0a00362 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -223,6 +223,9 @@
name: "service-connectivity-defaults",
sdk_version: "system_server_current",
min_sdk_version: "30",
+ defaults: [
+ "standalone-system-server-module-optimize-defaults",
+ ],
// This library combines system server jars that have access to different bootclasspath jars.
// Lower SDK service jars must not depend on higher SDK jars as that would let them
// transitively depend on the wrong bootclasspath jars. Sources also cannot be added here as
@@ -252,8 +255,6 @@
"com.android.tethering",
],
optimize: {
- enabled: true,
- shrink: true,
proguard_flags_files: ["proguard.flags"],
},
lint: { strict_updatability_linting: true },
diff --git a/service/ServiceConnectivityResources/res/values-kk/strings.xml b/service/ServiceConnectivityResources/res/values-kk/strings.xml
index 00c0f39..efe23b6 100644
--- a/service/ServiceConnectivityResources/res/values-kk/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-kk/strings.xml
@@ -33,7 +33,7 @@
<string name="network_switch_metered_detail" msgid="1257300152739542096">"Құрылғы <xliff:g id="PREVIOUS_NETWORK">%2$s</xliff:g> желісінде интернетпен байланыс жоғалған жағдайда <xliff:g id="NEW_NETWORK">%1$s</xliff:g> желісін пайдаланады. Деректер ақысы алынуы мүмкін."</string>
<string name="network_switch_metered_toast" msgid="70691146054130335">"<xliff:g id="PREVIOUS_NETWORK">%1$s</xliff:g> желісінен <xliff:g id="NEW_NETWORK">%2$s</xliff:g> желісіне ауысты"</string>
<string-array name="network_switch_type_name">
- <item msgid="3004933964374161223">"мобильдік деректер"</item>
+ <item msgid="3004933964374161223">"мобильдік интернет"</item>
<item msgid="5624324321165953608">"Wi-Fi"</item>
<item msgid="5667906231066981731">"Bluetooth"</item>
<item msgid="346574747471703768">"Ethernet"</item>
diff --git a/service/ServiceConnectivityResources/res/values-ro/strings.xml b/service/ServiceConnectivityResources/res/values-ro/strings.xml
index fa5848f..4ff5290 100644
--- a/service/ServiceConnectivityResources/res/values-ro/strings.xml
+++ b/service/ServiceConnectivityResources/res/values-ro/strings.xml
@@ -18,12 +18,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="connectivityResourcesAppLabel" msgid="2476261877900882974">"Resurse pentru conectivitatea sistemului"</string>
- <string name="wifi_available_sign_in" msgid="8041178343789805553">"Conectați-vă la rețeaua Wi-Fi"</string>
- <string name="network_available_sign_in" msgid="2622520134876355561">"Conectați-vă la rețea"</string>
+ <string name="wifi_available_sign_in" msgid="8041178343789805553">"Conectează-te la rețeaua Wi-Fi"</string>
+ <string name="network_available_sign_in" msgid="2622520134876355561">"Conectează-te la rețea"</string>
<!-- no translation found for network_available_sign_in_detailed (8439369644697866359) -->
<skip />
<string name="wifi_no_internet" msgid="1326348603404555475">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> nu are acces la internet"</string>
- <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Atingeți pentru opțiuni"</string>
+ <string name="wifi_no_internet_detailed" msgid="1746921096565304090">"Atinge pentru opțiuni"</string>
<string name="mobile_no_internet" msgid="4087718456753201450">"Rețeaua mobilă nu are acces la internet"</string>
<string name="other_networks_no_internet" msgid="5693932964749676542">"Rețeaua nu are acces la internet"</string>
<string name="private_dns_broken_detailed" msgid="2677123850463207823">"Serverul DNS privat nu poate fi accesat"</string>
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index bff6953..22d9b01 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -78,6 +78,27 @@
Settings.Global.NETWORK_AVOID_BAD_WIFI. This is the default value of that setting. -->
<integer translatable="false" name="config_networkAvoidBadWifi">1</integer>
+ <!-- Whether the device should actively prefer bad wifi to good cell on Android 12/13.
+
+ This setting only makes sense if the system is configured not to avoid bad wifis
+ (config_networkAvoidBadWifi=0 and Settings.Global.NETWORK_AVOID_BAD_WIFI=IGNORE
+ or PROMPT), otherwise it's not used.
+
+ On Android 12 and 13, if this is 0, when ranking a bad wifi that never validated against
+ validated mobile data, the system will prefer mobile data. It will prefer wifi if wifi
+ loses validation later. This is the default behavior up to Android 13.
+ This behavior avoids the device losing internet access when walking past a wifi network
+ with no internet access.
+
+ If this is 1, then in the same scenario, the system will prefer mobile data until the wifi
+ completes its first validation attempt (or the attempt times out), and after that it
+ will prefer the wifi even if it doesn't provide internet access, unless there is a captive
+ portal on that wifi.
+
+ On Android 14 and above, the behavior is always like 1, regardless of the value of this
+ setting. -->
+ <integer translatable="false" name="config_activelyPreferBadWifi">0</integer>
+
<!-- Array of ConnectivityManager.TYPE_xxxx constants for networks that may only
be controlled by systemOrSignature apps. -->
<integer-array translatable="false" name="config_protectedNetworks">
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 3389d63..4c85e8c 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -24,6 +24,7 @@
<item type="integer" name="config_networkMeteredMultipathPreference"/>
<item type="array" name="config_networkSupportedKeepaliveCount"/>
<item type="integer" name="config_networkAvoidBadWifi"/>
+ <item type="integer" name="config_activelyPreferBadWifi"/>
<item type="array" name="config_protectedNetworks"/>
<item type="bool" name="config_vehicleInternalNetworkAlwaysRequested"/>
<item type="integer" name="config_networkWakeupPacketMark"/>
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 49b23c9..245b9d8 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -667,71 +667,6 @@
}
dw.blankline();
-
- uint32_t key = UID_RULES_CONFIGURATION_KEY;
- auto configuration = mConfigurationMap.readValue(key);
- if (configuration.ok()) {
- dw.println("current ownerMatch configuration: %d%s", configuration.value(),
- uidMatchTypeToString(configuration.value()).c_str());
- } else {
- dw.println("mConfigurationMap read ownerMatch configure failed with error: %s",
- configuration.error().message().c_str());
- }
-
- key = CURRENT_STATS_MAP_CONFIGURATION_KEY;
- configuration = mConfigurationMap.readValue(key);
- if (configuration.ok()) {
- const char* statsMapDescription = "???";
- switch (configuration.value()) {
- case SELECT_MAP_A:
- statsMapDescription = "SELECT_MAP_A";
- break;
- case SELECT_MAP_B:
- statsMapDescription = "SELECT_MAP_B";
- break;
- // No default clause, so if we ever add a third map, this code will fail to build.
- }
- dw.println("current statsMap configuration: %d %s", configuration.value(),
- statsMapDescription);
- } else {
- dw.println("mConfigurationMap read stats map configure failed with error: %s",
- configuration.error().message().c_str());
- }
- dumpBpfMap("mUidOwnerMap", dw, "");
- const auto printUidMatchInfo = [&dw, this](const uint32_t& key, const UidOwnerValue& value,
- const BpfMap<uint32_t, UidOwnerValue>&) {
- if (value.rule & IIF_MATCH) {
- auto ifname = mIfaceIndexNameMap.readValue(value.iif);
- if (ifname.ok()) {
- dw.println("%u %s %s", key, uidMatchTypeToString(value.rule).c_str(),
- ifname.value().name);
- } else {
- dw.println("%u %s %u", key, uidMatchTypeToString(value.rule).c_str(), value.iif);
- }
- } else {
- dw.println("%u %s", key, uidMatchTypeToString(value.rule).c_str());
- }
- return base::Result<void>();
- };
- res = mUidOwnerMap.iterateWithValue(printUidMatchInfo);
- if (!res.ok()) {
- dw.println("mUidOwnerMap print end with error: %s", res.error().message().c_str());
- }
- dumpBpfMap("mUidPermissionMap", dw, "");
- const auto printUidPermissionInfo = [&dw](const uint32_t& key, const int& value,
- const BpfMap<uint32_t, uint8_t>&) {
- dw.println("%u %s", key, UidPermissionTypeToString(value).c_str());
- return base::Result<void>();
- };
- res = mUidPermissionMap.iterateWithValue(printUidPermissionInfo);
- if (!res.ok()) {
- dw.println("mUidPermissionMap print end with error: %s", res.error().message().c_str());
- }
-
- dumpBpfMap("mPrivilegedUser", dw, "");
- for (uid_t uid : mPrivilegedUser) {
- dw.println("%u ALLOW_UPDATE_DEVICE_STATS", (uint32_t)uid);
- }
}
} // namespace net
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 8525738..2f5960f 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -793,16 +793,6 @@
"mCookieTagMap:",
fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID)};
- ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, HAPPY_BOX_MATCH,
- TrafficController::IptOpInsert)));
- expectedLines.emplace_back("mUidOwnerMap:");
- expectedLines.emplace_back(fmt::format("{} HAPPY_BOX_MATCH", TEST_UID));
-
- mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, {TEST_UID2});
- expectedLines.emplace_back("mUidPermissionMap:");
- expectedLines.emplace_back(fmt::format("{} BPF_PERMISSION_UPDATE_DEVICE_STATS", TEST_UID2));
- expectedLines.emplace_back("mPrivilegedUser:");
- expectedLines.emplace_back(fmt::format("{} ALLOW_UPDATE_DEVICE_STATS", TEST_UID2));
EXPECT_TRUE(expectDumpsysContains(expectedLines));
}
@@ -813,59 +803,13 @@
"Bad file descriptor";
const std::string kErrReadRulesConfig = "read ownerMatch configure failed with error: "
"Read value of map -1 failed: Bad file descriptor";
- const std::string kErrReadStatsMapConfig = "read stats map configure failed with error: "
- "Read value of map -1 failed: Bad file descriptor";
std::vector<std::string> expectedLines = {
fmt::format("mCookieTagMap {}", kErrIterate),
- fmt::format("mIfaceStatsMap {}", kErrIterate),
- fmt::format("mConfigurationMap {}", kErrReadRulesConfig),
- fmt::format("mConfigurationMap {}", kErrReadStatsMapConfig),
- fmt::format("mUidOwnerMap {}", kErrIterate),
- fmt::format("mUidPermissionMap {}", kErrIterate)};
+ fmt::format("mIfaceStatsMap {}", kErrIterate)};
EXPECT_TRUE(expectDumpsysContains(expectedLines));
}
-TEST_F(TrafficControllerTest, uidMatchTypeToString) {
- // NO_MATCH(0) can't be verified because match type flag is added by OR operator.
- // See TrafficController::addRule()
- static const struct TestConfig {
- UidOwnerMatchType uidOwnerMatchType;
- std::string expected;
- } testConfigs[] = {
- // clang-format off
- {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
- {DOZABLE_MATCH, "DOZABLE_MATCH"},
- {STANDBY_MATCH, "STANDBY_MATCH"},
- {POWERSAVE_MATCH, "POWERSAVE_MATCH"},
- {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
- {RESTRICTED_MATCH, "RESTRICTED_MATCH"},
- {LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"},
- {IIF_MATCH, "IIF_MATCH"},
- {LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"},
- {OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"},
- {OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"},
- {OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH"},
- // clang-format on
- };
-
- for (const auto& config : testConfigs) {
- SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.uidOwnerMatchType,
- config.expected));
-
- // Test private function uidMatchTypeToString() via dumpsys.
- ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
- TrafficController::IptOpInsert)));
- std::vector<std::string> expectedLines;
- expectedLines.emplace_back(fmt::format("{} {}", TEST_UID, config.expected));
- EXPECT_TRUE(expectDumpsysContains(expectedLines));
-
- // Clean up the stubs.
- ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
- TrafficController::IptOpDelete)));
- }
-}
-
TEST_F(TrafficControllerTest, getFirewallType) {
static const struct TestConfig {
ChildChain childChain;
diff --git a/service/proguard.flags b/service/proguard.flags
index f546e82..478566c 100644
--- a/service/proguard.flags
+++ b/service/proguard.flags
@@ -1,15 +1,8 @@
-# Make sure proguard keeps all connectivity classes
-# TODO: instead of keeping everything, consider listing only "entry points"
-# (service loader, JNI registered methods, etc) and letting the optimizer do its job
--keep class android.net.** { *; }
--keep class !com.android.server.nearby.**,com.android.server.** { *; }
-# Prevent proguard from stripping out any nearby-service and fast-pair-lite-protos fields.
--keep class com.android.server.nearby.NearbyService { *; }
+# Keep JNI registered methods
+-keepclasseswithmembers,includedescriptorclasses class * { native <methods>; }
-# The lite proto runtime uses reflection to access fields based on the names in
-# the schema, keep all the fields.
-# This replicates the base proguard rule used by the build by default
-# (proguard_basic_keeps.flags), but needs to be specified here because the
-# com.google.protobuf package is jarjared to use a package prefix.
--keepclassmembers class * extends **.com.google.protobuf.MessageLite { <fields>; }
+# Keep classes extending structured message.
+-keepclassmembers public class * extends **.com.android.net.module.util.Struct {
+ *;
+}
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index dbfc383..d560747 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -27,7 +27,9 @@
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.INetd.PERMISSION_INTERNET;
+import static android.net.INetd.PERMISSION_NONE;
import static android.net.INetd.PERMISSION_UNINSTALLED;
+import static android.net.INetd.PERMISSION_UPDATE_DEVICE_STATS;
import static android.system.OsConstants.EINVAL;
import static android.system.OsConstants.ENODEV;
import static android.system.OsConstants.ENOENT;
@@ -44,12 +46,15 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Log;
+import android.util.Pair;
import android.util.StatsEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.BackgroundThread;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.DeviceConfigUtils;
import com.android.net.module.util.IBpfMap;
@@ -62,8 +67,10 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.util.Arrays;
import java.util.List;
import java.util.Set;
+import java.util.StringJoiner;
/**
* BpfNetMaps is responsible for providing traffic controller relevant functionality.
@@ -134,6 +141,25 @@
@VisibleForTesting public static final long OEM_DENY_3_MATCH = (1 << 11);
// LINT.ThenChange(packages/modules/Connectivity/bpf_progs/bpf_shared.h)
+ private static final List<Pair<Integer, String>> PERMISSION_LIST = Arrays.asList(
+ Pair.create(PERMISSION_INTERNET, "PERMISSION_INTERNET"),
+ Pair.create(PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS")
+ );
+ private static final List<Pair<Long, String>> MATCH_LIST = Arrays.asList(
+ Pair.create(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"),
+ Pair.create(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH"),
+ Pair.create(DOZABLE_MATCH, "DOZABLE_MATCH"),
+ Pair.create(STANDBY_MATCH, "STANDBY_MATCH"),
+ Pair.create(POWERSAVE_MATCH, "POWERSAVE_MATCH"),
+ Pair.create(RESTRICTED_MATCH, "RESTRICTED_MATCH"),
+ Pair.create(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"),
+ Pair.create(IIF_MATCH, "IIF_MATCH"),
+ Pair.create(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"),
+ Pair.create(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"),
+ Pair.create(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"),
+ Pair.create(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH")
+ );
+
/**
* Set sEnableJavaBpfMap for test.
*/
@@ -914,14 +940,80 @@
return StatsManager.PULL_SUCCESS;
}
+ private String permissionToString(int permissionMask) {
+ if (permissionMask == PERMISSION_NONE) {
+ return "PERMISSION_NONE";
+ }
+ if (permissionMask == PERMISSION_UNINSTALLED) {
+ // PERMISSION_UNINSTALLED should never appear in the map
+ return "PERMISSION_UNINSTALLED error!";
+ }
+
+ final StringJoiner sj = new StringJoiner(" ");
+ for (Pair<Integer, String> permission: PERMISSION_LIST) {
+ final int permissionFlag = permission.first;
+ final String permissionName = permission.second;
+ if ((permissionMask & permissionFlag) != 0) {
+ sj.add(permissionName);
+ permissionMask &= ~permissionFlag;
+ }
+ }
+ if (permissionMask != 0) {
+ sj.add("PERMISSION_UNKNOWN(" + permissionMask + ")");
+ }
+ return sj.toString();
+ }
+
+ private String matchToString(long matchMask) {
+ if (matchMask == NO_MATCH) {
+ return "NO_MATCH";
+ }
+
+ final StringJoiner sj = new StringJoiner(" ");
+ for (Pair<Long, String> match: MATCH_LIST) {
+ final long matchFlag = match.first;
+ final String matchName = match.second;
+ if ((matchMask & matchFlag) != 0) {
+ sj.add(matchName);
+ matchMask &= ~matchFlag;
+ }
+ }
+ if (matchMask != 0) {
+ sj.add("UNKNOWN_MATCH(" + matchMask + ")");
+ }
+ return sj.toString();
+ }
+
+ private void dumpOwnerMatchConfig(final IndentingPrintWriter pw) {
+ try {
+ final long match = sConfigurationMap.getValue(UID_RULES_CONFIGURATION_KEY).val;
+ pw.println("current ownerMatch configuration: " + match + " " + matchToString(match));
+ } catch (ErrnoException e) {
+ pw.println("Failed to read ownerMatch configuration: " + e);
+ }
+ }
+
+ private void dumpCurrentStatsMapConfig(final IndentingPrintWriter pw) {
+ try {
+ final long config = sConfigurationMap.getValue(CURRENT_STATS_MAP_CONFIGURATION_KEY).val;
+ final String currentStatsMap =
+ (config == STATS_SELECT_MAP_A) ? "SELECT_MAP_A" : "SELECT_MAP_B";
+ pw.println("current statsMap configuration: " + config + " " + currentStatsMap);
+ } catch (ErrnoException e) {
+ pw.println("Falied to read current statsMap configuration: " + e);
+ }
+ }
+
/**
* Dump BPF maps
*
+ * @param pw print writer
* @param fd file descriptor to output
+ * @param verbose verbose dump flag, if true dump the BpfMap contents
* @throws IOException when file descriptor is invalid.
* @throws ServiceSpecificException when the method is called on an unsupported device.
*/
- public void dump(final FileDescriptor fd, boolean verbose)
+ public void dump(final IndentingPrintWriter pw, final FileDescriptor fd, boolean verbose)
throws IOException, ServiceSpecificException {
if (PRE_T) {
throw new ServiceSpecificException(
@@ -929,6 +1021,24 @@
+ " devices, use dumpsys netd trafficcontroller instead.");
}
mDeps.nativeDump(fd, verbose);
+
+ if (verbose) {
+ dumpOwnerMatchConfig(pw);
+ dumpCurrentStatsMapConfig(pw);
+ pw.println();
+
+ BpfDump.dumpMap(sUidOwnerMap, pw, "sUidOwnerMap",
+ (uid, match) -> {
+ if ((match.rule & IIF_MATCH) != 0) {
+ // TODO: convert interface index to interface name by IfaceIndexNameMap
+ return uid.val + " " + matchToString(match.rule) + " " + match.iif;
+ } else {
+ return uid.val + " " + matchToString(match.rule);
+ }
+ });
+ BpfDump.dumpMap(sUidPermissionMap, pw, "sUidPermissionMap",
+ (uid, permission) -> uid.val + " " + permissionToString(permission.val));
+ }
}
private static native void native_init(boolean startSkDestroyListener);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index db0e9e5..10b3dc8 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -205,7 +205,6 @@
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig;
-import android.net.util.MultinetworkPolicyTracker;
import android.net.wifi.WifiInfo;
import android.os.BatteryStatsManager;
import android.os.Binder;
@@ -272,6 +271,7 @@
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
+import com.android.server.connectivity.MultinetworkPolicyTracker;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.NetworkNotificationManager;
@@ -357,7 +357,10 @@
// of connectivity (valid, partial, captive portal). If none has been detected after this
// delay, the stack considers this network bad, which may affect how it's handled in ranking
// according to config_networkAvoidBadWifi.
- private static final int INITIAL_EVALUATION_TIMEOUT_MS = 20 * 1000;
+ // Timeout in case the "actively prefer bad wifi" feature is on
+ private static final int ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS = 20 * 1000;
+ // Timeout in case the "actively prefer bad wifi" feature is off
+ private static final int DONT_ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS = 8 * 1000;
// Default to 30s linger time-out, and 5s for nascent network. Modifiable only for testing.
private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger";
@@ -783,7 +786,8 @@
final ConnectivityDiagnosticsHandler mConnectivityDiagnosticsHandler;
private final DnsManager mDnsManager;
- private final NetworkRanker mNetworkRanker;
+ @VisibleForTesting
+ final NetworkRanker mNetworkRanker;
private boolean mSystemReady;
private Intent mInitialBroadcast;
@@ -1417,7 +1421,6 @@
new RequestInfoPerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1);
mMetricsLog = logger;
- mNetworkRanker = new NetworkRanker();
final NetworkRequest defaultInternetRequest = createDefaultRequest();
mDefaultRequest = new NetworkRequestInfo(
Process.myUid(), defaultInternetRequest, null,
@@ -1538,6 +1541,9 @@
mMultinetworkPolicyTracker = mDeps.makeMultinetworkPolicyTracker(
mContext, mHandler, () -> updateAvoidBadWifi());
+ mNetworkRanker =
+ new NetworkRanker(new NetworkRanker.Configuration(activelyPreferBadWifi()));
+
mMultinetworkPolicyTracker.start();
mDnsManager = new DnsManager(mContext, mDnsResolver);
@@ -3454,7 +3460,7 @@
private void dumpTrafficController(IndentingPrintWriter pw, final FileDescriptor fd,
boolean verbose) {
try {
- mBpfNetMaps.dump(fd, verbose);
+ mBpfNetMaps.dump(pw, fd, verbose);
} catch (ServiceSpecificException e) {
pw.println(e.getMessage());
} catch (IOException e) {
@@ -3745,17 +3751,6 @@
case EVENT_PROVISIONING_NOTIFICATION: {
final boolean visible = toBool(msg.arg1);
// If captive portal status has changed, update capabilities or disconnect.
- if (nai != null && (visible != nai.captivePortalDetected())) {
- nai.setCaptivePortalDetected(visible);
- if (visible && ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
- == getCaptivePortalMode()) {
- if (DBG) log("Avoiding captive portal network: " + nai.toShortString());
- nai.onPreventAutomaticReconnect();
- teardownUnneededNetwork(nai);
- break;
- }
- updateCapabilitiesForNetwork(nai);
- }
if (!visible) {
// Only clear SIGN_IN and NETWORK_SWITCH notifications here, or else other
// notifications belong to the same network may be cleared unexpectedly.
@@ -3793,13 +3788,18 @@
@NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) {
final boolean valid = (testResult & NETWORK_VALIDATION_RESULT_VALID) != 0;
final boolean partial = (testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0;
- final boolean captive = !TextUtils.isEmpty(redirectUrl);
+ final boolean portal = !TextUtils.isEmpty(redirectUrl);
// If there is any kind of working networking, then the NAI has been evaluated
// once. {@see NetworkAgentInfo#setEvaluated}, which returns whether this is
// the first time this ever happened.
- final boolean someConnectivity = (valid || partial || captive);
+ final boolean someConnectivity = (valid || partial || portal);
final boolean becameEvaluated = someConnectivity && nai.setEvaluated();
+ // Because of b/245893397, if the score is updated when updateCapabilities is called,
+ // any callback that receives onAvailable for that rematch receives an extra caps
+ // callback. To prevent that, update the score in the agent so the updates below won't
+ // see an update to both caps and score at the same time.
+ // TODO : fix b/245893397 and remove this.
if (becameEvaluated) nai.updateScoreForNetworkAgentUpdate();
if (!valid && shouldIgnoreValidationFailureAfterRoam(nai)) {
@@ -3812,8 +3812,12 @@
final boolean wasValidated = nai.isValidated();
final boolean wasPartial = nai.partialConnectivity();
- nai.setPartialConnectivity((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
- final boolean partialConnectivityChanged = (wasPartial != nai.partialConnectivity());
+ final boolean wasPortal = nai.captivePortalDetected();
+ nai.setPartialConnectivity(partial);
+ nai.setCaptivePortalDetected(portal);
+ nai.updateScoreForNetworkAgentUpdate();
+ final boolean partialConnectivityChanged = (wasPartial != partial);
+ final boolean portalChanged = (wasPortal != portal);
if (DBG) {
final String logMsg = !TextUtils.isEmpty(redirectUrl)
@@ -3821,7 +3825,7 @@
: "";
log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg);
}
- if (valid != nai.isValidated()) {
+ if (valid != wasValidated) {
final FullScore oldScore = nai.getScore();
nai.setValidated(valid);
updateCapabilities(oldScore, nai, nai.networkCapabilities);
@@ -3844,6 +3848,16 @@
}
} else if (partialConnectivityChanged) {
updateCapabilitiesForNetwork(nai);
+ } else if (portalChanged) {
+ if (portal && ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
+ == getCaptivePortalMode()) {
+ if (DBG) log("Avoiding captive portal network: " + nai.toShortString());
+ nai.onPreventAutomaticReconnect();
+ teardownUnneededNetwork(nai);
+ return;
+ } else {
+ updateCapabilitiesForNetwork(nai);
+ }
} else if (becameEvaluated) {
// If valid or partial connectivity changed, updateCapabilities* has
// done the rematch.
@@ -5053,6 +5067,10 @@
return mMultinetworkPolicyTracker.getAvoidBadWifi();
}
+ private boolean activelyPreferBadWifi() {
+ return mMultinetworkPolicyTracker.getActivelyPreferBadWifi();
+ }
+
/**
* Return whether the device should maintain continuous, working connectivity by switching away
* from WiFi networks having no connectivity.
@@ -5068,14 +5086,21 @@
private void updateAvoidBadWifi() {
ensureRunningOnConnectivityServiceThread();
// Agent info scores and offer scores depend on whether cells yields to bad wifi.
+ final boolean avoidBadWifi = avoidBadWifi();
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
nai.updateScoreForNetworkAgentUpdate();
+ if (avoidBadWifi) {
+ // If the device is now avoiding bad wifi, remove notifications that might have
+ // been put up when the device didn't.
+ mNotifier.clearNotification(nai.network.getNetId(), NotificationType.LOST_INTERNET);
+ }
}
// UpdateOfferScore will update mNetworkOffers inline, so make a copy first.
final ArrayList<NetworkOfferInfo> offersToUpdate = new ArrayList<>(mNetworkOffers);
for (final NetworkOfferInfo noi : offersToUpdate) {
updateOfferScore(noi.offer);
}
+ mNetworkRanker.setConfiguration(new NetworkRanker.Configuration(activelyPreferBadWifi()));
rematchAllNetworksAndRequests();
}
@@ -5090,21 +5115,32 @@
pw.println("Bad Wi-Fi avoidance: " + avoidBadWifi());
pw.increaseIndent();
- pw.println("Config restrict: " + configRestrict);
+ pw.println("Config restrict: " + configRestrict);
+ pw.println("Actively prefer bad wifi: " + activelyPreferBadWifi());
- final String value = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
+ final String settingValue = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
String description;
// Can't use a switch statement because strings are legal case labels, but null is not.
- if ("0".equals(value)) {
+ if ("0".equals(settingValue)) {
description = "get stuck";
- } else if (value == null) {
+ } else if (settingValue == null) {
description = "prompt";
- } else if ("1".equals(value)) {
+ } else if ("1".equals(settingValue)) {
description = "avoid";
} else {
- description = value + " (?)";
+ description = settingValue + " (?)";
}
- pw.println("User setting: " + description);
+ pw.println("Avoid bad wifi setting: " + description);
+ final Boolean configValue = mMultinetworkPolicyTracker.deviceConfigActivelyPreferBadWifi();
+ if (null == configValue) {
+ description = "unset";
+ } else if (configValue) {
+ description = "force true";
+ } else {
+ description = "force false";
+ }
+ pw.println("Actively prefer bad wifi conf: " + description);
+ pw.println();
pw.println("Network overrides:");
pw.increaseIndent();
for (NetworkAgentInfo nai : networksSortedById()) {
@@ -5215,6 +5251,16 @@
// network was found not to have Internet access.
nai.updateScoreForNetworkAgentUpdate();
rematchAllNetworksAndRequests();
+
+ // Also, if this is WiFi and it should be preferred actively, now is the time to
+ // prompt the user that they walked past and connected to a bad WiFi.
+ if (nai.networkCapabilities.hasTransport(TRANSPORT_WIFI)
+ && !avoidBadWifi()
+ && activelyPreferBadWifi()) {
+ // The notification will be removed if the network validates or disconnects.
+ showNetworkNotification(nai, NotificationType.LOST_INTERNET);
+ return;
+ }
}
if (!shouldPromptUnvalidated(nai)) return;
@@ -9228,7 +9274,10 @@
networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
params.networkCapabilities);
}
- scheduleEvaluationTimeout(networkAgent.network, INITIAL_EVALUATION_TIMEOUT_MS);
+ final long delay = activelyPreferBadWifi()
+ ? ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS
+ : DONT_ACTIVELY_PREFER_BAD_WIFI_INITIAL_TIMEOUT_MS;
+ scheduleEvaluationTimeout(networkAgent.network, delay);
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
// be communicated to a particular NetworkAgent depends only on the network's immutable,
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index e1c7b64..d7c3287 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -595,13 +595,17 @@
Log.i(TAG, "untag socket cookie " + cookie);
}
+ private boolean isStarted() {
+ return mClatdTracker != null;
+ }
+
/**
* Start clatd for a given interface and NAT64 prefix.
*/
public String clatStart(final String iface, final int netId,
@NonNull final IpPrefix nat64Prefix)
throws IOException {
- if (mClatdTracker != null) {
+ if (isStarted()) {
throw new IOException("Clatd is already running on " + mClatdTracker.iface
+ " (pid " + mClatdTracker.pid + ")");
}
@@ -833,7 +837,7 @@
* Stop clatd
*/
public void clatStop() throws IOException {
- if (mClatdTracker == null) {
+ if (!isStarted()) {
throw new IOException("Clatd has not started");
}
Log.i(TAG, "Stopping clatd pid=" + mClatdTracker.pid + " on " + mClatdTracker.iface);
@@ -902,12 +906,16 @@
public void dump(@NonNull IndentingPrintWriter pw) {
// TODO: move map dump to a global place to avoid duplicate dump while there are two or
// more IPv6 only networks.
- pw.println("CLAT tracker: " + mClatdTracker.toString());
- pw.println("Forwarding rules:");
- pw.increaseIndent();
- dumpBpfIngress(pw);
- dumpBpfEgress(pw);
- pw.decreaseIndent();
+ if (isStarted()) {
+ pw.println("CLAT tracker: " + mClatdTracker.toString());
+ pw.println("Forwarding rules:");
+ pw.increaseIndent();
+ dumpBpfIngress(pw);
+ dumpBpfEgress(pw);
+ pw.decreaseIndent();
+ } else {
+ pw.println("<not started>");
+ }
pw.println();
}
diff --git a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
new file mode 100644
index 0000000..58196f7
--- /dev/null
+++ b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2016 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.connectivity;
+
+import static android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
+import static android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE;
+
+import android.annotation.NonNull;
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.ConnectivityResources;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Handler;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.connectivity.resources.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.DeviceConfigUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * A class to encapsulate management of the "Smart Networking" capability of
+ * avoiding bad Wi-Fi when, for example upstream connectivity is lost or
+ * certain critical link failures occur.
+ *
+ * This enables the device to switch to another form of connectivity, like
+ * mobile, if it's available and working.
+ *
+ * The Runnable |avoidBadWifiCallback|, if given, is posted to the supplied
+ * Handler' whenever the computed "avoid bad wifi" value changes.
+ *
+ * Disabling this reverts the device to a level of networking sophistication
+ * circa 2012-13 by disabling disparate code paths each of which contribute to
+ * maintaining continuous, working Internet connectivity.
+ *
+ * @hide
+ */
+public class MultinetworkPolicyTracker {
+ private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
+
+ // See Dependencies#getConfigActivelyPreferBadWifi
+ public static final String CONFIG_ACTIVELY_PREFER_BAD_WIFI = "actively_prefer_bad_wifi";
+
+ private final Context mContext;
+ private final ConnectivityResources mResources;
+ private final Handler mHandler;
+ private final Runnable mAvoidBadWifiCallback;
+ private final List<Uri> mSettingsUris;
+ private final ContentResolver mResolver;
+ private final SettingObserver mSettingObserver;
+ private final BroadcastReceiver mBroadcastReceiver;
+
+ private volatile boolean mAvoidBadWifi = true;
+ private volatile int mMeteredMultipathPreference;
+ private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private volatile long mTestAllowBadWifiUntilMs = 0;
+
+ /**
+ * Dependencies for testing
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * @see DeviceConfigUtils#getDeviceConfigPropertyInt
+ */
+ protected int getConfigActivelyPreferBadWifi() {
+ // CONFIG_ACTIVELY_PREFER_BAD_WIFI is not a feature to be rolled out, but an override
+ // for tests and an emergency kill switch (which could force the behavior on OR off).
+ // As such it uses a -1/null/1 scheme, but features should use
+ // DeviceConfigUtils#isFeatureEnabled instead, to make sure rollbacks disable the
+ // feature before it's ready on R and before.
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_CONNECTIVITY,
+ CONFIG_ACTIVELY_PREFER_BAD_WIFI, 0);
+ }
+
+ /**
+ @see DeviceConfig#addOnPropertiesChangedListener
+ */
+ protected void addOnDevicePropertiesChangedListener(@NonNull final Executor executor,
+ @NonNull final DeviceConfig.OnPropertiesChangedListener listener) {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONNECTIVITY,
+ executor, listener);
+ }
+
+ @VisibleForTesting
+ @NonNull
+ protected Resources getResourcesForActiveSubId(
+ @NonNull final ConnectivityResources resources, final int activeSubId) {
+ return SubscriptionManager.getResourcesForSubId(
+ resources.getResourcesContext(), activeSubId);
+ }
+ }
+ private final Dependencies mDeps;
+
+ /**
+ * Whether to prefer bad wifi to a network that yields to bad wifis, even if it never validated
+ *
+ * This setting only makes sense if the system is configured not to avoid bad wifis, i.e.
+ * if mAvoidBadWifi is true. If it's not, then no network ever yields to bad wifis
+ * ({@see FullScore#POLICY_YIELD_TO_BAD_WIFI}) and this setting has therefore no effect.
+ *
+ * If this is false, when ranking a bad wifi that never validated against cell data (or any
+ * network that yields to bad wifis), the ranker will prefer cell data. It will prefer wifi
+ * if wifi loses validation later. This behavior avoids the device losing internet access when
+ * walking past a wifi network with no internet access.
+ * This is the default behavior up to Android T, but it can be overridden through an overlay
+ * to behave like below.
+ *
+ * If this is true, then in the same scenario, the ranker will prefer cell data until
+ * the wifi completes its first validation attempt (or the attempt times out after
+ * ConnectivityService#PROMPT_UNVALIDATED_DELAY_MS), then it will prefer the wifi even if it
+ * doesn't provide internet access, unless there is a captive portal on that wifi.
+ * This is the behavior in U and above.
+ */
+ private boolean mActivelyPreferBadWifi;
+
+ // Mainline module can't use internal HandlerExecutor, so add an identical executor here.
+ private static class HandlerExecutor implements Executor {
+ @NonNull
+ private final Handler mHandler;
+
+ HandlerExecutor(@NonNull Handler handler) {
+ mHandler = handler;
+ }
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
+ }
+ }
+ // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
+ @VisibleForTesting @TargetApi(Build.VERSION_CODES.S)
+ protected class ActiveDataSubscriptionIdListener extends TelephonyCallback
+ implements TelephonyCallback.ActiveDataSubscriptionIdListener {
+ @Override
+ public void onActiveDataSubscriptionIdChanged(int subId) {
+ mActiveSubId = subId;
+ reevaluateInternal();
+ }
+ }
+
+ public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
+ this(ctx, handler, avoidBadWifiCallback, new Dependencies());
+ }
+
+ public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback,
+ Dependencies deps) {
+ mContext = ctx;
+ mResources = new ConnectivityResources(ctx);
+ mHandler = handler;
+ mAvoidBadWifiCallback = avoidBadWifiCallback;
+ mDeps = deps;
+ mSettingsUris = Arrays.asList(
+ Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
+ Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
+ mResolver = mContext.getContentResolver();
+ mSettingObserver = new SettingObserver();
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reevaluateInternal();
+ }
+ };
+
+ updateAvoidBadWifi();
+ updateMeteredMultipathPreference();
+ }
+
+ // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed.
+ @TargetApi(Build.VERSION_CODES.S)
+ public void start() {
+ for (Uri uri : mSettingsUris) {
+ mResolver.registerContentObserver(uri, false, mSettingObserver);
+ }
+
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+ mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter,
+ null /* broadcastPermission */, mHandler);
+
+ final Executor handlerExecutor = new HandlerExecutor(mHandler);
+ mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback(
+ handlerExecutor, new ActiveDataSubscriptionIdListener());
+ mDeps.addOnDevicePropertiesChangedListener(handlerExecutor,
+ properties -> reevaluateInternal());
+
+ reevaluate();
+ }
+
+ public void shutdown() {
+ mResolver.unregisterContentObserver(mSettingObserver);
+
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ public boolean getAvoidBadWifi() {
+ return mAvoidBadWifi;
+ }
+
+ public boolean getActivelyPreferBadWifi() {
+ return mActivelyPreferBadWifi;
+ }
+
+ // TODO: move this to MultipathPolicyTracker.
+ public int getMeteredMultipathPreference() {
+ return mMeteredMultipathPreference;
+ }
+
+ /**
+ * Whether the device or carrier configuration disables avoiding bad wifi by default.
+ */
+ public boolean configRestrictsAvoidBadWifi() {
+ final boolean allowBadWifi = mTestAllowBadWifiUntilMs > 0
+ && mTestAllowBadWifiUntilMs > System.currentTimeMillis();
+ // If the config returns true, then avoid bad wifi design can be controlled by the
+ // NETWORK_AVOID_BAD_WIFI setting.
+ if (allowBadWifi) return true;
+
+ return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
+ .getInteger(R.integer.config_networkAvoidBadWifi) == 0;
+ }
+
+ /**
+ * Whether the device config prefers bad wifi actively, when it doesn't avoid them
+ *
+ * This is only relevant when the device is configured not to avoid bad wifis. In this
+ * case, "actively" preferring a bad wifi means that the device will switch to a bad
+ * wifi it just connected to, as long as it's not a captive portal.
+ *
+ * On U and above this always returns true. On T and below it reads a configuration option.
+ */
+ public boolean configActivelyPrefersBadWifi() {
+ // See the definition of config_activelyPreferBadWifi for a description of its meaning.
+ // On U and above, the config is ignored, and bad wifi is always actively preferred.
+ if (SdkLevel.isAtLeastU()) return true;
+
+ // On T and below, 1 means to actively prefer bad wifi, 0 means not to prefer
+ // bad wifi (only stay stuck on it if already on there). This implementation treats
+ // any non-0 value like 1, on the assumption that anybody setting it non-zero wants
+ // the newer behavior.
+ return 0 != mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
+ .getInteger(R.integer.config_activelyPreferBadWifi);
+ }
+
+ /**
+ * Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration.
+ * The value works when the time set is more than {@link System.currentTimeMillis()}.
+ */
+ public void setTestAllowBadWifiUntil(long timeMs) {
+ Log.d(TAG, "setTestAllowBadWifiUntil: " + timeMs);
+ mTestAllowBadWifiUntilMs = timeMs;
+ reevaluateInternal();
+ }
+
+ /**
+ * Whether we should display a notification when wifi becomes unvalidated.
+ */
+ public boolean shouldNotifyWifiUnvalidated() {
+ return configRestrictsAvoidBadWifi() && getAvoidBadWifiSetting() == null;
+ }
+
+ public String getAvoidBadWifiSetting() {
+ return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
+ }
+
+ /**
+ * Returns whether device config says the device should actively prefer bad wifi.
+ *
+ * {@see #configActivelyPrefersBadWifi} for a description of what this does. This device
+ * config overrides that config overlay.
+ *
+ * @return True on Android U and above.
+ * True if device config says to actively prefer bad wifi.
+ * False if device config says not to actively prefer bad wifi.
+ * null if device config doesn't have an opinion (then fall back on the resource).
+ */
+ public Boolean deviceConfigActivelyPreferBadWifi() {
+ if (SdkLevel.isAtLeastU()) return true;
+ switch (mDeps.getConfigActivelyPreferBadWifi()) {
+ case 1:
+ return Boolean.TRUE;
+ case -1:
+ return Boolean.FALSE;
+ default:
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ public void reevaluate() {
+ mHandler.post(this::reevaluateInternal);
+ }
+
+ /**
+ * Reevaluate the settings. Must be called on the handler thread.
+ */
+ private void reevaluateInternal() {
+ if (updateAvoidBadWifi() && mAvoidBadWifiCallback != null) {
+ mAvoidBadWifiCallback.run();
+ }
+ updateMeteredMultipathPreference();
+ }
+
+ public boolean updateAvoidBadWifi() {
+ final boolean settingAvoidBadWifi = "1".equals(getAvoidBadWifiSetting());
+ final boolean prevAvoid = mAvoidBadWifi;
+ mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
+
+ final boolean prevActive = mActivelyPreferBadWifi;
+ final Boolean deviceConfigPreferBadWifi = deviceConfigActivelyPreferBadWifi();
+ if (null == deviceConfigPreferBadWifi) {
+ mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
+ } else {
+ mActivelyPreferBadWifi = deviceConfigPreferBadWifi;
+ }
+
+ return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
+ }
+
+ /**
+ * The default (device and carrier-dependent) value for metered multipath preference.
+ */
+ public int configMeteredMultipathPreference() {
+ return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
+ .getInteger(R.integer.config_networkMeteredMultipathPreference);
+ }
+
+ public void updateMeteredMultipathPreference() {
+ String setting = Settings.Global.getString(mResolver, NETWORK_METERED_MULTIPATH_PREFERENCE);
+ try {
+ mMeteredMultipathPreference = Integer.parseInt(setting);
+ } catch (NumberFormatException e) {
+ mMeteredMultipathPreference = configMeteredMultipathPreference();
+ }
+ }
+
+ private class SettingObserver extends ContentObserver {
+ public SettingObserver() {
+ super(null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.wtf(TAG, "Should never be reached.");
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (!mSettingsUris.contains(uri)) {
+ Log.wtf(TAG, "Unexpected settings observation: " + uri);
+ }
+ reevaluate();
+ }
+ }
+}
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index a57e992..4e19781 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -540,6 +540,9 @@
*/
public void dump(IndentingPrintWriter pw) {
if (SdkLevel.isAtLeastT()) {
+ // Dump ClatCoordinator information while clatd has been started but not running. The
+ // reason is that it helps to have more information if clatd is started but the
+ // v4-* interface doesn't bring up. See #isStarted, #isRunning.
if (isStarted()) {
pw.println("ClatCoordinator:");
pw.increaseIndent();
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index c732170..2e92d43 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -1208,7 +1208,7 @@
*/
public void setScore(final NetworkScore score) {
mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
- everValidated(), 0L == getAvoidUnvalidated(), yieldToBadWiFi(),
+ everValidated(), 0L != getAvoidUnvalidated(), yieldToBadWiFi(),
0L != mFirstEvaluationConcludedTime, isDestroyed());
}
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index f2c6aa1..d94c8dc 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -27,6 +28,7 @@
import static com.android.net.module.util.CollectionUtils.filter;
import static com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED;
import static com.android.server.connectivity.FullScore.POLICY_AVOIDED_WHEN_UNVALIDATED;
+import static com.android.server.connectivity.FullScore.POLICY_EVER_EVALUATED;
import static com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED;
import static com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED;
import static com.android.server.connectivity.FullScore.POLICY_IS_DESTROYED;
@@ -39,18 +41,39 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.function.Predicate;
/**
* A class that knows how to find the best network matching a request out of a list of networks.
*/
public class NetworkRanker {
+ /**
+ * Home for all configurations of NetworkRanker
+ */
+ public static final class Configuration {
+ private final boolean mActivelyPreferBadWifi;
+
+ public Configuration(final boolean activelyPreferBadWifi) {
+ this.mActivelyPreferBadWifi = activelyPreferBadWifi;
+ }
+
+ /**
+ * @see MultinetworkPolicyTracker#getActivelyPreferBadWifi()
+ */
+ public boolean activelyPreferBadWifi() {
+ return mActivelyPreferBadWifi;
+ }
+ }
+ @NonNull private volatile Configuration mConf;
+
// Historically the legacy ints have been 0~100 in principle (though the highest score in
// AOSP has always been 90). This is relied on by VPNs that send a legacy score of 101.
public static final int LEGACY_INT_MAX = 100;
@@ -65,7 +88,22 @@
NetworkCapabilities getCapsNoCopy();
}
- public NetworkRanker() { }
+ public NetworkRanker(@NonNull final Configuration conf) {
+ // Because mConf is volatile, the only way it could be seen null would be an access to it
+ // on some other thread during this constructor. But this is not possible because mConf is
+ // private and `this` doesn't escape this constructor.
+ setConfiguration(conf);
+ }
+
+ public void setConfiguration(@NonNull final Configuration conf) {
+ mConf = Objects.requireNonNull(conf);
+ }
+
+ // There shouldn't be a use case outside of testing
+ @VisibleForTesting
+ public Configuration getConfiguration() {
+ return mConf;
+ }
/**
* Find the best network satisfying this request among the list of passed networks.
@@ -104,11 +142,42 @@
}
}
- private <T extends Scoreable> boolean isBadWiFi(@NonNull final T candidate) {
+ /**
+ * Returns whether the wifi passed as an argument is a preferred network to yielding cell.
+ *
+ * When comparing bad wifi to cell with POLICY_YIELD_TO_BAD_WIFI, it may be necessary to
+ * know if a particular bad wifi is preferred to such a cell network. This method computes
+ * and returns this.
+ *
+ * @param candidate a bad wifi to evaluate
+ * @return whether this candidate is preferred to cell with POLICY_YIELD_TO_BAD_WIFI
+ */
+ private <T extends Scoreable> boolean isPreferredBadWiFi(@NonNull final T candidate) {
final FullScore score = candidate.getScore();
- return score.hasPolicy(POLICY_EVER_VALIDATED)
- && !score.hasPolicy(POLICY_AVOIDED_WHEN_UNVALIDATED)
- && candidate.getCapsNoCopy().hasTransport(TRANSPORT_WIFI);
+ final NetworkCapabilities caps = candidate.getCapsNoCopy();
+
+ // Whatever the policy, only WiFis can be preferred bad WiFis.
+ if (!caps.hasTransport(TRANSPORT_WIFI)) return false;
+ // Validated networks aren't bad networks, so a fortiori can't be preferred bad WiFis.
+ if (score.hasPolicy(POLICY_IS_VALIDATED)) return false;
+ // A WiFi that the user explicitly wanted to avoid in UI is never a preferred bad WiFi.
+ if (score.hasPolicy(POLICY_AVOIDED_WHEN_UNVALIDATED)) return false;
+
+ if (mConf.activelyPreferBadWifi()) {
+ // If a network is still evaluating, don't prefer it.
+ if (!score.hasPolicy(POLICY_EVER_EVALUATED)) return false;
+
+ // If a network is not a captive portal, then prefer it.
+ if (!caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return true;
+
+ // If it's a captive portal, prefer it if it previously validated but is no longer
+ // validated (i.e., the user logged in in the past, but later the portal closed).
+ return score.hasPolicy(POLICY_EVER_VALIDATED);
+ } else {
+ // Under the original "prefer bad WiFi" policy, only networks that have ever validated
+ // are preferred.
+ return score.hasPolicy(POLICY_EVER_VALIDATED);
+ }
}
/**
@@ -131,7 +200,7 @@
// No network with the policy : do nothing.
return;
}
- if (!CollectionUtils.any(rejected, n -> isBadWiFi(n))) {
+ if (!CollectionUtils.any(rejected, n -> isPreferredBadWiFi(n))) {
// No bad WiFi : do nothing.
return;
}
@@ -141,7 +210,7 @@
// wifis by the following policies (e.g. exiting).
final ArrayList<T> acceptedYielders = new ArrayList<>(accepted);
final ArrayList<T> rejectedWithBadWiFis = new ArrayList<>(rejected);
- partitionInto(rejectedWithBadWiFis, n -> isBadWiFi(n), accepted, rejected);
+ partitionInto(rejectedWithBadWiFis, n -> isPreferredBadWiFi(n), accepted, rejected);
accepted.addAll(acceptedYielders);
return;
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 8dbcc00..310d7bf 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2751,6 +2751,27 @@
mCm.getActiveNetwork(), false /* accept */ , false /* always */));
}
+ private void ensureCellIsValidatedBeforeMockingValidationUrls() {
+ // Verify that current supported network is validated so that the mock http server will not
+ // apply to unexpected networks. Also see aosp/2208680.
+ //
+ // This may also apply to wifi in principle, but in practice methods that mock validation
+ // URL all disconnect wifi forcefully anyway, so don't wait for wifi to validate.
+ if (mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
+ ensureValidatedNetwork(makeCellNetworkRequest());
+ }
+ }
+
+ private void ensureValidatedNetwork(NetworkRequest request) {
+ final TestableNetworkCallback cb = new TestableNetworkCallback();
+ mCm.registerNetworkCallback(request, cb);
+ cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> ((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .hasCapability(NET_CAPABILITY_VALIDATED));
+ mCm.unregisterNetworkCallback(cb);
+ }
+
@AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
@Test
public void testAcceptPartialConnectivity_validatedNetwork() throws Exception {
@@ -2882,7 +2903,8 @@
assertTrue(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
NET_CAPABILITY_VALIDATED));
- // Configure response code for unvalidated network
+ // The cell network has already been checked to be validated.
+ // Configure response code for unvalidated network.
configTestServer(Status.INTERNAL_ERROR, Status.INTERNAL_ERROR);
mCm.reportNetworkConnectivity(wifiNetwork, false);
// Default network should stay on unvalidated wifi because avoid bad wifi is disabled.
@@ -2970,6 +2992,8 @@
}
private Network prepareValidatedNetwork() throws Exception {
+ ensureCellIsValidatedBeforeMockingValidationUrls();
+
prepareHttpServer();
configTestServer(Status.NO_CONTENT, Status.NO_CONTENT);
// Disconnect wifi first then start wifi network with configuration.
@@ -2980,6 +3004,8 @@
}
private Network preparePartialConnectivity() throws Exception {
+ ensureCellIsValidatedBeforeMockingValidationUrls();
+
prepareHttpServer();
// Configure response code for partial connectivity
configTestServer(Status.INTERNAL_ERROR /* httpsStatusCode */,
@@ -2993,6 +3019,8 @@
}
private Network prepareUnvalidatedNetwork() throws Exception {
+ ensureCellIsValidatedBeforeMockingValidationUrls();
+
prepareHttpServer();
// Configure response code for unvalidated network
configTestServer(Status.INTERNAL_ERROR /* httpsStatusCode */,
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index f1a6d92..3635b7b 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -41,6 +41,8 @@
import android.net.MacAddress
import android.net.Network
import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
@@ -56,6 +58,7 @@
import android.os.Looper
import android.os.OutcomeReceiver
import android.os.SystemProperties
+import android.os.Process
import android.platform.test.annotations.AppModeFull
import android.util.ArraySet
import androidx.test.platform.app.InstrumentationRegistry
@@ -67,22 +70,25 @@
import com.android.testutils.DeviceInfoUtils.isKernelVersionAtLeast
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.RouterAdvertisementResponder
import com.android.testutils.TapPacketReader
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.anyNetwork
+import com.android.testutils.assertThrows
import com.android.testutils.runAsShell
import com.android.testutils.waitForIdle
import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
-import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
+import java.io.IOException
import java.net.Inet6Address
import java.util.Random
+import java.net.Socket
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
@@ -110,6 +116,10 @@
.addTransportType(TRANSPORT_ETHERNET)
.removeCapability(NET_CAPABILITY_TRUSTED)
.build()
+private val TEST_CAPS = NetworkCapabilities.Builder(ETH_REQUEST.networkCapabilities)
+ .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .build()
private val STATIC_IP_CONFIGURATION = IpConfiguration.Builder()
.setStaticIpConfiguration(StaticIpConfiguration.Builder()
.setIpAddress(LinkAddress("192.0.2.1/30")).build())
@@ -145,6 +155,7 @@
private val raResponder: RouterAdvertisementResponder
private val tnm: TestNetworkManager
val name get() = tapInterface.interfaceName
+ val onLinkPrefix get() = raResponder.prefix
init {
tnm = runAsShell(MANAGE_TEST_NETWORKS) {
@@ -503,7 +514,7 @@
// NetworkRequest.Builder does not create a copy of the passed NetworkRequest, so in order to
// keep ETH_REQUEST as it is, a defensive copy is created here.
- private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
+ private fun NetworkRequest.copyWithEthernetSpecifier(ifaceName: String) =
NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
.setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
@@ -532,17 +543,25 @@
it.networkSpecifier == EthernetNetworkSpecifier(name)
}
- private fun TestableNetworkCallback.expectCapabilitiesWith(cap: Int) =
- expectCapabilitiesThat(anyNetwork(), TIMEOUT_MS) {
- it.hasCapability(cap)
+ private fun TestableNetworkCallback.eventuallyExpectCapabilities(nc: NetworkCapabilities) {
+ // b/233534110: eventuallyExpect<CapabilitiesChanged>() does not advance ReadHead.
+ eventuallyExpect(CapabilitiesChanged::class, TIMEOUT_MS) {
+ // CS may mix in additional capabilities, so NetworkCapabilities#equals cannot be used.
+ // Check if all expected capabilities are present instead.
+ it is CapabilitiesChanged && nc.capabilities.all { c -> it.caps.hasCapability(c) }
}
+ }
- private fun TestableNetworkCallback.expectLinkPropertiesWithLinkAddress(addr: LinkAddress) =
- expectLinkPropertiesThat(anyNetwork(), TIMEOUT_MS) {
- // LinkAddress.equals isn't possible as the system changes the LinkAddress.flags value.
- // any() must be used since the interface may also have a link-local address.
- it.linkAddresses.any { x -> x.isSameAddressAs(addr) }
+ private fun TestableNetworkCallback.eventuallyExpectLpForStaticConfig(
+ config: StaticIpConfiguration
+ ) {
+ // b/233534110: eventuallyExpect<LinkPropertiesChanged>() does not advance ReadHead.
+ eventuallyExpect(LinkPropertiesChanged::class, TIMEOUT_MS) {
+ it is LinkPropertiesChanged && it.lp.linkAddresses.any { la ->
+ la.isSameAddressAs(config.ipAddress)
+ }
}
+ }
@Test
fun testCallbacks() {
@@ -685,6 +704,8 @@
// install a listener which will later be used to verify the Lost callback
val listenerCb = registerNetworkListener(ETH_REQUEST)
+ // assert the network is only brought up by a request.
+ listenerCb.assertNeverAvailable()
val cb = requestNetwork(ETH_REQUEST)
val network = cb.expectAvailable()
@@ -717,7 +738,7 @@
val iface1 = createInterface()
val iface2 = createInterface()
- val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
+ val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface2.name))
val network = cb.expectAvailable()
cb.expectCapabilitiesWithInterfaceName(iface2.name)
@@ -750,8 +771,8 @@
val iface1 = createInterface()
val iface2 = createInterface()
- val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.name))
- val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
+ val cb1 = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface1.name))
+ val cb2 = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface2.name))
val cb3 = requestNetwork(ETH_REQUEST)
cb1.expectAvailable()
@@ -794,7 +815,10 @@
val iface = createInterface(false /* hasCarrier */)
val cb = requestNetwork(ETH_REQUEST)
- cb.assertNeverAvailable()
+ // TUNSETCARRIER races with the bring up code, so the network *can* become available despite
+ // it being "created with no carrier".
+ // TODO(b/249611919): re-enable assertion once kernel supports IFF_NO_CARRIER.
+ // cb.assertNeverAvailable()
iface.setCarrierEnabled(true)
cb.expectAvailable()
@@ -803,6 +827,19 @@
cb.eventuallyExpectLost()
}
+ // TODO: move to MTS
+ @Test
+ fun testNetworkRequest_linkPropertiesUpdate() {
+ val iface = createInterface()
+ val cb = requestNetwork(ETH_REQUEST)
+ // b/233534110: eventuallyExpect<LinkPropertiesChanged>() does not advance ReadHead
+ cb.eventuallyExpect(LinkPropertiesChanged::class, TIMEOUT_MS) {
+ it is LinkPropertiesChanged && it.lp.addresses.any {
+ address -> iface.onLinkPrefix.contains(address)
+ }
+ }
+ }
+
@Test
fun testRemoveInterface_whileInServerMode() {
assumeNoInterfaceForTetheringAvailable()
@@ -862,65 +899,84 @@
@Test
fun testUpdateConfiguration_forBothIpConfigAndCapabilities() {
val iface = createInterface()
- val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+ val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
val network = cb.expectAvailable()
- cb.assertNeverLost()
- val testCapability = NET_CAPABILITY_TEMPORARILY_NOT_METERED
- val nc = NetworkCapabilities
- .Builder(ETH_REQUEST.networkCapabilities)
- .addCapability(testCapability)
- .build()
- updateConfiguration(iface, STATIC_IP_CONFIGURATION, nc).expectResult(iface.name)
-
- // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
- // will be expected first before available, as part of the restart.
- cb.expectLost(network)
- cb.expectAvailable()
- cb.expectCapabilitiesWith(testCapability)
- cb.expectLinkPropertiesWithLinkAddress(
- STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
+ updateConfiguration(iface, STATIC_IP_CONFIGURATION, TEST_CAPS).expectResult(iface.name)
+ cb.eventuallyExpectCapabilities(TEST_CAPS)
+ cb.eventuallyExpectLpForStaticConfig(STATIC_IP_CONFIGURATION.staticIpConfiguration)
}
@Test
fun testUpdateConfiguration_forOnlyIpConfig() {
val iface = createInterface()
- val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+ val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
val network = cb.expectAvailable()
- cb.assertNeverLost()
updateConfiguration(iface, STATIC_IP_CONFIGURATION).expectResult(iface.name)
-
- // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
- // will be expected first before available, as part of the restart.
- cb.expectLost(network)
- cb.expectAvailable()
- cb.expectCallback<CapabilitiesChanged>()
- cb.expectLinkPropertiesWithLinkAddress(
- STATIC_IP_CONFIGURATION.staticIpConfiguration.ipAddress!!)
+ cb.eventuallyExpectLpForStaticConfig(STATIC_IP_CONFIGURATION.staticIpConfiguration)
}
- // TODO(b/240323229): This test is currently flaky due to a race between IpClient restarting and
- // NetworkAgent tearing down the routes. This problem is exacerbated by disabling RS delay.
- @Ignore
@Test
fun testUpdateConfiguration_forOnlyCapabilities() {
val iface = createInterface()
- val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface.name))
+ val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
val network = cb.expectAvailable()
- cb.assertNeverLost()
- val testCapability = NET_CAPABILITY_TEMPORARILY_NOT_METERED
- val nc = NetworkCapabilities
- .Builder(ETH_REQUEST.networkCapabilities)
- .addCapability(testCapability)
- .build()
+ updateConfiguration(iface, capabilities = TEST_CAPS).expectResult(iface.name)
+ cb.eventuallyExpectCapabilities(TEST_CAPS)
+ }
+
+ @Test
+ fun testUpdateConfiguration_forAllowedUids() {
+ // Configure a restricted network.
+ val iface = createInterface()
+ val request = NetworkRequest.Builder(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED).build()
+ updateConfiguration(iface, capabilities = request.networkCapabilities)
+ .expectResult(iface.name)
+
+ // Request the restricted network as the shell with CONNECTIVITY_USE_RESTRICTED_NETWORKS.
+ val cb = runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) { requestNetwork(request) }
+ val network = cb.expectAvailable()
+ cb.assertNeverLost(network)
+
+ // The network is restricted therefore binding to it when available will fail.
+ Socket().use { socket ->
+ assertThrows(IOException::class.java, { network.bindSocket(socket) })
+ }
+
+ // Add the test process UID to the allowed UIDs for the network and ultimately bind again.
+ val allowedUids = setOf(Process.myUid())
+ val nc = NetworkCapabilities.Builder(request.networkCapabilities)
+ .setAllowedUids(allowedUids).build()
updateConfiguration(iface, capabilities = nc).expectResult(iface.name)
- // UpdateConfiguration() currently does a restarts on the ethernet interface therefore lost
+ // UpdateConfiguration() currently does a restart on the ethernet interface therefore lost
// will be expected first before available, as part of the restart.
cb.expectLost(network)
+ val updatedNetwork = cb.expectAvailable()
+ // With the test process UID allowed, binding to a restricted network should be successful.
+ Socket().use { socket -> updatedNetwork.bindSocket(socket) }
+ }
+
+ // TODO: consider only having this test in MTS as it makes use of the fact that
+ // setEthernetEnabled(false) untracks all interfaces. This behavior is an implementation detail
+ // and may change in the future.
+ @Test
+ fun testUpdateConfiguration_onUntrackedInterface() {
+ assumeFalse(isAdbOverEthernet())
+ val iface = createInterface()
+ setEthernetEnabled(false)
+
+ // Updating the IpConfiguration and NetworkCapabilities on absent interfaces is a supported
+ // use case.
+ updateConfiguration(iface, STATIC_IP_CONFIGURATION, TEST_CAPS).expectResult(iface.name)
+
+ setEthernetEnabled(true)
+ val cb = requestNetwork(ETH_REQUEST)
cb.expectAvailable()
- cb.expectCapabilitiesWith(testCapability)
+ cb.eventuallyExpectCapabilities(TEST_CAPS)
+ cb.eventuallyExpectLpForStaticConfig(STATIC_IP_CONFIGURATION.staticIpConfiguration)
}
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index d4f3d57..d2cd7aa 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -73,6 +73,8 @@
import android.os.Message
import android.os.SystemClock
import android.platform.test.annotations.AppModeFull
+import android.system.OsConstants.IPPROTO_TCP
+import android.system.OsConstants.IPPROTO_UDP
import android.telephony.TelephonyManager
import android.telephony.data.EpsBearerQosSessionAttributes
import android.util.DebugUtils.valueToString
@@ -117,6 +119,7 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
+import java.io.Closeable
import java.io.IOException
import java.net.DatagramSocket
import java.net.InetAddress
@@ -174,7 +177,7 @@
private val mFakeConnectivityService = FakeConnectivityService()
private val agentsToCleanUp = mutableListOf<NetworkAgent>()
private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
- private var qosTestSocket: Socket? = null
+ private var qosTestSocket: Closeable? = null // either Socket or DatagramSocket
@Before
fun setUp() {
@@ -930,34 +933,49 @@
}
}
- private fun setupForQosCallbackTesting(): Pair<TestableNetworkAgent, Socket> {
- val request = NetworkRequest.Builder()
- .clearCapabilities()
- .addTransportType(TRANSPORT_TEST)
- .build()
+ private fun <T : Closeable> setupForQosCallbackTest(creator: (TestableNetworkAgent) -> T) =
+ createConnectedNetworkAgent().first.let { Pair(it, creator(it)) }
- val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
- requestNetwork(request, callback)
- val (agent, _) = createConnectedNetworkAgent()
+ private fun setupForQosSocket() = setupForQosCallbackTest {
+ agent: TestableNetworkAgent -> Socket()
+ .also { assertNotNull(agent.network?.bindSocket(it))
+ it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0)) }
+ }
- qosTestSocket = assertNotNull(agent.network?.socketFactory?.createSocket()).also {
- it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0))
- }
- return Pair(agent, qosTestSocket!!)
+ private fun setupForQosDatagram() = setupForQosCallbackTest {
+ agent: TestableNetworkAgent -> DatagramSocket(
+ InetSocketAddress(InetAddress.getLoopbackAddress(), 0))
+ .also { assertNotNull(agent.network?.bindSocket(it)) }
}
@AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
- fun testQosCallbackRegisterWithUnregister() {
- val (agent, socket) = setupForQosCallbackTesting()
+ fun testQosCallbackRegisterAndUnregister() {
+ validateQosCallbackRegisterAndUnregister(IPPROTO_TCP)
+ }
+ @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
+ @Test
+ fun testQosCallbackRegisterAndUnregisterWithDatagramSocket() {
+ validateQosCallbackRegisterAndUnregister(IPPROTO_UDP)
+ }
+
+ private fun validateQosCallbackRegisterAndUnregister(proto: Int) {
+ val (agent, qosTestSocket) = when (proto) {
+ IPPROTO_TCP -> setupForQosSocket()
+ IPPROTO_UDP -> setupForQosDatagram()
+ else -> fail("unsupported protocol")
+ }
val qosCallback = TestableQosCallback()
var callbackId = -1
Executors.newSingleThreadExecutor().let { executor ->
try {
- val info = QosSocketInfo(agent.network!!, socket)
+ val info = QosSocketInfo(agent, qosTestSocket)
mCM.registerQosCallback(info, executor, qosCallback)
- callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
+ agent.expectCallback<OnRegisterQosCallback>().let {
+ callbackId = it.callbackId
+ assertTrue(it.filter.matchesProtocol(proto))
+ }
assertFailsWith<QosCallbackRegistrationException>(
"The same callback cannot be " +
@@ -965,7 +983,7 @@
mCM.registerQosCallback(info, executor, qosCallback)
}
} finally {
- socket.close()
+ qosTestSocket.close()
mCM.unregisterQosCallback(qosCallback)
agent.expectCallback<OnUnregisterQosCallback> { it.callbackId == callbackId }
executor.shutdown()
@@ -976,11 +994,31 @@
@AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackOnQosSession() {
- val (agent, socket) = setupForQosCallbackTesting()
+ validateQosCallbackOnQosSession(IPPROTO_TCP)
+ }
+
+ @AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
+ @Test
+ fun testQosCallbackOnQosSessionWithDatagramSocket() {
+ validateQosCallbackOnQosSession(IPPROTO_UDP)
+ }
+
+ fun QosSocketInfo(agent: NetworkAgent, socket: Closeable) = when (socket) {
+ is Socket -> QosSocketInfo(agent.network, socket)
+ is DatagramSocket -> QosSocketInfo(agent.network, socket)
+ else -> fail("unexpected socket type")
+ }
+
+ private fun validateQosCallbackOnQosSession(proto: Int) {
+ val (agent, qosTestSocket) = when (proto) {
+ IPPROTO_TCP -> setupForQosSocket()
+ IPPROTO_UDP -> setupForQosDatagram()
+ else -> fail("unsupported protocol")
+ }
val qosCallback = TestableQosCallback()
Executors.newSingleThreadExecutor().let { executor ->
try {
- val info = QosSocketInfo(agent.network!!, socket)
+ val info = QosSocketInfo(agent, qosTestSocket)
assertEquals(agent.network, info.getNetwork())
mCM.registerQosCallback(info, executor, qosCallback)
val callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
@@ -1009,8 +1047,7 @@
agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
qosCallback.assertNoCallback()
} finally {
- socket.close()
-
+ qosTestSocket.close()
// safety precaution
mCM.unregisterQosCallback(qosCallback)
@@ -1022,11 +1059,11 @@
@AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackOnError() {
- val (agent, socket) = setupForQosCallbackTesting()
+ val (agent, qosTestSocket) = setupForQosSocket()
val qosCallback = TestableQosCallback()
Executors.newSingleThreadExecutor().let { executor ->
try {
- val info = QosSocketInfo(agent.network!!, socket)
+ val info = QosSocketInfo(agent.network!!, qosTestSocket)
mCM.registerQosCallback(info, executor, qosCallback)
val callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
@@ -1048,7 +1085,7 @@
agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
qosCallback.assertNoCallback()
} finally {
- socket.close()
+ qosTestSocket.close()
// Make sure that the callback is fully unregistered
mCM.unregisterQosCallback(qosCallback)
@@ -1061,12 +1098,12 @@
@AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackIdsAreMappedCorrectly() {
- val (agent, socket) = setupForQosCallbackTesting()
+ val (agent, qosTestSocket) = setupForQosSocket()
val qosCallback1 = TestableQosCallback()
val qosCallback2 = TestableQosCallback()
Executors.newSingleThreadExecutor().let { executor ->
try {
- val info = QosSocketInfo(agent.network!!, socket)
+ val info = QosSocketInfo(agent.network!!, qosTestSocket)
mCM.registerQosCallback(info, executor, qosCallback1)
val callbackId1 = agent.expectCallback<OnRegisterQosCallback>().callbackId
@@ -1088,7 +1125,7 @@
qosCallback1.assertNoCallback()
qosCallback2.expectCallback<OnQosSessionAvailable> { sessId2 == it.sess.sessionId }
} finally {
- socket.close()
+ qosTestSocket.close()
// Make sure that the callback is fully unregistered
mCM.unregisterQosCallback(qosCallback1)
@@ -1102,13 +1139,13 @@
@AppModeFull(reason = "Instant apps don't have permission to bind sockets.")
@Test
fun testQosCallbackWhenNetworkReleased() {
- val (agent, socket) = setupForQosCallbackTesting()
+ val (agent, qosTestSocket) = setupForQosSocket()
Executors.newSingleThreadExecutor().let { executor ->
try {
val qosCallback1 = TestableQosCallback()
val qosCallback2 = TestableQosCallback()
try {
- val info = QosSocketInfo(agent.network!!, socket)
+ val info = QosSocketInfo(agent.network!!, qosTestSocket)
mCM.registerQosCallback(info, executor, qosCallback1)
mCM.registerQosCallback(info, executor, qosCallback2)
agent.unregister()
@@ -1121,12 +1158,12 @@
it.ex.cause is NetworkReleasedException
}
} finally {
- socket.close()
+ qosTestSocket.close()
mCM.unregisterQosCallback(qosCallback1)
mCM.unregisterQosCallback(qosCallback2)
}
} finally {
- socket.close()
+ qosTestSocket.close()
executor.shutdown()
}
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
index 8f17199..eb41d71 100644
--- a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
@@ -34,6 +34,7 @@
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.TestableNetworkCallback.HasNetwork
import org.junit.After
@@ -76,7 +77,18 @@
@After
fun tearDown() {
- agentsToCleanUp.forEach { it.unregister() }
+ val agentCleanUpCb = TestableNetworkCallback(TIMEOUT_MS).also { cb ->
+ mCm.registerNetworkCallback(
+ NetworkRequest.Builder().clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST).build(), cb, mHandler
+ )
+ }
+ agentsToCleanUp.forEach {
+ it.unregister()
+ agentCleanUpCb.eventuallyExpect<CallbackEntry.Lost> { cb -> cb.network == it.network }
+ }
+ mCm.unregisterNetworkCallback(agentCleanUpCb)
+
mHandlerThread.quitSafely()
callbacksToCleanUp.forEach { mCm.unregisterNetworkCallback(it) }
}
diff --git a/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java b/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java
index cd43a34..04abd72 100644
--- a/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java
+++ b/tests/cts/net/src/android/net/cts/QosCallbackExceptionTest.java
@@ -17,6 +17,7 @@
package android.net.cts;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -24,8 +25,11 @@
import android.net.QosCallbackException;
import android.net.SocketLocalAddressChangedException;
import android.net.SocketNotBoundException;
+import android.net.SocketNotConnectedException;
+import android.net.SocketRemoteAddressChangedException;
import android.os.Build;
+import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -34,14 +38,9 @@
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(Build.VERSION_CODES.R)
+@ConnectivityModuleTest
public class QosCallbackExceptionTest {
private static final String ERROR_MESSAGE = "Test Error Message";
- private static final String ERROR_MSG_SOCK_NOT_BOUND = "The socket is unbound";
- private static final String ERROR_MSG_NET_RELEASED =
- "The network was released and is no longer available";
- private static final String ERROR_MSG_SOCK_ADDR_CHANGED =
- "The local address of the socket changed";
-
@Test
public void testQosCallbackException() throws Exception {
@@ -57,33 +56,65 @@
public void testNetworkReleasedExceptions() throws Exception {
final Throwable netReleasedException = new NetworkReleasedException();
final QosCallbackException exception = new QosCallbackException(netReleasedException);
-
- assertTrue(exception.getCause() instanceof NetworkReleasedException);
- assertEquals(netReleasedException, exception.getCause());
- assertTrue(exception.getMessage().contains(ERROR_MSG_NET_RELEASED));
- assertThrowableMessageContains(exception, ERROR_MSG_NET_RELEASED);
+ validateQosCallbackException(
+ exception, netReleasedException, NetworkReleasedException.class);
}
@Test
public void testSocketNotBoundExceptions() throws Exception {
final Throwable sockNotBoundException = new SocketNotBoundException();
final QosCallbackException exception = new QosCallbackException(sockNotBoundException);
-
- assertTrue(exception.getCause() instanceof SocketNotBoundException);
- assertEquals(sockNotBoundException, exception.getCause());
- assertTrue(exception.getMessage().contains(ERROR_MSG_SOCK_NOT_BOUND));
- assertThrowableMessageContains(exception, ERROR_MSG_SOCK_NOT_BOUND);
+ validateQosCallbackException(
+ exception, sockNotBoundException, SocketNotBoundException.class);
}
@Test
public void testSocketLocalAddressChangedExceptions() throws Exception {
- final Throwable localAddrChangedException = new SocketLocalAddressChangedException();
- final QosCallbackException exception = new QosCallbackException(localAddrChangedException);
+ final Throwable localAddressChangedException = new SocketLocalAddressChangedException();
+ final QosCallbackException exception =
+ new QosCallbackException(localAddressChangedException);
+ validateQosCallbackException(
+ exception, localAddressChangedException, SocketLocalAddressChangedException.class);
+ }
- assertTrue(exception.getCause() instanceof SocketLocalAddressChangedException);
- assertEquals(localAddrChangedException, exception.getCause());
- assertTrue(exception.getMessage().contains(ERROR_MSG_SOCK_ADDR_CHANGED));
- assertThrowableMessageContains(exception, ERROR_MSG_SOCK_ADDR_CHANGED);
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S)
+ public void testSocketNotConnectedExceptions() throws Exception {
+ final Throwable sockNotConnectedException = new SocketNotConnectedException();
+ final QosCallbackException exception = new QosCallbackException(sockNotConnectedException);
+ validateQosCallbackException(
+ exception, sockNotConnectedException, SocketNotConnectedException.class);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S)
+ public void testSocketRemoteAddressChangedExceptions() throws Exception {
+ final Throwable remoteAddressChangedException = new SocketRemoteAddressChangedException();
+ final QosCallbackException exception =
+ new QosCallbackException(remoteAddressChangedException);
+ validateQosCallbackException(
+ exception, remoteAddressChangedException,
+ SocketRemoteAddressChangedException.class);
+ }
+
+ private void validateQosCallbackException(
+ QosCallbackException e, Throwable cause, Class c) throws Exception {
+ if (c == SocketNotConnectedException.class) {
+ assertTrue(e.getCause() instanceof SocketNotConnectedException);
+ } else if (c == SocketRemoteAddressChangedException.class) {
+ assertTrue(e.getCause() instanceof SocketRemoteAddressChangedException);
+ } else if (c == SocketLocalAddressChangedException.class) {
+ assertTrue(e.getCause() instanceof SocketLocalAddressChangedException);
+ } else if (c == SocketNotBoundException.class) {
+ assertTrue(e.getCause() instanceof SocketNotBoundException);
+ } else if (c == NetworkReleasedException.class) {
+ assertTrue(e.getCause() instanceof NetworkReleasedException);
+ } else {
+ fail("unexpected error msg.");
+ }
+ assertEquals(cause, e.getCause());
+ assertFalse(e.getMessage().isEmpty());
+ assertThrowableMessageContains(e, e.getMessage());
}
private void assertThrowableMessageContains(QosCallbackException exception, String errorMsg)
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 73e4c0e..26b058d 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -37,7 +37,7 @@
import android.net.TestNetworkStackClient
import android.net.Uri
import android.net.metrics.IpConnectivityLog
-import android.net.util.MultinetworkPolicyTracker
+import com.android.server.connectivity.MultinetworkPolicyTracker
import android.os.ConditionVariable
import android.os.IBinder
import android.os.SystemConfigManager
@@ -211,10 +211,15 @@
doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager()
doReturn(mock(BpfNetMaps::class.java)).`when`(deps).getBpfNetMaps(any(), any())
doAnswer { inv ->
- object : MultinetworkPolicyTracker(inv.getArgument(0), inv.getArgument(1),
- inv.getArgument(2)) {
- override fun getResourcesForActiveSubId() = resources
- }
+ MultinetworkPolicyTracker(inv.getArgument(0),
+ inv.getArgument(1),
+ inv.getArgument(2),
+ object : MultinetworkPolicyTracker.Dependencies() {
+ override fun getResourcesForActiveSubId(
+ connResources: ConnectivityResources,
+ activeSubId: Int
+ ) = resources
+ })
}.`when`(deps).makeMultinetworkPolicyTracker(any(), any(), any())
return deps
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 4966aed..0c00bc0 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -37,10 +37,15 @@
import static com.android.server.BpfNetMaps.HAPPY_BOX_MATCH;
import static com.android.server.BpfNetMaps.IIF_MATCH;
import static com.android.server.BpfNetMaps.LOCKDOWN_VPN_MATCH;
+import static com.android.server.BpfNetMaps.LOW_POWER_STANDBY_MATCH;
import static com.android.server.BpfNetMaps.NO_MATCH;
+import static com.android.server.BpfNetMaps.OEM_DENY_1_MATCH;
+import static com.android.server.BpfNetMaps.OEM_DENY_2_MATCH;
+import static com.android.server.BpfNetMaps.OEM_DENY_3_MATCH;
import static com.android.server.BpfNetMaps.PENALTY_BOX_MATCH;
import static com.android.server.BpfNetMaps.POWERSAVE_MATCH;
import static com.android.server.BpfNetMaps.RESTRICTED_MATCH;
+import static com.android.server.BpfNetMaps.STANDBY_MATCH;
import static com.android.server.ConnectivityStatsLog.NETWORK_BPF_MAP_INFO;
import static org.junit.Assert.assertEquals;
@@ -61,6 +66,7 @@
import android.os.Build;
import android.os.ServiceSpecificException;
import android.system.ErrnoException;
+import android.util.IndentingPrintWriter;
import androidx.test.filters.SmallTest;
@@ -84,6 +90,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.FileDescriptor;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
@@ -138,6 +146,9 @@
doReturn(0).when(mDeps).synchronizeKernelRCU();
BpfNetMaps.setEnableJavaBpfMapForTest(true /* enable */);
BpfNetMaps.setConfigurationMapForTest(mConfigurationMap);
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(0));
+ mConfigurationMap.updateEntry(
+ CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
BpfNetMaps.setUidOwnerMapForTest(mUidOwnerMap);
BpfNetMaps.setUidPermissionMapForTest(mUidPermissionMap);
BpfNetMaps.setCookieTagMapForTest(mCookieTagMap);
@@ -927,4 +938,136 @@
final int ret = mBpfNetMaps.pullBpfMapInfoAtom(-1 /* atomTag */, new ArrayList<>());
assertEquals(StatsManager.PULL_SKIP, ret);
}
+
+ private void assertDumpContains(final String dump, final String message) {
+ assertTrue(String.format("dump(%s) does not contain '%s'", dump, message),
+ dump.contains(message));
+ }
+
+ private String getDump() throws Exception {
+ final StringWriter sw = new StringWriter();
+ mBpfNetMaps.dump(new IndentingPrintWriter(sw), new FileDescriptor(), true /* verbose */);
+ return sw.toString();
+ }
+
+ private void doTestDumpUidPermissionMap(final int permission, final String permissionString)
+ throws Exception {
+ mUidPermissionMap.updateEntry(new S32(TEST_UID), new U8((short) permission));
+ assertDumpContains(getDump(), TEST_UID + " " + permissionString);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpUidPermissionMap() throws Exception {
+ doTestDumpUidPermissionMap(PERMISSION_NONE, "PERMISSION_NONE");
+ doTestDumpUidPermissionMap(PERMISSION_INTERNET | PERMISSION_UPDATE_DEVICE_STATS,
+ "PERMISSION_INTERNET PERMISSION_UPDATE_DEVICE_STATS");
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpUidPermissionMapInvalidPermission() throws Exception {
+ doTestDumpUidPermissionMap(PERMISSION_UNINSTALLED, "PERMISSION_UNINSTALLED error!");
+ doTestDumpUidPermissionMap(PERMISSION_INTERNET | 1 << 6,
+ "PERMISSION_INTERNET PERMISSION_UNKNOWN(64)");
+ }
+
+ void doTestDumpUidOwnerMap(final int iif, final long match, final String matchString)
+ throws Exception {
+ mUidOwnerMap.updateEntry(new S32(TEST_UID), new UidOwnerValue(iif, match));
+ assertDumpContains(getDump(), TEST_UID + " " + matchString);
+ }
+
+ void doTestDumpUidOwnerMap(final long match, final String matchString) throws Exception {
+ doTestDumpUidOwnerMap(0 /* iif */, match, matchString);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpUidOwnerMap() throws Exception {
+ doTestDumpUidOwnerMap(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH");
+ doTestDumpUidOwnerMap(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH");
+ doTestDumpUidOwnerMap(DOZABLE_MATCH, "DOZABLE_MATCH");
+ doTestDumpUidOwnerMap(STANDBY_MATCH, "STANDBY_MATCH");
+ doTestDumpUidOwnerMap(POWERSAVE_MATCH, "POWERSAVE_MATCH");
+ doTestDumpUidOwnerMap(RESTRICTED_MATCH, "RESTRICTED_MATCH");
+ doTestDumpUidOwnerMap(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH");
+ doTestDumpUidOwnerMap(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH");
+ doTestDumpUidOwnerMap(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH");
+ doTestDumpUidOwnerMap(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH");
+ doTestDumpUidOwnerMap(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH");
+
+ doTestDumpUidOwnerMap(HAPPY_BOX_MATCH | POWERSAVE_MATCH,
+ "HAPPY_BOX_MATCH POWERSAVE_MATCH");
+ doTestDumpUidOwnerMap(DOZABLE_MATCH | LOCKDOWN_VPN_MATCH | OEM_DENY_1_MATCH,
+ "DOZABLE_MATCH LOCKDOWN_VPN_MATCH OEM_DENY_1_MATCH");
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpUidOwnerMapWithIifMatch() throws Exception {
+ doTestDumpUidOwnerMap(TEST_IF_INDEX, IIF_MATCH, "IIF_MATCH " + TEST_IF_INDEX);
+ doTestDumpUidOwnerMap(TEST_IF_INDEX,
+ IIF_MATCH | DOZABLE_MATCH | LOCKDOWN_VPN_MATCH | OEM_DENY_1_MATCH,
+ "DOZABLE_MATCH IIF_MATCH LOCKDOWN_VPN_MATCH OEM_DENY_1_MATCH " + TEST_IF_INDEX);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpUidOwnerMapWithInvalidMatch() throws Exception {
+ final long invalid_match = 1L << 31;
+ doTestDumpUidOwnerMap(invalid_match, "UNKNOWN_MATCH(" + invalid_match + ")");
+ doTestDumpUidOwnerMap(DOZABLE_MATCH | invalid_match,
+ "DOZABLE_MATCH UNKNOWN_MATCH(" + invalid_match + ")");
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpCurrentStatsMapConfig() throws Exception {
+ mConfigurationMap.updateEntry(
+ CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_A));
+ assertDumpContains(getDump(), "current statsMap configuration: 0 SELECT_MAP_A");
+
+ mConfigurationMap.updateEntry(
+ CURRENT_STATS_MAP_CONFIGURATION_KEY, new U32(STATS_SELECT_MAP_B));
+ assertDumpContains(getDump(), "current statsMap configuration: 1 SELECT_MAP_B");
+ }
+
+ private void doTestDumpOwnerMatchConfig(final long match, final String matchString)
+ throws Exception {
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(match));
+ assertDumpContains(getDump(),
+ "current ownerMatch configuration: " + match + " " + matchString);
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpUidOwnerMapConfig() throws Exception {
+ doTestDumpOwnerMatchConfig(HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH");
+ doTestDumpOwnerMatchConfig(PENALTY_BOX_MATCH, "PENALTY_BOX_MATCH");
+ doTestDumpOwnerMatchConfig(DOZABLE_MATCH, "DOZABLE_MATCH");
+ doTestDumpOwnerMatchConfig(STANDBY_MATCH, "STANDBY_MATCH");
+ doTestDumpOwnerMatchConfig(POWERSAVE_MATCH, "POWERSAVE_MATCH");
+ doTestDumpOwnerMatchConfig(RESTRICTED_MATCH, "RESTRICTED_MATCH");
+ doTestDumpOwnerMatchConfig(LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH");
+ doTestDumpOwnerMatchConfig(IIF_MATCH, "IIF_MATCH");
+ doTestDumpOwnerMatchConfig(LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH");
+ doTestDumpOwnerMatchConfig(OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH");
+ doTestDumpOwnerMatchConfig(OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH");
+ doTestDumpOwnerMatchConfig(OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH");
+
+ doTestDumpOwnerMatchConfig(HAPPY_BOX_MATCH | POWERSAVE_MATCH,
+ "HAPPY_BOX_MATCH POWERSAVE_MATCH");
+ doTestDumpOwnerMatchConfig(DOZABLE_MATCH | LOCKDOWN_VPN_MATCH | OEM_DENY_1_MATCH,
+ "DOZABLE_MATCH LOCKDOWN_VPN_MATCH OEM_DENY_1_MATCH");
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testDumpUidOwnerMapConfigWithInvalidMatch() throws Exception {
+ final long invalid_match = 1L << 31;
+ doTestDumpOwnerMatchConfig(invalid_match, "UNKNOWN_MATCH(" + invalid_match + ")");
+ doTestDumpOwnerMatchConfig(DOZABLE_MATCH | invalid_match,
+ "DOZABLE_MATCH UNKNOWN_MATCH(" + invalid_match + ")");
+ }
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 61ba144..c8aa59b 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -302,7 +302,6 @@
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.PrivateDnsConfig;
-import android.net.util.MultinetworkPolicyTracker;
import android.net.wifi.WifiInfo;
import android.os.BadParcelableException;
import android.os.BatteryStatsManager;
@@ -357,14 +356,16 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.net.module.util.NetworkMonitorUtils;
+import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.NetworkAgentConfigShimImpl;
-import com.android.networkstack.apishim.api29.ConstantsShim;
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
+import com.android.server.connectivity.MultinetworkPolicyTracker;
+import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkNotificationManager;
@@ -1067,38 +1068,41 @@
* @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
*/
public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
- ConnectivityManager.NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
+ final ConditionVariable capsChangedCv = new ConditionVariable();
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(getNetworkCapabilities().getTransportTypes()[0])
+ .clearCapabilities()
+ .build();
if (validated) {
setNetworkValid(isStrictMode);
- NetworkRequest request = new NetworkRequest.Builder()
- .addTransportType(getNetworkCapabilities().getTransportTypes()[0])
- .clearCapabilities()
- .build();
- callback = new ConnectivityManager.NetworkCallback() {
- public void onCapabilitiesChanged(Network network,
- NetworkCapabilities networkCapabilities) {
- if (network.equals(getNetwork()) &&
- networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ }
+ final NetworkCallback callback = new NetworkCallback() {
+ public void onCapabilitiesChanged(Network network,
+ NetworkCapabilities networkCapabilities) {
+ if (network.equals(getNetwork())) {
+ capsChangedCv.open();
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
validatedCv.open();
}
}
- };
- mCm.registerNetworkCallback(request, callback);
- }
+ }
+ };
+ mCm.registerNetworkCallback(request, callback);
+
if (hasInternet) {
addCapability(NET_CAPABILITY_INTERNET);
}
connectWithoutInternet();
+ waitFor(capsChangedCv);
if (validated) {
// Wait for network to validate.
waitFor(validatedCv);
setNetworkInvalid(isStrictMode);
}
-
- if (callback != null) mCm.unregisterNetworkCallback(callback);
+ mCm.unregisterNetworkCallback(callback);
}
public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) {
@@ -1169,10 +1173,11 @@
void setNetworkPartialValid(boolean isStrictMode) {
setNetworkPartial();
mNmValidationResult |= NETWORK_VALIDATION_RESULT_VALID;
+ mNmValidationRedirectUrl = null;
int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS
| NETWORK_VALIDATION_PROBE_HTTP;
int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP;
- // Suppose the partial network cannot pass the private DNS validation as well, so only
+ // Assume the partial network cannot pass the private DNS validation as well, so only
// add NETWORK_VALIDATION_PROBE_DNS in probesCompleted but not probesSucceeded.
if (isStrictMode) {
probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
@@ -1632,12 +1637,7 @@
volatile int mConfigMeteredMultipathPreference;
WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
- super(c, h, r);
- }
-
- @Override
- protected Resources getResourcesForActiveSubId() {
- return mResources;
+ super(c, h, r, new MultinetworkPolicyTrackerTestDependencies(mResources));
}
@Override
@@ -1836,9 +1836,8 @@
.getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any());
doReturn(R.array.network_switch_type_name).when(mResources)
.getIdentifier(eq("network_switch_type_name"), eq("array"), any());
- doReturn(R.integer.config_networkAvoidBadWifi).when(mResources)
- .getIdentifier(eq("config_networkAvoidBadWifi"), eq("integer"), any());
doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+ doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
doReturn(true).when(mResources)
.getBoolean(R.bool.config_cellular_radio_timesharing_capable);
}
@@ -3526,12 +3525,16 @@
/** Expects the specified notification and returns the notification ID. */
private int expectNotification(TestNetworkAgentWrapper agent, NotificationType type) {
- verify(mNotificationManager).notify(
+ verify(mNotificationManager, timeout(TIMEOUT_MS)).notify(
eq(NetworkNotificationManager.tagFor(agent.getNetwork().netId)),
eq(type.eventId), any());
return type.eventId;
}
+ private void expectNoNotification(@NonNull final TestNetworkAgentWrapper agent) {
+ verify(mNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(), any());
+ }
+
/**
* Expects the specified notification happens when the unvalidated prompt message arrives
*
@@ -3550,9 +3553,9 @@
* This generally happens when the network disconnects or when the newtwork validates. During
* normal usage the notification is also cleared by the system when the notification is tapped.
*/
- private void expectClearNotification(TestNetworkAgentWrapper agent, int expectedId) {
- verify(mNotificationManager).cancel(
- eq(NetworkNotificationManager.tagFor(agent.getNetwork().netId)), eq(expectedId));
+ private void expectClearNotification(TestNetworkAgentWrapper agent, NotificationType type) {
+ verify(mNotificationManager, timeout(TIMEOUT_MS)).cancel(
+ eq(NetworkNotificationManager.tagFor(agent.getNetwork().netId)), eq(type.eventId));
}
/**
@@ -3563,13 +3566,13 @@
private void expectUnvalidationCheckWillNotNotify(TestNetworkAgentWrapper agent) {
mService.scheduleEvaluationTimeout(agent.getNetwork(), 0 /*delayMs */);
waitForIdle();
- verify(mNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(), any());
+ expectNoNotification(agent);
}
private void expectDisconnectAndClearNotifications(TestNetworkCallback callback,
- TestNetworkAgentWrapper agent, int id) {
+ TestNetworkAgentWrapper agent, NotificationType type) {
callback.expectCallback(CallbackEntry.LOST, agent);
- expectClearNotification(agent, id);
+ expectClearNotification(agent, type);
}
private NativeNetworkConfig nativeNetworkConfigPhysical(int netId, int permission) {
@@ -3713,7 +3716,7 @@
// Disconnect wifi, and then reconnect, again with explicitlySelected=true.
mWiFiNetworkAgent.disconnect();
expectDisconnectAndClearNotifications(callback, mWiFiNetworkAgent,
- NotificationType.NO_INTERNET.eventId);
+ NotificationType.NO_INTERNET);
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.explicitlySelected(true, false);
@@ -3727,7 +3730,7 @@
// network to disconnect.
mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false);
expectDisconnectAndClearNotifications(callback, mWiFiNetworkAgent,
- NotificationType.NO_INTERNET.eventId);
+ NotificationType.NO_INTERNET);
reset(mNotificationManager);
// Reconnect, again with explicitlySelected=true, but this time validate.
@@ -4227,7 +4230,7 @@
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
// Once the network validates, the notification disappears.
- expectClearNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY.eventId);
+ expectClearNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY);
// Disconnect and reconnect wifi with partial connectivity again.
mWiFiNetworkAgent.disconnect();
@@ -4250,7 +4253,7 @@
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false /* accept */,
false /* always */);
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
- expectClearNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY.eventId);
+ expectClearNotification(mWiFiNetworkAgent, NotificationType.PARTIAL_CONNECTIVITY);
reset(mNotificationManager);
// If the user accepted partial connectivity before, and the device connects to that network
@@ -4326,10 +4329,11 @@
@Test
public void testCaptivePortalOnPartialConnectivity() throws Exception {
- final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
- final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
- .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
- mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+ final TestNetworkCallback wifiCallback = new TestNetworkCallback();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .build();
+ mCm.registerNetworkCallback(wifiRequest, wifiCallback);
final TestNetworkCallback validatedCallback = new TestNetworkCallback();
final NetworkRequest validatedRequest = new NetworkRequest.Builder()
@@ -4341,21 +4345,28 @@
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String redirectUrl = "http://android.com/path";
mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
- captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
+ // This is necessary because of b/245893397, the same bug that happens where we use
+ // expectAvailableDoubleValidatedCallbacks.
+ // TODO : fix b/245893397 and remove this.
+ wifiCallback.expectCapabilitiesWith(NET_CAPABILITY_CAPTIVE_PORTAL, mWiFiNetworkAgent);
+
// Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork());
verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
.launchCaptivePortalApp();
// Report that the captive portal is dismissed with partial connectivity, and check that
- // callbacks are fired.
+ // callbacks are fired with PARTIAL and without CAPTIVE_PORTAL.
mWiFiNetworkAgent.setNetworkPartial();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
waitForIdle();
- captivePortalCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
- mWiFiNetworkAgent);
+ wifiCallback.expectCapabilitiesThat(
+ mWiFiNetworkAgent, nc ->
+ nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)
+ && !nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL));
// Report partial connectivity is accepted.
mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
@@ -4363,13 +4374,12 @@
false /* always */);
waitForIdle();
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
- captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ wifiCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
- NetworkCapabilities nc =
- validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
mWiFiNetworkAgent);
- mCm.unregisterNetworkCallback(captivePortalCallback);
+ mCm.unregisterNetworkCallback(wifiCallback);
mCm.unregisterNetworkCallback(validatedCallback);
}
@@ -4452,6 +4462,11 @@
mCm.reportNetworkConnectivity(wifiNetwork, false);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ // This is necessary because of b/245893397, the same bug that happens where we use
+ // expectAvailableDoubleValidatedCallbacks.
+ // TODO : fix b/245893397 and remove this.
+ captivePortalCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED,
+ mWiFiNetworkAgent);
// Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
mCm.startCaptivePortalApp(wifiNetwork);
@@ -5621,6 +5636,24 @@
}
@Test
+ public void testActivelyPreferBadWifiSetting() throws Exception {
+ doReturn(1).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
+ mPolicyTracker.reevaluate();
+ waitForIdle();
+ assertTrue(mService.mNetworkRanker.getConfiguration().activelyPreferBadWifi());
+
+ doReturn(0).when(mResources).getInteger(R.integer.config_activelyPreferBadWifi);
+ mPolicyTracker.reevaluate();
+ waitForIdle();
+ if (SdkLevel.isAtLeastU()) {
+ // U+ ignore the setting and always actively prefers bad wifi
+ assertTrue(mService.mNetworkRanker.getConfiguration().activelyPreferBadWifi());
+ } else {
+ assertFalse(mService.mNetworkRanker.getConfiguration().activelyPreferBadWifi());
+ }
+ }
+
+ @Test
public void testOffersAvoidsBadWifi() throws Exception {
// Normal mode : the carrier doesn't restrict moving away from bad wifi.
// This has getAvoidBadWifi return true.
@@ -5737,6 +5770,52 @@
wifiCallback.assertNoCallback();
}
+ public void doTestPreferBadWifi(final boolean preferBadWifi) throws Exception {
+ // Pretend we're on a carrier that restricts switching away from bad wifi, and
+ // depending on the parameter one that may indeed prefer bad wifi.
+ doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+ doReturn(preferBadWifi ? 1 : 0).when(mResources)
+ .getInteger(R.integer.config_activelyPreferBadWifi);
+ mPolicyTracker.reevaluate();
+
+ registerDefaultNetworkCallbacks();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_WIFI)
+ .build();
+ final TestableNetworkCallback wifiCallback = new TestableNetworkCallback();
+ mCm.registerNetworkCallback(wifiRequest, wifiCallback);
+
+ // Bring up validated cell and unvalidated wifi.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+ wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+ if (preferBadWifi) {
+ expectUnvalidationCheckWillNotify(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
+ mDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ } else {
+ expectUnvalidationCheckWillNotNotify(mWiFiNetworkAgent);
+ mDefaultNetworkCallback.assertNoCallback();
+ }
+ }
+
+ @Test
+ public void testPreferBadWifi_doNotPrefer() throws Exception {
+ // Starting with U this mode is no longer supported and can't actually be tested
+ assumeFalse(SdkLevel.isAtLeastU());
+ doTestPreferBadWifi(false /* preferBadWifi */);
+ }
+
+ @Test
+ public void testPreferBadWifi_doPrefer() throws Exception {
+ doTestPreferBadWifi(true /* preferBadWifi */);
+ }
+
@Test
public void testAvoidBadWifi() throws Exception {
final ContentResolver cr = mServiceContext.getContentResolver();
@@ -5760,7 +5839,8 @@
TestNetworkCallback validatedWifiCallback = new TestNetworkCallback();
mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback);
- Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 0);
+ // Prompt mode, so notifications can be tested
+ Settings.Global.putString(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null);
mPolicyTracker.reevaluate();
// Bring up validated cell.
@@ -5782,6 +5862,7 @@
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ expectNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
// Because avoid bad wifi is off, we don't switch to cellular.
defaultCallback.assertNoCallback();
@@ -5797,14 +5878,20 @@
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
+ expectClearNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
// Switch back to a restrictive carrier.
doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
+ // A notification was already shown for this very network.
+ expectNoNotification(mWiFiNetworkAgent);
// Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
+ // In principle this is a little bit unrealistic because the switch to a less restrictive
+ // carrier above should have remove the notification but this doesn't matter for the
+ // purposes of this test.
mCm.setAvoidUnvalidated(wifiNetwork);
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
@@ -5826,6 +5913,7 @@
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ expectNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
// Simulate the user selecting "switch" and checking the don't ask again checkbox.
Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1);
@@ -5838,6 +5926,7 @@
assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
NET_CAPABILITY_VALIDATED));
assertEquals(mCm.getActiveNetwork(), cellNetwork);
+ expectClearNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
// Simulate the user turning the cellular fallback setting off and then on.
// We switch to wifi and then to cell.
@@ -5845,6 +5934,9 @@
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
+ // Notification is cleared again because CS doesn't particularly remember that it has
+ // cleared it before, and if it hasn't cleared it before then it should do so now.
+ expectClearNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1);
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
@@ -5855,6 +5947,8 @@
defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedWifiCallback.assertNoCallback();
+ // Notification is cleared yet again because the device switched to wifi.
+ expectClearNotification(mWiFiNetworkAgent, NotificationType.LOST_INTERNET);
mCm.unregisterNetworkCallback(cellNetworkCallback);
mCm.unregisterNetworkCallback(validatedWifiCallback);
@@ -12958,6 +13052,12 @@
if (null != mTestPackageDefaultNetworkCallback2) {
mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback2);
}
+ mSystemDefaultNetworkCallback = null;
+ mDefaultNetworkCallback = null;
+ mProfileDefaultNetworkCallback = null;
+ mTestPackageDefaultNetworkCallback = null;
+ mProfileDefaultNetworkCallbackAsAppUid2 = null;
+ mTestPackageDefaultNetworkCallback2 = null;
}
private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index 7646c19..85bc4a9 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -516,28 +516,38 @@
assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU + 1 /* over maximum mtu */));
}
- @Test
- public void testDump() throws Exception {
- final ClatCoordinator coordinator = makeClatCoordinator();
+ private void verifyDump(final ClatCoordinator coordinator, boolean clatStarted) {
final StringWriter stringWriter = new StringWriter();
final IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ");
- coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
coordinator.dump(ipw);
final String[] dumpStrings = stringWriter.toString().split("\n");
- assertEquals(6, dumpStrings.length);
- assertEquals("CLAT tracker: iface: test0 (1000), v4iface: v4-test0 (1001), "
- + "v4: /192.0.0.46, v6: /2001:db8:0:b11::464, pfx96: /64:ff9b::, "
- + "pid: 10483, cookie: 27149", dumpStrings[0].trim());
- assertEquals("Forwarding rules:", dumpStrings[1].trim());
- assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif",
- dumpStrings[2].trim());
- assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001",
- dumpStrings[3].trim());
- assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif",
- dumpStrings[4].trim());
- assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
- dumpStrings[5].trim());
+ if (clatStarted) {
+ assertEquals(6, dumpStrings.length);
+ assertEquals("CLAT tracker: iface: test0 (1000), v4iface: v4-test0 (1001), "
+ + "v4: /192.0.0.46, v6: /2001:db8:0:b11::464, pfx96: /64:ff9b::, "
+ + "pid: 10483, cookie: 27149", dumpStrings[0].trim());
+ assertEquals("Forwarding rules:", dumpStrings[1].trim());
+ assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif",
+ dumpStrings[2].trim());
+ assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001",
+ dumpStrings[3].trim());
+ assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif",
+ dumpStrings[4].trim());
+ assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
+ dumpStrings[5].trim());
+ } else {
+ assertEquals(1, dumpStrings.length);
+ assertEquals("<not started>", dumpStrings[0].trim());
+ }
+ }
+
+ @Test
+ public void testDump() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ verifyDump(coordinator, false /* clatStarted */);
+ coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
+ verifyDump(coordinator, true /* clatStarted */);
}
@Test
@@ -548,25 +558,18 @@
() -> coordinator.clatStart(BASE_IFACE, NETID, invalidPrefix));
}
- private void assertStartClat(final TestDependencies deps) throws Exception {
- final ClatCoordinator coordinator = new ClatCoordinator(deps);
- assertNotNull(coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
- }
-
- private void assertNotStartClat(final TestDependencies deps) {
- // Expect that the injection function of TestDependencies causes clatStart() failed.
- final ClatCoordinator coordinator = new ClatCoordinator(deps);
- assertThrows(IOException.class,
- () -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
- }
-
private void checkNotStartClat(final TestDependencies deps, final boolean needToCloseTunFd,
final boolean needToClosePacketSockFd, final boolean needToCloseRawSockFd)
throws Exception {
- // [1] Expect that modified TestDependencies can't start clatd.
- // Use precise check to make sure that there is no unexpected file descriptor closing.
clearInvocations(TUN_PFD, RAW_SOCK_PFD, PACKET_SOCK_PFD);
- assertNotStartClat(deps);
+
+ // [1] Expect that modified TestDependencies can't start clatd.
+ // Expect that the injection function of TestDependencies causes clatStart() failed.
+ final ClatCoordinator coordinatorWithBrokenDeps = new ClatCoordinator(deps);
+ assertThrows(IOException.class,
+ () -> coordinatorWithBrokenDeps.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+
+ // Use precise check to make sure that there is no unexpected file descriptor closing.
if (needToCloseTunFd) {
verify(TUN_PFD).close();
} else {
@@ -583,10 +586,15 @@
verify(RAW_SOCK_PFD, never()).close();
}
+ // Check that dump doesn't crash after any clat starting failure.
+ verifyDump(coordinatorWithBrokenDeps, false /* clatStarted */);
+
// [2] Expect that unmodified TestDependencies can start clatd.
// Used to make sure that the above modified TestDependencies has really broken the
// clatd starting.
- assertStartClat(new TestDependencies());
+ final ClatCoordinator coordinatorWithDefaultDeps = new ClatCoordinator(
+ new TestDependencies());
+ assertNotNull(coordinatorWithDefaultDeps.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
}
// The following testNotStartClat* tests verifies bunches of code for unwinding the
diff --git a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
similarity index 76%
rename from tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
rename to tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
index 0dbee5d..b52e8a8 100644
--- a/tests/unit/java/android/net/util/MultinetworkPolicyTrackerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.net.util
+package com.android.server.connectivity
import android.content.Context
import android.content.res.Resources
@@ -24,9 +24,10 @@
import android.net.ConnectivityResources
import android.net.ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI
import android.net.ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE
-import android.net.util.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
+import com.android.server.connectivity.MultinetworkPolicyTracker.ActiveDataSubscriptionIdListener
import android.os.Build
import android.os.Handler
+import android.os.test.TestLooper
import android.provider.Settings
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
@@ -35,6 +36,7 @@
import androidx.test.filters.SmallTest
import com.android.connectivity.resources.R
import com.android.internal.util.test.FakeSettingsProvider
+import com.android.modules.utils.build.SdkLevel
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import org.junit.After
@@ -46,30 +48,28 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.argThat
-import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.any
import org.mockito.Mockito.doCallRealMethod
-import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+const val HANDLER_TIMEOUT_MS = 400
+
/**
* Tests for [MultinetworkPolicyTracker].
*
* Build, install and run with:
- * atest android.net.util.MultinetworkPolicyTrackerTest
+ * atest FrameworksNetTest:MultinetworkPolicyTrackerTest
*/
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
class MultinetworkPolicyTrackerTest {
private val resources = mock(Resources::class.java).also {
- doReturn(R.integer.config_networkAvoidBadWifi).`when`(it).getIdentifier(
- eq("config_networkAvoidBadWifi"), eq("integer"), any())
doReturn(0).`when`(it).getInteger(R.integer.config_networkAvoidBadWifi)
+ doReturn(0).`when`(it).getInteger(R.integer.config_activelyPreferBadWifi)
}
private val telephonyManager = mock(TelephonyManager::class.java)
private val subscriptionManager = mock(SubscriptionManager::class.java).also {
@@ -93,8 +93,11 @@
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "1")
ConnectivityResources.setResourcesContextForTest(it)
}
- private val handler = mock(Handler::class.java)
- private val tracker = MultinetworkPolicyTracker(context, handler)
+ private val csLooper = TestLooper()
+ private val handler = Handler(csLooper.looper)
+ private val trackerDependencies = MultinetworkPolicyTrackerTestDependencies(resources)
+ private val tracker = MultinetworkPolicyTracker(context, handler,
+ null /* avoidBadWifiCallback */, trackerDependencies)
private fun assertMultipathPreference(preference: Int) {
Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
@@ -122,6 +125,7 @@
@Test
fun testUpdateAvoidBadWifi() {
+ doReturn(0).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
Settings.Global.putString(resolver, NETWORK_AVOID_BAD_WIFI, "0")
assertTrue(tracker.updateAvoidBadWifi())
assertFalse(tracker.avoidBadWifi)
@@ -129,6 +133,36 @@
doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi)
assertTrue(tracker.updateAvoidBadWifi())
assertTrue(tracker.avoidBadWifi)
+
+ if (SdkLevel.isAtLeastU()) {
+ // On U+, the system always prefers bad wifi.
+ assertTrue(tracker.activelyPreferBadWifi)
+ } else {
+ assertFalse(tracker.activelyPreferBadWifi)
+ }
+
+ doReturn(1).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
+ if (SdkLevel.isAtLeastU()) {
+ // On U+, this didn't change the setting
+ assertFalse(tracker.updateAvoidBadWifi())
+ } else {
+ // On T-, this must have changed the setting
+ assertTrue(tracker.updateAvoidBadWifi())
+ }
+ // In all cases, now the system actively prefers bad wifi
+ assertTrue(tracker.activelyPreferBadWifi)
+
+ // Remaining tests are only useful on T-, which support both the old and new mode.
+ if (SdkLevel.isAtLeastU()) return
+
+ doReturn(0).`when`(resources).getInteger(R.integer.config_activelyPreferBadWifi)
+ assertTrue(tracker.updateAvoidBadWifi())
+ assertFalse(tracker.activelyPreferBadWifi)
+
+ // Simulate update of device config
+ trackerDependencies.putConfigActivelyPreferBadWifi(1)
+ csLooper.dispatchAll()
+ assertTrue(tracker.activelyPreferBadWifi)
}
@Test
@@ -147,6 +181,8 @@
Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
MULTIPATH_PREFERENCE_PERFORMANCE.toString())
+ assertTrue(tracker.avoidBadWifi)
+
val listenerCaptor = ArgumentCaptor.forClass(
ActiveDataSubscriptionIdListener::class.java)
verify(telephonyManager, times(1))
@@ -154,10 +190,6 @@
val listener = listenerCaptor.value
listener.onActiveDataSubscriptionIdChanged(testSubId)
- // Check it get resource value with test sub id.
- verify(subscriptionManager, times(1)).getActiveSubscriptionInfo(testSubId)
- verify(context).createConfigurationContext(argThat { it.mcc == 310 && it.mnc == 210 })
-
// Check if avoidBadWifi and meteredMultipathPreference values have been updated.
assertFalse(tracker.avoidBadWifi)
assertEquals(MULTIPATH_PREFERENCE_PERFORMANCE, tracker.meteredMultipathPreference)
diff --git a/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
new file mode 100644
index 0000000..744c020
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/MultinetworkPolicyTrackerTestDependencies.kt
@@ -0,0 +1,47 @@
+package com.android.server.connectivity
+
+import android.content.res.Resources
+import android.net.ConnectivityResources
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
+import android.provider.DeviceConfig.OnPropertiesChangedListener
+import com.android.internal.annotations.GuardedBy
+import com.android.server.connectivity.MultinetworkPolicyTracker.CONFIG_ACTIVELY_PREFER_BAD_WIFI
+import java.util.concurrent.Executor
+
+class MultinetworkPolicyTrackerTestDependencies(private val resources: Resources) :
+ MultinetworkPolicyTracker.Dependencies() {
+ @GuardedBy("listeners")
+ private var configActivelyPreferBadWifi = 0
+ // TODO : move this to an actual fake device config object
+ @GuardedBy("listeners")
+ private val listeners = mutableListOf<Pair<Executor, OnPropertiesChangedListener>>()
+
+ fun putConfigActivelyPreferBadWifi(value: Int) {
+ synchronized(listeners) {
+ if (value == configActivelyPreferBadWifi) return
+ configActivelyPreferBadWifi = value
+ val p = DeviceConfig.Properties(NAMESPACE_CONNECTIVITY,
+ mapOf(CONFIG_ACTIVELY_PREFER_BAD_WIFI to value.toString()))
+ listeners.forEach { (executor, listener) ->
+ executor.execute { listener.onPropertiesChanged(p) }
+ }
+ }
+ }
+
+ override fun getConfigActivelyPreferBadWifi(): Int {
+ return synchronized(listeners) { configActivelyPreferBadWifi }
+ }
+
+ override fun addOnDevicePropertiesChangedListener(
+ e: Executor,
+ listener: OnPropertiesChangedListener
+ ) {
+ synchronized(listeners) {
+ listeners.add(e to listener)
+ }
+ }
+
+ override fun getResourcesForActiveSubId(res: ConnectivityResources, id: Int): Resources =
+ resources
+}
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 8b1c510..1e3f389 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -17,32 +17,38 @@
package com.android.server.connectivity
import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL as NET_CAP_PORTAL
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkScore.KEEP_CONNECTED_NONE
-import android.net.NetworkScore.POLICY_EXITING
-import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
-import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI
+import android.net.NetworkScore.POLICY_EXITING as EXITING
+import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY as PRIMARY
+import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI as YIELD_TO_BAD_WIFI
import android.os.Build
import androidx.test.filters.SmallTest
-import com.android.server.connectivity.FullScore.POLICY_AVOIDED_WHEN_UNVALIDATED
-import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED
-import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED
+import com.android.connectivity.resources.R
+import com.android.server.connectivity.FullScore.POLICY_AVOIDED_WHEN_UNVALIDATED as AVOIDED_UNVALID
+import com.android.server.connectivity.FullScore.POLICY_EVER_EVALUATED as EVER_EVALUATED
+import com.android.server.connectivity.FullScore.POLICY_EVER_VALIDATED as EVER_VALIDATED
+import com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED as IS_VALIDATED
import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
import kotlin.test.assertEquals
private fun score(vararg policies: Int) = FullScore(
policies.fold(0L) { acc, e -> acc or (1L shl e) }, KEEP_CONNECTED_NONE)
-private fun caps(transport: Int) = NetworkCapabilities.Builder().addTransportType(transport).build()
+private fun caps(transport: Int, vararg capabilities: Int) =
+ NetworkCapabilities.Builder().addTransportType(transport).apply {
+ capabilities.forEach { addCapability(it) }
+ }.build()
@SmallTest
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class NetworkRankerTest {
- private val mRanker = NetworkRanker()
+@RunWith(Parameterized::class)
+class NetworkRankerTest(private val activelyPreferBadWifi: Boolean) {
+ private val mRanker = NetworkRanker(NetworkRanker.Configuration(activelyPreferBadWifi))
private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities)
: NetworkRanker.Scoreable {
@@ -50,133 +56,144 @@
override fun getCapsNoCopy(): NetworkCapabilities = nc
}
+ @get:Rule
+ val mIgnoreRule: DevSdkIgnoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.R)
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun ranker() = listOf(true, false)
+ }
+
+ // Helpers to shorten syntax
+ private fun rank(vararg scores: TestScore) =
+ mRanker.getBestNetworkByPolicy(scores.toList(), null /* currentSatisfier */)
+ val CAPS_CELL = caps(TRANSPORT_CELLULAR)
+ val CAPS_WIFI = caps(TRANSPORT_WIFI)
+ val CAPS_WIFI_PORTAL = caps(TRANSPORT_WIFI, NET_CAP_PORTAL)
+
@Test
- fun testYieldToBadWiFiOneCell() {
+ fun testYieldToBadWiFi_oneCell() {
// Only cell, it wins
- val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- val scores = listOf(winner)
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(cell, rank(cell))
}
@Test
- fun testYieldToBadWiFiOneCellOneBadWiFi() {
+ fun testPreferBadWifi_oneCellOneEvaluatingWifi() {
+ val wifi = TestScore(score(), caps(TRANSPORT_WIFI))
+ val cell = TestScore(score(YIELD_TO_BAD_WIFI, IS_VALIDATED, EVER_EVALUATED), CAPS_CELL)
+ assertEquals(cell, rank(wifi, cell))
+ }
+
+ @Test
+ fun testYieldToBadWiFi_oneCellOneBadWiFi() {
// Bad wifi wins against yielding validated cell
- val winner = TestScore(score(POLICY_EVER_VALIDATED), caps(TRANSPORT_WIFI))
- val scores = listOf(
- winner,
- TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED), CAPS_WIFI)
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(badWifi, rank(badWifi, cell))
}
@Test
- fun testYieldToBadWifiAvoidUnvalidated() {
+ fun testPreferBadWifi_oneCellOneBadWifi() {
+ val badWifi = TestScore(score(EVER_EVALUATED), CAPS_WIFI)
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ val winner = if (activelyPreferBadWifi) badWifi else cell
+ assertEquals(winner, rank(badWifi, cell))
+ }
+
+ @Test
+ fun testPreferBadWifi_oneCellOneCaptivePortalWifi() {
+ val portalWifi = TestScore(score(EVER_EVALUATED), CAPS_WIFI_PORTAL)
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(cell, rank(portalWifi, cell))
+ }
+
+ @Test
+ fun testYieldToBadWifi_oneCellOneCaptivePortalWifiThatClosed() {
+ val portalWifiClosed = TestScore(score(EVER_EVALUATED, EVER_VALIDATED), CAPS_WIFI_PORTAL)
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(portalWifiClosed, rank(portalWifiClosed, cell))
+ }
+
+ @Test
+ fun testYieldToBadWifi_avoidUnvalidated() {
// Bad wifi avoided when unvalidated loses against yielding validated cell
- val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- val scores = listOf(
- winner,
- TestScore(score(POLICY_EVER_VALIDATED, POLICY_AVOIDED_WHEN_UNVALIDATED),
- caps(TRANSPORT_WIFI))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ val avoidedWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, AVOIDED_UNVALID),
+ CAPS_WIFI)
+ assertEquals(cell, rank(cell, avoidedWifi))
}
@Test
- fun testYieldToBadWiFiOneCellTwoBadWiFi() {
+ fun testYieldToBadWiFi_oneCellTwoBadWiFi() {
// Bad wifi wins against yielding validated cell. Prefer the one that's primary.
- val winner = TestScore(score(POLICY_EVER_VALIDATED,
- POLICY_TRANSPORT_PRIMARY), caps(TRANSPORT_WIFI))
- val scores = listOf(
- winner,
- TestScore(score(POLICY_EVER_VALIDATED), caps(TRANSPORT_WIFI)),
- TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val primaryBadWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, PRIMARY), CAPS_WIFI)
+ val secondaryBadWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED), CAPS_WIFI)
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(primaryBadWifi, rank(primaryBadWifi, secondaryBadWifi, cell))
}
@Test
- fun testYieldToBadWiFiOneCellTwoBadWiFiOneNotAvoided() {
+ fun testYieldToBadWiFi_oneCellTwoBadWiFiOneNotAvoided() {
// Bad wifi ever validated wins against bad wifi that never was validated (or was
// avoided when bad).
- val winner = TestScore(score(POLICY_EVER_VALIDATED), caps(TRANSPORT_WIFI))
- val scores = listOf(
- winner,
- TestScore(score(), caps(TRANSPORT_WIFI)),
- TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED), CAPS_WIFI)
+ val neverValidatedWifi = TestScore(score(), CAPS_WIFI)
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(badWifi, rank(badWifi, neverValidatedWifi, cell))
}
@Test
- fun testYieldToBadWiFiOneCellOneBadWiFiOneGoodWiFi() {
+ fun testYieldToBadWiFi_oneCellOneBadWiFiOneGoodWiFi() {
// Good wifi wins
- val winner = TestScore(score(POLICY_EVER_VALIDATED, POLICY_IS_VALIDATED),
- caps(TRANSPORT_WIFI))
- val scores = listOf(
- winner,
- TestScore(score(POLICY_EVER_VALIDATED, POLICY_TRANSPORT_PRIMARY),
- caps(TRANSPORT_WIFI)),
- TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val goodWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, IS_VALIDATED), CAPS_WIFI)
+ val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, PRIMARY), CAPS_WIFI)
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(goodWifi, rank(goodWifi, badWifi, cell))
}
@Test
- fun testYieldToBadWiFiTwoCellsOneBadWiFi() {
+ fun testPreferBadWifi_oneCellOneBadWifiOneEvaluatingWifi() {
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ val badWifi = TestScore(score(EVER_EVALUATED), CAPS_WIFI)
+ val evaluatingWifi = TestScore(score(), CAPS_WIFI)
+ val winner = if (activelyPreferBadWifi) badWifi else cell
+ assertEquals(winner, rank(cell, badWifi, evaluatingWifi))
+ }
+
+ @Test
+ fun testYieldToBadWiFi_twoCellsOneBadWiFi() {
// Cell that doesn't yield wins over cell that yields and bad wifi
- val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR))
- val scores = listOf(
- winner,
- TestScore(score(POLICY_EVER_VALIDATED, POLICY_TRANSPORT_PRIMARY),
- caps(TRANSPORT_WIFI)),
- TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val cellNotYield = TestScore(score(EVER_EVALUATED, IS_VALIDATED), CAPS_CELL)
+ val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, PRIMARY), CAPS_WIFI)
+ val cellYield = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(cellNotYield, rank(cellNotYield, badWifi, cellYield))
}
@Test
- fun testYieldToBadWiFiTwoCellsOneBadWiFiOneGoodWiFi() {
+ fun testYieldToBadWiFi_twoCellsOneBadWiFiOneGoodWiFi() {
// Good wifi wins over cell that doesn't yield and cell that yields
- val winner = TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_WIFI))
- val scores = listOf(
- winner,
- TestScore(score(POLICY_EVER_VALIDATED, POLICY_TRANSPORT_PRIMARY),
- caps(TRANSPORT_WIFI)),
- TestScore(score(POLICY_IS_VALIDATED), caps(TRANSPORT_CELLULAR)),
- TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val goodWifi = TestScore(score(EVER_EVALUATED, IS_VALIDATED), CAPS_WIFI)
+ val badWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, PRIMARY), CAPS_WIFI)
+ val cellNotYield = TestScore(score(EVER_EVALUATED, IS_VALIDATED), CAPS_CELL)
+ val cellYield = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ assertEquals(goodWifi, rank(goodWifi, badWifi, cellNotYield, cellYield))
}
@Test
- fun testYieldToBadWiFiOneExitingGoodWiFi() {
+ fun testYieldToBadWiFi_oneExitingGoodWiFi() {
// Yielding cell wins over good exiting wifi
- val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- val scores = listOf(
- winner,
- TestScore(score(POLICY_IS_VALIDATED, POLICY_EXITING), caps(TRANSPORT_WIFI))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ val exitingWifi = TestScore(score(EVER_EVALUATED, IS_VALIDATED, EXITING), CAPS_WIFI)
+ assertEquals(cell, rank(cell, exitingWifi))
}
@Test
- fun testYieldToBadWiFiOneExitingBadWiFi() {
+ fun testYieldToBadWiFi_oneExitingBadWiFi() {
// Yielding cell wins over bad exiting wifi
- val winner = TestScore(score(POLICY_YIELD_TO_BAD_WIFI, POLICY_IS_VALIDATED),
- caps(TRANSPORT_CELLULAR))
- val scores = listOf(
- winner,
- TestScore(score(POLICY_EVER_VALIDATED, POLICY_EXITING), caps(TRANSPORT_WIFI))
- )
- assertEquals(winner, mRanker.getBestNetworkByPolicy(scores, null))
+ val cell = TestScore(score(EVER_EVALUATED, YIELD_TO_BAD_WIFI, IS_VALIDATED), CAPS_CELL)
+ val badExitingWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, EXITING), CAPS_WIFI)
+ assertEquals(cell, rank(cell, badExitingWifi))
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 06dabb0..1c54651 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -29,7 +29,6 @@
import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.UserHandle.PER_USER_RANGE;
-import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.MiscAsserts.assertThrows;
@@ -41,7 +40,6 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -136,7 +134,6 @@
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.util.HexDump;
-import com.android.modules.utils.build.SdkLevel;
import com.android.server.DeviceIdleInternal;
import com.android.server.IpSecService;
import com.android.server.VpnTestBase;
@@ -341,7 +338,7 @@
@After
public void tearDown() throws Exception {
- doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
}
private <T> void mockService(Class<T> clazz, String name, T service) {
@@ -695,7 +692,6 @@
@Test
public void testPrepare_throwSecurityExceptionWhenGivenPackageDoesNotBelongToTheCaller()
throws Exception {
- assumeTrue(isAtLeastT());
final Vpn vpn = createVpnAndSetupUidChecks();
assertThrows(SecurityException.class,
() -> vpn.prepare("com.not.vpn.owner", null, VpnManager.TYPE_VPN_SERVICE));
@@ -1168,7 +1164,6 @@
@Test
public void testStartOpAndFinishOpWillBeCalledWhenPlatformVpnIsOnAndOff() throws Exception {
- assumeTrue(SdkLevel.isAtLeastT());
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
@@ -1194,7 +1189,6 @@
@Test
public void testStartOpWithSeamlessHandover() throws Exception {
- assumeTrue(SdkLevel.isAtLeastT());
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_VPN);
assertTrue(vpn.prepare(TEST_VPN_PKG, null, VpnManager.TYPE_VPN_SERVICE));
final VpnConfig config = new VpnConfig();
@@ -1226,7 +1220,7 @@
}
private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass,
- int errorCode, VpnProfileState... profileState) {
+ int errorCode, String[] packageName, VpnProfileState... profileState) {
final Context userContext =
mContext.createContextAsUser(UserHandle.of(PRIMARY_USER.id), 0 /* flags */);
final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1236,9 +1230,11 @@
for (int i = 0; i < verifyTimes; i++) {
final Intent intent = intentArgumentCaptor.getAllValues().get(i);
+ assertEquals(packageName[i], intent.getPackage());
assertEquals(sessionKey, intent.getStringExtra(VpnManager.EXTRA_SESSION_KEY));
final Set<String> categories = intent.getCategories();
assertTrue(categories.contains(category));
+ assertEquals(1, categories.size());
assertEquals(errorClass,
intent.getIntExtra(VpnManager.EXTRA_ERROR_CLASS, -1 /* defaultValue */));
assertEquals(errorCode,
@@ -1251,9 +1247,21 @@
reset(userContext);
}
+ private void verifyDeactivatedByUser(String sessionKey, String[] packageName) {
+ // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
+ // errorCode won't be set.
+ verifyVpnManagerEvent(sessionKey, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode */, packageName, null /* profileState */);
+ }
+
+ private void verifyAlwaysOnStateChanged(String[] packageName, VpnProfileState... profileState) {
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, packageName, profileState);
+ }
+
@Test
public void testVpnManagerEventForUserDeactivated() throws Exception {
- assumeTrue(SdkLevel.isAtLeastT());
// For security reasons, Vpn#prepare() will check that oldPackage and newPackage are either
// null or the package of the caller. This test will call Vpn#prepare() to pretend the old
// VPN is replaced by a new one. But only Settings can change to some other packages, and
@@ -1271,10 +1279,7 @@
verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
reset(mDeviceIdleInternal);
- // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
- // errorCode won't be set.
- verifyVpnManagerEvent(sessionKey1, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
- -1 /* errorClass */, -1 /* errorCode */, null /* profileState */);
+ verifyDeactivatedByUser(sessionKey1, new String[] {TEST_VPN_PKG});
reset(mAppOps);
// Test the case that the user chooses another vpn and the original one is replaced.
@@ -1284,15 +1289,11 @@
verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
reset(mDeviceIdleInternal);
- // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
- // errorCode won't be set.
- verifyVpnManagerEvent(sessionKey2, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
- -1 /* errorClass */, -1 /* errorCode */, null /* profileState */);
+ verifyDeactivatedByUser(sessionKey2, new String[] {TEST_VPN_PKG});
}
@Test
public void testVpnManagerEventForAlwaysOnChanged() throws Exception {
- assumeTrue(SdkLevel.isAtLeastT());
// Calling setAlwaysOnPackage() needs to hold CONTROL_VPN.
doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
final Vpn vpn = createVpn(PRIMARY_USER.id);
@@ -1301,9 +1302,8 @@
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
- verifyVpnManagerEvent(null /* sessionKey */,
- VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
- -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+ new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
// Enable VPN lockdown for PKGS[1].
@@ -1311,9 +1311,8 @@
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
- verifyVpnManagerEvent(null /* sessionKey */,
- VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
- -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+ new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, true /* lockdown */));
// Disable VPN lockdown for PKGS[1].
@@ -1321,9 +1320,8 @@
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
- verifyVpnManagerEvent(null /* sessionKey */,
- VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
- -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+ new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
// Disable VPN always-on.
@@ -1331,9 +1329,8 @@
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
- verifyVpnManagerEvent(null /* sessionKey */,
- VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
- -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+ new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, false /* alwaysOn */, false /* lockdown */));
// Enable VPN always-on for PKGS[1] again.
@@ -1341,9 +1338,8 @@
null /* lockdownAllowlist */));
verifyPowerSaveTempWhitelistApp(PKGS[1]);
reset(mDeviceIdleInternal);
- verifyVpnManagerEvent(null /* sessionKey */,
- VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
- -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ verifyAlwaysOnStateChanged(new String[] {PKGS[1]},
+ new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
// Enable VPN always-on for PKGS[2].
@@ -1355,9 +1351,8 @@
// Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to
// PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to
// PKGS[2] to notify PKGS[2] that the VPN always-on is enabled.
- verifyVpnManagerEvent(null /* sessionKey */,
- VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
- -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ verifyAlwaysOnStateChanged(new String[] {PKGS[1], PKGS[2]},
+ new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, false /* alwaysOn */, false /* lockdown */),
new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
@@ -1479,7 +1474,8 @@
verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
reset(mDeviceIdleInternal);
- verifyVpnManagerEvent(sessionKey, category, errorType, errorCode, null /* profileState */);
+ verifyVpnManagerEvent(sessionKey, category, errorType, errorCode,
+ new String[] {TEST_VPN_PKG}, null /* profileState */);
if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
.unregisterNetworkCallback(eq(cb));
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index aad80d5..949e0c2 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -25,7 +25,6 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
@@ -306,35 +305,6 @@
}
@Test
- public void testUpdateInterfaceLinkStateForProvisionedInterface() throws Exception {
- initEthernetNetworkFactory();
- createAndVerifyProvisionedInterface(TEST_IFACE);
-
- final boolean retDown = mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */);
-
- assertTrue(retDown);
- verifyStop();
-
- final boolean retUp = mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */);
-
- assertTrue(retUp);
- }
-
- @Test
- public void testUpdateInterfaceLinkStateForUnprovisionedInterface() throws Exception {
- initEthernetNetworkFactory();
- createUnprovisionedInterface(TEST_IFACE);
-
- final boolean ret = mNetFactory.updateInterfaceLinkState(TEST_IFACE, false /* up */);
-
- assertTrue(ret);
- // There should not be an active IPClient or NetworkAgent.
- verify(mDeps, never()).makeIpClient(any(), any(), any());
- verify(mDeps, never())
- .makeEthernetNetworkAgent(any(), any(), any(), any(), any(), any(), any());
- }
-
- @Test
public void testUpdateInterfaceLinkStateForNonExistingInterface() throws Exception {
initEthernetNetworkFactory();
@@ -346,17 +316,6 @@
}
@Test
- public void testUpdateInterfaceLinkStateWithNoChanges() throws Exception {
- initEthernetNetworkFactory();
- createAndVerifyProvisionedInterface(TEST_IFACE);
-
- final boolean ret = mNetFactory.updateInterfaceLinkState(TEST_IFACE, true /* up */);
-
- assertFalse(ret);
- verifyNoStopOrStart();
- }
-
- @Test
public void testProvisioningLoss() throws Exception {
initEthernetNetworkFactory();
when(mDeps.getNetworkInterfaceByName(TEST_IFACE)).thenReturn(mInterfaceParams);
@@ -390,17 +349,6 @@
}
@Test
- public void testLinkPropertiesChanged() throws Exception {
- initEthernetNetworkFactory();
- createAndVerifyProvisionedInterface(TEST_IFACE);
-
- LinkProperties lp = new LinkProperties();
- mIpClientCallbacks.onLinkPropertiesChange(lp);
- mLooper.dispatchAll();
- verify(mNetworkAgent).sendLinkPropertiesImpl(same(lp));
- }
-
- @Test
public void testNetworkUnwanted() throws Exception {
initEthernetNetworkFactory();
createAndVerifyProvisionedInterface(TEST_IFACE);
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index dde1d94..5e7f0ff 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -23,24 +23,15 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.content.Context;
-import android.net.EthernetManager;
-import android.net.IEthernetServiceListener;
import android.net.INetd;
import android.net.InetAddresses;
-import android.net.InterfaceConfigurationParcel;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
@@ -66,7 +57,6 @@
import java.net.InetAddress;
import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
@@ -384,74 +374,4 @@
assertTrue(isValidTestInterface);
}
-
- public static class EthernetStateListener extends IEthernetServiceListener.Stub {
- @Override
- public void onEthernetStateChanged(int state) { }
-
- @Override
- public void onInterfaceStateChanged(String iface, int state, int role,
- IpConfiguration configuration) { }
- }
-
- private InterfaceConfigurationParcel createMockedIfaceParcel(final String ifname,
- final String hwAddr) {
- final InterfaceConfigurationParcel ifaceParcel = new InterfaceConfigurationParcel();
- ifaceParcel.ifName = ifname;
- ifaceParcel.hwAddr = hwAddr;
- ifaceParcel.flags = new String[] {INetd.IF_STATE_UP};
- return ifaceParcel;
- }
-
- @Test
- public void testListenEthernetStateChange() throws Exception {
- tracker.setIncludeTestInterfaces(true);
- waitForIdle();
-
- final String testIface = "testtap123";
- final String testHwAddr = "11:22:33:44:55:66";
- final InterfaceConfigurationParcel ifaceParcel = createMockedIfaceParcel(testIface,
- testHwAddr);
- when(mNetd.interfaceGetList()).thenReturn(new String[] {testIface});
- when(mNetd.interfaceGetCfg(eq(testIface))).thenReturn(ifaceParcel);
- doReturn(new String[] {testIface}).when(mFactory).getAvailableInterfaces(anyBoolean());
-
- final AtomicBoolean ifaceUp = new AtomicBoolean(true);
- doAnswer(inv -> ifaceUp.get()).when(mFactory).hasInterface(testIface);
- doAnswer(inv ->
- ifaceUp.get() ? EthernetManager.STATE_LINK_UP : EthernetManager.STATE_ABSENT)
- .when(mFactory).getInterfaceState(testIface);
- doAnswer(inv -> {
- ifaceUp.set(true);
- return null;
- }).when(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
- doAnswer(inv -> {
- ifaceUp.set(false);
- return null;
- }).when(mFactory).removeInterface(testIface);
-
- final EthernetStateListener listener = spy(new EthernetStateListener());
- tracker.addListener(listener, true /* canUseRestrictedNetworks */);
- // Check default state.
- waitForIdle();
- verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
- anyInt(), any());
- verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
- reset(listener);
-
- tracker.setEthernetEnabled(false);
- waitForIdle();
- verify(mFactory).removeInterface(eq(testIface));
- verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_DISABLED));
- verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_ABSENT),
- anyInt(), any());
- reset(listener);
-
- tracker.setEthernetEnabled(true);
- waitForIdle();
- verify(mFactory).addInterface(eq(testIface), eq(testHwAddr), any(), any());
- verify(listener).onEthernetStateChanged(eq(EthernetManager.ETHERNET_STATE_ENABLED));
- verify(listener).onInterfaceStateChanged(eq(testIface), eq(EthernetManager.STATE_LINK_UP),
- anyInt(), any());
- }
}