Merge "Require location permission for ConnDiags WiFi only."
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 79c2b9d..5a08b23 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -146,7 +146,10 @@
manifest: "AndroidManifest.xml",
use_embedded_native_libs: true,
// The permission configuration *must* be included to ensure security of the device
- required: ["NetworkPermissionConfig"],
+ required: [
+ "NetworkPermissionConfig",
+ "privapp_whitelist_com.android.networkstack.tethering",
+ ],
apex_available: ["com.android.tethering"],
min_sdk_version: "30",
}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 917bf21..bd7ebda 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -41,8 +41,12 @@
"ServiceConnectivityResources",
"Tethering",
],
+ prebuilts: ["current_sdkinfo"],
manifest: "manifest.json",
key: "com.android.tethering.key",
+ // Indicates that pre-installed version of this apex can be compressed.
+ // Whether it actually will be compressed is controlled on per-device basis.
+ compressible: true,
androidManifest: "AndroidManifest.xml",
}
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index e47d802..4bc8f93 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -56,6 +56,8 @@
"src/android/net/TetheringConfigurationParcel.aidl",
"src/android/net/TetheringRequestParcel.aidl",
"src/android/net/TetherStatesParcel.aidl",
+ "src/android/net/TetheringInterface.aidl",
+ "src/android/net/TetheringInterface.java",
],
path: "src"
}
diff --git a/Tethering/common/TetheringLib/api/module-lib-current.txt b/Tethering/common/TetheringLib/api/module-lib-current.txt
index 6ddb122..0566040 100644
--- a/Tethering/common/TetheringLib/api/module-lib-current.txt
+++ b/Tethering/common/TetheringLib/api/module-lib-current.txt
@@ -28,13 +28,13 @@
}
public static interface TetheringManager.TetheringEventCallback {
- method public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
+ method @Deprecated public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps);
}
- public static class TetheringManager.TetheringInterfaceRegexps {
- method @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
- method @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
- method @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
+ @Deprecated public static class TetheringManager.TetheringInterfaceRegexps {
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableBluetoothRegexs();
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableUsbRegexs();
+ method @Deprecated @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs();
}
}
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index 105bab1..844ff64 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -19,6 +19,15 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR;
}
+ public final class TetheringInterface implements android.os.Parcelable {
+ ctor public TetheringInterface(int, @NonNull String);
+ method public int describeContents();
+ method @NonNull public String getInterface();
+ method public int getType();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheringInterface> CREATOR;
+ }
+
public class TetheringManager {
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
@@ -26,7 +35,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
- field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+ field @Deprecated public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
field public static final int CONNECTIVITY_SCOPE_GLOBAL = 1; // 0x1
field public static final int CONNECTIVITY_SCOPE_LOCAL = 2; // 0x2
field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
@@ -74,10 +83,14 @@
public static interface TetheringManager.TetheringEventCallback {
method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
method public default void onError(@NonNull String, int);
+ method public default void onError(@NonNull android.net.TetheringInterface, int);
method public default void onLocalOnlyInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+ method public default void onLocalOnlyInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
method public default void onOffloadStatusChanged(int);
method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+ method public default void onTetherableInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
+ method public default void onTetheredInterfacesChanged(@NonNull java.util.Set<android.net.TetheringInterface>);
method public default void onTetheringSupported(boolean);
method public default void onUpstreamChanged(@Nullable android.net.Network);
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
index 3d842b3..43262fb 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetherStatesParcel.aidl
@@ -16,15 +16,17 @@
package android.net;
+import android.net.TetheringInterface;
+
/**
* Status details for tethering downstream interfaces.
* {@hide}
*/
parcelable TetherStatesParcel {
- String[] availableList;
- String[] tetheredList;
- String[] localOnlyList;
- String[] erroredIfaceList;
+ TetheringInterface[] availableList;
+ TetheringInterface[] tetheredList;
+ TetheringInterface[] localOnlyList;
+ TetheringInterface[] erroredIfaceList;
// List of Last error code corresponding to each errored iface in erroredIfaceList. */
// TODO: Improve this as b/143122247.
int[] lastErrorList;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl
new file mode 100644
index 0000000..7151984
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2021 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;
+
+@JavaOnlyStableParcelable parcelable TetheringInterface;
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
new file mode 100644
index 0000000..84cdef1
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringInterface.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.TetheringManager.TetheringType;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The mapping of tethering interface and type.
+ * @hide
+ */
+@SystemApi
+public final class TetheringInterface implements Parcelable {
+ private final int mType;
+ private final String mInterface;
+
+ public TetheringInterface(@TetheringType int type, @NonNull String iface) {
+ Objects.requireNonNull(iface);
+ mType = type;
+ mInterface = iface;
+ }
+
+ private TetheringInterface(@NonNull Parcel in) {
+ this(in.readInt(), in.readString());
+ }
+
+ /** Get tethering type. */
+ public int getType() {
+ return mType;
+ }
+
+ /** Get tethering interface. */
+ @NonNull
+ public String getInterface() {
+ return mInterface;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeString(mInterface);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mInterface);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TetheringInterface)) return false;
+ final TetheringInterface other = (TetheringInterface) obj;
+ return mType == other.mType && mInterface.equals(other.mInterface);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<TetheringInterface> CREATOR = new Creator<TetheringInterface>() {
+ @NonNull
+ @Override
+ public TetheringInterface createFromParcel(@NonNull Parcel in) {
+ return new TetheringInterface(in);
+ }
+
+ @NonNull
+ @Override
+ public TetheringInterface[] newArray(int size) {
+ return new TetheringInterface[size];
+ }
+ };
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "TetheringInterface {mType=" + mType
+ + ", mInterface=" + mInterface + "}";
+ }
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index c64da8a..73ab908 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -30,6 +30,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -43,6 +44,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
@@ -83,7 +85,10 @@
* {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate
* the current state of tethering. Each include a list of
* interface names in that state (may be empty).
+ *
+ * @deprecated New client should use TetheringEventCallback instead.
*/
+ @Deprecated
public static final String ACTION_TETHER_STATE_CHANGED =
"android.net.conn.TETHER_STATE_CHANGED";
@@ -531,7 +536,7 @@
/**
* Attempt to both alter the mode of USB and Tethering of USB.
*
- * @deprecated New client should not use this API anymore. All clients should use
+ * @deprecated New clients should not use this API anymore. All clients should use
* #startTethering or #stopTethering which encapsulate proper entitlement logic. If the API is
* used and an entitlement check is needed, downstream USB tethering will be enabled but will
* not have any upstream.
@@ -978,8 +983,13 @@
* multiple times later upon changes.
* @param reg The new regular expressions.
*
+ * @deprecated New clients should use the callbacks with {@link TetheringInterface} which
+ * has the mapping between tethering type and interface. InterfaceRegex is no longer needed
+ * to determine the mapping of tethering type and interface.
+ *
* @hide
*/
+ @Deprecated
@SystemApi(client = MODULE_LIBRARIES)
default void onTetherableInterfaceRegexpsChanged(@NonNull TetheringInterfaceRegexps reg) {}
@@ -994,15 +1004,43 @@
default void onTetherableInterfacesChanged(@NonNull List<String> interfaces) {}
/**
+ * Called when there was a change in the list of tetherable interfaces. Tetherable
+ * interface means this interface is available and can be used for tethering.
+ *
+ * <p>This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The set of TetheringInterface of currently tetherable interface.
+ */
+ default void onTetherableInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
+ // By default, the new callback calls the old callback, so apps
+ // implementing the old callback just work.
+ onTetherableInterfacesChanged(toIfaces(interfaces));
+ }
+
+ /**
* Called when there was a change in the list of tethered interfaces.
*
* <p>This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes.
- * @param interfaces The list of 0 or more String of currently tethered interface names.
+ * @param interfaces The lit of 0 or more String of currently tethered interface names.
*/
default void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {}
/**
+ * Called when there was a change in the list of tethered interfaces.
+ *
+ * <p>This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The set of 0 or more TetheringInterface of currently tethered
+ * interface.
+ */
+ default void onTetheredInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
+ // By default, the new callback calls the old callback, so apps
+ // implementing the old callback just work.
+ onTetheredInterfacesChanged(toIfaces(interfaces));
+ }
+
+ /**
* Called when there was a change in the list of local-only interfaces.
*
* <p>This will be called immediately after the callback is registered, and may be called
@@ -1012,6 +1050,20 @@
default void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {}
/**
+ * Called when there was a change in the list of local-only interfaces.
+ *
+ * <p>This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The set of 0 or more TetheringInterface of active local-only
+ * interface.
+ */
+ default void onLocalOnlyInterfacesChanged(@NonNull Set<TetheringInterface> interfaces) {
+ // By default, the new callback calls the old callback, so apps
+ // implementing the old callback just work.
+ onLocalOnlyInterfacesChanged(toIfaces(interfaces));
+ }
+
+ /**
* Called when an error occurred configuring tethering.
*
* <p>This will be called immediately after the callback is registered if the latest status
@@ -1022,6 +1074,20 @@
default void onError(@NonNull String ifName, @TetheringIfaceError int error) {}
/**
+ * Called when an error occurred configuring tethering.
+ *
+ * <p>This will be called immediately after the callback is registered if the latest status
+ * on the interface is an error, and may be called multiple times later upon changes.
+ * @param iface The interface that experienced the error.
+ * @param error One of {@code TetheringManager#TETHER_ERROR_*}.
+ */
+ default void onError(@NonNull TetheringInterface iface, @TetheringIfaceError int error) {
+ // By default, the new callback calls the old callback, so apps
+ // implementing the old callback just work.
+ onError(iface.getInterface(), error);
+ }
+
+ /**
* Called when the list of tethered clients changes.
*
* <p>This callback provides best-effort information on connected clients based on state
@@ -1044,9 +1110,37 @@
}
/**
- * Regular expressions used to identify tethering interfaces.
+ * Covert DownStreamInterface collection to interface String array list. Internal use only.
+ *
* @hide
*/
+ public static ArrayList<String> toIfaces(Collection<TetheringInterface> tetherIfaces) {
+ final ArrayList<String> ifaces = new ArrayList<>();
+ for (TetheringInterface tether : tetherIfaces) {
+ ifaces.add(tether.getInterface());
+ }
+
+ return ifaces;
+ }
+
+ private static String[] toIfaces(TetheringInterface[] tetherIfaces) {
+ final String[] ifaces = new String[tetherIfaces.length];
+ for (int i = 0; i < tetherIfaces.length; i++) {
+ ifaces[i] = tetherIfaces[i].getInterface();
+ }
+
+ return ifaces;
+ }
+
+
+ /**
+ * Regular expressions used to identify tethering interfaces.
+ *
+ * @deprecated Instead of using regex to determine tethering type. New client could use the
+ * callbacks with {@link TetheringInterface} which has the mapping of type and interface.
+ * @hide
+ */
+ @Deprecated
@SystemApi(client = MODULE_LIBRARIES)
public static class TetheringInterfaceRegexps {
private final String[] mTetherableBluetoothRegexs;
@@ -1114,10 +1208,10 @@
}
final ITetheringEventCallback remoteCallback = new ITetheringEventCallback.Stub() {
// Only accessed with a lock on this object
- private final HashMap<String, Integer> mErrorStates = new HashMap<>();
- private String[] mLastTetherableInterfaces = null;
- private String[] mLastTetheredInterfaces = null;
- private String[] mLastLocalOnlyInterfaces = null;
+ private final HashMap<TetheringInterface, Integer> mErrorStates = new HashMap<>();
+ private TetheringInterface[] mLastTetherableInterfaces = null;
+ private TetheringInterface[] mLastTetheredInterfaces = null;
+ private TetheringInterface[] mLastLocalOnlyInterfaces = null;
@Override
public void onUpstreamChanged(Network network) throws RemoteException {
@@ -1128,14 +1222,14 @@
private synchronized void sendErrorCallbacks(final TetherStatesParcel newStates) {
for (int i = 0; i < newStates.erroredIfaceList.length; i++) {
- final String iface = newStates.erroredIfaceList[i];
- final Integer lastError = mErrorStates.get(iface);
+ final TetheringInterface tetherIface = newStates.erroredIfaceList[i];
+ final Integer lastError = mErrorStates.get(tetherIface);
final int newError = newStates.lastErrorList[i];
if (newError != TETHER_ERROR_NO_ERROR
&& !Objects.equals(lastError, newError)) {
- callback.onError(iface, newError);
+ callback.onError(tetherIface, newError);
}
- mErrorStates.put(iface, newError);
+ mErrorStates.put(tetherIface, newError);
}
}
@@ -1144,7 +1238,7 @@
if (Arrays.equals(mLastTetherableInterfaces, newStates.availableList)) return;
mLastTetherableInterfaces = newStates.availableList.clone();
callback.onTetherableInterfacesChanged(
- Collections.unmodifiableList(Arrays.asList(mLastTetherableInterfaces)));
+ Collections.unmodifiableSet((new ArraySet(mLastTetherableInterfaces))));
}
private synchronized void maybeSendTetheredIfacesChangedCallback(
@@ -1152,7 +1246,7 @@
if (Arrays.equals(mLastTetheredInterfaces, newStates.tetheredList)) return;
mLastTetheredInterfaces = newStates.tetheredList.clone();
callback.onTetheredInterfacesChanged(
- Collections.unmodifiableList(Arrays.asList(mLastTetheredInterfaces)));
+ Collections.unmodifiableSet((new ArraySet(mLastTetheredInterfaces))));
}
private synchronized void maybeSendLocalOnlyIfacesChangedCallback(
@@ -1160,7 +1254,7 @@
if (Arrays.equals(mLastLocalOnlyInterfaces, newStates.localOnlyList)) return;
mLastLocalOnlyInterfaces = newStates.localOnlyList.clone();
callback.onLocalOnlyInterfacesChanged(
- Collections.unmodifiableList(Arrays.asList(mLastLocalOnlyInterfaces)));
+ Collections.unmodifiableSet((new ArraySet(mLastLocalOnlyInterfaces))));
}
// Called immediately after the callbacks are registered.
@@ -1262,8 +1356,8 @@
if (mTetherStatesParcel == null) return TETHER_ERROR_NO_ERROR;
int i = 0;
- for (String errored : mTetherStatesParcel.erroredIfaceList) {
- if (iface.equals(errored)) return mTetherStatesParcel.lastErrorList[i];
+ for (TetheringInterface errored : mTetherStatesParcel.erroredIfaceList) {
+ if (iface.equals(errored.getInterface())) return mTetherStatesParcel.lastErrorList[i];
i++;
}
@@ -1327,7 +1421,7 @@
mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
- return mTetherStatesParcel.availableList;
+ return toIfaces(mTetherStatesParcel.availableList);
}
/**
@@ -1341,7 +1435,7 @@
mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
- return mTetherStatesParcel.tetheredList;
+ return toIfaces(mTetherStatesParcel.tetheredList);
}
/**
@@ -1361,7 +1455,7 @@
mCallback.waitForStarted();
if (mTetherStatesParcel == null) return new String[0];
- return mTetherStatesParcel.erroredIfaceList;
+ return toIfaces(mTetherStatesParcel.erroredIfaceList);
}
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 0e8b2b5..7596380 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -51,6 +51,7 @@
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
+import static android.net.TetheringManager.toIfaces;
import static android.net.util.TetheringMessageBase.BASE_MAIN_SM;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -92,6 +93,7 @@
import android.net.TetheredClient;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
+import android.net.TetheringInterface;
import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
import android.net.ip.IpServer;
@@ -143,7 +145,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -854,26 +855,27 @@
private void sendTetherStateChangedBroadcast() {
if (!isTetheringSupported()) return;
- final ArrayList<String> availableList = new ArrayList<>();
- final ArrayList<String> tetherList = new ArrayList<>();
- final ArrayList<String> localOnlyList = new ArrayList<>();
- final ArrayList<String> erroredList = new ArrayList<>();
- final ArrayList<Integer> lastErrorList = new ArrayList<>();
+ final ArrayList<TetheringInterface> available = new ArrayList<>();
+ final ArrayList<TetheringInterface> tethered = new ArrayList<>();
+ final ArrayList<TetheringInterface> localOnly = new ArrayList<>();
+ final ArrayList<TetheringInterface> errored = new ArrayList<>();
+ final ArrayList<Integer> lastErrors = new ArrayList<>();
final TetheringConfiguration cfg = mConfig;
- mTetherStatesParcel = new TetherStatesParcel();
int downstreamTypesMask = DOWNSTREAM_NONE;
for (int i = 0; i < mTetherStates.size(); i++) {
- TetherState tetherState = mTetherStates.valueAt(i);
- String iface = mTetherStates.keyAt(i);
+ final TetherState tetherState = mTetherStates.valueAt(i);
+ final int type = tetherState.ipServer.interfaceType();
+ final String iface = mTetherStates.keyAt(i);
+ final TetheringInterface tetheringIface = new TetheringInterface(type, iface);
if (tetherState.lastError != TETHER_ERROR_NO_ERROR) {
- erroredList.add(iface);
- lastErrorList.add(tetherState.lastError);
+ errored.add(tetheringIface);
+ lastErrors.add(tetherState.lastError);
} else if (tetherState.lastState == IpServer.STATE_AVAILABLE) {
- availableList.add(iface);
+ available.add(tetheringIface);
} else if (tetherState.lastState == IpServer.STATE_LOCAL_ONLY) {
- localOnlyList.add(iface);
+ localOnly.add(tetheringIface);
} else if (tetherState.lastState == IpServer.STATE_TETHERED) {
if (cfg.isUsb(iface)) {
downstreamTypesMask |= (1 << TETHERING_USB);
@@ -882,40 +884,63 @@
} else if (cfg.isBluetooth(iface)) {
downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
}
- tetherList.add(iface);
+ tethered.add(tetheringIface);
}
}
- mTetherStatesParcel.availableList = availableList.toArray(new String[0]);
- mTetherStatesParcel.tetheredList = tetherList.toArray(new String[0]);
- mTetherStatesParcel.localOnlyList = localOnlyList.toArray(new String[0]);
- mTetherStatesParcel.erroredIfaceList = erroredList.toArray(new String[0]);
- mTetherStatesParcel.lastErrorList = new int[lastErrorList.size()];
- Iterator<Integer> iterator = lastErrorList.iterator();
- for (int i = 0; i < lastErrorList.size(); i++) {
- mTetherStatesParcel.lastErrorList[i] = iterator.next().intValue();
- }
+ mTetherStatesParcel = buildTetherStatesParcel(available, localOnly, tethered, errored,
+ lastErrors);
reportTetherStateChanged(mTetherStatesParcel);
- final Intent bcast = new Intent(ACTION_TETHER_STATE_CHANGED);
- bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- bcast.putStringArrayListExtra(EXTRA_AVAILABLE_TETHER, availableList);
- bcast.putStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY, localOnlyList);
- bcast.putStringArrayListExtra(EXTRA_ACTIVE_TETHER, tetherList);
- bcast.putStringArrayListExtra(EXTRA_ERRORED_TETHER, erroredList);
- mContext.sendStickyBroadcastAsUser(bcast, UserHandle.ALL);
+ mContext.sendStickyBroadcastAsUser(buildStateChangeIntent(available, localOnly, tethered,
+ errored), UserHandle.ALL);
if (DBG) {
Log.d(TAG, String.format(
- "sendTetherStateChangedBroadcast %s=[%s] %s=[%s] %s=[%s] %s=[%s]",
- "avail", TextUtils.join(",", availableList),
- "local_only", TextUtils.join(",", localOnlyList),
- "tether", TextUtils.join(",", tetherList),
- "error", TextUtils.join(",", erroredList)));
+ "reportTetherStateChanged %s=[%s] %s=[%s] %s=[%s] %s=[%s]",
+ "avail", TextUtils.join(",", available),
+ "local_only", TextUtils.join(",", localOnly),
+ "tether", TextUtils.join(",", tethered),
+ "error", TextUtils.join(",", errored)));
}
mNotificationUpdater.onDownstreamChanged(downstreamTypesMask);
}
+ private TetherStatesParcel buildTetherStatesParcel(
+ final ArrayList<TetheringInterface> available,
+ final ArrayList<TetheringInterface> localOnly,
+ final ArrayList<TetheringInterface> tethered,
+ final ArrayList<TetheringInterface> errored,
+ final ArrayList<Integer> lastErrors) {
+ final TetherStatesParcel parcel = new TetherStatesParcel();
+
+ parcel.availableList = available.toArray(new TetheringInterface[0]);
+ parcel.tetheredList = tethered.toArray(new TetheringInterface[0]);
+ parcel.localOnlyList = localOnly.toArray(new TetheringInterface[0]);
+ parcel.erroredIfaceList = errored.toArray(new TetheringInterface[0]);
+ parcel.lastErrorList = new int[lastErrors.size()];
+ for (int i = 0; i < lastErrors.size(); i++) {
+ parcel.lastErrorList[i] = lastErrors.get(i);
+ }
+
+ return parcel;
+ }
+
+ private Intent buildStateChangeIntent(final ArrayList<TetheringInterface> available,
+ final ArrayList<TetheringInterface> localOnly,
+ final ArrayList<TetheringInterface> tethered,
+ final ArrayList<TetheringInterface> errored) {
+ final Intent bcast = new Intent(ACTION_TETHER_STATE_CHANGED);
+ bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+ bcast.putStringArrayListExtra(EXTRA_AVAILABLE_TETHER, toIfaces(available));
+ bcast.putStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY, toIfaces(localOnly));
+ bcast.putStringArrayListExtra(EXTRA_ACTIVE_TETHER, toIfaces(tethered));
+ bcast.putStringArrayListExtra(EXTRA_ERRORED_TETHER, toIfaces(errored));
+
+ return bcast;
+ }
+
private class StateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context content, Intent intent) {
@@ -2082,10 +2107,10 @@
private TetherStatesParcel emptyTetherStatesParcel() {
final TetherStatesParcel parcel = new TetherStatesParcel();
- parcel.availableList = new String[0];
- parcel.tetheredList = new String[0];
- parcel.localOnlyList = new String[0];
- parcel.erroredIfaceList = new String[0];
+ parcel.availableList = new TetheringInterface[0];
+ parcel.tetheredList = new TetheringInterface[0];
+ parcel.localOnlyList = new TetheringInterface[0];
+ parcel.erroredIfaceList = new TetheringInterface[0];
parcel.lastErrorList = new int[0];
return parcel;
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index de94cba..f1ddc6d 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -80,6 +80,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Random;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -349,7 +350,7 @@
private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1);
private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
- private final String mIface;
+ private final TetheringInterface mIface;
private volatile boolean mInterfaceWasTethered = false;
private volatile boolean mInterfaceWasLocalOnly = false;
@@ -358,20 +359,24 @@
MyTetheringEventCallback(TetheringManager tm, String iface) {
mTm = tm;
- mIface = iface;
+ mIface = new TetheringInterface(TETHERING_ETHERNET, iface);
}
public void unregister() {
mTm.unregisterTetheringEventCallback(this);
mUnregistered = true;
}
-
@Override
public void onTetheredInterfacesChanged(List<String> interfaces) {
+ fail("Should only call callback that takes a Set<TetheringInterface>");
+ }
+
+ @Override
+ public void onTetheredInterfacesChanged(Set<TetheringInterface> interfaces) {
// Ignore stale callbacks registered by previous test cases.
if (mUnregistered) return;
- if (!mInterfaceWasTethered && (mIface == null || interfaces.contains(mIface))) {
+ if (!mInterfaceWasTethered && interfaces.contains(mIface)) {
// This interface is being tethered for the first time.
Log.d(TAG, "Tethering started: " + interfaces);
mInterfaceWasTethered = true;
@@ -384,10 +389,15 @@
@Override
public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
+ fail("Should only call callback that takes a Set<TetheringInterface>");
+ }
+
+ @Override
+ public void onLocalOnlyInterfacesChanged(Set<TetheringInterface> interfaces) {
// Ignore stale callbacks registered by previous test cases.
if (mUnregistered) return;
- if (!mInterfaceWasLocalOnly && (mIface == null || interfaces.contains(mIface))) {
+ if (!mInterfaceWasLocalOnly && interfaces.contains(mIface)) {
// This interface is being put into local-only mode for the first time.
Log.d(TAG, "Local-only started: " + interfaces);
mInterfaceWasLocalOnly = true;
diff --git a/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java b/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
index 7ffe37a..07aab63 100644
--- a/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
+++ b/Tethering/tests/mts/src/android/tethering/mts/TetheringModuleTest.java
@@ -15,17 +15,18 @@
*/
package android.tethering.mts;
+import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.READ_DEVICE_CONFIG;
import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.Manifest.permission.WRITE_SETTINGS;
+import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
-import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -35,6 +36,7 @@
import android.content.Context;
import android.net.IpPrefix;
import android.net.LinkAddress;
+import android.net.TetheringInterface;
import android.net.TetheringManager;
import android.net.cts.util.CtsTetheringUtils;
import android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
@@ -69,7 +71,7 @@
@Before
public void setUp() throws Exception {
mUiAutomation.adoptShellPermissionIdentity(MANAGE_TEST_NETWORKS, NETWORK_SETTINGS,
- WRITE_SETTINGS, READ_DEVICE_CONFIG, TETHER_PRIVILEGED);
+ WRITE_SETTINGS, READ_DEVICE_CONFIG, TETHER_PRIVILEGED, ACCESS_WIFI_STATE);
mContext = InstrumentationRegistry.getContext();
mTm = mContext.getSystemService(TetheringManager.class);
mCtsTetheringUtils = new CtsTetheringUtils(mContext);
@@ -101,13 +103,14 @@
TestNetworkTracker tnt = null;
try {
tetherEventCallback.assumeTetheringSupported();
- assumeTrue(isWifiTetheringSupported(tetherEventCallback));
+ assumeTrue(isWifiTetheringSupported(mContext, tetherEventCallback));
+ tetherEventCallback.expectNoTetheringActive();
- mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+ final TetheringInterface tetheredIface =
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
- final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
- assertEquals(1, tetheredIfaces.size());
- final String wifiTetheringIface = tetheredIfaces.get(0);
+ assertNotNull(tetheredIface);
+ final String wifiTetheringIface = tetheredIface.getInterface();
NetworkInterface nif = NetworkInterface.getByName(wifiTetheringIface);
// Tethering downstream only have one ipv4 address.
@@ -120,11 +123,11 @@
tnt = setUpTestNetwork(
new LinkAddress(testPrefix.getAddress(), testPrefix.getPrefixLength()));
- tetherEventCallback.expectTetheredInterfacesChanged(null);
+ tetherEventCallback.expectNoTetheringActive();
final List<String> wifiRegexs =
tetherEventCallback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
- tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs);
+ tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
nif = NetworkInterface.getByName(wifiTetheringIface);
final LinkAddress newHotspotAddr = getFirstIpv4Address(nif);
assertNotNull(newHotspotAddr);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 94a9238..5235eb7 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -130,6 +130,7 @@
import android.net.TetheredClient.AddressInfo;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
+import android.net.TetheringInterface;
import android.net.TetheringRequestParcel;
import android.net.dhcp.DhcpLeaseParcelable;
import android.net.dhcp.DhcpServerCallbacks;
@@ -1793,6 +1794,8 @@
public void testRegisterTetheringEventCallback() throws Exception {
TestTetheringEventCallback callback = new TestTetheringEventCallback();
TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
+ final TetheringInterface wifiIface = new TetheringInterface(
+ TETHERING_WIFI, TEST_WLAN_IFNAME);
// 1. Register one callback before running any tethering.
mTethering.registerTetheringEventCallback(callback);
@@ -1811,12 +1814,12 @@
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
mLooper.dispatchAll();
tetherState = callback.pollTetherStatesChanged();
- assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
+ assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
tetherState = callback.pollTetherStatesChanged();
- assertArrayEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
+ assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface});
callback.expectUpstreamChanged(upstreamState.network);
callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
@@ -1828,7 +1831,7 @@
callback2.expectConfigurationChanged(
mTethering.getTetheringConfiguration().toStableParcelable());
tetherState = callback2.pollTetherStatesChanged();
- assertEquals(tetherState.tetheredList, new String[] {TEST_WLAN_IFNAME});
+ assertEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface});
callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STARTED);
// 4. Unregister first callback and disable wifi tethering
@@ -1837,7 +1840,7 @@
mTethering.stopTethering(TETHERING_WIFI);
sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
tetherState = callback2.pollTetherStatesChanged();
- assertArrayEquals(tetherState.availableList, new String[] {TEST_WLAN_IFNAME});
+ assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
mLooper.dispatchAll();
callback2.expectUpstreamChanged(new Network[] {null});
callback2.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
diff --git a/framework/Android.bp b/framework/Android.bp
index c91017f..4fa9ccb 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -108,10 +108,8 @@
"-Wthread-safety",
],
shared_libs: [
- "libbase",
"liblog",
"libnativehelper",
- "libnetd_client",
],
header_libs: [
"dnsproxyd_protocol_headers",
@@ -124,6 +122,7 @@
srcs: [
"jni/android_net_NetworkUtils.cpp",
],
+ shared_libs: ["libandroid_net"],
apex_available: [
"//apex_available:platform",
"com.android.tethering",
@@ -134,9 +133,11 @@
name: "libframework-connectivity-jni",
defaults: ["libframework-connectivity-defaults"],
srcs: [
+ "jni/android_net_NetworkUtils.cpp",
"jni/onload.cpp",
],
- static_libs: ["libconnectivityframeworkutils"],
+ shared_libs: ["libandroid"],
+ stl: "libc++_static",
apex_available: [
"//apex_available:platform",
"com.android.tethering",
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index 6c454bc..7fc0382 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -48,7 +48,6 @@
public class ConnectivitySettingsManager {
method public static void clearGlobalProxy(@NonNull android.content.Context);
- method @NonNull public static java.util.Set<java.lang.String> getAppsAllowedOnRestrictedNetworks(@NonNull android.content.Context);
method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context);
method public static int getCaptivePortalMode(@NonNull android.content.Context, int);
method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
@@ -66,9 +65,9 @@
method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context);
method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context);
method public static int getPrivateDnsMode(@NonNull android.content.Context);
+ method @NonNull public static java.util.Set<java.lang.Integer> getUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context);
method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean);
method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
- method public static void setAppsAllowedOnRestrictedNetworks(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.String>);
method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String);
method public static void setCaptivePortalMode(@NonNull android.content.Context, int);
method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
@@ -86,6 +85,7 @@
method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull int);
method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String);
method public static void setPrivateDnsMode(@NonNull android.content.Context, int);
+ method public static void setUidsAllowedOnRestrictedNetworks(@NonNull android.content.Context, @NonNull java.util.Set<java.lang.Integer>);
method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean);
method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 27bf114..27836c1 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -316,9 +316,16 @@
method public int getProviderId();
method public void onNetworkRequestWithdrawn(@NonNull android.net.NetworkRequest);
method public void onNetworkRequested(@NonNull android.net.NetworkRequest, @IntRange(from=0, to=99) int, int);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void registerNetworkOffer(@NonNull android.net.NetworkScore, @NonNull android.net.NetworkCapabilities, @NonNull java.util.concurrent.Executor, @NonNull android.net.NetworkProvider.NetworkOfferCallback);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkOffer(@NonNull android.net.NetworkProvider.NetworkOfferCallback);
field public static final int ID_NONE = -1; // 0xffffffff
}
+ public static interface NetworkProvider.NetworkOfferCallback {
+ method public void onNetworkNeeded(@NonNull android.net.NetworkRequest);
+ method public void onNetworkUnneeded(@NonNull android.net.NetworkRequest);
+ }
+
public class NetworkReleasedException extends java.lang.Exception {
}
@@ -334,15 +341,23 @@
public final class NetworkScore implements android.os.Parcelable {
method public int describeContents();
+ method public int getKeepConnectedReason();
method public int getLegacyInt();
+ method public boolean isExiting();
+ method public boolean isTransportPrimary();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkScore> CREATOR;
+ field public static final int KEEP_CONNECTED_FOR_HANDOVER = 1; // 0x1
+ field public static final int KEEP_CONNECTED_NONE = 0; // 0x0
}
public static final class NetworkScore.Builder {
ctor public NetworkScore.Builder();
method @NonNull public android.net.NetworkScore build();
+ method @NonNull public android.net.NetworkScore.Builder setExiting(boolean);
+ method @NonNull public android.net.NetworkScore.Builder setKeepConnectedReason(int);
method @NonNull public android.net.NetworkScore.Builder setLegacyInt(int);
+ method @NonNull public android.net.NetworkScore.Builder setTransportPrimary(boolean);
}
public final class OemNetworkPreferences implements android.os.Parcelable {
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index f17baf9..26484fb 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -19,6 +19,7 @@
#include <vector>
#include <android/file_descriptor_jni.h>
+#include <android/multinetwork.h>
#include <arpa/inet.h>
#include <linux/filter.h>
#include <linux/if_arp.h>
@@ -36,7 +37,6 @@
#include <utils/Log.h>
#include <utils/misc.h>
-#include "NetdClient.h"
#include "jni.h"
extern "C" {
@@ -94,25 +94,32 @@
}
}
-static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
+static jboolean android_net_utils_bindProcessToNetworkHandle(JNIEnv *env, jobject thiz,
+ jlong netHandle)
{
- return (jboolean) !setNetworkForProcess(netId);
+ return (jboolean) !android_setprocnetwork(netHandle);
}
-static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz)
+static jlong android_net_utils_getBoundNetworkHandleForProcess(JNIEnv *env, jobject thiz)
{
- return getNetworkForProcess();
+ net_handle_t network;
+ if (android_getprocnetwork(&network) != 0) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "android_getprocnetwork(): %s", strerror(errno));
+ return NETWORK_UNSPECIFIED;
+ }
+ return (jlong) network;
}
static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
- jint netId)
+ jint netId, jlong netHandle)
{
- return (jboolean) !setNetworkForResolv(netId);
+ return (jboolean) !android_setprocdns(netHandle);
}
-static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd,
- jint netId) {
- return setNetworkForSocket(netId, AFileDescriptor_getFd(env, javaFd));
+static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jobject thiz, jobject javaFd,
+ jlong netHandle) {
+ return android_setsocknetwork(netHandle, AFileDescriptor_getFd(env, javaFd));
}
static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
@@ -124,7 +131,7 @@
return true;
}
-static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jlong netHandle,
jstring dname, jint ns_class, jint ns_type, jint flags) {
const jsize javaCharsCount = env->GetStringLength(dname);
const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
@@ -134,7 +141,8 @@
std::vector<char> queryname(byteCountUTF8 + 1, 0);
env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
- int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+ int fd = android_res_nquery(netHandle, queryname.data(), ns_class, ns_type, flags);
if (fd < 0) {
jniThrowErrnoException(env, "resNetworkQuery", -fd);
@@ -144,12 +152,12 @@
return jniCreateFileDescriptor(env, fd);
}
-static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jlong netHandle,
jbyteArray msg, jint msgLen, jint flags) {
uint8_t data[MAXCMDSIZE];
checkLenAndCopy(env, msg, msgLen, data);
- int fd = resNetworkSend(netId, data, msgLen, flags);
+ int fd = android_res_nsend(netHandle, data, msgLen, flags);
if (fd < 0) {
jniThrowErrnoException(env, "resNetworkSend", -fd);
@@ -164,7 +172,7 @@
int rcode;
std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
- int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+ int res = android_res_nresult(fd, &rcode, buf.data(), MAXPACKETSIZE);
jniSetFileDescriptorOfFD(env, javaFd, -1);
if (res < 0) {
jniThrowErrnoException(env, "resNetworkResult", -res);
@@ -188,23 +196,22 @@
static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
int fd = AFileDescriptor_getFd(env, javaFd);
- resNetworkCancel(fd);
+ android_res_cancel(fd);
jniSetFileDescriptorOfFD(env, javaFd, -1);
}
static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
- unsigned dnsNetId = 0;
- if (int res = getNetworkForDns(&dnsNetId) < 0) {
- jniThrowErrnoException(env, "getDnsNetId", -res);
+ net_handle_t dnsNetHandle = NETWORK_UNSPECIFIED;
+ if (int res = android_getprocdns(&dnsNetHandle) < 0) {
+ jniThrowErrnoException(env, "getDnsNetwork", -res);
return nullptr;
}
- bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
static jclass class_Network = MakeGlobalRefOrDie(
env, FindClassOrDie(env, "android/net/Network"));
- static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V");
- return env->NewObject(
- class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass);
+ static jmethodID method = env->GetStaticMethodID(class_Network, "fromNetworkHandle",
+ "(J)Landroid/net/Network;");
+ return env->CallStaticObjectMethod(class_Network, method, static_cast<jlong>(dnsNetHandle));
}
static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
@@ -250,15 +257,15 @@
// clang-format off
static const JNINativeMethod gNetworkUtilMethods[] = {
/* name, signature, funcPtr */
- { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
- { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
+ { "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle },
+ { "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
- { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
+ { "bindSocketToNetworkHandle", "(Ljava/io/FileDescriptor;J)I", (void*) android_net_utils_bindSocketToNetworkHandle },
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
{ "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
- { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
- { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+ { "resNetworkSend", "(J[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+ { "resNetworkQuery", "(JLjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
{ "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
{ "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork },
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 1a6b37b..20f3853 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -3341,9 +3341,10 @@
* Register or update a network offer with ConnectivityService.
*
* ConnectivityService keeps track of offers made by the various providers and matches
- * them to networking requests made by apps or the system. The provider supplies a score
- * and the capabilities of the network it might be able to bring up ; these act as filters
- * used by ConnectivityService to only send those requests that can be fulfilled by the
+ * them to networking requests made by apps or the system. A callback identifies an offer
+ * uniquely, and later calls with the same callback update the offer. The provider supplies a
+ * score and the capabilities of the network it might be able to bring up ; these act as
+ * filters used by ConnectivityService to only send those requests that can be fulfilled by the
* provider.
*
* The provider is under no obligation to be able to bring up the network it offers at any
@@ -3359,16 +3360,16 @@
* @param score The prospective score of the network.
* @param caps The prospective capabilities of the network.
* @param callback The callback to call when this offer is needed or unneeded.
- * @hide
+ * @hide exposed via the NetworkProvider class.
*/
@RequiresPermission(anyOf = {
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_FACTORY})
- public void offerNetwork(@NonNull final NetworkProvider provider,
+ public void offerNetwork(@NonNull final int providerId,
@NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps,
@NonNull final INetworkOfferCallback callback) {
try {
- mService.offerNetwork(Objects.requireNonNull(provider.getMessenger(), "null messenger"),
+ mService.offerNetwork(providerId,
Objects.requireNonNull(score, "null score"),
Objects.requireNonNull(caps, "null caps"),
Objects.requireNonNull(callback, "null callback"));
@@ -3382,7 +3383,7 @@
*
* @param callback The callback passed at registration time. This must be the same object
* that was passed to {@link #offerNetwork}
- * @hide
+ * @hide exposed via the NetworkProvider class.
*/
public void unofferNetwork(@NonNull final INetworkOfferCallback callback) {
try {
@@ -4916,7 +4917,7 @@
InetAddressCompat.clearDnsCache();
// Must flush socket pool as idle sockets will be bound to previous network and may
// cause subsequent fetches to be performed on old network.
- NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
+ NetworkEventDispatcher.getInstance().dispatchNetworkConfigurationChange();
}
return true;
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 762f24f..5f7f539 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -374,12 +374,12 @@
private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname";
/**
- * A list of apps that is allowed on restricted networks.
+ * A list of uids that is allowed to use restricted networks.
*
* @hide
*/
- public static final String APPS_ALLOWED_ON_RESTRICTED_NETWORKS =
- "apps_allowed_on_restricted_networks";
+ public static final String UIDS_ALLOWED_ON_RESTRICTED_NETWORKS =
+ "uids_allowed_on_restricted_networks";
/**
* Get mobile data activity timeout from {@link Settings}.
@@ -1003,6 +1003,28 @@
context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference);
}
+ private static Set<Integer> getUidSetFromString(@Nullable String uidList) {
+ final Set<Integer> uids = new ArraySet<>();
+ if (TextUtils.isEmpty(uidList)) {
+ return uids;
+ }
+ for (String uid : uidList.split(";")) {
+ uids.add(Integer.valueOf(uid));
+ }
+ return uids;
+ }
+
+ private static String getUidStringFromSet(@NonNull Set<Integer> uidList) {
+ final StringJoiner joiner = new StringJoiner(";");
+ for (Integer uid : uidList) {
+ if (uid < 0 || UserHandle.getAppId(uid) > Process.LAST_APPLICATION_UID) {
+ throw new IllegalArgumentException("Invalid uid");
+ }
+ joiner.add(uid.toString());
+ }
+ return joiner.toString();
+ }
+
/**
* Get the list of uids(from {@link Settings}) that should go on cellular networks in preference
* even when higher-priority networks are connected.
@@ -1015,14 +1037,7 @@
public static Set<Integer> getMobileDataPreferredUids(@NonNull Context context) {
final String uidList = Settings.Secure.getString(
context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS);
- final Set<Integer> uids = new ArraySet<>();
- if (TextUtils.isEmpty(uidList)) {
- return uids;
- }
- for (String uid : uidList.split(";")) {
- uids.add(Integer.valueOf(uid));
- }
- return uids;
+ return getUidSetFromString(uidList);
}
/**
@@ -1035,53 +1050,41 @@
*/
public static void setMobileDataPreferredUids(@NonNull Context context,
@NonNull Set<Integer> uidList) {
- final StringJoiner joiner = new StringJoiner(";");
- for (Integer uid : uidList) {
- if (uid < 0 || UserHandle.getAppId(uid) > Process.LAST_APPLICATION_UID) {
- throw new IllegalArgumentException("Invalid uid");
- }
- joiner.add(uid.toString());
- }
- Settings.Secure.putString(
- context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS, joiner.toString());
+ final String uids = getUidStringFromSet(uidList);
+ Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_UIDS, uids);
}
/**
- * Get the list of apps(from {@link Settings}) that is allowed on restricted networks.
+ * Get the list of uids (from {@link Settings}) allowed to use restricted networks.
+ *
+ * Access to restricted networks is controlled by the (preinstalled-only)
+ * CONNECTIVITY_USE_RESTRICTED_NETWORKS permission, but highly privileged
+ * callers can also set a list of uids that can access restricted networks.
+ *
+ * This is useful for example in some jurisdictions where government apps,
+ * that can't be preinstalled, must still have access to emergency services.
*
* @param context The {@link Context} to query the setting.
- * @return A list of apps that is allowed on restricted networks or null if no setting
+ * @return A list of uids that is allowed to use restricted networks or null if no setting
* value.
*/
@NonNull
- public static Set<String> getAppsAllowedOnRestrictedNetworks(@NonNull Context context) {
- final String appList = Settings.Secure.getString(
- context.getContentResolver(), APPS_ALLOWED_ON_RESTRICTED_NETWORKS);
- if (TextUtils.isEmpty(appList)) {
- return new ArraySet<>();
- }
- return new ArraySet<>(appList.split(";"));
+ public static Set<Integer> getUidsAllowedOnRestrictedNetworks(@NonNull Context context) {
+ final String uidList = Settings.Secure.getString(
+ context.getContentResolver(), UIDS_ALLOWED_ON_RESTRICTED_NETWORKS);
+ return getUidSetFromString(uidList);
}
/**
- * Set the list of apps(from {@link Settings}) that is allowed on restricted networks.
- *
- * Note: Please refer to android developer guidelines for valid app(package name).
- * https://developer.android.com/guide/topics/manifest/manifest-element.html#package
+ * Set the list of uids(from {@link Settings}) that is allowed to use restricted networks.
*
* @param context The {@link Context} to set the setting.
- * @param list A list of apps that is allowed on restricted networks.
+ * @param uidList A list of uids that is allowed to use restricted networks.
*/
- public static void setAppsAllowedOnRestrictedNetworks(@NonNull Context context,
- @NonNull Set<String> list) {
- final StringJoiner joiner = new StringJoiner(";");
- for (String app : list) {
- if (app == null || app.contains(";")) {
- throw new IllegalArgumentException("Invalid app(package name)");
- }
- joiner.add(app);
- }
- Settings.Secure.putString(context.getContentResolver(), APPS_ALLOWED_ON_RESTRICTED_NETWORKS,
- joiner.toString());
+ public static void setUidsAllowedOnRestrictedNetworks(@NonNull Context context,
+ @NonNull Set<Integer> uidList) {
+ final String uids = getUidStringFromSet(uidList);
+ Settings.Secure.putString(context.getContentResolver(), UIDS_ALLOWED_ON_RESTRICTED_NETWORKS,
+ uids);
}
}
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index d937c9c..c434bbc 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -223,7 +223,7 @@
int getRestrictBackgroundStatusByCaller();
- void offerNetwork(in Messenger messenger, in NetworkScore score,
+ void offerNetwork(int providerId, in NetworkScore score,
in NetworkCapabilities caps, in INetworkOfferCallback callback);
void unofferNetwork(in INetworkOfferCallback callback);
}
diff --git a/framework/src/android/net/INetworkOfferCallback.aidl b/framework/src/android/net/INetworkOfferCallback.aidl
index 67d2d40..ecfba21 100644
--- a/framework/src/android/net/INetworkOfferCallback.aidl
+++ b/framework/src/android/net/INetworkOfferCallback.aidl
@@ -22,15 +22,16 @@
* A callback registered with connectivity by network providers together with
* a NetworkOffer.
*
- * When the offer is needed to satisfy some application or system component,
- * connectivity will call onOfferNeeded on this callback. When this happens,
- * the provider should try and bring up the network.
+ * When the network for this offer is needed to satisfy some application or
+ * system component, connectivity will call onNetworkNeeded on this callback.
+ * When this happens, the provider should try and bring up the network.
*
- * When the offer is no longer needed, for example because the application has
- * withdrawn the request or if the request is being satisfied by a network
- * that this offer will never be able to beat, connectivity calls
- * onOfferUnneeded. When this happens, the provider should stop trying to
- * bring up the network, or tear it down if it has already been brought up.
+ * When the network for this offer is no longer needed, for example because
+ * the application has withdrawn the request or if the request is being
+ * satisfied by a network that this offer will never be able to beat,
+ * connectivity calls onNetworkUnneeded. When this happens, the provider
+ * should stop trying to bring up the network, or tear it down if it has
+ * already been brought up.
*
* When NetworkProvider#offerNetwork is called, the provider can expect to
* immediately receive all requests that can be fulfilled by that offer and
@@ -38,25 +39,23 @@
* request is currently outstanding, because no requests have been made that
* can be satisfied by this offer, or because all such requests are already
* satisfied by a better network.
- * onOfferNeeded can be called at any time after registration and until the
+ * onNetworkNeeded can be called at any time after registration and until the
* offer is withdrawn with NetworkProvider#unofferNetwork is called. This
* typically happens when a new network request is filed by an application,
* or when the network satisfying a request disconnects and this offer now
- * stands a chance to be the best network for it.
+ * stands a chance to supply the best network for it.
*
* @hide
*/
oneway interface INetworkOfferCallback {
/**
- * Informs the registrant that the offer is needed to fulfill this request.
+ * Called when a network for this offer is needed to fulfill this request.
* @param networkRequest the request to satisfy
- * @param providerId the ID of the provider currently satisfying
- * this request, or NetworkProvider.ID_NONE if none.
*/
- void onOfferNeeded(in NetworkRequest networkRequest, int providerId);
+ void onNetworkNeeded(in NetworkRequest networkRequest);
/**
- * Informs the registrant that the offer is no longer needed to fulfill this request.
+ * Informs the registrant that the offer is no longer valuable to fulfill this request.
*/
- void onOfferUnneeded(in NetworkRequest networkRequest);
+ void onNetworkUnneeded(in NetworkRequest networkRequest);
}
diff --git a/framework/src/android/net/Network.java b/framework/src/android/net/Network.java
index 41fad63..1f49033 100644
--- a/framework/src/android/net/Network.java
+++ b/framework/src/android/net/Network.java
@@ -92,6 +92,7 @@
// value in the native/android/net.c NDK implementation.
private static final long HANDLE_MAGIC = 0xcafed00dL;
private static final int HANDLE_MAGIC_SIZE = 32;
+ private static final int USE_LOCAL_NAMESERVERS_FLAG = 0x80000000;
// A boolean to control how getAllByName()/getByName() behaves in the face
// of Private DNS.
@@ -189,7 +190,7 @@
*/
public int getNetIdForResolv() {
return mPrivateDnsBypass
- ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag.
+ ? (USE_LOCAL_NAMESERVERS_FLAG | netId) // Non-portable DNS resolution flag.
: netId;
}
@@ -452,12 +453,13 @@
throw new IllegalArgumentException(
"Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network.");
}
- if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC
- || networkHandle < 0) {
+ if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC) {
throw new IllegalArgumentException(
"Value passed to fromNetworkHandle() is not a network handle.");
}
- return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE));
+ final int netIdForResolv = (int) (networkHandle >>> HANDLE_MAGIC_SIZE);
+ return new Network((netIdForResolv & ~USE_LOCAL_NAMESERVERS_FLAG),
+ ((netIdForResolv & USE_LOCAL_NAMESERVERS_FLAG) != 0) /* privateDnsBypass */);
}
/**
@@ -485,7 +487,7 @@
if (netId == 0) {
return 0L; // make this zero condition obvious for debugging
}
- return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
+ return (((long) getNetIdForResolv()) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
}
// implement the Parcelable interface
diff --git a/framework/src/android/net/NetworkProvider.java b/framework/src/android/net/NetworkProvider.java
index 8f93047..cfb7325 100644
--- a/framework/src/android/net/NetworkProvider.java
+++ b/framework/src/android/net/NetworkProvider.java
@@ -168,12 +168,17 @@
}
/** @hide */
- // TODO : make @SystemApi when the impl is complete
+ @SystemApi
public interface NetworkOfferCallback {
- /** Called by the system when this offer is needed to satisfy some networking request. */
- void onOfferNeeded(@NonNull NetworkRequest request, int providerId);
- /** Called by the system when this offer is no longer needed. */
- void onOfferUnneeded(@NonNull NetworkRequest request);
+ /**
+ * Called by the system when a network for this offer is needed to satisfy some
+ * networking request.
+ */
+ void onNetworkNeeded(@NonNull NetworkRequest request);
+ /**
+ * Called by the system when this offer is no longer valuable for this request.
+ */
+ void onNetworkUnneeded(@NonNull NetworkRequest request);
}
private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub {
@@ -187,14 +192,13 @@
}
@Override
- public void onOfferNeeded(final @NonNull NetworkRequest request,
- final int providerId) {
- mExecutor.execute(() -> callback.onOfferNeeded(request, providerId));
+ public void onNetworkNeeded(final @NonNull NetworkRequest request) {
+ mExecutor.execute(() -> callback.onNetworkNeeded(request));
}
@Override
- public void onOfferUnneeded(final @NonNull NetworkRequest request) {
- mExecutor.execute(() -> callback.onOfferUnneeded(request));
+ public void onNetworkUnneeded(final @NonNull NetworkRequest request) {
+ mExecutor.execute(() -> callback.onNetworkUnneeded(request));
}
}
@@ -213,48 +217,51 @@
}
/**
- * Register or update an offer for network with the passed caps and score.
+ * Register or update an offer for network with the passed capabilities and score.
*
- * A NetworkProvider's job is to provide networks. This function is how a provider tells the
+ * A NetworkProvider's role is to provide networks. This method is how a provider tells the
* connectivity stack what kind of network it may provide. The score and caps arguments act
- * as filters that the connectivity stack uses to tell when the offer is necessary. When an
- * offer might be advantageous over existing networks, the provider will receive a call to
- * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider
- * should then try to bring up this network. When an offer is no longer needed, the stack
- * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The
+ * as filters that the connectivity stack uses to tell when the offer is valuable. When an
+ * offer might be preferred over existing networks, the provider will receive a call to
+ * the associated callback's {@link NetworkOfferCallback#onNetworkNeeded} method. The provider
+ * should then try to bring up this network. When an offer is no longer useful, the stack
+ * will inform the provider by calling {@link NetworkOfferCallback#onNetworkUnneeded}. The
* provider should stop trying to bring up such a network, or disconnect it if it already has
* one.
*
- * The stack determines what offers are needed according to what networks are currently
+ * The stack determines what offers are valuable according to what networks are currently
* available to the system, and what networking requests are made by applications. If an
- * offer looks like it could be a better choice than any existing network for any particular
- * request, that's when the stack decides the offer is needed. If the current networking
- * requests are all satisfied by networks that this offer can't possibly be a better match
- * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider
- * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded}
- * is called.
+ * offer looks like it could connect a better network than any existing network for any
+ * particular request, that's when the stack decides the network is needed. If the current
+ * networking requests are all satisfied by networks that this offer couldn't possibly be a
+ * better match for, that's when the offer is no longer valuable. An offer starts out as
+ * unneeded ; the provider should not try to bring up the network until
+ * {@link NetworkOfferCallback#onNetworkNeeded} is called.
*
* Note that the offers are non-binding to the providers, in particular because providers
* often don't know if they will be able to bring up such a network at any given time. For
- * example, no wireless network may be in range when the offer is needed. This is fine and
- * expected ; the provider should simply continue to try to bring up the network and do so
+ * example, no wireless network may be in range when the offer would be valuable. This is fine
+ * and expected ; the provider should simply continue to try to bring up the network and do so
* if/when it becomes possible. In the mean time, the stack will continue to satisfy requests
* with the best network currently available, or if none, keep the apps informed that no
* network can currently satisfy this request. When/if the provider can bring up the network,
* the connectivity stack will match it against requests, and inform interested apps of the
* availability of this network. This may, in turn, render the offer of some other provider
- * unneeded if all requests it used to satisfy are now better served by this network.
+ * low-value if all requests it used to satisfy are now better served by this network.
*
* A network can become unneeded for a reason like the above : whether the provider managed
* to bring up the offered network after it became needed or not, some other provider may
- * bring up a better network than this one, making this offer unneeded. A network may also
+ * bring up a better network than this one, making this network unneeded. A network may also
* become unneeded if the application making the request withdrew it (for example, after it
* is done transferring data, or if the user canceled an operation).
*
* The capabilities and score act as filters as to what requests the provider will see.
* They are not promises, but for best performance, the providers should strive to put
- * as much known information as possible in the offer. For capabilities in particular, it
- * should put all NetworkAgent-managed capabilities a network may have, even if it doesn't
+ * as much known information as possible in the offer. For the score, it should put as
+ * strong a score as the networks will have, since this will filter what requests the
+ * provider sees – it's not a promise, it only serves to avoid sending requests that
+ * the provider can't ever hope to satisfy better than any current network. For capabilities,
+ * it should put all NetworkAgent-managed capabilities a network may have, even if it doesn't
* have them at first. This applies to INTERNET, for example ; if a provider thinks the
* network it can bring up for this offer may offer Internet access it should include the
* INTERNET bit. It's fine if the brought up network ends up not actually having INTERNET.
@@ -267,11 +274,14 @@
*
* @hide
*/
- // TODO : make @SystemApi when the impl is complete
+ @SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
- public void offerNetwork(@NonNull final NetworkScore score,
+ public void registerNetworkOffer(@NonNull final NetworkScore score,
@NonNull final NetworkCapabilities caps, @NonNull final Executor executor,
@NonNull final NetworkOfferCallback callback) {
+ // Can't offer a network with a provider that is not yet registered or already unregistered.
+ final int providerId = mProviderId;
+ if (providerId == ID_NONE) return;
NetworkOfferCallbackProxy proxy = null;
synchronized (mProxies) {
for (final NetworkOfferCallbackProxy existingProxy : mProxies) {
@@ -285,7 +295,8 @@
mProxies.add(proxy);
}
}
- mContext.getSystemService(ConnectivityManager.class).offerNetwork(this, score, caps, proxy);
+ mContext.getSystemService(ConnectivityManager.class)
+ .offerNetwork(providerId, score, caps, proxy);
}
/**
@@ -302,9 +313,9 @@
*
* @hide
*/
- // TODO : make @SystemApi when the impl is complete
+ @SystemApi
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
- public void unofferNetwork(final @NonNull NetworkOfferCallback callback) {
+ public void unregisterNetworkOffer(final @NonNull NetworkOfferCallback callback) {
final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback);
if (null == proxy) return;
mProxies.remove(proxy);
diff --git a/framework/src/android/net/NetworkScore.java b/framework/src/android/net/NetworkScore.java
index 6584993..0dee225 100644
--- a/framework/src/android/net/NetworkScore.java
+++ b/framework/src/android/net/NetworkScore.java
@@ -16,6 +16,7 @@
package android.net;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
@@ -23,6 +24,9 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Object representing the quality of a network as perceived by the user.
*
@@ -36,25 +40,57 @@
// a migration.
private final int mLegacyInt;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ KEEP_CONNECTED_NONE,
+ KEEP_CONNECTED_FOR_HANDOVER
+ })
+ public @interface KeepConnectedReason { }
+
+ public static final int KEEP_CONNECTED_NONE = 0;
+ public static final int KEEP_CONNECTED_FOR_HANDOVER = 1;
+
// Agent-managed policies
- // TODO : add them here, starting from 1
+ // This network should lose to a wifi that has ever been validated
+ // NOTE : temporarily this policy is managed by ConnectivityService, because of legacy. The
+ // legacy design has this bit global to the system and tacked on WiFi which means it will affect
+ // networks from carriers who don't want it and non-carrier networks, which is bad for users.
+ // The S design has this on mobile networks only, so this can be fixed eventually ; as CS
+ // doesn't know what carriers need this bit, the initial S implementation will continue to
+ // affect other carriers but will at least leave non-mobile networks alone. Eventually Telephony
+ // should set this on networks from carriers that require it.
/** @hide */
- public static final int MIN_AGENT_MANAGED_POLICY = 0;
+ public static final int POLICY_YIELD_TO_BAD_WIFI = 1;
+ // This network is primary for this transport.
/** @hide */
- public static final int MAX_AGENT_MANAGED_POLICY = -1;
+ public static final int POLICY_TRANSPORT_PRIMARY = 2;
+ // This network is exiting : it will likely disconnect in a few seconds.
+ /** @hide */
+ public static final int POLICY_EXITING = 3;
+
+ /** @hide */
+ public static final int MIN_AGENT_MANAGED_POLICY = POLICY_YIELD_TO_BAD_WIFI;
+ /** @hide */
+ public static final int MAX_AGENT_MANAGED_POLICY = POLICY_EXITING;
// Bitmask of all the policies applied to this score.
private final long mPolicies;
+ private final int mKeepConnectedReason;
+
/** @hide */
- NetworkScore(final int legacyInt, final long policies) {
+ NetworkScore(final int legacyInt, final long policies,
+ @KeepConnectedReason final int keepConnectedReason) {
mLegacyInt = legacyInt;
mPolicies = policies;
+ mKeepConnectedReason = keepConnectedReason;
}
private NetworkScore(@NonNull final Parcel in) {
mLegacyInt = in.readInt();
mPolicies = in.readLong();
+ mKeepConnectedReason = in.readInt();
}
public int getLegacyInt() {
@@ -62,6 +98,13 @@
}
/**
+ * Returns the keep-connected reason, or KEEP_CONNECTED_NONE.
+ */
+ public int getKeepConnectedReason() {
+ return mKeepConnectedReason;
+ }
+
+ /**
* @return whether this score has a particular policy.
*
* @hide
@@ -71,15 +114,70 @@
return 0 != (mPolicies & (1L << policy));
}
+ /**
+ * To the exclusive usage of FullScore
+ * @hide
+ */
+ public long getPolicies() {
+ return mPolicies;
+ }
+
+ /**
+ * Whether this network should yield to a previously validated wifi gone bad.
+ *
+ * If this policy is set, other things being equal, the device will prefer a previously
+ * validated WiFi even if this network is validated and the WiFi is not.
+ * If this policy is not set, the device prefers the validated network.
+ *
+ * @hide
+ */
+ // TODO : Unhide this for telephony and have telephony call it on the relevant carriers.
+ // In the mean time this is handled by Connectivity in a backward-compatible manner.
+ public boolean shouldYieldToBadWifi() {
+ return hasPolicy(POLICY_YIELD_TO_BAD_WIFI);
+ }
+
+ /**
+ * Whether this network is primary for this transport.
+ *
+ * When multiple networks of the same transport are active, the device prefers the ones that
+ * are primary. This is meant in particular for DS-DA devices with a user setting to choose the
+ * default SIM card, or for WiFi STA+STA and make-before-break cases.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isTransportPrimary() {
+ return hasPolicy(POLICY_TRANSPORT_PRIMARY);
+ }
+
+ /**
+ * Whether this network is exiting.
+ *
+ * If this policy is set, the device will expect this network to disconnect within seconds.
+ * It will try to migrate to some other network if any is available, policy permitting, to
+ * avoid service disruption.
+ * This is useful in particular when a good cellular network is available and WiFi is getting
+ * weak and risks disconnecting soon. The WiFi network should be marked as exiting so that
+ * the device will prefer the reliable mobile network over this soon-to-be-lost WiFi.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isExiting() {
+ return hasPolicy(POLICY_EXITING);
+ }
+
@Override
public String toString() {
- return "Score(" + mLegacyInt + ")";
+ return "Score(" + mLegacyInt + " ; Policies : " + mPolicies + ")";
}
@Override
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
dest.writeInt(mLegacyInt);
dest.writeLong(mPolicies);
+ dest.writeInt(mKeepConnectedReason);
}
@Override
@@ -108,6 +206,8 @@
private static final long POLICY_NONE = 0L;
private static final int INVALID_LEGACY_INT = Integer.MIN_VALUE;
private int mLegacyInt = INVALID_LEGACY_INT;
+ private int mKeepConnectedReason = KEEP_CONNECTED_NONE;
+ private int mPolicies = 0;
/**
* Sets the legacy int for this score.
@@ -123,13 +223,93 @@
return this;
}
+
+ /**
+ * Set for a network that should never be preferred to a wifi that has ever been validated
+ *
+ * If this policy is set, other things being equal, the device will prefer a previously
+ * validated WiFi even if this network is validated and the WiFi is not.
+ * If this policy is not set, the device prefers the validated network.
+ *
+ * @return this builder
+ * @hide
+ */
+ // TODO : Unhide this for telephony and have telephony call it on the relevant carriers.
+ // In the mean time this is handled by Connectivity in a backward-compatible manner.
+ @NonNull
+ public Builder setShouldYieldToBadWifi(final boolean val) {
+ if (val) {
+ mPolicies |= (1L << POLICY_YIELD_TO_BAD_WIFI);
+ } else {
+ mPolicies &= ~(1L << POLICY_YIELD_TO_BAD_WIFI);
+ }
+ return this;
+ }
+
+ /**
+ * Set for a network that is primary for this transport.
+ *
+ * When multiple networks of the same transport are active, the device prefers the ones that
+ * are primary. This is meant in particular for DS-DA devices with a user setting to choose
+ * the default SIM card, or for WiFi STA+STA and make-before-break cases.
+ *
+ * @return this builder
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public Builder setTransportPrimary(final boolean val) {
+ if (val) {
+ mPolicies |= (1L << POLICY_TRANSPORT_PRIMARY);
+ } else {
+ mPolicies &= ~(1L << POLICY_TRANSPORT_PRIMARY);
+ }
+ return this;
+ }
+
+ /**
+ * Set for a network that will likely disconnect in a few seconds.
+ *
+ * If this policy is set, the device will expect this network to disconnect within seconds.
+ * It will try to migrate to some other network if any is available, policy permitting, to
+ * avoid service disruption.
+ * This is useful in particular when a good cellular network is available and WiFi is
+ * getting weak and risks disconnecting soon. The WiFi network should be marked as exiting
+ * so that the device will prefer the reliable mobile network over this soon-to-be-lost
+ * WiFi.
+ *
+ * @return this builder
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ public Builder setExiting(final boolean val) {
+ if (val) {
+ mPolicies |= (1L << POLICY_EXITING);
+ } else {
+ mPolicies &= ~(1L << POLICY_EXITING);
+ }
+ return this;
+ }
+
+ /**
+ * Set the keep-connected reason.
+ *
+ * This can be reset by calling it again with {@link KEEP_CONNECTED_NONE}.
+ */
+ @NonNull
+ public Builder setKeepConnectedReason(@KeepConnectedReason final int reason) {
+ mKeepConnectedReason = reason;
+ return this;
+ }
+
/**
* Builds this NetworkScore.
* @return The built NetworkScore object.
*/
@NonNull
public NetworkScore build() {
- return new NetworkScore(mLegacyInt, POLICY_NONE);
+ return new NetworkScore(mLegacyInt, mPolicies, mKeepConnectedReason);
}
}
}
diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java
index a92fda1..16a49bc 100644
--- a/framework/src/android/net/NetworkUtils.java
+++ b/framework/src/android/net/NetworkUtils.java
@@ -16,6 +16,8 @@
package android.net;
+import static android.net.ConnectivityManager.NETID_UNSET;
+
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.system.ErrnoException;
@@ -55,6 +57,8 @@
*/
public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
+ private static native boolean bindProcessToNetworkHandle(long netHandle);
+
/**
* Binds the current process to the network designated by {@code netId}. All sockets created
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
@@ -63,13 +67,20 @@
* is by design so an application doesn't accidentally use sockets it thinks are still bound to
* a particular {@code Network}. Passing NETID_UNSET clears the binding.
*/
- public native static boolean bindProcessToNetwork(int netId);
+ public static boolean bindProcessToNetwork(int netId) {
+ return bindProcessToNetworkHandle(new Network(netId).getNetworkHandle());
+ }
+
+ private static native long getBoundNetworkHandleForProcess();
/**
* Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
* {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
*/
- public native static int getBoundNetworkForProcess();
+ public static int getBoundNetworkForProcess() {
+ final long netHandle = getBoundNetworkHandleForProcess();
+ return netHandle == 0L ? NETID_UNSET : Network.fromNetworkHandle(netHandle).getNetId();
+ }
/**
* Binds host resolutions performed by this process to the network designated by {@code netId}.
@@ -81,12 +92,16 @@
@Deprecated
public native static boolean bindProcessToNetworkForHostResolution(int netId);
+ private static native int bindSocketToNetworkHandle(FileDescriptor fd, long netHandle);
+
/**
* Explicitly binds {@code fd} to the network designated by {@code netId}. This
* overrides any binding via {@link #bindProcessToNetwork}.
* @return 0 on success or negative errno on failure.
*/
- public static native int bindSocketToNetwork(FileDescriptor fd, int netId);
+ public static int bindSocketToNetwork(FileDescriptor fd, int netId) {
+ return bindSocketToNetworkHandle(fd, new Network(netId).getNetworkHandle());
+ }
/**
* Determine if {@code uid} can access network designated by {@code netId}.
@@ -97,14 +112,22 @@
return false;
}
+ private static native FileDescriptor resNetworkSend(
+ long netHandle, byte[] msg, int msglen, int flags) throws ErrnoException;
+
/**
* DNS resolver series jni method.
* Issue the query {@code msg} on the network designated by {@code netId}.
* {@code flags} is an additional config to control actual querying behavior.
* @return a file descriptor to watch for read events
*/
- public static native FileDescriptor resNetworkSend(
- int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+ public static FileDescriptor resNetworkSend(
+ int netId, byte[] msg, int msglen, int flags) throws ErrnoException {
+ return resNetworkSend(new Network(netId).getNetworkHandle(), msg, msglen, flags);
+ }
+
+ private static native FileDescriptor resNetworkQuery(
+ long netHandle, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
/**
* DNS resolver series jni method.
@@ -113,8 +136,11 @@
* {@code flags} is an additional config to control actual querying behavior.
* @return a file descriptor to watch for read events
*/
- public static native FileDescriptor resNetworkQuery(
- int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+ public static FileDescriptor resNetworkQuery(
+ int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException {
+ return resNetworkQuery(new Network(netId).getNetworkHandle(), dname, nsClass, nsType,
+ flags);
+ }
/**
* DNS resolver series jni method.
diff --git a/service/Android.bp b/service/Android.bp
index b6adf79..ec8887c 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -32,6 +32,7 @@
"jni/com_android_server_TestNetworkService.cpp",
"jni/onload.cpp",
],
+ stl: "libc++_static",
shared_libs: [
"libbase",
"liblog",
@@ -47,22 +48,33 @@
java_library {
name: "service-connectivity-pre-jarjar",
+ sdk_version: "system_server_current",
srcs: [
"src/**/*.java",
":framework-connectivity-shared-srcs",
+ ":services-connectivity-shared-srcs",
+ // TODO: move to net-utils-device-common, enable shrink optimization to avoid extra classes
+ ":net-module-utils-srcs",
],
libs: [
- "android.net.ipsec.ike",
- "services.core",
- "services.net",
+ // TODO (b/183097033) remove once system_server_current includes core_current
+ "stable.core.platform.api.stubs",
+ "android_system_server_stubs_current",
+ "framework-annotations-lib",
+ "framework-connectivity.impl",
+ "framework-tethering.stubs.module_lib",
+ "framework-wifi.stubs.module_lib",
"unsupportedappusage",
"ServiceConnectivityResources",
],
static_libs: [
+ "dnsresolver_aidl_interface-V7-java",
"modules-utils-os",
"net-utils-device-common",
"net-utils-framework-common",
"netd-client",
+ "netlink-client",
+ "networkstack-client",
"PlatformProperties",
"service-connectivity-protos",
],
@@ -74,6 +86,7 @@
java_library {
name: "service-connectivity-protos",
+ sdk_version: "system_current",
proto: {
type: "nano",
},
@@ -89,6 +102,7 @@
java_library {
name: "service-connectivity",
+ sdk_version: "system_server_current",
installable: true,
static_libs: [
"service-connectivity-pre-jarjar",
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index d54923c..f57761f 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -88,7 +88,6 @@
import static java.util.Map.Entry;
import android.Manifest;
-import android.annotation.BoolRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
@@ -151,7 +150,6 @@
import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
-import android.net.NetworkStackClient;
import android.net.NetworkState;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTestResultParcelable;
@@ -178,13 +176,14 @@
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
+import android.net.networkstack.ModuleNetworkStackClient;
+import android.net.networkstack.NetworkStackClientBase;
import android.net.resolv.aidl.DnsHealthEventParcel;
import android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener;
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.util.NetdService;
import android.os.BatteryStatsManager;
import android.os.Binder;
import android.os.Build;
@@ -619,6 +618,11 @@
private static final int EVENT_UNREGISTER_NETWORK_OFFER = 53;
/**
+ * Used internally when MOBILE_DATA_PREFERRED_UIDS setting changed.
+ */
+ private static final int EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED = 54;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -1185,10 +1189,10 @@
}
/**
- * Get a reference to the NetworkStackClient.
+ * Get a reference to the ModuleNetworkStackClient.
*/
- public NetworkStackClient getNetworkStack() {
- return NetworkStackClient.getInstance();
+ public NetworkStackClientBase getNetworkStack() {
+ return ModuleNetworkStackClient.getInstance(null);
}
/**
@@ -1247,7 +1251,8 @@
public ConnectivityService(Context context) {
this(context, getDnsResolver(context), new IpConnectivityLog(),
- NetdService.getInstance(), new Dependencies());
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+ new Dependencies());
}
@VisibleForTesting
@@ -1379,7 +1384,7 @@
mLingerMonitor = new LingerMonitor(mContext, mNotifier, dailyLimit, rateLimit);
mMultinetworkPolicyTracker = mDeps.makeMultinetworkPolicyTracker(
- mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
+ mContext, mHandler, () -> updateAvoidBadWifi());
mMultinetworkPolicyTracker.start();
mDnsManager = new DnsManager(mContext, mDnsResolver);
@@ -1461,7 +1466,12 @@
mHandler.sendEmptyMessage(EVENT_PRIVATE_DNS_SETTINGS_CHANGED);
}
- private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, @BoolRes int id) {
+ @VisibleForTesting
+ void updateMobileDataPreferredUids() {
+ mHandler.sendEmptyMessage(EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
+ }
+
+ private void handleAlwaysOnNetworkRequest(NetworkRequest networkRequest, int id) {
final boolean enable = mContext.getResources().getBoolean(id);
handleAlwaysOnNetworkRequest(networkRequest, enable);
}
@@ -1505,6 +1515,8 @@
vehicleAlwaysRequested || legacyAlwaysRequested);
}
+ // Note that registering observer for setting do not get initial callback when registering,
+ // callers might have self-initialization to update status if need.
private void registerSettingsCallbacks() {
// Watch for global HTTP proxy changes.
mSettingsObserver.observe(
@@ -1520,6 +1532,11 @@
mSettingsObserver.observe(
Settings.Global.getUriFor(ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED),
EVENT_CONFIGURE_ALWAYS_ON_NETWORKS);
+
+ // Watch for mobile data preferred uids changes.
+ mSettingsObserver.observe(
+ Settings.Secure.getUriFor(ConnectivitySettingsManager.MOBILE_DATA_PREFERRED_UIDS),
+ EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
}
private void registerPrivateDnsSettingsCallbacks() {
@@ -2718,6 +2735,13 @@
// Create network requests for always-on networks.
mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_ALWAYS_ON_NETWORKS));
+
+ // Update mobile data preference if necessary.
+ // Note that empty uid list can be skip here only because no uid rules applied before system
+ // ready. Normally, the empty uid list means to clear the uids rules on netd.
+ if (!ConnectivitySettingsManager.getMobileDataPreferredUids(mContext).isEmpty()) {
+ updateMobileDataPreferredUids();
+ }
}
/**
@@ -2990,10 +3014,6 @@
}
pw.println();
- pw.println("NetworkStackClient logs:");
- pw.increaseIndent();
- NetworkStackClient.getInstance().dump(pw);
- pw.decreaseIndent();
pw.println();
pw.println("Permission Monitor:");
@@ -3340,8 +3360,6 @@
nai.lastValidated = valid;
nai.everValidated |= valid;
updateCapabilities(oldScore, nai, nai.networkCapabilities);
- // If score has changed, rebroadcast to NetworkProviders. b/17726566
- if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
if (valid) {
handleFreshlyValidatedNetwork(nai);
// Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and
@@ -3762,9 +3780,12 @@
if (currentNetwork != null
&& currentNetwork.network.getNetId() == nai.network.getNetId()) {
// uid rules for this network will be removed in destroyNativeNetwork(nai).
+ // TODO : setting the satisfier is in fact the job of the rematch. Teach the
+ // rematch not to keep disconnected agents instead of setting it here ; this
+ // will also allow removing updating the offers below.
nri.setSatisfier(null, null);
- if (request.isRequest()) {
- sendUpdatedScoreToFactories(request, null);
+ for (final NetworkOfferInfo noi : mNetworkOffers) {
+ informOffer(nri, noi.offer, mNetworkRanker);
}
if (mDefaultRequest == nri) {
@@ -3920,16 +3941,13 @@
}
rematchAllNetworksAndRequests();
- for (final NetworkRequestInfo nri : nris) {
- // If the nri is satisfied, return as its score has already been sent if needed.
- if (nri.isBeingSatisfied()) {
- return;
- }
- // As this request was not satisfied on rematch and thus never had any scores sent to
- // the factories, send null now for each request of type REQUEST.
- for (final NetworkRequest req : nri.mRequests) {
- if (req.isRequest()) sendUpdatedScoreToFactories(req, null);
+ // Requests that have not been matched to a network will not have been sent to the
+ // providers, because the old satisfier and the new satisfier are the same (null in this
+ // case). Send these requests to the providers.
+ for (final NetworkRequestInfo nri : nris) {
+ for (final NetworkOfferInfo noi : mNetworkOffers) {
+ informOffer(nri, noi.offer, mNetworkRanker);
}
}
}
@@ -3957,6 +3975,12 @@
// then it should be lingered.
private boolean unneeded(NetworkAgentInfo nai, UnneededFor reason) {
ensureRunningOnConnectivityServiceThread();
+
+ if (!nai.everConnected || nai.isVPN() || nai.isInactive()
+ || nai.getScore().getKeepConnectedReason() != NetworkScore.KEEP_CONNECTED_NONE) {
+ return false;
+ }
+
final int numRequests;
switch (reason) {
case TEARDOWN:
@@ -3970,9 +3994,8 @@
return true;
}
- if (!nai.everConnected || nai.isVPN() || nai.isInactive() || numRequests > 0) {
- return false;
- }
+ if (numRequests > 0) return false;
+
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
if (reason == UnneededFor.LINGER
&& !nri.isMultilayerRequest()
@@ -4137,7 +4160,15 @@
}
}
- cancelNpiRequests(nri);
+ // For all outstanding offers, cancel any of the layers of this NRI that used to be
+ // needed for this offer.
+ for (final NetworkOfferInfo noi : mNetworkOffers) {
+ for (final NetworkRequest req : nri.mRequests) {
+ if (req.isRequest() && noi.offer.neededFor(req)) {
+ noi.offer.onNetworkUnneeded(req);
+ }
+ }
+ }
}
private void handleRemoveNetworkRequests(@NonNull final Set<NetworkRequestInfo> nris) {
@@ -4150,20 +4181,6 @@
}
}
- private void cancelNpiRequests(@NonNull final NetworkRequestInfo nri) {
- for (final NetworkRequest req : nri.mRequests) {
- cancelNpiRequest(req);
- }
- }
-
- private void cancelNpiRequest(@NonNull final NetworkRequest req) {
- if (req.isRequest()) {
- for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
- npi.cancelRequest(req);
- }
- }
- }
-
private void removeListenRequestFromNetworks(@NonNull final NetworkRequest req) {
// listens don't have a singular affected Network. Check all networks to see
// if this listen request applies and remove it.
@@ -4290,7 +4307,6 @@
nai.networkAgentConfig.acceptPartialConnectivity = accept;
nai.updateScoreForNetworkAgentConfigUpdate();
rematchAllNetworksAndRequests();
- sendUpdatedScoreToFactories(nai);
}
if (always) {
@@ -4358,7 +4374,6 @@
if (!nai.avoidUnvalidated) {
nai.avoidUnvalidated = true;
rematchAllNetworksAndRequests();
- sendUpdatedScoreToFactories(nai);
}
}
@@ -4463,14 +4478,11 @@
return avoidBadWifi();
}
-
- private void rematchForAvoidBadWifiUpdate() {
- rematchAllNetworksAndRequests();
- for (NetworkAgentInfo nai: mNetworkAgentInfos) {
- if (nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
- sendUpdatedScoreToFactories(nai);
- }
+ private void updateAvoidBadWifi() {
+ for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ nai.updateScoreForNetworkAgentConfigUpdate();
}
+ rematchAllNetworksAndRequests();
}
// TODO: Evaluate whether this is of interest to other consumers of
@@ -4806,6 +4818,9 @@
case EVENT_REPORT_NETWORK_ACTIVITY:
mNetworkActivityTracker.handleReportNetworkActivity();
break;
+ case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED:
+ handleMobileDataPreferredUidsChanged();
+ break;
}
}
}
@@ -5492,24 +5507,6 @@
}
}
- void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) {
- try {
- messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
- } catch (RemoteException e) {
- // Remote process died. Ignore; the death recipient will remove this
- // NetworkProviderInfo from mNetworkProviderInfos.
- }
- }
-
- void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
- sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
- servingProviderId, request);
- }
-
- void cancelRequest(NetworkRequest request) {
- sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request);
- }
-
void connect(Context context, Handler handler) {
try {
messenger.getBinder().linkToDeath(mDeathRecipient, 0);
@@ -5610,7 +5607,7 @@
* Get the list of UIDs this nri applies to.
*/
@NonNull
- private Set<UidRange> getUids() {
+ Set<UidRange> getUids() {
// networkCapabilities.getUids() returns a defensive copy.
// multilayer requests will all have the same uids so return the first one.
final Set<UidRange> uids = mRequests.get(0).networkCapabilities.getUidRanges();
@@ -6218,7 +6215,6 @@
if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
mNetworkProviderInfos.put(npi.messenger, npi);
npi.connect(mContext, mTrackerHandler);
- sendAllRequestsToProvider(npi);
}
@Override
@@ -6238,11 +6234,14 @@
}
@Override
- public void offerNetwork(@NonNull final Messenger providerMessenger,
+ public void offerNetwork(final int providerId,
@NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps,
@NonNull final INetworkOfferCallback callback) {
+ Objects.requireNonNull(score);
+ Objects.requireNonNull(caps);
+ Objects.requireNonNull(callback);
final NetworkOffer offer = new NetworkOffer(
- FullScore.makeProspectiveScore(score, caps), caps, callback, providerMessenger);
+ FullScore.makeProspectiveScore(score, caps), caps, callback, providerId);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_OFFER, offer));
}
@@ -6260,12 +6259,12 @@
// Unregister all the offers from this provider
final ArrayList<NetworkOfferInfo> toRemove = new ArrayList<>();
for (final NetworkOfferInfo noi : mNetworkOffers) {
- if (noi.offer.provider == messenger) {
+ if (noi.offer.providerId == npi.providerId) {
// Can't call handleUnregisterNetworkOffer here because iteration is in progress
toRemove.add(noi);
}
}
- for (NetworkOfferInfo noi : toRemove) {
+ for (final NetworkOfferInfo noi : toRemove) {
handleUnregisterNetworkOffer(noi);
}
if (DBG) log("unregisterNetworkProvider for " + npi.name);
@@ -6324,6 +6323,11 @@
@NonNull
private ProfileNetworkPreferences mProfileNetworkPreferences = new ProfileNetworkPreferences();
+ // A set of UIDs that should use mobile data preferentially if available. This object follows
+ // the same threading rules as the OEM network preferences above.
+ @NonNull
+ private Set<Integer> mMobileDataPreferredUids = new ArraySet<>();
+
// OemNetworkPreferences activity String log entries.
private static final int MAX_OEM_NETWORK_PREFERENCE_LOGS = 20;
@NonNull
@@ -6645,6 +6649,13 @@
}
}
+ private boolean isNetworkProviderWithIdRegistered(final int providerId) {
+ for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ if (npi.providerId == providerId) return true;
+ }
+ return false;
+ }
+
/**
* Register or update a network offer.
* @param newOffer The new offer. If the callback member is the same as an existing
@@ -6652,7 +6663,7 @@
*/
private void handleRegisterNetworkOffer(@NonNull final NetworkOffer newOffer) {
ensureRunningOnConnectivityServiceThread();
- if (null == mNetworkProviderInfos.get(newOffer.provider)) {
+ if (!isNetworkProviderWithIdRegistered(newOffer.providerId)) {
// This may actually happen if a provider updates its score or registers and then
// immediately unregisters. The offer would still be in the handler queue, but the
// provider would have been removed.
@@ -6667,19 +6678,19 @@
}
final NetworkOfferInfo noi = new NetworkOfferInfo(newOffer);
try {
- noi.offer.provider.getBinder().linkToDeath(noi, 0 /* flags */);
+ noi.offer.callback.asBinder().linkToDeath(noi, 0 /* flags */);
} catch (RemoteException e) {
noi.binderDied();
return;
}
mNetworkOffers.add(noi);
- // TODO : send requests to the provider.
+ issueNetworkNeeds(noi);
}
private void handleUnregisterNetworkOffer(@NonNull final NetworkOfferInfo noi) {
ensureRunningOnConnectivityServiceThread();
mNetworkOffers.remove(noi);
- noi.offer.provider.getBinder().unlinkToDeath(noi, 0 /* flags */);
+ noi.offer.callback.asBinder().unlinkToDeath(noi, 0 /* flags */);
}
@Nullable private NetworkOfferInfo findNetworkOfferInfoByCallback(
@@ -7448,100 +7459,6 @@
updateLinkProperties(nai, newLp, new LinkProperties(nai.linkProperties));
}
- private void sendUpdatedScoreToFactories(NetworkAgentInfo nai) {
- for (int i = 0; i < nai.numNetworkRequests(); i++) {
- NetworkRequest nr = nai.requestAt(i);
- // Don't send listening or track default request to factories. b/17393458
- if (!nr.isRequest()) continue;
- sendUpdatedScoreToFactories(nr, nai);
- }
- }
-
- private void sendUpdatedScoreToFactories(
- @NonNull final NetworkReassignment.RequestReassignment event) {
- // If a request of type REQUEST is now being satisfied by a new network.
- if (null != event.mNewNetworkRequest && event.mNewNetworkRequest.isRequest()) {
- sendUpdatedScoreToFactories(event.mNewNetworkRequest, event.mNewNetwork);
- }
-
- // If a previously satisfied request of type REQUEST is no longer being satisfied.
- if (null != event.mOldNetworkRequest && event.mOldNetworkRequest.isRequest()
- && event.mOldNetworkRequest != event.mNewNetworkRequest) {
- sendUpdatedScoreToFactories(event.mOldNetworkRequest, null);
- }
-
- cancelMultilayerLowerPriorityNpiRequests(event.mNetworkRequestInfo);
- }
-
- /**
- * Cancel with all NPIs the given NRI's multilayer requests that are a lower priority than
- * its currently satisfied active request.
- * @param nri the NRI to cancel lower priority requests for.
- */
- private void cancelMultilayerLowerPriorityNpiRequests(
- @NonNull final NetworkRequestInfo nri) {
- if (!nri.isMultilayerRequest() || null == nri.mActiveRequest) {
- return;
- }
-
- final int indexOfNewRequest = nri.mRequests.indexOf(nri.mActiveRequest);
- for (int i = indexOfNewRequest + 1; i < nri.mRequests.size(); i++) {
- cancelNpiRequest(nri.mRequests.get(i));
- }
- }
-
- private void sendUpdatedScoreToFactories(@NonNull NetworkRequest networkRequest,
- @Nullable NetworkAgentInfo nai) {
- final int score;
- final int serial;
- if (nai != null) {
- score = nai.getCurrentScore();
- serial = nai.factorySerialNumber;
- } else {
- score = 0;
- serial = 0;
- }
- if (VDBG || DDBG){
- log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
- }
- for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
- npi.requestNetwork(networkRequest, score, serial);
- }
- }
-
- /** Sends all current NetworkRequests to the specified factory. */
- private void sendAllRequestsToProvider(@NonNull final NetworkProviderInfo npi) {
- ensureRunningOnConnectivityServiceThread();
- for (final NetworkRequestInfo nri : getNrisFromGlobalRequests()) {
- for (final NetworkRequest req : nri.mRequests) {
- if (!req.isRequest() && nri.getActiveRequest() == req) {
- break;
- }
- if (!req.isRequest()) {
- continue;
- }
- // Only set the nai for the request it is satisfying.
- final NetworkAgentInfo nai =
- nri.getActiveRequest() == req ? nri.getSatisfier() : null;
- final int score;
- final int serial;
- if (null != nai) {
- score = nai.getCurrentScore();
- serial = nai.factorySerialNumber;
- } else {
- score = 0;
- serial = NetworkProvider.ID_NONE;
- }
- npi.requestNetwork(req, score, serial);
- // For multilayer requests, don't send lower priority requests if a higher priority
- // request is already satisfied.
- if (null != nai) {
- break;
- }
- }
- }
- }
-
private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
int notificationType) {
if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
@@ -7985,7 +7902,7 @@
NetworkAgentInfo bestNetwork = null;
NetworkRequest bestRequest = null;
for (final NetworkRequest req : nri.mRequests) {
- bestNetwork = mNetworkRanker.getBestNetwork(req, nais);
+ bestNetwork = mNetworkRanker.getBestNetwork(req, nais, nri.getSatisfier());
// Stop evaluating as the highest possible priority request is satisfied.
if (null != bestNetwork) {
bestRequest = req;
@@ -8033,6 +7950,7 @@
log(changes.toString()); // Shorter form, only one line of log
}
applyNetworkReassignment(changes, now);
+ issueNetworkNeeds();
}
private void applyNetworkReassignment(@NonNull final NetworkReassignment changes,
@@ -8064,12 +7982,6 @@
// before LegacyTypeTracker sends legacy broadcasts
for (final NetworkReassignment.RequestReassignment event :
changes.getRequestReassignments()) {
- // Tell NetworkProviders about the new score, so they can stop
- // trying to connect if they know they cannot match it.
- // TODO - this could get expensive if there are a lot of outstanding requests for this
- // network. Think of a way to reduce this. Push netid->request mapping to each factory?
- sendUpdatedScoreToFactories(event);
-
if (null != event.mNewNetwork) {
notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
} else {
@@ -8206,6 +8118,106 @@
}
}
+ private void issueNetworkNeeds() {
+ ensureRunningOnConnectivityServiceThread();
+ for (final NetworkOfferInfo noi : mNetworkOffers) {
+ issueNetworkNeeds(noi);
+ }
+ }
+
+ private void issueNetworkNeeds(@NonNull final NetworkOfferInfo noi) {
+ ensureRunningOnConnectivityServiceThread();
+ for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+ informOffer(nri, noi.offer, mNetworkRanker);
+ }
+ }
+
+ /**
+ * Inform a NetworkOffer about any new situation of a request.
+ *
+ * This function handles updates to offers. A number of events may happen that require
+ * updating the registrant for this offer about the situation :
+ * • The offer itself was updated. This may lead the offer to no longer being able
+ * to satisfy a request or beat a satisfier (and therefore be no longer needed),
+ * or conversely being strengthened enough to beat the satisfier (and therefore
+ * start being needed)
+ * • The network satisfying a request changed (including cases where the request
+ * starts or stops being satisfied). The new network may be a stronger or weaker
+ * match than the old one, possibly affecting whether the offer is needed.
+ * • The network satisfying a request updated their score. This may lead the offer
+ * to no longer be able to beat it if the current satisfier got better, or
+ * conversely start being a good choice if the current satisfier got weaker.
+ *
+ * @param nri The request
+ * @param offer The offer. This may be an updated offer.
+ */
+ private static void informOffer(@NonNull NetworkRequestInfo nri,
+ @NonNull final NetworkOffer offer, @NonNull final NetworkRanker networkRanker) {
+ final NetworkRequest activeRequest = nri.isBeingSatisfied() ? nri.getActiveRequest() : null;
+ final NetworkAgentInfo satisfier = null != activeRequest ? nri.getSatisfier() : null;
+
+ // Multi-layer requests have a currently active request, the one being satisfied.
+ // Since the system will try to bring up a better network than is currently satisfying
+ // the request, NetworkProviders need to be told the offers matching the requests *above*
+ // the currently satisfied one are needed, that the ones *below* the satisfied one are
+ // not needed, and the offer is needed for the active request iff the offer can beat
+ // the satisfier.
+ // For non-multilayer requests, the logic above gracefully degenerates to only the
+ // last case.
+ // To achieve this, the loop below will proceed in three steps. In a first phase, inform
+ // providers that the offer is needed for this request, until the active request is found.
+ // In a second phase, deal with the currently active request. In a third phase, inform
+ // the providers that offer is unneeded for the remaining requests.
+
+ // First phase : inform providers of all requests above the active request.
+ int i;
+ for (i = 0; nri.mRequests.size() > i; ++i) {
+ final NetworkRequest request = nri.mRequests.get(i);
+ if (activeRequest == request) break; // Found the active request : go to phase 2
+ if (!request.isRequest()) continue; // Listens/track defaults are never sent to offers
+ // Since this request is higher-priority than the one currently satisfied, if the
+ // offer can satisfy it, the provider should try and bring up the network for sure ;
+ // no need to even ask the ranker – an offer that can satisfy is always better than
+ // no network. Hence tell the provider so unless it already knew.
+ if (request.canBeSatisfiedBy(offer.caps) && !offer.neededFor(request)) {
+ offer.onNetworkNeeded(request);
+ }
+ }
+
+ // Second phase : deal with the active request (if any)
+ if (null != activeRequest && activeRequest.isRequest()) {
+ final boolean oldNeeded = offer.neededFor(activeRequest);
+ // An offer is needed if it is currently served by this provider or if this offer
+ // can beat the current satisfier.
+ final boolean currentlyServing = satisfier != null
+ && satisfier.factorySerialNumber == offer.providerId;
+ final boolean newNeeded = (currentlyServing
+ || (activeRequest.canBeSatisfiedBy(offer.caps)
+ && networkRanker.mightBeat(activeRequest, satisfier, offer)));
+ if (newNeeded != oldNeeded) {
+ if (newNeeded) {
+ offer.onNetworkNeeded(activeRequest);
+ } else {
+ // The offer used to be able to beat the satisfier. Now it can't.
+ offer.onNetworkUnneeded(activeRequest);
+ }
+ }
+ }
+
+ // Third phase : inform the providers that the offer isn't needed for any request
+ // below the active one.
+ for (++i /* skip the active request */; nri.mRequests.size() > i; ++i) {
+ final NetworkRequest request = nri.mRequests.get(i);
+ if (!request.isRequest()) continue; // Listens/track defaults are never sent to offers
+ // Since this request is lower-priority than the one currently satisfied, if the
+ // offer can satisfy it, the provider should not try and bring up the network.
+ // Hence tell the provider so unless it already knew.
+ if (offer.neededFor(request)) {
+ offer.onNetworkUnneeded(request);
+ }
+ }
+ }
+
private void addNetworkToLegacyTypeTracker(@NonNull final NetworkAgentInfo nai) {
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
@@ -8371,7 +8383,6 @@
if (VDBG || DDBG) log("updateNetworkScore for " + nai.toShortString() + " to " + score);
nai.setScore(score);
rematchAllNetworksAndRequests();
- sendUpdatedScoreToFactories(nai);
}
// Notify only this one new request of the current state. Transfer all the
@@ -8778,7 +8789,7 @@
}
}
- private @VpnManager.VpnType int getVpnType(@Nullable NetworkAgentInfo vpn) {
+ private int getVpnType(@Nullable NetworkAgentInfo vpn) {
if (vpn == null) return VpnManager.TYPE_VPN_NONE;
final TransportInfo ti = vpn.networkCapabilities.getTransportInfo();
if (!(ti instanceof VpnTransportInfo)) return VpnManager.TYPE_VPN_NONE;
@@ -9691,7 +9702,8 @@
// safe - it's just possible the value is slightly outdated. For the final check,
// see #handleSetProfileNetworkPreference. But if this can be caught here it is a
// lot easier to understand, so opportunistically check it.
- if (!mOemNetworkPreferences.isEmpty()) {
+ // TODO: Have a priority for each preference.
+ if (!mOemNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) {
throwConcurrentPreferenceException();
}
final NetworkCapabilities nc;
@@ -9750,7 +9762,8 @@
// The binder call has already checked this, but as mOemNetworkPreferences is only
// touched on the handler thread, it's theoretically not impossible that it has changed
// since.
- if (!mOemNetworkPreferences.isEmpty()) {
+ // TODO: Have a priority for each preference.
+ if (!mOemNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) {
// This may happen on a device with an OEM preference set when a user is removed.
// In this case, it's safe to ignore. In particular this happens in the tests.
loge("handleSetProfileNetworkPreference, but OEM network preferences not empty");
@@ -9779,6 +9792,56 @@
}
}
+ @VisibleForTesting
+ @NonNull
+ ArraySet<NetworkRequestInfo> createNrisFromMobileDataPreferredUids(
+ @NonNull final Set<Integer> uids) {
+ final ArraySet<NetworkRequestInfo> nris = new ArraySet<>();
+ if (uids.size() == 0) {
+ // Should not create NetworkRequestInfo if no preferences. Without uid range in
+ // NetworkRequestInfo, makeDefaultForApps() would treat it as a illegal NRI.
+ if (DBG) log("Don't create NetworkRequestInfo because no preferences");
+ return nris;
+ }
+
+ final List<NetworkRequest> requests = new ArrayList<>();
+ // The NRI should be comprised of two layers:
+ // - The request for the mobile network preferred.
+ // - The request for the default network, for fallback.
+ requests.add(createDefaultInternetRequestForTransport(
+ TRANSPORT_CELLULAR, NetworkRequest.Type.LISTEN));
+ requests.add(createDefaultInternetRequestForTransport(
+ TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
+ final Set<UidRange> ranges = new ArraySet<>();
+ for (final int uid : uids) {
+ ranges.add(new UidRange(uid, uid));
+ }
+ setNetworkRequestUids(requests, ranges);
+ nris.add(new NetworkRequestInfo(Process.myUid(), requests));
+ return nris;
+ }
+
+ private void handleMobileDataPreferredUidsChanged() {
+ // Ignore update preference because it's not clear what preference should win in case both
+ // apply to the same app.
+ // TODO: Have a priority for each preference.
+ if (!mOemNetworkPreferences.isEmpty() || !mProfileNetworkPreferences.isEmpty()) {
+ loge("Ignore mobile data preference change because other preferences are not empty");
+ return;
+ }
+
+ mMobileDataPreferredUids = ConnectivitySettingsManager.getMobileDataPreferredUids(mContext);
+ mSystemNetworkRequestCounter.transact(
+ mDeps.getCallingUid(), 1 /* numOfNewRequests */,
+ () -> {
+ final ArraySet<NetworkRequestInfo> nris =
+ createNrisFromMobileDataPreferredUids(mMobileDataPreferredUids);
+ replaceDefaultNetworkRequestsForPreference(nris);
+ });
+ // Finally, rematch.
+ rematchAllNetworksAndRequests();
+ }
+
private void enforceAutomotiveDevice() {
final boolean isAutomotiveDevice =
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
@@ -9808,7 +9871,8 @@
enforceAutomotiveDevice();
enforceOemNetworkPreferencesPermission();
- if (!mProfileNetworkPreferences.isEmpty()) {
+ // TODO: Have a priority for each preference.
+ if (!mProfileNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) {
// Strictly speaking, mProfileNetworkPreferences should only be touched on the
// handler thread. However it is an immutable object, so reading the reference is
// safe - it's just possible the value is slightly outdated. For the final check,
@@ -9846,7 +9910,8 @@
// The binder call has already checked this, but as mOemNetworkPreferences is only
// touched on the handler thread, it's theoretically not impossible that it has changed
// since.
- if (!mProfileNetworkPreferences.isEmpty()) {
+ // TODO: Have a priority for each preference.
+ if (!mProfileNetworkPreferences.isEmpty() || !mMobileDataPreferredUids.isEmpty()) {
logwtf("handleSetOemPreference, but per-profile network preferences not empty");
return;
}
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index f566277..09873f4 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -35,7 +35,6 @@
import android.net.RouteInfo;
import android.net.TestNetworkInterface;
import android.net.TestNetworkSpecifier;
-import android.net.util.NetdService;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -86,7 +85,9 @@
mHandler = new Handler(mHandlerThread.getLooper());
mContext = Objects.requireNonNull(context, "missing Context");
- mNetd = Objects.requireNonNull(NetdService.getInstance(), "could not get netd instance");
+ mNetd = Objects.requireNonNull(
+ INetd.Stub.asInterface((IBinder) context.getSystemService(Context.NETD_SERVICE)),
+ "could not get netd instance");
mCm = mContext.getSystemService(ConnectivityManager.class);
mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(),
TEST_NETWORK_PROVIDER_NAME);
diff --git a/service/src/com/android/server/connectivity/ConnectivityConstants.java b/service/src/com/android/server/connectivity/ConnectivityConstants.java
deleted file mode 100644
index 325a2cd..0000000
--- a/service/src/com/android/server/connectivity/ConnectivityConstants.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2018 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;
-
-/**
- * A class encapsulating various constants used by Connectivity.
- * TODO : remove this class.
- * @hide
- */
-public class ConnectivityConstants {
- // VPNs typically have priority over other networks. Give them a score that will
- // let them win every single time.
- public static final int VPN_DEFAULT_SCORE = 101;
-}
diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java
index 9326d69..6e38e2d 100644
--- a/service/src/com/android/server/connectivity/FullScore.java
+++ b/service/src/com/android/server/connectivity/FullScore.java
@@ -17,14 +17,20 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.net.NetworkScore.KEEP_CONNECTED_NONE;
+import static android.net.NetworkScore.POLICY_EXITING;
+import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
+import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkScore;
+import android.net.NetworkScore.KeepConnectedReason;
import com.android.internal.annotations.VisibleForTesting;
@@ -50,7 +56,8 @@
POLICY_IS_VALIDATED,
POLICY_IS_VPN,
POLICY_EVER_USER_SELECTED,
- POLICY_ACCEPT_UNVALIDATED
+ POLICY_ACCEPT_UNVALIDATED,
+ POLICY_IS_UNMETERED
})
public @interface Policy {
}
@@ -75,12 +82,22 @@
/** @hide */
public static final int POLICY_ACCEPT_UNVALIDATED = 60;
+ // This network is unmetered. {@see NetworkCapabilities.NET_CAPABILITY_NOT_METERED}.
+ /** @hide */
+ public static final int POLICY_IS_UNMETERED = 59;
+
// To help iterate when printing
@VisibleForTesting
- static final int MIN_CS_MANAGED_POLICY = POLICY_ACCEPT_UNVALIDATED;
+ static final int MIN_CS_MANAGED_POLICY = POLICY_IS_UNMETERED;
@VisibleForTesting
static final int MAX_CS_MANAGED_POLICY = POLICY_IS_VALIDATED;
+ // Mask for policies in NetworkScore. This should have all bits managed by NetworkScore set
+ // and all bits managed by FullScore unset. As bits are handled from 0 up in NetworkScore and
+ // from 63 down in FullScore, cut at the 32rd bit for simplicity, but change this if some day
+ // there are more than 32 bits handled on either side.
+ private static final int EXTERNAL_POLICIES_MASK = 0x0000FFFF;
+
@VisibleForTesting
static @NonNull String policyNameOf(final int policy) {
switch (policy) {
@@ -88,6 +105,10 @@
case POLICY_IS_VPN: return "IS_VPN";
case POLICY_EVER_USER_SELECTED: return "EVER_USER_SELECTED";
case POLICY_ACCEPT_UNVALIDATED: return "ACCEPT_UNVALIDATED";
+ case POLICY_IS_UNMETERED: return "IS_UNMETERED";
+ case POLICY_YIELD_TO_BAD_WIFI: return "YIELD_TO_BAD_WIFI";
+ case POLICY_TRANSPORT_PRIMARY: return "TRANSPORT_PRIMARY";
+ case POLICY_EXITING: return "EXITING";
}
throw new IllegalArgumentException("Unknown policy : " + policy);
}
@@ -95,9 +116,13 @@
// Bitmask of all the policies applied to this score.
private final long mPolicies;
- FullScore(final int legacyInt, final long policies) {
+ private final int mKeepConnectedReason;
+
+ FullScore(final int legacyInt, final long policies,
+ @KeepConnectedReason final int keepConnectedReason) {
mLegacyInt = legacyInt;
mPolicies = policies;
+ mKeepConnectedReason = keepConnectedReason;
}
/**
@@ -106,18 +131,27 @@
* @param score the score supplied by the agent
* @param caps the NetworkCapabilities of the network
* @param config the NetworkAgentConfig of the network
- * @return an FullScore that is appropriate to use for ranking.
+ * @param yieldToBadWiFi whether this network yields to a previously validated wifi gone bad
+ * @return a FullScore that is appropriate to use for ranking.
*/
+ // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+ // telephony factory, so that it depends on the carrier. For now this is handled by
+ // connectivity for backward compatibility.
public static FullScore fromNetworkScore(@NonNull final NetworkScore score,
- @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config) {
- return withPolicies(score.getLegacyInt(), caps.hasCapability(NET_CAPABILITY_VALIDATED),
+ @NonNull final NetworkCapabilities caps, @NonNull final NetworkAgentConfig config,
+ final boolean yieldToBadWiFi) {
+ return withPolicies(score.getLegacyInt(), score.getPolicies(),
+ score.getKeepConnectedReason(),
+ caps.hasCapability(NET_CAPABILITY_VALIDATED),
caps.hasTransport(TRANSPORT_VPN),
+ caps.hasCapability(NET_CAPABILITY_NOT_METERED),
config.explicitlySelected,
- config.acceptUnvalidated);
+ config.acceptUnvalidated,
+ yieldToBadWiFi);
}
/**
- * Given a score supplied by the NetworkAgent, produce a prospective score for an offer.
+ * Given a score supplied by a NetworkProvider, produce a prospective score for an offer.
*
* NetworkOffers have score filters that are compared to the scores of actual networks
* to see if they could possibly beat the current satisfier. Some things the agent can't
@@ -135,12 +169,17 @@
final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET);
// VPN transports are known in advance.
final boolean vpn = caps.hasTransport(TRANSPORT_VPN);
+ // Prospective scores are always unmetered, because unmetered networks are stronger
+ // than metered networks, and it's not known in advance whether the network is metered.
+ final boolean unmetered = true;
// The network hasn't been chosen by the user (yet, at least).
final boolean everUserSelected = false;
// Don't assume the user will accept unvalidated connectivity.
final boolean acceptUnvalidated = false;
- return withPolicies(score.getLegacyInt(), mayValidate, vpn, everUserSelected,
- acceptUnvalidated);
+ // Don't assume clinging to bad wifi
+ final boolean yieldToBadWiFi = false;
+ return withPolicies(score.getLegacyInt(), score.getPolicies(), KEEP_CONNECTED_NONE,
+ mayValidate, vpn, unmetered, everUserSelected, acceptUnvalidated, yieldToBadWiFi);
}
/**
@@ -150,24 +189,40 @@
* @param config the NetworkAgentConfig of the network
* @return a score with the policies from the arguments reset
*/
+ // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+ // telephony factory, so that it depends on the carrier. For now this is handled by
+ // connectivity for backward compatibility.
public FullScore mixInScore(@NonNull final NetworkCapabilities caps,
- @NonNull final NetworkAgentConfig config) {
- return withPolicies(mLegacyInt, caps.hasCapability(NET_CAPABILITY_VALIDATED),
+ @NonNull final NetworkAgentConfig config, final boolean yieldToBadWifi) {
+ return withPolicies(mLegacyInt, mPolicies, mKeepConnectedReason,
+ caps.hasCapability(NET_CAPABILITY_VALIDATED),
caps.hasTransport(TRANSPORT_VPN),
+ caps.hasCapability(NET_CAPABILITY_NOT_METERED),
config.explicitlySelected,
- config.acceptUnvalidated);
+ config.acceptUnvalidated,
+ yieldToBadWifi);
}
+ // TODO : this shouldn't manage bad wifi avoidance – instead this should be done by the
+ // telephony factory, so that it depends on the carrier. For now this is handled by
+ // connectivity for backward compatibility.
private static FullScore withPolicies(@NonNull final int legacyInt,
+ final long externalPolicies,
+ @KeepConnectedReason final int keepConnectedReason,
final boolean isValidated,
final boolean isVpn,
+ final boolean isUnmetered,
final boolean everUserSelected,
- final boolean acceptUnvalidated) {
- return new FullScore(legacyInt,
- (isValidated ? 1L << POLICY_IS_VALIDATED : 0)
+ final boolean acceptUnvalidated,
+ final boolean yieldToBadWiFi) {
+ return new FullScore(legacyInt, (externalPolicies & EXTERNAL_POLICIES_MASK)
+ | (isValidated ? 1L << POLICY_IS_VALIDATED : 0)
| (isVpn ? 1L << POLICY_IS_VPN : 0)
+ | (isUnmetered ? 1L << POLICY_IS_UNMETERED : 0)
| (everUserSelected ? 1L << POLICY_EVER_USER_SELECTED : 0)
- | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0));
+ | (acceptUnvalidated ? 1L << POLICY_ACCEPT_UNVALIDATED : 0)
+ | (yieldToBadWiFi ? 1L << POLICY_YIELD_TO_BAD_WIFI : 0),
+ keepConnectedReason);
}
/**
@@ -219,13 +274,21 @@
return 0 != (mPolicies & (1L << policy));
}
+ /**
+ * Returns the keep-connected reason, or KEEP_CONNECTED_NONE.
+ */
+ public int getKeepConnectedReason() {
+ return mKeepConnectedReason;
+ }
+
// Example output :
// Score(50 ; Policies : EVER_USER_SELECTED&IS_VALIDATED)
@Override
public String toString() {
final StringJoiner sj = new StringJoiner(
"&", // delimiter
- "Score(" + mLegacyInt + " ; Policies : ", // prefix
+ "Score(" + mLegacyInt + " ; KeepConnected : " + mKeepConnectedReason
+ + " ; Policies : ", // prefix
")"); // suffix
for (int i = NetworkScore.MIN_AGENT_MANAGED_POLICY;
i <= NetworkScore.MAX_AGENT_MANAGED_POLICY; ++i) {
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index ee32fbf..5d793fd 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.transportNamesOf;
import android.annotation.NonNull;
@@ -142,7 +143,7 @@
// the network is no longer considered "lingering". After the linger timer expires, if the network
// is satisfying one or more background NetworkRequests it is kept up in the background. If it is
// not, ConnectivityService disconnects the NetworkAgent's AsyncChannel.
-public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
+public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRanker.Scoreable {
@NonNull public NetworkInfo networkInfo;
// This Network object should always be used if possible, so as to encourage reuse of the
@@ -362,9 +363,9 @@
linkProperties = lp;
networkCapabilities = nc;
networkAgentConfig = config;
- setScore(score); // uses members networkCapabilities and networkAgentConfig
- clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
mConnService = connService;
+ setScore(score); // uses members connService, networkCapabilities and networkAgentConfig
+ clatd = new Nat464Xlat(this, netd, dnsResolver, deps);
mContext = context;
mHandler = handler;
this.factorySerialNumber = factorySerialNumber;
@@ -706,7 +707,7 @@
@NonNull final NetworkCapabilities nc) {
final NetworkCapabilities oldNc = networkCapabilities;
networkCapabilities = nc;
- mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig);
+ mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, yieldToBadWiFi());
final NetworkMonitorManager nm = mNetworkMonitor;
if (nm != null) {
nm.notifyNetworkCapabilitiesChanged(nc);
@@ -714,6 +715,11 @@
return oldNc;
}
+ private boolean yieldToBadWiFi() {
+ // Only cellular networks yield to bad wifi
+ return networkCapabilities.hasTransport(TRANSPORT_CELLULAR) && !mConnService.avoidBadWifi();
+ }
+
public ConnectivityService connService() {
return mConnService;
}
@@ -884,13 +890,16 @@
return isVPN();
}
- // Return true on devices configured to ignore score penalty for wifi networks
- // that become unvalidated (b/31075769).
- private boolean ignoreWifiUnvalidationPenalty() {
- boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
- networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
- boolean avoidBadWifi = mConnService.avoidBadWifi() || avoidUnvalidated;
- return isWifi && !avoidBadWifi && everValidated;
+ // Caller must not mutate. This method is called frequently and making a defensive copy
+ // would be too expensive. This is used by NetworkRanker.Scoreable, so it can be compared
+ // against other scoreables.
+ @Override public NetworkCapabilities getCaps() {
+ return networkCapabilities;
+ }
+
+ // NetworkRanker.Scoreable
+ @Override public FullScore getScore() {
+ return mScore;
}
// Get the current score for this Network. This may be modified from what the
@@ -909,7 +918,8 @@
* Mix-in the ConnectivityService-managed bits in the score.
*/
public void setScore(final NetworkScore score) {
- mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig);
+ mScore = FullScore.fromNetworkScore(score, networkCapabilities, networkAgentConfig,
+ yieldToBadWiFi());
}
/**
@@ -918,7 +928,7 @@
* Call this after updating the network agent config.
*/
public void updateScoreForNetworkAgentConfigUpdate() {
- mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig);
+ mScore = mScore.mixInScore(networkCapabilities, networkAgentConfig, yieldToBadWiFi());
}
/**
@@ -1081,7 +1091,7 @@
return "NetworkAgentInfo{"
+ "network{" + network + "} handle{" + network.getNetworkHandle() + "} ni{"
+ networkInfo.toShortString() + "} "
- + " Score{" + getCurrentScore() + "} "
+ + mScore + " "
+ (isNascent() ? " nascent" : (isLingering() ? " lingering" : ""))
+ (everValidated ? " everValidated" : "")
+ (lastValidated ? " lastValidated" : "")
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 0c0d459..b57ad5d 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -84,7 +84,7 @@
// The context is for the current user (system server)
private final Context mContext;
- private final Resources mResources;
+ private final ConnectivityResources mResources;
private final TelephonyManager mTelephonyManager;
// The notification manager is created from a context for User.ALL, so notifications
// will be sent to all users.
@@ -99,7 +99,7 @@
(NotificationManager) c.createContextAsUser(UserHandle.ALL, 0 /* flags */)
.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationTypeMap = new SparseIntArray();
- mResources = new ConnectivityResources(mContext).get();
+ mResources = new ConnectivityResources(mContext);
}
@VisibleForTesting
@@ -118,11 +118,11 @@
}
private String getTransportName(final int transportType) {
- String[] networkTypes = mResources.getStringArray(R.array.network_switch_type_name);
+ String[] networkTypes = mResources.get().getStringArray(R.array.network_switch_type_name);
try {
return networkTypes[transportType];
} catch (IndexOutOfBoundsException e) {
- return mResources.getString(R.string.network_switch_type_name_unknown);
+ return mResources.get().getString(R.string.network_switch_type_name_unknown);
}
}
@@ -197,10 +197,11 @@
tag, nameOf(eventId), getTransportName(transportType), name, highPriority));
}
- final Resources r = mResources;
+ final Resources r = mResources.get();
final CharSequence title;
final CharSequence details;
- Icon icon = Icon.createWithResource(r, getIcon(transportType));
+ Icon icon = Icon.createWithResource(
+ mResources.getResourcesContext(), getIcon(transportType));
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.wifi_no_internet, name);
details = r.getString(R.string.wifi_no_internet_detailed);
@@ -355,7 +356,7 @@
public void showToast(NetworkAgentInfo fromNai, NetworkAgentInfo toNai) {
String fromTransport = getTransportName(approximateTransportType(fromNai));
String toTransport = getTransportName(approximateTransportType(toNai));
- String text = mResources.getString(
+ String text = mResources.get().getString(
R.string.network_switch_metered_toast, fromTransport, toTransport);
Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
}
diff --git a/service/src/com/android/server/connectivity/NetworkOffer.java b/service/src/com/android/server/connectivity/NetworkOffer.java
index fa2d465..5336593 100644
--- a/service/src/com/android/server/connectivity/NetworkOffer.java
+++ b/service/src/com/android/server/connectivity/NetworkOffer.java
@@ -17,14 +17,14 @@
package com.android.server.connectivity;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.net.INetworkOfferCallback;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.os.Messenger;
+import android.os.RemoteException;
+import java.util.HashSet;
import java.util.Objects;
-
+import java.util.Set;
/**
* Represents an offer made by a NetworkProvider to create a network if a need arises.
@@ -40,27 +40,88 @@
*
* @hide
*/
-public class NetworkOffer {
+public class NetworkOffer implements NetworkRanker.Scoreable {
@NonNull public final FullScore score;
@NonNull public final NetworkCapabilities caps;
@NonNull public final INetworkOfferCallback callback;
- @NonNull public final Messenger provider;
+ @NonNull public final int providerId;
+ // While this could, in principle, be deduced from the old values of the satisfying networks,
+ // doing so would add a lot of complexity and performance penalties. For each request, the
+ // ranker would have to run again to figure out if this offer used to be able to beat the
+ // previous satisfier to know if there is a change in whether this offer is now needed ;
+ // besides, there would be a need to handle an edge case when a new request comes online,
+ // where it's not satisfied before the first rematch, where starting to satisfy a request
+ // should not result in sending unneeded to this offer. This boolean, while requiring that
+ // the offers are only ever manipulated on the CS thread, is by far a simpler and
+ // economical solution.
+ private final Set<NetworkRequest> mCurrentlyNeeded = new HashSet<>();
- private static NetworkCapabilities emptyCaps() {
- final NetworkCapabilities nc = new NetworkCapabilities();
- return nc;
+ public NetworkOffer(@NonNull final FullScore score,
+ @NonNull final NetworkCapabilities caps,
+ @NonNull final INetworkOfferCallback callback,
+ @NonNull final int providerId) {
+ this.score = Objects.requireNonNull(score);
+ this.caps = Objects.requireNonNull(caps);
+ this.callback = Objects.requireNonNull(callback);
+ this.providerId = providerId;
}
- // Ideally the caps argument would be non-null, but null has historically meant no filter
- // and telephony passes null. Keep backward compatibility.
- public NetworkOffer(@NonNull final FullScore score,
- @Nullable final NetworkCapabilities caps,
- @NonNull final INetworkOfferCallback callback,
- @NonNull final Messenger provider) {
- this.score = Objects.requireNonNull(score);
- this.caps = null != caps ? caps : emptyCaps();
- this.callback = Objects.requireNonNull(callback);
- this.provider = Objects.requireNonNull(provider);
+ /**
+ * Get the score filter of this offer
+ */
+ @Override @NonNull public FullScore getScore() {
+ return score;
+ }
+
+ /**
+ * Get the capabilities filter of this offer
+ */
+ @Override @NonNull public NetworkCapabilities getCaps() {
+ return caps;
+ }
+
+ /**
+ * Tell the provider for this offer that the network is needed for a request.
+ * @param request the request for which the offer is needed
+ */
+ public void onNetworkNeeded(@NonNull final NetworkRequest request) {
+ if (mCurrentlyNeeded.contains(request)) {
+ throw new IllegalStateException("Network already needed");
+ }
+ mCurrentlyNeeded.add(request);
+ try {
+ callback.onNetworkNeeded(request);
+ } catch (final RemoteException e) {
+ // The provider is dead. It will be removed by the death recipient.
+ }
+ }
+
+ /**
+ * Tell the provider for this offer that the network is no longer needed for this request.
+ *
+ * onNetworkNeeded will have been called with the same request before.
+ *
+ * @param request the request
+ */
+ public void onNetworkUnneeded(@NonNull final NetworkRequest request) {
+ if (!mCurrentlyNeeded.contains(request)) {
+ throw new IllegalStateException("Network already unneeded");
+ }
+ mCurrentlyNeeded.remove(request);
+ try {
+ callback.onNetworkUnneeded(request);
+ } catch (final RemoteException e) {
+ // The provider is dead. It will be removed by the death recipient.
+ }
+ }
+
+ /**
+ * Returns whether this offer is currently needed for this request.
+ * @param request the request
+ * @return whether the offer is currently considered needed
+ */
+ public boolean neededFor(@NonNull final NetworkRequest request) {
+ return mCurrentlyNeeded.contains(request);
}
/**
@@ -69,22 +130,15 @@
* When an updated offer is sent from a provider, call this method on the new offer, passing
* the old one, to take over the state.
*
- * @param previousOffer
+ * @param previousOffer the previous offer
*/
public void migrateFrom(@NonNull final NetworkOffer previousOffer) {
if (!callback.equals(previousOffer.callback)) {
throw new IllegalArgumentException("Can only migrate from a previous version of"
+ " the same offer");
}
- }
-
- /**
- * Returns whether an offer can satisfy a NetworkRequest, according to its capabilities.
- * @param request The request to test against.
- * @return Whether this offer can satisfy the request.
- */
- public final boolean canSatisfy(@NonNull final NetworkRequest request) {
- return request.networkCapabilities.satisfiedByNetworkCapabilities(caps);
+ mCurrentlyNeeded.clear();
+ mCurrentlyNeeded.addAll(previousOffer.mCurrentlyNeeded);
}
@Override
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index d0aabf9..698744a 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -16,30 +16,223 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkScore.POLICY_EXITING;
+import static android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY;
+import static android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI;
+
+import static com.android.server.connectivity.FullScore.POLICY_ACCEPT_UNVALIDATED;
+import static com.android.server.connectivity.FullScore.POLICY_EVER_USER_SELECTED;
+import static com.android.server.connectivity.FullScore.POLICY_IS_VALIDATED;
+import static com.android.server.connectivity.FullScore.POLICY_IS_VPN;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
+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.function.Predicate;
/**
* A class that knows how to find the best network matching a request out of a list of networks.
*/
public class NetworkRanker {
+ /**
+ * A class that can be scored against other scoreables.
+ */
+ public interface Scoreable {
+ /** Get score of this scoreable */
+ FullScore getScore();
+ /** Get capabilities of this scoreable */
+ NetworkCapabilities getCaps();
+ }
+
+ private static final boolean USE_POLICY_RANKING = false;
+
public NetworkRanker() { }
+ // TODO : move to module utils CollectionUtils.
+ @NonNull private static <T> ArrayList<T> filter(@NonNull final Collection<T> source,
+ @NonNull final Predicate<T> test) {
+ final ArrayList<T> matches = new ArrayList<>();
+ for (final T e : source) {
+ if (test.test(e)) {
+ matches.add(e);
+ }
+ }
+ return matches;
+ }
+
+
/**
* Find the best network satisfying this request among the list of passed networks.
*/
- // Almost equivalent to Collections.max(nais), but allows returning null if no network
- // satisfies the request.
@Nullable
public NetworkAgentInfo getBestNetwork(@NonNull final NetworkRequest request,
+ @NonNull final Collection<NetworkAgentInfo> nais,
+ @Nullable final NetworkAgentInfo currentSatisfier) {
+ final ArrayList<NetworkAgentInfo> candidates = filter(nais, nai -> nai.satisfies(request));
+ if (candidates.size() == 1) return candidates.get(0); // Only one potential satisfier
+ if (candidates.size() <= 0) return null; // No network can satisfy this request
+ if (USE_POLICY_RANKING) {
+ return getBestNetworkByPolicy(candidates, currentSatisfier);
+ } else {
+ return getBestNetworkByLegacyInt(candidates);
+ }
+ }
+
+ // Transport preference order, if it comes down to that.
+ private static final int[] PREFERRED_TRANSPORTS_ORDER = { TRANSPORT_ETHERNET, TRANSPORT_WIFI,
+ TRANSPORT_BLUETOOTH, TRANSPORT_CELLULAR };
+
+ // Function used to partition a list into two working areas depending on whether they
+ // satisfy a predicate. All items satisfying the predicate will be put in |positive|, all
+ // items that don't will be put in |negative|.
+ // This is useful in this file because many of the ranking checks will retain only networks that
+ // satisfy a predicate if any of them do, but keep them all if all of them do. Having working
+ // areas is uncustomary in Java, but this function is called in a fairly intensive manner
+ // and doing allocation quite that often might affect performance quite badly.
+ private static <T> void partitionInto(@NonNull final List<T> source, @NonNull Predicate<T> test,
+ @NonNull final List<T> positive, @NonNull final List<T> negative) {
+ positive.clear();
+ negative.clear();
+ for (final T item : source) {
+ if (test.test(item)) {
+ positive.add(item);
+ } else {
+ negative.add(item);
+ }
+ }
+ }
+
+ @Nullable private <T extends Scoreable> T getBestNetworkByPolicy(
+ @NonNull List<T> candidates,
+ @Nullable final T currentSatisfier) {
+ // Used as working areas.
+ final ArrayList<T> accepted =
+ new ArrayList<>(candidates.size() /* initialCapacity */);
+ final ArrayList<T> rejected =
+ new ArrayList<>(candidates.size() /* initialCapacity */);
+
+ // The following tests will search for a network matching a given criterion. They all
+ // function the same way : if any network matches the criterion, drop from consideration
+ // all networks that don't. To achieve this, the tests below :
+ // 1. partition the list of remaining candidates into accepted and rejected networks.
+ // 2. if only one candidate remains, that's the winner : if accepted.size == 1 return [0]
+ // 3. if multiple remain, keep only the accepted networks and go on to the next criterion.
+ // Because the working areas will be wiped, a copy of the accepted networks needs to be
+ // made.
+ // 4. if none remain, the criterion did not help discriminate so keep them all. As an
+ // optimization, skip creating a new array and go on to the next criterion.
+
+ // If there is a connected VPN, use it.
+ partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VPN),
+ accepted, rejected);
+ if (accepted.size() == 1) return accepted.get(0);
+ if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+ // Selected & Accept-unvalidated policy : if any network has both of these, then don't
+ // choose one that doesn't.
+ partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_EVER_USER_SELECTED)
+ && nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED),
+ accepted, rejected);
+ if (accepted.size() == 1) return accepted.get(0);
+ if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+ // Yield to bad wifi policy : if any wifi has ever been validated, keep only networks
+ // that don't yield to such a wifi network.
+ final boolean anyWiFiEverValidated = CollectionUtils.any(candidates,
+ nai -> nai.getScore().hasPolicy(POLICY_EVER_USER_SELECTED)
+ && nai.getCaps().hasTransport(TRANSPORT_WIFI));
+ if (anyWiFiEverValidated) {
+ partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_YIELD_TO_BAD_WIFI),
+ accepted, rejected);
+ if (accepted.size() == 1) return accepted.get(0);
+ if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+ }
+
+ // If any network is validated (or should be accepted even if it's not validated), then
+ // don't choose one that isn't.
+ partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_VALIDATED)
+ || nai.getScore().hasPolicy(POLICY_ACCEPT_UNVALIDATED),
+ accepted, rejected);
+ if (accepted.size() == 1) return accepted.get(0);
+ if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+ // If any network is not exiting, don't choose one that is.
+ partitionInto(candidates, nai -> !nai.getScore().hasPolicy(POLICY_EXITING),
+ accepted, rejected);
+ if (accepted.size() == 1) return accepted.get(0);
+ if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+ // TODO : If any network is unmetered, don't choose a metered network.
+ // This can't be implemented immediately because prospective networks are always
+ // considered unmetered because factories don't know if the network will be metered.
+ // Saying an unmetered network always beats a metered one would mean that when metered wifi
+ // is connected, the offer for telephony would beat WiFi but the actual metered network
+ // would lose, so we'd have an infinite loop where telephony would continually bring up
+ // a network that is immediately torn down.
+ // Fix this by getting the agent to tell connectivity whether the network they will
+ // bring up is metered. Cell knows that in advance, while WiFi has a good estimate and
+ // can revise it if the network later turns out to be metered.
+ // partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_IS_UNMETERED),
+ // accepted, rejected);
+ // if (accepted.size() == 1) return accepted.get(0);
+ // if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
+
+ // If any network is for the default subscription, don't choose a network for another
+ // subscription with the same transport.
+ partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY),
+ accepted, rejected);
+ for (final Scoreable defaultSubNai : accepted) {
+ // Remove all networks without the DEFAULT_SUBSCRIPTION policy and the same transports
+ // as a network that has it.
+ final int[] transports = defaultSubNai.getCaps().getTransportTypes();
+ candidates.removeIf(nai -> !nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY)
+ && Arrays.equals(transports, nai.getCaps().getTransportTypes()));
+ }
+ if (1 == candidates.size()) return candidates.get(0);
+ // It's guaranteed candidates.size() > 0 because there is at least one with DEFAULT_SUB
+ // policy and only those without it were removed.
+
+ // If some of the networks have a better transport than others, keep only the ones with
+ // the best transports.
+ for (final int transport : PREFERRED_TRANSPORTS_ORDER) {
+ partitionInto(candidates, nai -> nai.getCaps().hasTransport(transport),
+ accepted, rejected);
+ if (accepted.size() == 1) return accepted.get(0);
+ if (accepted.size() > 0 && rejected.size() > 0) {
+ candidates = new ArrayList<>(accepted);
+ break;
+ }
+ }
+
+ // At this point there are still multiple networks passing all the tests above. If any
+ // of them is the previous satisfier, keep it.
+ if (candidates.contains(currentSatisfier)) return currentSatisfier;
+
+ // If there are still multiple options at this point but none of them is any of the
+ // transports above, it doesn't matter which is returned. They are all the same.
+ return candidates.get(0);
+ }
+
+ // TODO : switch to the policy implementation and remove
+ // Almost equivalent to Collections.max(nais), but allows returning null if no network
+ // satisfies the request.
+ private NetworkAgentInfo getBestNetworkByLegacyInt(
@NonNull final Collection<NetworkAgentInfo> nais) {
NetworkAgentInfo bestNetwork = null;
int bestScore = Integer.MIN_VALUE;
for (final NetworkAgentInfo nai : nais) {
- if (!nai.satisfies(request)) continue;
if (nai.getCurrentScore() > bestScore) {
bestNetwork = nai;
bestScore = nai.getCurrentScore();
@@ -47,4 +240,59 @@
}
return bestNetwork;
}
+
+ /**
+ * Returns whether an offer has a chance to beat a champion network for a request.
+ *
+ * Offers are sent by network providers when they think they might be able to make a network
+ * with the characteristics contained in the offer. If the offer has no chance to beat
+ * the currently best network for a given request, there is no point in the provider spending
+ * power trying to find and bring up such a network.
+ *
+ * Note that having an offer up does not constitute a commitment from the provider part
+ * to be able to bring up a network with these characteristics, or a network at all for
+ * that matter. This is only used to save power by letting providers know when they can't
+ * beat a current champion.
+ *
+ * @param request The request to evaluate against.
+ * @param champion The currently best network for this request.
+ * @param offer The offer.
+ * @return Whether the offer stands a chance to beat the champion.
+ */
+ public boolean mightBeat(@NonNull final NetworkRequest request,
+ @Nullable final NetworkAgentInfo champion,
+ @NonNull final NetworkOffer offer) {
+ // If this network can't even satisfy the request then it can't beat anything, not
+ // even an absence of network. It can't satisfy it anyway.
+ if (!request.canBeSatisfiedBy(offer.caps)) return false;
+ // If there is no satisfying network, then this network can beat, because some network
+ // is always better than no network.
+ if (null == champion) return true;
+ if (USE_POLICY_RANKING) {
+ // If there is no champion, the offer can always beat.
+ // Otherwise rank them.
+ final ArrayList<Scoreable> candidates = new ArrayList<>();
+ candidates.add(champion);
+ candidates.add(offer);
+ return offer == getBestNetworkByPolicy(candidates, champion);
+ } else {
+ return mightBeatByLegacyInt(request, champion.getScore(), offer);
+ }
+ }
+
+ /**
+ * Returns whether an offer might beat a champion according to the legacy int.
+ */
+ public boolean mightBeatByLegacyInt(@NonNull final NetworkRequest request,
+ @Nullable final FullScore championScore,
+ @NonNull final NetworkOffer offer) {
+ final int offerIntScore;
+ if (offer.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ // If the offer might have Internet access, then it might validate.
+ offerIntScore = offer.score.getLegacyIntAsValidated();
+ } else {
+ offerIntScore = offer.score.getLegacyInt();
+ }
+ return championScore.getLegacyInt() < offerIntScore;
+ }
}
diff --git a/service/src/com/android/server/connectivity/OsCompat.java b/service/src/com/android/server/connectivity/OsCompat.java
new file mode 100644
index 0000000..57e3dcd
--- /dev/null
+++ b/service/src/com/android/server/connectivity/OsCompat.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 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 android.system.ErrnoException;
+import android.system.Os;
+
+import java.io.FileDescriptor;
+
+/**
+ * Compatibility utility for android.system.Os core platform APIs.
+ *
+ * Connectivity has access to such APIs, but they are not part of the module_current stubs yet
+ * (only core_current). Most stable core platform APIs are included manually in the connectivity
+ * build rules, but because Os is also part of the base java SDK that is earlier on the
+ * classpath, the extra core platform APIs are not seen.
+ *
+ * TODO (b/157639992, b/183097033): remove as soon as core_current is part of system_server_current
+ * @hide
+ */
+public class OsCompat {
+ // This value should be correct on all architectures supported by Android, but hardcoding ioctl
+ // numbers should be avoided.
+ /**
+ * @see android.system.OsConstants#TIOCOUTQ
+ */
+ public static final int TIOCOUTQ = 0x5411;
+
+ /**
+ * @see android.system.Os#getsockoptInt(FileDescriptor, int, int)
+ */
+ public static int getsockoptInt(FileDescriptor fd, int level, int option) throws
+ ErrnoException {
+ try {
+ return (int) Os.class.getMethod(
+ "getsockoptInt", FileDescriptor.class, int.class, int.class)
+ .invoke(null, fd, level, option);
+ } catch (ReflectiveOperationException e) {
+ if (e.getCause() instanceof ErrnoException) {
+ throw (ErrnoException) e.getCause();
+ }
+ throw new IllegalStateException("Error calling getsockoptInt", e);
+ }
+ }
+
+ /**
+ * @see android.system.Os#ioctlInt(FileDescriptor, int)
+ */
+ public static int ioctlInt(FileDescriptor fd, int cmd) throws
+ ErrnoException {
+ try {
+ return (int) Os.class.getMethod(
+ "ioctlInt", FileDescriptor.class, int.class).invoke(null, fd, cmd);
+ } catch (ReflectiveOperationException e) {
+ if (e.getCause() instanceof ErrnoException) {
+ throw (ErrnoException) e.getCause();
+ }
+ throw new IllegalStateException("Error calling ioctlInt", e);
+ }
+ }
+}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 673c804..49b43f8 100644
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -24,7 +24,7 @@
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
-import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS;
+import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
@@ -109,13 +109,13 @@
@GuardedBy("this")
private final Set<Integer> mAllApps = new HashSet<>();
- // A set of apps which are allowed to use restricted networks. These apps can't hold the
- // CONNECTIVITY_USE_RESTRICTED_NETWORKS permission because they can't be signature|privileged
- // apps. However, these apps should still be able to use restricted networks under certain
- // conditions (e.g. government app using emergency services). So grant netd system permission
- // to uids whose package name is listed in APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting.
+ // A set of uids which are allowed to use restricted networks. The packages of these uids can't
+ // hold the CONNECTIVITY_USE_RESTRICTED_NETWORKS permission because they can't be
+ // signature|privileged apps. However, these apps should still be able to use restricted
+ // networks under certain conditions (e.g. government app using emergency services). So grant
+ // netd system permission to these uids which is listed in UIDS_ALLOWED_ON_RESTRICTED_NETWORKS.
@GuardedBy("this")
- private final Set<String> mAppsAllowedOnRestrictedNetworks = new ArraySet<>();
+ private final Set<Integer> mUidsAllowedOnRestrictedNetworks = new ArraySet<>();
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -149,10 +149,10 @@
}
/**
- * Get apps allowed to use restricted networks via ConnectivitySettingsManager.
+ * Get uids allowed to use restricted networks via ConnectivitySettingsManager.
*/
- public Set<String> getAppsAllowedOnRestrictedNetworks(@NonNull Context context) {
- return ConnectivitySettingsManager.getAppsAllowedOnRestrictedNetworks(context);
+ public Set<Integer> getUidsAllowedOnRestrictedNetworks(@NonNull Context context) {
+ return ConnectivitySettingsManager.getUidsAllowedOnRestrictedNetworks(context);
}
/**
@@ -194,10 +194,10 @@
mIntentReceiver, intentFilter, null /* broadcastPermission */,
null /* scheduler */);
- // Register APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer
+ // Register UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer
mDeps.registerContentObserver(
userAllContext,
- Settings.Secure.getUriFor(APPS_ALLOWED_ON_RESTRICTED_NETWORKS),
+ Settings.Secure.getUriFor(UIDS_ALLOWED_ON_RESTRICTED_NETWORKS),
false /* notifyForDescendants */,
new ContentObserver(null) {
@Override
@@ -206,9 +206,9 @@
}
});
- // Read APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting and update
- // mAppsAllowedOnRestrictedNetworks.
- updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext));
+ // Read UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting and update
+ // mUidsAllowedOnRestrictedNetworks.
+ updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
| MATCH_ANY_USER);
@@ -265,9 +265,9 @@
}
@VisibleForTesting
- void updateAppsAllowedOnRestrictedNetworks(final Set<String> apps) {
- mAppsAllowedOnRestrictedNetworks.clear();
- mAppsAllowedOnRestrictedNetworks.addAll(apps);
+ void updateUidsAllowedOnRestrictedNetworks(final Set<Integer> uids) {
+ mUidsAllowedOnRestrictedNetworks.clear();
+ mUidsAllowedOnRestrictedNetworks.addAll(uids);
}
@VisibleForTesting
@@ -285,10 +285,11 @@
}
@VisibleForTesting
- boolean isAppAllowedOnRestrictedNetworks(@NonNull final PackageInfo app) {
- // Check whether package name is in allowed on restricted networks app list. If so, this app
- // can have netd system permission.
- return mAppsAllowedOnRestrictedNetworks.contains(app.packageName);
+ boolean isUidAllowedOnRestrictedNetworks(final ApplicationInfo appInfo) {
+ if (appInfo == null) return false;
+ // Check whether package's uid is in allowed on restricted networks uid list. If so, this
+ // uid can have netd system permission.
+ return mUidsAllowedOnRestrictedNetworks.contains(appInfo.uid);
}
@VisibleForTesting
@@ -310,7 +311,8 @@
boolean hasRestrictedNetworkPermission(@NonNull final PackageInfo app) {
// TODO : remove carryover package check in the future(b/31479477). All apps should just
// request the appropriate permission for their use case since android Q.
- return isCarryoverPackage(app.applicationInfo) || isAppAllowedOnRestrictedNetworks(app)
+ return isCarryoverPackage(app.applicationInfo)
+ || isUidAllowedOnRestrictedNetworks(app.applicationInfo)
|| hasPermission(app, PERMISSION_MAINLINE_NETWORK_STACK)
|| hasPermission(app, NETWORK_STACK)
|| hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
@@ -770,35 +772,31 @@
}
private synchronized void onSettingChanged() {
- // Step1. Update apps allowed to use restricted networks and compute the set of packages to
+ // Step1. Update uids allowed to use restricted networks and compute the set of uids to
// update.
- final Set<String> packagesToUpdate = new ArraySet<>(mAppsAllowedOnRestrictedNetworks);
- updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext));
- packagesToUpdate.addAll(mAppsAllowedOnRestrictedNetworks);
+ final Set<Integer> uidsToUpdate = new ArraySet<>(mUidsAllowedOnRestrictedNetworks);
+ updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
+ uidsToUpdate.addAll(mUidsAllowedOnRestrictedNetworks);
- final Map<Integer, Boolean> updatedApps = new HashMap<>();
- final Map<Integer, Boolean> removedApps = new HashMap<>();
+ final Map<Integer, Boolean> updatedUids = new HashMap<>();
+ final Map<Integer, Boolean> removedUids = new HashMap<>();
- // Step2. For each package to update, find out its new permission.
- for (String app : packagesToUpdate) {
- final PackageInfo info = getPackageInfo(app);
- if (info == null || info.applicationInfo == null) continue;
-
- final int uid = info.applicationInfo.uid;
+ // Step2. For each uid to update, find out its new permission.
+ for (Integer uid : uidsToUpdate) {
final Boolean permission = highestUidNetworkPermission(uid);
if (null == permission) {
- removedApps.put(uid, NETWORK); // Doesn't matter which permission is set here.
+ removedUids.put(uid, NETWORK); // Doesn't matter which permission is set here.
mApps.remove(uid);
} else {
- updatedApps.put(uid, permission);
+ updatedUids.put(uid, permission);
mApps.put(uid, permission);
}
}
// Step3. Update or revoke permission for uids with netd.
- update(mUsers, updatedApps, true /* add */);
- update(mUsers, removedApps, false /* add */);
+ update(mUsers, updatedUids, true /* add */);
+ update(mUsers, removedUids, false /* add */);
}
/** Dump info to dumpsys */
diff --git a/service/src/com/android/server/connectivity/TcpKeepaliveController.java b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
index c480594..73f3475 100644
--- a/service/src/com/android/server/connectivity/TcpKeepaliveController.java
+++ b/service/src/com/android/server/connectivity/TcpKeepaliveController.java
@@ -27,7 +27,8 @@
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IP_TOS;
import static android.system.OsConstants.IP_TTL;
-import static android.system.OsConstants.TIOCOUTQ;
+
+import static com.android.server.connectivity.OsCompat.TIOCOUTQ;
import android.annotation.NonNull;
import android.net.InvalidPacketException;
@@ -175,10 +176,10 @@
}
// Query write sequence number from SEND_QUEUE.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
- tcpDetails.seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ tcpDetails.seq = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
// Query read sequence number from RECV_QUEUE.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
- tcpDetails.ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ tcpDetails.ack = OsCompat.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
// Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
// Finally, check if socket is still idle. TODO : this check needs to move to
@@ -198,9 +199,9 @@
tcpDetails.rcvWndScale = trw.rcvWndScale;
if (tcpDetails.srcAddress.length == 4 /* V4 address length */) {
// Query TOS.
- tcpDetails.tos = Os.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
+ tcpDetails.tos = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TOS);
// Query TTL.
- tcpDetails.ttl = Os.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
+ tcpDetails.ttl = OsCompat.getsockoptInt(fd, IPPROTO_IP, IP_TTL);
}
} catch (ErrnoException e) {
Log.e(TAG, "Exception reading TCP state from socket", e);
@@ -305,7 +306,7 @@
private static boolean isReceiveQueueEmpty(FileDescriptor fd)
throws ErrnoException {
- final int result = Os.ioctlInt(fd, SIOCINQ);
+ final int result = OsCompat.ioctlInt(fd, SIOCINQ);
if (result != 0) {
Log.e(TAG, "Read queue has data");
return false;
@@ -315,7 +316,7 @@
private static boolean isSendQueueEmpty(FileDescriptor fd)
throws ErrnoException {
- final int result = Os.ioctlInt(fd, SIOCOUTQ);
+ final int result = OsCompat.ioctlInt(fd, SIOCOUTQ);
if (result != 0) {
Log.e(TAG, "Write queue has data");
return false;
diff --git a/tests/OWNERS b/tests/OWNERS
deleted file mode 100644
index d3836d4..0000000
--- a/tests/OWNERS
+++ /dev/null
@@ -1,8 +0,0 @@
-set noparent
-
-codewiz@google.com
-jchalard@google.com
-junyulai@google.com
-lorenzo@google.com
-reminv@google.com
-satk@google.com
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index 340e6f9..7424157 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -198,4 +198,4 @@
cb.expectCallback<OnUnavailable>() { nr.getNetworkSpecifier() == specifier }
mCm.unregisterNetworkProvider(provider)
}
-}
\ No newline at end of file
+}
diff --git a/tests/cts/net/jni/Android.bp b/tests/cts/net/jni/Android.bp
index 13f38d7..8f0d78f 100644
--- a/tests/cts/net/jni/Android.bp
+++ b/tests/cts/net/jni/Android.bp
@@ -16,35 +16,14 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_library_shared {
- name: "libnativedns_jni",
+cc_defaults {
+ name: "net_jni_defaults",
- srcs: ["NativeDnsJni.c"],
- sdk_version: "current",
-
- shared_libs: [
- "libnativehelper_compat_libc++",
- "liblog",
- ],
- stl: "libc++_static",
-
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- ],
-
-}
-
-cc_library_shared {
- name: "libnativemultinetwork_jni",
-
- srcs: ["NativeMultinetworkJni.cpp"],
- sdk_version: "current",
cflags: [
"-Wall",
"-Werror",
"-Wno-format",
+ "-Wno-unused-parameter",
],
shared_libs: [
"libandroid",
@@ -52,4 +31,19 @@
"liblog",
],
stl: "libc++_static",
+ // To be compatible with Q devices, the min_sdk_version must be 29.
+ sdk_version: "current",
+ min_sdk_version: "29",
+}
+
+cc_library_shared {
+ name: "libnativedns_jni",
+ defaults: ["net_jni_defaults"],
+ srcs: ["NativeDnsJni.c"],
+}
+
+cc_library_shared {
+ name: "libnativemultinetwork_jni",
+ defaults: ["net_jni_defaults"],
+ srcs: ["NativeMultinetworkJni.cpp"],
}
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 5e9af8e..434e529 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -24,6 +24,8 @@
"liblog",
"libutils",
],
+ // To be compatible with Q devices, the min_sdk_version must be 29.
+ min_sdk_version: "29",
}
cc_test {
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 8023aa0..33f704f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -49,11 +49,16 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TetheringRequest;
import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
import static android.net.cts.util.CtsNetUtils.TEST_HOST;
import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
+import static android.net.cts.util.CtsTetheringUtils.StartTetheringCallback;
+import static android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
+import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
import static android.system.OsConstants.AF_INET;
@@ -90,6 +95,7 @@
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivitySettingsManager;
import android.net.InetAddresses;
import android.net.IpSecManager;
import android.net.IpSecManager.UdpEncapsulationSocket;
@@ -106,6 +112,7 @@
import android.net.SocketKeepalive;
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
+import android.net.TetheringManager;
import android.net.cts.util.CtsNetUtils;
import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
@@ -220,6 +227,9 @@
private static final LinkAddress TEST_LINKADDR = new LinkAddress(
InetAddresses.parseNumericAddress("2001:db8::8"), 64);
+ private static final int AIRPLANE_MODE_OFF = 0;
+ private static final int AIRPLANE_MODE_ON = 1;
+
private Context mContext;
private Instrumentation mInstrumentation;
private ConnectivityManager mCm;
@@ -229,6 +239,7 @@
private final ArraySet<Integer> mNetworkTypes = new ArraySet<>();
private UiAutomation mUiAutomation;
private CtsNetUtils mCtsNetUtils;
+ private TetheringManager mTm;
// Used for cleanup purposes.
private final List<Range<Integer>> mVpnRequiredUidRanges = new ArrayList<>();
@@ -242,6 +253,7 @@
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mPackageManager = mContext.getPackageManager();
mCtsNetUtils = new CtsNetUtils(mContext);
+ mTm = mContext.getSystemService(TetheringManager.class);
if (DevSdkIgnoreRuleKt.isDevSdkInRange(null /* minExclusive */,
Build.VERSION_CODES.R /* maxInclusive */)) {
@@ -1922,4 +1934,76 @@
assertThrows(SecurityException.class, () -> mCm.setGlobalProxy(
ProxyInfo.buildDirectProxy("example.com" /* host */, 8080 /* port */)));
}
+
+ @Test
+ public void testFactoryResetWithoutPermission() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ assertThrows(SecurityException.class, () -> mCm.factoryReset());
+ }
+
+ @Test
+ public void testFactoryReset() throws Exception {
+ assumeTrue(TestUtils.shouldTestSApis());
+
+ // Store current settings.
+ final int curAvoidBadWifi =
+ ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext);
+ final int curPrivateDnsMode = ConnectivitySettingsManager.getPrivateDnsMode(mContext);
+
+ final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback();
+ try {
+ mTm.registerTetheringEventCallback(c -> c.run() /* executor */, tetherEventCallback);
+ // Adopt for NETWORK_SETTINGS permission.
+ mUiAutomation.adoptShellPermissionIdentity();
+ // start tethering
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ startWifiTethering(tetherEventCallback);
+ // Update setting to verify the behavior.
+ mCm.setAirplaneMode(true);
+ ConnectivitySettingsManager.setPrivateDnsMode(mContext,
+ ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF);
+ ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext,
+ ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE);
+ assertEquals(AIRPLANE_MODE_ON, Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON));
+ // Verify factoryReset
+ mCm.factoryReset();
+ verifySettings(AIRPLANE_MODE_OFF,
+ ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC,
+ ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_PROMPT);
+
+ tetherEventCallback.expectNoTetheringActive();
+ } finally {
+ // Restore settings.
+ mCm.setAirplaneMode(false);
+ ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext, curAvoidBadWifi);
+ ConnectivitySettingsManager.setPrivateDnsMode(mContext, curPrivateDnsMode);
+ mTm.unregisterTetheringEventCallback(tetherEventCallback);
+ mTm.stopAllTethering();
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private void verifySettings(int expectedAirplaneMode, int expectedPrivateDnsMode,
+ int expectedAvoidBadWifi) throws Exception {
+ assertEquals(expectedAirplaneMode, Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON));
+ assertEquals(expectedPrivateDnsMode,
+ ConnectivitySettingsManager.getPrivateDnsMode(mContext));
+ assertEquals(expectedAvoidBadWifi,
+ ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext));
+ }
+
+ private void startWifiTethering(final TestTetheringEventCallback callback) throws Exception {
+ if (!isWifiTetheringSupported(mContext, callback)) return;
+
+ final List<String> wifiRegexs =
+ callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
+ final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
+ .setShouldShowEntitlementUi(false).build();
+ mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
+ startTetheringCallback.verifyTetheringStarted();
+ callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 8c35b97..63863da 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -30,6 +30,7 @@
import static junit.framework.Assert.fail;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -418,6 +419,21 @@
}
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testGetCapabilities() {
+ final int[] netCapabilities = new int[] {
+ NET_CAPABILITY_INTERNET,
+ NET_CAPABILITY_NOT_ROAMING };
+ final NetworkCapabilities.Builder builder = NetworkCapabilities.Builder
+ .withoutDefaultCapabilities();
+ for (int capability : netCapabilities) builder.addCapability(capability);
+ final NetworkRequest nr = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .setCapabilities(builder.build())
+ .build();
+ assertArrayEquals(netCapabilities, nr.getCapabilities());
+ }
+
@Test
public void testBuildRequestFromExistingRequestWithBuilder() {
assumeTrue(TestUtils.shouldTestSApis());
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index c95dc28..c220326 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -22,7 +22,7 @@
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -35,6 +35,7 @@
import android.content.pm.PackageManager;
import android.net.Network;
import android.net.TetheredClient;
+import android.net.TetheringInterface;
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringEventCallback;
import android.net.TetheringManager.TetheringInterfaceRegexps;
@@ -51,6 +52,7 @@
import java.util.Collection;
import java.util.List;
+import java.util.Set;
public final class CtsTetheringUtils {
private TetheringManager mTm;
@@ -116,23 +118,38 @@
}
}
- public static boolean isIfaceMatch(final List<String> ifaceRegexs, final List<String> ifaces) {
- return isIfaceMatch(ifaceRegexs.toArray(new String[0]), ifaces);
- }
-
- public static boolean isIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) {
+ private static boolean isRegexMatch(final String[] ifaceRegexs, String iface) {
if (ifaceRegexs == null) fail("ifaceRegexs should not be null");
+ for (String regex : ifaceRegexs) {
+ if (iface.matches(regex)) return true;
+ }
+
+ return false;
+ }
+
+ public static boolean isAnyIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) {
if (ifaces == null) return false;
for (String s : ifaces) {
- for (String regex : ifaceRegexs) {
- if (s.matches(regex)) {
- return true;
- }
+ if (isRegexMatch(ifaceRegexs, s)) return true;
+ }
+
+ return false;
+ }
+
+ private static TetheringInterface getFirstMatchingTetheringInterface(final List<String> regexs,
+ final int type, final Set<TetheringInterface> ifaces) {
+ if (ifaces == null || regexs == null) return null;
+
+ final String[] regexArray = regexs.toArray(new String[0]);
+ for (TetheringInterface iface : ifaces) {
+ if (isRegexMatch(regexArray, iface.getInterface()) && type == iface.getType()) {
+ return iface;
}
}
- return false;
+
+ return null;
}
// Must poll the callback before looking at the member.
@@ -171,6 +188,8 @@
private TetheringInterfaceRegexps mTetherableRegex;
private List<String> mTetherableIfaces;
private List<String> mTetheredIfaces;
+ private String mErrorIface;
+ private int mErrorCode;
@Override
public void onTetheringSupported(boolean supported) {
@@ -191,17 +210,41 @@
@Override
public void onTetherableInterfacesChanged(List<String> interfaces) {
mTetherableIfaces = interfaces;
+ }
+ // Call the interface default implementation, which will call
+ // onTetherableInterfacesChanged(List<String>). This ensures that the default implementation
+ // of the new callback method calls the old callback method and avoids the need to convert
+ // Set<TetheringInterface> to List<String> in this code.
+ @Override
+ public void onTetherableInterfacesChanged(Set<TetheringInterface> interfaces) {
+ TetheringEventCallback.super.onTetherableInterfacesChanged(interfaces);
+ assertHasAllTetheringInterfaces(interfaces, mTetherableIfaces);
mHistory.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0));
}
@Override
public void onTetheredInterfacesChanged(List<String> interfaces) {
mTetheredIfaces = interfaces;
+ }
+
+ @Override
+ public void onTetheredInterfacesChanged(Set<TetheringInterface> interfaces) {
+ TetheringEventCallback.super.onTetheredInterfacesChanged(interfaces);
+ assertHasAllTetheringInterfaces(interfaces, mTetheredIfaces);
mHistory.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0));
}
@Override
public void onError(String ifName, int error) {
+ mErrorIface = ifName;
+ mErrorCode = error;
+ }
+
+ @Override
+ public void onError(TetheringInterface ifName, int error) {
+ TetheringEventCallback.super.onError(ifName, error);
+ assertEquals(ifName.getInterface(), mErrorIface);
+ assertEquals(error, mErrorCode);
mHistory.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error));
}
@@ -215,30 +258,66 @@
mHistory.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0));
}
- public void expectTetherableInterfacesChanged(@NonNull List<String> regexs) {
+ private void assertHasAllTetheringInterfaces(Set<TetheringInterface> tetheringIfaces,
+ List<String> ifaces) {
+ // This does not check that the interfaces are the same. This checks that the
+ // List<String> has all the interface names contained by the Set<TetheringInterface>.
+ assertEquals(tetheringIfaces.size(), ifaces.size());
+ for (TetheringInterface tether : tetheringIfaces) {
+ assertTrue("iface " + tether.getInterface()
+ + " seen by new callback but not old callback",
+ ifaces.contains(tether.getInterface()));
+ }
+ }
+
+ public void expectTetherableInterfacesChanged(@NonNull final List<String> regexs,
+ final int type) {
assertNotNull("No expected tetherable ifaces callback", mCurrent.poll(TIMEOUT_MS,
(cv) -> {
if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) return false;
- final List<String> interfaces = (List<String>) cv.callbackParam;
- return isIfaceMatch(regexs, interfaces);
+ final Set<TetheringInterface> interfaces =
+ (Set<TetheringInterface>) cv.callbackParam;
+ return getFirstMatchingTetheringInterface(regexs, type, interfaces) != null;
}));
}
- public void expectTetheredInterfacesChanged(@NonNull List<String> regexs) {
- assertNotNull("No expected tethered ifaces callback", mCurrent.poll(TIMEOUT_MS,
- (cv) -> {
- if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false;
+ public void expectNoTetheringActive() {
+ assertNotNull("At least one tethering type unexpectedly active",
+ mCurrent.poll(TIMEOUT_MS, (cv) -> {
+ if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) return false;
- final List<String> interfaces = (List<String>) cv.callbackParam;
+ return ((Set<TetheringInterface>) cv.callbackParam).isEmpty();
+ }));
+ }
- // Null regexs means no active tethering.
- if (regexs == null) return interfaces.isEmpty();
+ public TetheringInterface expectTetheredInterfacesChanged(
+ @NonNull final List<String> regexs, final int type) {
+ while (true) {
+ final CallbackValue cv = mCurrent.poll(TIMEOUT_MS, c -> true);
+ if (cv == null) {
+ fail("No expected tethered ifaces callback, expected type: " + type);
+ }
- return isIfaceMatch(regexs, interfaces);
- }));
+ if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) continue;
+
+ final Set<TetheringInterface> interfaces =
+ (Set<TetheringInterface>) cv.callbackParam;
+
+ final TetheringInterface iface =
+ getFirstMatchingTetheringInterface(regexs, type, interfaces);
+
+ if (iface != null) return iface;
+ }
}
public void expectCallbackStarted() {
+ // This method uses its own readhead because it just check whether last tethering status
+ // is updated after TetheringEventCallback get registered but do not check content
+ // of received callbacks. Using shared readhead (mCurrent) only when the callbacks the
+ // method polled is also not necessary for other methods which using shared readhead.
+ // All of methods using mCurrent is order mattered.
+ final ArrayTrackRecord<CallbackValue>.ReadHead history =
+ mHistory.newReadHead();
int receivedBitMap = 0;
// The each bit represent a type from CallbackType.ON_*.
// Expect all of callbacks except for ON_ERROR.
@@ -246,7 +325,7 @@
// Receive ON_ERROR on started callback is not matter. It just means tethering is
// failed last time, should able to continue the test this time.
while ((receivedBitMap & expectedBitMap) != expectedBitMap) {
- final CallbackValue cv = mCurrent.poll(TIMEOUT_MS, c -> true);
+ final CallbackValue cv = history.poll(TIMEOUT_MS, c -> true);
if (cv == null) {
fail("No expected callbacks, " + "expected bitmap: "
+ expectedBitMap + ", actual: " + receivedBitMap);
@@ -269,14 +348,14 @@
}));
}
- public void expectErrorOrTethered(final String iface) {
+ public void expectErrorOrTethered(final TetheringInterface iface) {
assertNotNull("No expected callback", mCurrent.poll(TIMEOUT_MS, (cv) -> {
if (cv.callbackType == CallbackType.ON_ERROR
- && iface.equals((String) cv.callbackParam)) {
+ && iface.equals((TetheringInterface) cv.callbackParam)) {
return true;
}
if (cv.callbackType == CallbackType.ON_TETHERED_IFACES
- && ((List<String>) cv.callbackParam).contains(iface)) {
+ && ((Set<TetheringInterface>) cv.callbackParam).contains(iface)) {
return true;
}
@@ -309,33 +388,12 @@
assumeTetheringSupported();
assumeTrue(!getTetheringInterfaceRegexps().getTetherableWifiRegexs().isEmpty());
-
- final PackageManager pm = ctx.getPackageManager();
- assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_WIFI));
-
- WifiManager wm = ctx.getSystemService(WifiManager.class);
- // Wifi feature flags only work when wifi is on.
- final boolean previousWifiEnabledState = wm.isWifiEnabled();
- try {
- if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi enable");
- waitForWifiEnabled(ctx);
- assumeTrue(wm.isPortableHotspotSupported());
- } finally {
- if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi disable");
- }
+ assumeTrue(isPortableHotspotSupported(ctx));
}
public TetheringInterfaceRegexps getTetheringInterfaceRegexps() {
return mTetherableRegex;
}
-
- public List<String> getTetherableInterfaces() {
- return mTetherableIfaces;
- }
-
- public List<String> getTetheredInterfaces() {
- return mTetheredIfaces;
- }
}
private static void waitForWifiEnabled(final Context ctx) throws Exception {
@@ -382,14 +440,31 @@
return callback.getTetheringInterfaceRegexps().getTetherableWifiRegexs();
}
- public static boolean isWifiTetheringSupported(final TestTetheringEventCallback callback) {
- return !getWifiTetherableInterfaceRegexps(callback).isEmpty();
+ public static boolean isWifiTetheringSupported(final Context ctx,
+ final TestTetheringEventCallback callback) throws Exception {
+ return !getWifiTetherableInterfaceRegexps(callback).isEmpty()
+ && isPortableHotspotSupported(ctx);
}
- public void startWifiTethering(final TestTetheringEventCallback callback)
+ /* Returns if wifi supports hotspot. */
+ private static boolean isPortableHotspotSupported(final Context ctx) throws Exception {
+ final PackageManager pm = ctx.getPackageManager();
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_WIFI)) return false;
+ final WifiManager wm = ctx.getSystemService(WifiManager.class);
+ // Wifi feature flags only work when wifi is on.
+ final boolean previousWifiEnabledState = wm.isWifiEnabled();
+ try {
+ if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi enable");
+ waitForWifiEnabled(ctx);
+ return wm.isPortableHotspotSupported();
+ } finally {
+ if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi disable");
+ }
+ }
+
+ public TetheringInterface startWifiTethering(final TestTetheringEventCallback callback)
throws InterruptedException {
final List<String> wifiRegexs = getWifiTetherableInterfaceRegexps(callback);
- assertFalse(isIfaceMatch(wifiRegexs, callback.getTetheredInterfaces()));
final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
@@ -397,11 +472,14 @@
mTm.startTethering(request, c -> c.run() /* executor */, startTetheringCallback);
startTetheringCallback.verifyTetheringStarted();
- callback.expectTetheredInterfacesChanged(wifiRegexs);
+ final TetheringInterface iface =
+ callback.expectTetheredInterfacesChanged(wifiRegexs, TETHERING_WIFI);
callback.expectOneOfOffloadStatusChanged(
TETHER_HARDWARE_OFFLOAD_STARTED,
TETHER_HARDWARE_OFFLOAD_FAILED);
+
+ return iface;
}
private static class StopSoftApCallback implements SoftApCallback {
@@ -441,7 +519,7 @@
public void stopWifiTethering(final TestTetheringEventCallback callback) {
mTm.stopTethering(TETHERING_WIFI);
expectSoftApDisabled();
- callback.expectTetheredInterfacesChanged(null);
+ callback.expectNoTetheringActive();
callback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
}
}
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 71a81ff..0a5e506 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -26,8 +26,7 @@
import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
-import static android.net.cts.util.CtsTetheringUtils.isIfaceMatch;
-import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
+import static android.net.cts.util.CtsTetheringUtils.isAnyIfaceMatch;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -48,6 +47,7 @@
import android.net.LinkAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.TetheringInterface;
import android.net.TetheringManager;
import android.net.TetheringManager.OnTetheringEntitlementResultListener;
import android.net.TetheringManager.TetheringInterfaceRegexps;
@@ -190,13 +190,13 @@
}
private boolean isIfaceActive(final String[] ifaceRegexs, final TetherState state) {
- return isIfaceMatch(ifaceRegexs, state.mActive);
+ return isAnyIfaceMatch(ifaceRegexs, state.mActive);
}
private void assertNoErroredIfaces(final TetherState state, final String[] ifaceRegexs) {
if (state == null || state.mErrored == null) return;
- if (isIfaceMatch(ifaceRegexs, state.mErrored)) {
+ if (isAnyIfaceMatch(ifaceRegexs, state.mErrored)) {
fail("Found failed tethering interfaces: " + Arrays.toString(state.mErrored.toArray()));
}
}
@@ -256,12 +256,13 @@
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ tetherEventCallback.expectNoTetheringActive();
- mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
+ final TetheringInterface tetheredIface =
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
- final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
- assertEquals(1, tetheredIfaces.size());
- final String wifiTetheringIface = tetheredIfaces.get(0);
+ assertNotNull(tetheredIface);
+ final String wifiTetheringIface = tetheredIface.getInterface();
mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
@@ -272,7 +273,8 @@
if (ret == TETHER_ERROR_NO_ERROR) {
// If calling #tether successful, there is a callback to tell the result of
// tethering setup.
- tetherEventCallback.expectErrorOrTethered(wifiTetheringIface);
+ tetherEventCallback.expectErrorOrTethered(
+ new TetheringInterface(TETHERING_WIFI, wifiTetheringIface));
}
} finally {
mTM.untether(wifiTetheringIface);
@@ -319,7 +321,7 @@
mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
mTM.stopAllTethering();
- tetherEventCallback.expectTetheredInterfacesChanged(null);
+ tetherEventCallback.expectNoTetheringActive();
} finally {
mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
}
@@ -417,6 +419,7 @@
try {
tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ tetherEventCallback.expectNoTetheringActive();
previousWifiEnabledState = mWm.isWifiEnabled();
if (previousWifiEnabledState) {
diff --git a/tests/integration/src/android/net/TestNetworkStackClient.kt b/tests/integration/src/android/net/TestNetworkStackClient.kt
index 01eb514..61ef5bd 100644
--- a/tests/integration/src/android/net/TestNetworkStackClient.kt
+++ b/tests/integration/src/android/net/TestNetworkStackClient.kt
@@ -19,6 +19,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.net.networkstack.NetworkStackClientBase
import android.os.IBinder
import com.android.server.net.integrationtests.TestNetworkStackService
import org.mockito.Mockito.any
@@ -29,28 +30,22 @@
const val TEST_ACTION_SUFFIX = ".Test"
-class TestNetworkStackClient(context: Context) : NetworkStackClient(TestDependencies(context)) {
+class TestNetworkStackClient(private val context: Context) : NetworkStackClientBase() {
// TODO: consider switching to TrackRecord for more expressive checks
private val lastCallbacks = HashMap<Network, INetworkMonitorCallbacks>()
+ private val moduleConnector = ConnectivityModuleConnector { _, action, _, _ ->
+ val intent = Intent(action)
+ val serviceName = TestNetworkStackService::class.qualifiedName
+ ?: fail("TestNetworkStackService name not found")
+ intent.component = ComponentName(context.packageName, serviceName)
+ return@ConnectivityModuleConnector intent
+ }.also { it.init(context) }
- private class TestDependencies(private val context: Context) : Dependencies {
- override fun addToServiceManager(service: IBinder) = Unit
- override fun checkCallerUid() = Unit
-
- override fun getConnectivityModuleConnector(): ConnectivityModuleConnector {
- return ConnectivityModuleConnector { _, _, _, inSystemProcess ->
- getNetworkStackIntent(inSystemProcess)
- }.also { it.init(context) }
- }
-
- private fun getNetworkStackIntent(inSystemProcess: Boolean): Intent? {
- // Simulate out-of-system-process config: in-process service not found (null intent)
- if (inSystemProcess) return null
- val intent = Intent(INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX)
- val serviceName = TestNetworkStackService::class.qualifiedName
- ?: fail("TestNetworkStackService name not found")
- intent.component = ComponentName(context.packageName, serviceName)
- return intent
+ fun start() {
+ moduleConnector.startModuleService(
+ INetworkStackConnector::class.qualifiedName + TEST_ACTION_SUFFIX,
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) { connector ->
+ onNetworkStackConnected(INetworkStackConnector.Stub.asInterface(connector))
}
}
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 b6e4274..e039ef0 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -157,7 +157,6 @@
doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString())
networkStackClient = TestNetworkStackClient(realContext)
- networkStackClient.init()
networkStackClient.start()
service = TestConnectivityService(makeDependencies())
diff --git a/tests/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
index e809550..17db179 100644
--- a/tests/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -41,6 +41,7 @@
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkProvider;
+import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.QosFilter;
import android.net.SocketKeepalive;
@@ -51,7 +52,6 @@
import android.util.Range;
import com.android.net.module.util.ArrayTrackRecord;
-import com.android.server.connectivity.ConnectivityConstants;
import com.android.testutils.HandlerUtils;
import com.android.testutils.TestableNetworkCallback;
@@ -69,7 +69,7 @@
private final ConditionVariable mDisconnected = new ConditionVariable();
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
private final AtomicBoolean mConnected = new AtomicBoolean(false);
- private int mScore;
+ private NetworkScore mScore;
private NetworkAgent mNetworkAgent;
private int mStartKeepaliveError = SocketKeepalive.ERROR_UNSUPPORTED;
private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
@@ -90,23 +90,23 @@
mNetworkCapabilities.addTransportType(transport);
switch (transport) {
case TRANSPORT_ETHERNET:
- mScore = 70;
+ mScore = new NetworkScore.Builder().setLegacyInt(70).build();
break;
case TRANSPORT_WIFI:
- mScore = 60;
+ mScore = new NetworkScore.Builder().setLegacyInt(60).build();
break;
case TRANSPORT_CELLULAR:
- mScore = 50;
+ mScore = new NetworkScore.Builder().setLegacyInt(50).build();
break;
case TRANSPORT_WIFI_AWARE:
- mScore = 20;
+ mScore = new NetworkScore.Builder().setLegacyInt(20).build();
break;
case TRANSPORT_VPN:
mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_VPN);
// VPNs deduce the SUSPENDED capability from their underlying networks and there
// is no public API to let VPN services set it.
mNetworkCapabilities.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
- mScore = ConnectivityConstants.VPN_DEFAULT_SCORE;
+ mScore = new NetworkScore.Builder().setLegacyInt(101).build();
break;
default:
throw new UnsupportedOperationException("unimplemented network type");
@@ -199,12 +199,23 @@
}
}
+ public void setScore(@NonNull final NetworkScore score) {
+ mScore = score;
+ mNetworkAgent.sendNetworkScore(score);
+ }
+
public void adjustScore(int change) {
- mScore += change;
+ final int newLegacyScore = mScore.getLegacyInt() + change;
+ final NetworkScore.Builder builder = new NetworkScore.Builder()
+ .setLegacyInt(newLegacyScore);
+ if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI) && newLegacyScore < 50) {
+ builder.setExiting(true);
+ }
+ mScore = builder.build();
mNetworkAgent.sendNetworkScore(mScore);
}
- public int getScore() {
+ public NetworkScore getScore() {
return mScore;
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index fcf3754..7d6f95f 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -104,6 +104,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
+import static android.net.NetworkScore.KEEP_CONNECTED_FOR_HANDOVER;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
@@ -228,7 +229,6 @@
import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
-import android.net.NetworkStackClient;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTestResultParcelable;
import android.net.OemNetworkPreferences;
@@ -248,6 +248,7 @@
import android.net.VpnManager;
import android.net.VpnTransportInfo;
import android.net.metrics.IpConnectivityLog;
+import android.net.networkstack.NetworkStackClientBase;
import android.net.resolv.aidl.Nat64PrefixEventParcel;
import android.net.resolv.aidl.PrivateDnsValidationEventParcel;
import android.net.shared.NetworkMonitorUtils;
@@ -299,8 +300,9 @@
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.ArrayTrackRecord;
+import com.android.net.module.util.CollectionUtils;
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
-import com.android.server.connectivity.ConnectivityConstants;
+import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
@@ -415,6 +417,7 @@
private static final String VPN_IFNAME = "tun10042";
private static final String TEST_PACKAGE_NAME = "com.android.test.package";
private static final int TEST_PACKAGE_UID = 123;
+ private static final int TEST_PACKAGE_UID2 = 321;
private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
private static final String INTERFACE_NAME = "interface";
@@ -461,7 +464,7 @@
@Mock NetworkStatsManager mStatsManager;
@Mock IDnsResolver mMockDnsResolver;
@Mock INetd mMockNetd;
- @Mock NetworkStackClient mNetworkStack;
+ @Mock NetworkStackClientBase mNetworkStack;
@Mock PackageManager mPackageManager;
@Mock UserManager mUserManager;
@Mock NotificationManager mNotificationManager;
@@ -1026,8 +1029,6 @@
* operations have been processed and test for them.
*/
private static class MockNetworkFactory extends NetworkFactory {
- private final ConditionVariable mNetworkStartedCV = new ConditionVariable();
- private final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
static class RequestEntry {
@@ -1039,11 +1040,8 @@
}
static final class Add extends RequestEntry {
- public final int factorySerialNumber;
-
- Add(@NonNull final NetworkRequest request, final int factorySerialNumber) {
+ Add(@NonNull final NetworkRequest request) {
super(request);
- this.factorySerialNumber = factorySerialNumber;
}
}
@@ -1052,6 +1050,11 @@
super(request);
}
}
+
+ @Override
+ public String toString() {
+ return "RequestEntry [ " + getClass().getName() + " : " + request + " ]";
+ }
}
// History of received requests adds and removes.
@@ -1063,7 +1066,6 @@
return obj;
}
-
public RequestEntry.Add expectRequestAdd() {
return failIfNull((RequestEntry.Add) mRequestHistory.poll(TIMEOUT_MS,
it -> it instanceof RequestEntry.Add), "Expected request add");
@@ -1103,40 +1105,28 @@
protected void startNetwork() {
mNetworkStarted.set(true);
- mNetworkStartedCV.open();
}
protected void stopNetwork() {
mNetworkStarted.set(false);
- mNetworkStoppedCV.open();
}
public boolean getMyStartRequested() {
return mNetworkStarted.get();
}
- public ConditionVariable getNetworkStartedCV() {
- mNetworkStartedCV.close();
- return mNetworkStartedCV;
- }
-
- public ConditionVariable getNetworkStoppedCV() {
- mNetworkStoppedCV.close();
- return mNetworkStoppedCV;
- }
@Override
- protected void handleAddRequest(NetworkRequest request, int score,
- int factorySerialNumber) {
+ protected void needNetworkFor(NetworkRequest request) {
mNetworkRequests.put(request.requestId, request);
- super.handleAddRequest(request, score, factorySerialNumber);
- mRequestHistory.add(new RequestEntry.Add(request, factorySerialNumber));
+ super.needNetworkFor(request);
+ mRequestHistory.add(new RequestEntry.Add(request));
}
@Override
- protected void handleRemoveRequest(NetworkRequest request) {
+ protected void releaseNetworkFor(NetworkRequest request) {
mNetworkRequests.remove(request.requestId);
- super.handleRemoveRequest(request);
+ super.releaseNetworkFor(request);
mRequestHistory.add(new RequestEntry.Remove(request));
}
@@ -1170,6 +1160,10 @@
return ranges;
}
+ private Set<UidRange> uidRangesForUids(Collection<Integer> uids) {
+ return uidRangesForUids(CollectionUtils.toIntArray(uids));
+ }
+
private static Looper startHandlerThreadAndReturnLooper() {
final HandlerThread handlerThread = new HandlerThread("MockVpnThread");
handlerThread.start();
@@ -1536,6 +1530,8 @@
private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER, "",
UserInfo.FLAG_PRIMARY);
private static final UserHandle PRIMARY_USER_HANDLE = new UserHandle(PRIMARY_USER);
+ private static final int SECONDARY_USER = 10;
+ private static final UserHandle SECONDARY_USER_HANDLE = new UserHandle(SECONDARY_USER);
private static final int RESTRICTED_USER = 1;
private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "",
@@ -3077,8 +3073,9 @@
}
NetworkCapabilities filter = new NetworkCapabilities();
+ filter.addTransportType(TRANSPORT_CELLULAR);
filter.addCapability(capability);
- // Add NOT_VCN_MANAGED capability into filter unconditionally since some request will add
+ // Add NOT_VCN_MANAGED capability into filter unconditionally since some requests will add
// NOT_VCN_MANAGED automatically but not for NetworkCapabilities,
// see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details.
filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
@@ -3086,47 +3083,67 @@
handlerThread.start();
final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
mServiceContext, "testFactory", filter, mCsHandlerThread);
- testFactory.setScoreFilter(40);
- ConditionVariable cv = testFactory.getNetworkStartedCV();
+ testFactory.setScoreFilter(45);
testFactory.register();
- testFactory.expectRequestAdd();
- testFactory.assertRequestCountEquals(1);
- int expectedRequestCount = 1;
- NetworkCallback networkCallback = null;
- // For non-INTERNET capabilities we cannot rely on the default request being present, so
- // add one.
+
+ final NetworkCallback networkCallback;
if (capability != NET_CAPABILITY_INTERNET) {
+ // If the capability passed in argument is part of the default request, then the
+ // factory will see the default request. Otherwise the filter will prevent the
+ // factory from seeing it. In that case, add a request so it can be tested.
assertFalse(testFactory.getMyStartRequested());
NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
networkCallback = new NetworkCallback();
mCm.requestNetwork(request, networkCallback);
- expectedRequestCount++;
- testFactory.expectRequestAdd();
+ } else {
+ networkCallback = null;
}
- waitFor(cv);
- testFactory.assertRequestCountEquals(expectedRequestCount);
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
assertTrue(testFactory.getMyStartRequested());
// Now bring in a higher scored network.
TestNetworkAgentWrapper testAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
- // Rather than create a validated network which complicates things by registering it's
- // own NetworkRequest during startup, just bump up the score to cancel out the
- // unvalidated penalty.
- testAgent.adjustScore(40);
- cv = testFactory.getNetworkStoppedCV();
-
- // When testAgent connects, ConnectivityService will re-send us all current requests with
- // the new score. There are expectedRequestCount such requests, and we must wait for all of
- // them.
- testAgent.connect(false);
+ // When testAgent connects, because of its score (50 legacy int / cell transport)
+ // it will beat or equal the testFactory's offer, so the request will be removed.
+ // Note the agent as validated only if the capability is INTERNET, as it's the only case
+ // where it makes sense.
+ testAgent.connect(NET_CAPABILITY_INTERNET == capability /* validated */);
testAgent.addCapability(capability);
- waitFor(cv);
- testFactory.expectRequestAdds(expectedRequestCount);
- testFactory.assertRequestCountEquals(expectedRequestCount);
+ testFactory.expectRequestRemove();
+ testFactory.assertRequestCountEquals(0);
assertFalse(testFactory.getMyStartRequested());
+ // Add a request and make sure it's not sent to the factory, because the agent
+ // is satisfying it better.
+ final NetworkCallback cb = new ConnectivityManager.NetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder().addCapability(capability).build(), cb);
+ expectNoRequestChanged(testFactory);
+ testFactory.assertRequestCountEquals(0);
+ assertFalse(testFactory.getMyStartRequested());
+
+ // If using legacy scores, make the test agent weak enough to have the exact same score as
+ // the factory (50 for cell - 5 adjustment). Make sure the factory doesn't see the request.
+ // If not using legacy score, this is a no-op and the "same score removes request" behavior
+ // has already been tested above.
+ testAgent.adjustScore(-5);
+ expectNoRequestChanged(testFactory);
+ assertFalse(testFactory.getMyStartRequested());
+
+ // Make the test agent weak enough that the factory will see the two requests (the one that
+ // was just sent, and either the default one or the one sent at the top of this test if
+ // the default won't be seen).
+ testAgent.setScore(new NetworkScore.Builder().setLegacyInt(2).setExiting(true).build());
+ testFactory.expectRequestAdds(2);
+ testFactory.assertRequestCountEquals(2);
+ assertTrue(testFactory.getMyStartRequested());
+
+ // Now unregister and make sure the request is removed.
+ mCm.unregisterNetworkCallback(cb);
+ testFactory.expectRequestRemove();
+
// Bring in a bunch of requests.
- assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ assertEquals(1, testFactory.getMyRequestCount());
ConnectivityManager.NetworkCallback[] networkCallbacks =
new ConnectivityManager.NetworkCallback[10];
for (int i = 0; i< networkCallbacks.length; i++) {
@@ -3136,24 +3153,28 @@
mCm.requestNetwork(builder.build(), networkCallbacks[i]);
}
testFactory.expectRequestAdds(10);
- testFactory.assertRequestCountEquals(10 + expectedRequestCount);
- assertFalse(testFactory.getMyStartRequested());
+ testFactory.assertRequestCountEquals(11); // +1 for the default/test specific request
+ assertTrue(testFactory.getMyStartRequested());
// Remove the requests.
for (int i = 0; i < networkCallbacks.length; i++) {
mCm.unregisterNetworkCallback(networkCallbacks[i]);
}
testFactory.expectRequestRemoves(10);
- testFactory.assertRequestCountEquals(expectedRequestCount);
+ testFactory.assertRequestCountEquals(1);
+ assertTrue(testFactory.getMyStartRequested());
+
+ // Adjust the agent score up again. Expect the request to be withdrawn.
+ testAgent.setScore(new NetworkScore.Builder().setLegacyInt(50).build());
+ testFactory.expectRequestRemove();
+ testFactory.assertRequestCountEquals(0);
assertFalse(testFactory.getMyStartRequested());
// Drop the higher scored network.
- cv = testFactory.getNetworkStartedCV();
testAgent.disconnect();
- waitFor(cv);
- testFactory.expectRequestAdds(expectedRequestCount);
- testFactory.assertRequestCountEquals(expectedRequestCount);
- assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
+ assertEquals(1, testFactory.getMyRequestCount());
assertTrue(testFactory.getMyStartRequested());
testFactory.terminate();
@@ -3184,10 +3205,49 @@
// Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
}
+ @Ignore("Refactoring in progress b/184028345")
+ @Test
+ public void testRegisterIgnoringScore() throws Exception {
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(90).build());
+ mWiFiNetworkAgent.connect(true /* validated */);
+
+ // Make sure the factory sees the default network
+ final NetworkCapabilities filter = new NetworkCapabilities();
+ filter.addTransportType(TRANSPORT_CELLULAR);
+ filter.addCapability(NET_CAPABILITY_INTERNET);
+ filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+ final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
+ handlerThread.start();
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter, mCsHandlerThread);
+ testFactory.register();
+
+ final MockNetworkFactory testFactoryAll = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactoryAll", filter, mCsHandlerThread);
+ testFactoryAll.registerIgnoringScore();
+
+ // The regular test factory should not see the request, because WiFi is stronger than cell.
+ expectNoRequestChanged(testFactory);
+ // With ignoringScore though the request is seen.
+ testFactoryAll.expectRequestAdd();
+
+ // The legacy int will be ignored anyway, set the only other knob to true
+ mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(110)
+ .setTransportPrimary(true).build());
+
+ expectNoRequestChanged(testFactory); // still not seeing the request
+ expectNoRequestChanged(testFactoryAll); // still seeing the request
+
+ mWiFiNetworkAgent.disconnect();
+ }
+
@Test
public void testNetworkFactoryUnregister() throws Exception {
+ // Make sure the factory sees the default network
final NetworkCapabilities filter = new NetworkCapabilities();
- filter.clearAll();
+ filter.addCapability(NET_CAPABILITY_INTERNET);
+ filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
handlerThread.start();
@@ -4446,6 +4506,7 @@
testFactory.register();
try {
+ // Expect the factory to receive the default network request.
testFactory.expectRequestAdd();
testFactory.assertRequestCountEquals(1);
assertTrue(testFactory.getMyStartRequested());
@@ -4454,25 +4515,44 @@
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
// Score 60 - 40 penalty for not validated yet, then 60 when it validates
mWiFiNetworkAgent.connect(true);
- // Default request and mobile always on request
- testFactory.expectRequestAdds(2);
+ // The network connects with a low score, so the offer can still beat it and
+ // nothing happens. Then the network validates, and the offer with its filter score
+ // of 40 can no longer beat it and the request is removed.
+ testFactory.expectRequestRemove();
+ testFactory.assertRequestCountEquals(0);
+
assertFalse(testFactory.getMyStartRequested());
- // Turn on mobile data always on. The factory starts looking again.
+ // Turn on mobile data always on. This request will not match the wifi request, so
+ // it will be sent to the test factory whose filters allow to see it.
setAlwaysOnNetworks(true);
testFactory.expectRequestAdd();
- testFactory.assertRequestCountEquals(2);
+ testFactory.assertRequestCountEquals(1);
assertTrue(testFactory.getMyStartRequested());
// Bring up cell data and check that the factory stops looking.
assertLength(1, mCm.getAllNetworks());
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
- mCellNetworkAgent.connect(true);
- cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
- testFactory.expectRequestAdds(2); // Unvalidated and validated
- testFactory.assertRequestCountEquals(2);
- // The cell network outscores the factory filter, so start is not requested.
+ mCellNetworkAgent.connect(false);
+ cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent, false, false, false,
+ TEST_CALLBACK_TIMEOUT_MS);
+ // When cell connects, it will satisfy the "mobile always on request" right away
+ // by virtue of being the only network that can satisfy the request. However, its
+ // score is low (50 - 40 = 10) so the test factory can still hope to beat it.
+ expectNoRequestChanged(testFactory);
+
+ // Next, cell validates. This gives it a score of 50 and the test factory can't
+ // hope to beat that according to its filters. It will see the message that its
+ // offer is now unnecessary.
+ mCellNetworkAgent.setNetworkValid(true);
+ // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
+ // validated – see testPartialConnectivity.
+ mCm.reportNetworkConnectivity(mCellNetworkAgent.getNetwork(), true);
+ cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent);
+ testFactory.expectRequestRemove();
+ testFactory.assertRequestCountEquals(0);
+ // Accordingly, the factory shouldn't be started.
assertFalse(testFactory.getMyStartRequested());
// Check that cell data stays up.
@@ -4480,10 +4560,27 @@
verifyActiveNetwork(TRANSPORT_WIFI);
assertLength(2, mCm.getAllNetworks());
- // Turn off mobile data always on and expect the request to disappear...
- setAlwaysOnNetworks(false);
- testFactory.expectRequestRemove();
+ // Cell disconnects. There is still the "mobile data always on" request outstanding,
+ // and the test factory should see it now that it isn't hopelessly outscored.
+ mCellNetworkAgent.disconnect();
+ cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
+ assertLength(1, mCm.getAllNetworks());
+ testFactory.expectRequestAdd();
+ testFactory.assertRequestCountEquals(1);
+ // Reconnect cell validated, see the request disappear again. Then withdraw the
+ // mobile always on request. This will tear down cell, and there shouldn't be a
+ // blip where the test factory briefly sees the request or anything.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ assertLength(2, mCm.getAllNetworks());
+ testFactory.expectRequestRemove();
+ testFactory.assertRequestCountEquals(0);
+ setAlwaysOnNetworks(false);
+ expectNoRequestChanged(testFactory);
+ testFactory.assertRequestCountEquals(0);
+ assertFalse(testFactory.getMyStartRequested());
// ... and cell data to be torn down after nascent network timeout.
cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
mService.mNascentDelayMs + TEST_CALLBACK_TIMEOUT_MS);
@@ -4786,7 +4883,8 @@
handlerThread.start();
NetworkCapabilities filter = new NetworkCapabilities()
.addTransportType(TRANSPORT_WIFI)
- .addCapability(NET_CAPABILITY_INTERNET);
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
mServiceContext, "testFactory", filter, mCsHandlerThread);
testFactory.setScoreFilter(40);
@@ -4795,32 +4893,43 @@
testFactory.register();
testFactory.expectRequestAdd();
- // Now file the test request and expect it.
- mCm.requestNetwork(nr, networkCallback);
- final NetworkRequest newRequest = testFactory.expectRequestAdd().request;
+ try {
+ // Now file the test request and expect it.
+ mCm.requestNetwork(nr, networkCallback);
+ final NetworkRequest newRequest = testFactory.expectRequestAdd().request;
- if (preUnregister) {
- mCm.unregisterNetworkCallback(networkCallback);
+ if (preUnregister) {
+ mCm.unregisterNetworkCallback(networkCallback);
- // Simulate the factory releasing the request as unfulfillable: no-op since
- // the callback has already been unregistered (but a test that no exceptions are
- // thrown).
- testFactory.triggerUnfulfillable(newRequest);
- } else {
- // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
- testFactory.triggerUnfulfillable(newRequest);
+ // The request has been released : the factory should see it removed
+ // immediately.
+ testFactory.expectRequestRemove();
- networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
+ // Simulate the factory releasing the request as unfulfillable: no-op since
+ // the callback has already been unregistered (but a test that no exceptions are
+ // thrown).
+ testFactory.triggerUnfulfillable(newRequest);
+ } else {
+ // Simulate the factory releasing the request as unfulfillable and expect
+ // onUnavailable!
+ testFactory.triggerUnfulfillable(newRequest);
- // unregister network callback - a no-op (since already freed by the
- // on-unavailable), but should not fail or throw exceptions.
- mCm.unregisterNetworkCallback(networkCallback);
+ networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null);
+
+ // Declaring a request unfulfillable releases it automatically.
+ testFactory.expectRequestRemove();
+
+ // unregister network callback - a no-op (since already freed by the
+ // on-unavailable), but should not fail or throw exceptions.
+ mCm.unregisterNetworkCallback(networkCallback);
+
+ // The factory should not see any further removal, as this request has
+ // already been removed.
+ }
+ } finally {
+ testFactory.terminate();
+ handlerThread.quit();
}
-
- testFactory.expectRequestRemove();
-
- testFactory.terminate();
- handlerThread.quit();
}
private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
@@ -5977,7 +6086,8 @@
// called again, it does. For example, connect Ethernet, but with a low score, such that it
// does not become the default network.
mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET);
- mEthernetNetworkAgent.adjustScore(-40);
+ mEthernetNetworkAgent.setScore(
+ new NetworkScore.Builder().setLegacyInt(30).setExiting(true).build());
mEthernetNetworkAgent.connect(false);
waitForIdle();
verify(mStatsManager).notifyNetworkStatus(any(List.class),
@@ -6852,8 +6962,6 @@
callback.expectAvailableCallbacksUnvalidated(mMockVpn);
callback.assertNoCallback();
- assertTrue(mMockVpn.getAgent().getScore() > mEthernetNetworkAgent.getScore());
- assertEquals(ConnectivityConstants.VPN_DEFAULT_SCORE, mMockVpn.getAgent().getScore());
assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork());
@@ -10083,7 +10191,7 @@
mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
waitForIdle();
- final ConnectivityService.NetworkRequestInfo[] nriOutput = mService.requestsSortedById();
+ final NetworkRequestInfo[] nriOutput = mService.requestsSortedById();
assertTrue(nriOutput.length > 1);
for (int i = 0; i < nriOutput.length - 1; i++) {
@@ -10162,6 +10270,83 @@
}
}
+ @Test
+ public void testKeepConnected() throws Exception {
+ setAlwaysOnNetworks(false);
+ registerDefaultNetworkCallbacks();
+ final TestNetworkCallback allNetworksCb = new TestNetworkCallback();
+ final NetworkRequest allNetworksRequest = new NetworkRequest.Builder().clearCapabilities()
+ .build();
+ mCm.registerNetworkCallback(allNetworksRequest, allNetworksCb);
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true /* validated */);
+
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true /* validated */);
+
+ mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+ // While the default callback doesn't see the network before it's validated, the listen
+ // sees the network come up and validate later
+ allNetworksCb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
+ allNetworksCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+ allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
+ TEST_LINGER_DELAY_MS * 2);
+
+ // The cell network has disconnected (see LOST above) because it was outscored and
+ // had no requests (see setAlwaysOnNetworks(false) above)
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ final NetworkScore score = new NetworkScore.Builder().setLegacyInt(30).build();
+ mCellNetworkAgent.setScore(score);
+ mCellNetworkAgent.connect(false /* validated */);
+
+ // The cell network gets torn down right away.
+ allNetworksCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
+ TEST_NASCENT_DELAY_MS * 2);
+ allNetworksCb.assertNoCallback();
+
+ // Now create a cell network with KEEP_CONNECTED_FOR_HANDOVER and make sure it's
+ // not disconnected immediately when outscored.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ final NetworkScore scoreKeepup = new NetworkScore.Builder().setLegacyInt(30)
+ .setKeepConnectedReason(KEEP_CONNECTED_FOR_HANDOVER).build();
+ mCellNetworkAgent.setScore(scoreKeepup);
+ mCellNetworkAgent.connect(true /* validated */);
+
+ allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.assertNoCallback();
+
+ mWiFiNetworkAgent.disconnect();
+
+ allNetworksCb.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+ // Reconnect a WiFi network and make sure the cell network is still not torn down.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true /* validated */);
+
+ allNetworksCb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+
+ // Now remove the reason to keep connected and make sure the network lingers and is
+ // torn down.
+ mCellNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).build());
+ allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent,
+ TEST_NASCENT_DELAY_MS * 2);
+ allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent,
+ TEST_LINGER_DELAY_MS * 2);
+ mDefaultNetworkCallback.assertNoCallback();
+
+ mCm.unregisterNetworkCallback(allNetworksCb);
+ // mDefaultNetworkCallback will be unregistered by tearDown()
+ }
+
private class QosCallbackMockHelper {
@NonNull public final QosFilter mFilter;
@NonNull public final IQosCallback mCallback;
@@ -10366,8 +10551,7 @@
.thenReturn(hasFeature);
}
- private Range<Integer> getNriFirstUidRange(
- @NonNull final ConnectivityService.NetworkRequestInfo nri) {
+ private Range<Integer> getNriFirstUidRange(@NonNull final NetworkRequestInfo nri) {
return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next();
}
@@ -10406,7 +10590,7 @@
OEM_NETWORK_PREFERENCE_OEM_PAID;
// Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
- final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ final ArraySet<NetworkRequestInfo> nris =
mService.new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(
createDefaultOemNetworkPreferences(prefToTest));
@@ -10435,7 +10619,7 @@
OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
// Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
- final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ final ArraySet<NetworkRequestInfo> nris =
mService.new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(
createDefaultOemNetworkPreferences(prefToTest));
@@ -10461,7 +10645,7 @@
OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
// Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
- final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ final ArraySet<NetworkRequestInfo> nris =
mService.new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(
createDefaultOemNetworkPreferences(prefToTest));
@@ -10484,7 +10668,7 @@
OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
// Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
- final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ final ArraySet<NetworkRequestInfo> nris =
mService.new OemNetworkRequestFactory()
.createNrisFromOemNetworkPreferences(
createDefaultOemNetworkPreferences(prefToTest));
@@ -10517,7 +10701,7 @@
.build();
// Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
- final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ final ArraySet<NetworkRequestInfo> nris =
mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
assertNotNull(nris);
@@ -10542,7 +10726,7 @@
.build();
// Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
- final List<ConnectivityService.NetworkRequestInfo> nris =
+ final List<NetworkRequestInfo> nris =
new ArrayList<>(
mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
pref));
@@ -10574,7 +10758,7 @@
.build();
// Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
- final List<ConnectivityService.NetworkRequestInfo> nris =
+ final List<NetworkRequestInfo> nris =
new ArrayList<>(
mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(
pref));
@@ -10616,7 +10800,7 @@
.build();
// Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences()
- final ArraySet<ConnectivityService.NetworkRequestInfo> nris =
+ final ArraySet<NetworkRequestInfo> nris =
mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref);
assertEquals(expectedNumOfNris, nris.size());
@@ -10709,8 +10893,7 @@
// each time to confirm it doesn't change under test.
final int expectedDefaultNetworkRequestsSize = 2;
assertEquals(expectedDefaultNetworkRequestsSize, mService.mDefaultNetworkRequests.size());
- for (final ConnectivityService.NetworkRequestInfo defaultRequest
- : mService.mDefaultNetworkRequests) {
+ for (final NetworkRequestInfo defaultRequest : mService.mDefaultNetworkRequests) {
final Network defaultNetwork = defaultRequest.getSatisfier() == null
? null : defaultRequest.getSatisfier().network();
// If this is the default request.
@@ -11614,6 +11797,118 @@
// default callbacks will be unregistered in tearDown
}
+ @Test
+ public void testNetworkFactoryRequestsWithMultilayerRequest()
+ throws Exception {
+ // First use OEM_PAID preference to create a multi-layer request : 1. listen for
+ // unmetered, 2. request network with cap OEM_PAID, 3, request the default network for
+ // fallback.
+ @OemNetworkPreferences.OemNetworkPreference final int networkPref =
+ OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
+ setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref);
+
+ final HandlerThread handlerThread = new HandlerThread("MockFactory");
+ handlerThread.start();
+ NetworkCapabilities internetFilter = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
+ final MockNetworkFactory internetFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "internetFactory", internetFilter, mCsHandlerThread);
+ internetFactory.setScoreFilter(40);
+ internetFactory.register();
+ // Default internet request only. The unmetered request is never sent to factories (it's a
+ // LISTEN, not requestable). The 3rd (fallback) request in OEM_PAID NRI is TRACK_DEFAULT
+ // which is also not sent to factories. Finally, the OEM_PAID request doesn't match the
+ // internetFactory filter.
+ internetFactory.expectRequestAdds(1);
+ internetFactory.assertRequestCountEquals(1);
+
+ NetworkCapabilities oemPaidFilter = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_OEM_PAID)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ final MockNetworkFactory oemPaidFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "oemPaidFactory", oemPaidFilter, mCsHandlerThread);
+ oemPaidFactory.setScoreFilter(40);
+ oemPaidFactory.register();
+ oemPaidFactory.expectRequestAdd(); // Because nobody satisfies the request
+
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ // A network connected that satisfies the default internet request. For the OEM_PAID
+ // preference, this is not as good as an OEM_PAID network, so even if the score of
+ // the network is better than the factory announced, it still should try to bring up
+ // the network.
+ expectNoRequestChanged(oemPaidFactory);
+ oemPaidFactory.assertRequestCountEquals(1);
+ // The internet factory however is outscored, and should lose its requests.
+ internetFactory.expectRequestRemove();
+ internetFactory.assertRequestCountEquals(0);
+
+ final NetworkCapabilities oemPaidNc = new NetworkCapabilities();
+ oemPaidNc.addCapability(NET_CAPABILITY_OEM_PAID);
+ oemPaidNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ final TestNetworkAgentWrapper oemPaidAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
+ new LinkProperties(), oemPaidNc);
+ oemPaidAgent.connect(true);
+
+ // The oemPaidAgent has score 50/cell transport, so it beats what the oemPaidFactory can
+ // provide, therefore it loses the request.
+ oemPaidFactory.expectRequestRemove();
+ oemPaidFactory.assertRequestCountEquals(0);
+ expectNoRequestChanged(internetFactory);
+ internetFactory.assertRequestCountEquals(0);
+
+ oemPaidAgent.setScore(new NetworkScore.Builder().setLegacyInt(20).setExiting(true).build());
+ // Now the that the agent is weak, the oemPaidFactory can beat the existing network for the
+ // OEM_PAID request. The internet factory however can't beat a network that has OEM_PAID
+ // for the preference request, so it doesn't see the request.
+ oemPaidFactory.expectRequestAdd();
+ oemPaidFactory.assertRequestCountEquals(1);
+ expectNoRequestChanged(internetFactory);
+ internetFactory.assertRequestCountEquals(0);
+
+ mCellNetworkAgent.disconnect();
+ // The network satisfying the default internet request has disconnected, so the
+ // internetFactory sees the default request again. However there is a network with OEM_PAID
+ // connected, so the 2nd OEM_PAID req is already satisfied, so the oemPaidFactory doesn't
+ // care about networks that don't have OEM_PAID.
+ expectNoRequestChanged(oemPaidFactory);
+ oemPaidFactory.assertRequestCountEquals(1);
+ internetFactory.expectRequestAdd();
+ internetFactory.assertRequestCountEquals(1);
+
+ // Cell connects again, still with score 50. Back to the previous state.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ expectNoRequestChanged(oemPaidFactory);
+ oemPaidFactory.assertRequestCountEquals(1);
+ internetFactory.expectRequestRemove();
+ internetFactory.assertRequestCountEquals(0);
+
+ // Now WiFi connects and it's unmetered, but it's weaker than cell.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
+ mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).setExiting(true)
+ .build()); // Not the best Internet network, but unmetered
+ mWiFiNetworkAgent.connect(true);
+
+ // The OEM_PAID preference prefers an unmetered network to an OEM_PAID network, so
+ // the oemPaidFactory can't beat this no matter how high its score.
+ oemPaidFactory.expectRequestRemove();
+ expectNoRequestChanged(internetFactory);
+
+ mCellNetworkAgent.disconnect();
+ // Now that the best internet network (cell, with its 50 score compared to 30 for WiFi
+ // at this point), the default internet request is satisfied by a network worse than
+ // the internetFactory announced, so it gets the request. However, there is still an
+ // unmetered network, so the oemPaidNetworkFactory still can't beat this.
+ expectNoRequestChanged(oemPaidFactory);
+ internetFactory.expectRequestAdd();
+ }
+
/**
* Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order:
* NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID
@@ -11935,11 +12230,11 @@
testFactory.setScoreFilter(40);
try {
- // Register the factory and expect it will see default request, because all requests
- // are sent to all factories.
+ // Register the factory. It doesn't see the default request because its filter does
+ // not include INTERNET.
testFactory.register();
- testFactory.expectRequestAdd();
- testFactory.assertRequestCountEquals(1);
+ expectNoRequestChanged(testFactory);
+ testFactory.assertRequestCountEquals(0);
// The factory won't try to start the network since the default request doesn't
// match the filter (no INTERNET capability).
assertFalse(testFactory.getMyStartRequested());
@@ -11952,7 +12247,7 @@
bestMatchingCb, mCsHandlerThread.getThreadHandler());
bestMatchingCb.assertNoCallback();
expectNoRequestChanged(testFactory);
- testFactory.assertRequestCountEquals(1);
+ testFactory.assertRequestCountEquals(0);
assertFalse(testFactory.getMyStartRequested());
// Fire a normal mms request, verify the factory will only see the request.
@@ -11961,13 +12256,13 @@
.addCapability(NET_CAPABILITY_MMS).build();
mCm.requestNetwork(mmsRequest, mmsNetworkCallback);
testFactory.expectRequestAdd();
- testFactory.assertRequestCountEquals(2);
+ testFactory.assertRequestCountEquals(1);
assertTrue(testFactory.getMyStartRequested());
// Unregister best matching callback, verify factory see no change.
mCm.unregisterNetworkCallback(bestMatchingCb);
expectNoRequestChanged(testFactory);
- testFactory.assertRequestCountEquals(2);
+ testFactory.assertRequestCountEquals(1);
assertTrue(testFactory.getMyStartRequested());
} finally {
testFactory.terminate();
@@ -12539,4 +12834,73 @@
}
}
}
+
+ private void assertCreateNrisFromMobileDataPreferredUids(Set<Integer> uids) {
+ final Set<NetworkRequestInfo> nris =
+ mService.createNrisFromMobileDataPreferredUids(uids);
+ final NetworkRequestInfo nri = nris.iterator().next();
+ // Verify that one NRI is created with multilayer requests. Because one NRI can contain
+ // multiple uid ranges, so it only need create one NRI here.
+ assertEquals(1, nris.size());
+ assertTrue(nri.isMultilayerRequest());
+ assertEquals(nri.getUids(), uidRangesForUids(uids));
+ }
+
+ /**
+ * Test createNrisFromMobileDataPreferredUids returns correct NetworkRequestInfo.
+ */
+ @Test
+ public void testCreateNrisFromMobileDataPreferredUids() {
+ // Verify that empty uid set should not create any NRI for it.
+ final Set<NetworkRequestInfo> nrisNoUid =
+ mService.createNrisFromMobileDataPreferredUids(new ArraySet<>());
+ assertEquals(0, nrisNoUid.size());
+
+ final int uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID);
+ final int uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2);
+ final int uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID);
+ assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1));
+ assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1, uid3));
+ assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1, uid2));
+ }
+
+ private void setAndUpdateMobileDataPreferredUids(Set<Integer> uids) {
+ ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext, uids);
+ mService.updateMobileDataPreferredUids();
+ waitForIdle();
+ }
+
+ /**
+ * Test that MOBILE_DATA_PREFERRED_UIDS changes will send correct net id and uid ranges to netd.
+ */
+ @Test
+ public void testMobileDataPreferredUidsChanged() throws Exception {
+ final InOrder inorder = inOrder(mMockNetd);
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ waitForIdle();
+
+ final int cellNetId = mCellNetworkAgent.getNetwork().netId;
+ inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
+ cellNetId, INetd.PERMISSION_NONE));
+
+ // Initial mobile data preferred uids status.
+ setAndUpdateMobileDataPreferredUids(Set.of());
+ inorder.verify(mMockNetd, never()).networkAddUidRanges(anyInt(), any());
+ inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+
+ final Set<Integer> uids1 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID));
+ final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1));
+ setAndUpdateMobileDataPreferredUids(uids1);
+ inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges1);
+ inorder.verify(mMockNetd, never()).networkRemoveUidRanges(anyInt(), any());
+
+ final Set<Integer> uids2 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID),
+ PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2),
+ SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID));
+ final UidRangeParcel[] uidRanges2 = toUidRangeStableParcels(uidRangesForUids(uids2));
+ setAndUpdateMobileDataPreferredUids(uids2);
+ inorder.verify(mMockNetd, times(1)).networkRemoveUidRanges(cellNetId, uidRanges1);
+ inorder.verify(mMockNetd, times(1)).networkAddUidRanges(cellNetId, uidRanges2);
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index eb3b4df..f0d7d86 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -18,6 +18,7 @@
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
+import android.net.NetworkScore.KEEP_CONNECTED_NONE
import android.text.TextUtils
import android.util.ArraySet
import androidx.test.filters.SmallTest
@@ -55,16 +56,16 @@
if (vpn) addTransportType(NetworkCapabilities.TRANSPORT_VPN)
if (validated) addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}.build()
- return mixInScore(nc, nac)
+ return mixInScore(nc, nac, false /* avoidBadWifi */)
}
@Test
fun testGetLegacyInt() {
- val ns = FullScore(50, 0L /* policy */)
+ val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE)
assertEquals(10, ns.legacyInt) // -40 penalty for not being validated
assertEquals(50, ns.legacyIntAsValidated)
- val vpnNs = FullScore(101, 0L /* policy */).withPolicies(vpn = true)
+ val vpnNs = FullScore(101, 0L /* policy */, KEEP_CONNECTED_NONE).withPolicies(vpn = true)
assertEquals(101, vpnNs.legacyInt) // VPNs are not subject to unvalidation penalty
assertEquals(101, vpnNs.legacyIntAsValidated)
assertEquals(101, vpnNs.withPolicies(validated = true).legacyInt)
@@ -83,7 +84,7 @@
@Test
fun testToString() {
- val string = FullScore(10, 0L /* policy */)
+ val string = FullScore(10, 0L /* policy */, KEEP_CONNECTED_NONE)
.withPolicies(vpn = true, acceptUnvalidated = true).toString()
assertTrue(string.contains("Score(10"), string)
assertTrue(string.contains("ACCEPT_UNVALIDATED"), string)
@@ -107,7 +108,7 @@
@Test
fun testHasPolicy() {
- val ns = FullScore(50, 0L /* policy */)
+ val ns = FullScore(50, 0L /* policy */, KEEP_CONNECTED_NONE)
assertFalse(ns.hasPolicy(POLICY_IS_VALIDATED))
assertFalse(ns.hasPolicy(POLICY_IS_VPN))
assertFalse(ns.hasPolicy(POLICY_EVER_USER_SELECTED))
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
new file mode 100644
index 0000000..409f8c3
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/NetworkOfferTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2021 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 android.net.INetworkOfferCallback
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.NetworkScore.KEEP_CONNECTED_NONE
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+const val POLICY_NONE = 0L
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkOfferTest {
+ val mockCallback = mock(INetworkOfferCallback::class.java)
+
+ @Test
+ fun testOfferNeededUnneeded() {
+ val score = FullScore(50, POLICY_NONE, KEEP_CONNECTED_NONE)
+ val offer = NetworkOffer(score, NetworkCapabilities.Builder().build(), mockCallback,
+ 1 /* providerId */)
+ val request1 = mock(NetworkRequest::class.java)
+ val request2 = mock(NetworkRequest::class.java)
+ offer.onNetworkNeeded(request1)
+ verify(mockCallback).onNetworkNeeded(eq(request1))
+ assertTrue(offer.neededFor(request1))
+ assertFalse(offer.neededFor(request2))
+
+ offer.onNetworkNeeded(request2)
+ verify(mockCallback).onNetworkNeeded(eq(request2))
+ assertTrue(offer.neededFor(request1))
+ assertTrue(offer.neededFor(request2))
+
+ // Note that the framework never calls onNetworkNeeded multiple times with the same
+ // request without calling onNetworkUnneeded first. It would be incorrect usage and the
+ // behavior would be undefined, so there is nothing to test.
+
+ offer.onNetworkUnneeded(request1)
+ verify(mockCallback).onNetworkUnneeded(eq(request1))
+ assertFalse(offer.neededFor(request1))
+ assertTrue(offer.neededFor(request2))
+
+ offer.onNetworkUnneeded(request2)
+ verify(mockCallback).onNetworkUnneeded(eq(request2))
+ assertFalse(offer.neededFor(request1))
+ assertFalse(offer.neededFor(request2))
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 86c9116..1348c6a 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -43,7 +43,7 @@
val nais = scores.map { makeNai(true, it) }
val bestNetwork = nais[2] // The one with the top score
val someRequest = mock(NetworkRequest::class.java)
- assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
+ assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, bestNetwork))
}
@Test
@@ -52,20 +52,20 @@
makeNai(false, 60), makeNai(true, 23), makeNai(false, 68))
val bestNetwork = nais[1] // Top score that's satisfying
val someRequest = mock(NetworkRequest::class.java)
- assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais))
+ assertEquals(bestNetwork, ranker.getBestNetwork(someRequest, nais, nais[1]))
}
@Test
fun testNoMatch() {
val nais = listOf(makeNai(false, 20), makeNai(false, 50), makeNai(false, 90))
val someRequest = mock(NetworkRequest::class.java)
- assertNull(ranker.getBestNetwork(someRequest, nais))
+ assertNull(ranker.getBestNetwork(someRequest, nais, null))
}
@Test
fun testEmpty() {
val someRequest = mock(NetworkRequest::class.java)
- assertNull(ranker.getBestNetwork(someRequest, emptyList()))
+ assertNull(ranker.getBestNetwork(someRequest, emptyList(), null))
}
// Make sure the ranker is "stable" (as in stable sort), that is, it always returns the FIRST
@@ -75,10 +75,10 @@
val nais1 = listOf(makeNai(true, 30), makeNai(true, 30), makeNai(true, 30),
makeNai(true, 30), makeNai(true, 30), makeNai(true, 30))
val someRequest = mock(NetworkRequest::class.java)
- assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1))
+ assertEquals(nais1[0], ranker.getBestNetwork(someRequest, nais1, nais1[0]))
val nais2 = listOf(makeNai(true, 30), makeNai(true, 50), makeNai(true, 20),
makeNai(true, 50), makeNai(true, 50), makeNai(true, 40))
- assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2))
+ assertEquals(nais2[1], ranker.getBestNetwork(someRequest, nais2, nais2[1]))
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index c75618f..c6e7606 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -30,7 +30,7 @@
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
-import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS;
+import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.os.Process.SYSTEM_UID;
@@ -142,7 +142,7 @@
final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mContext));
doReturn(UserHandle.ALL).when(asUserCtx).getUser();
when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
- when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+ when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps));
@@ -341,9 +341,9 @@
}
@Test
- public void testHasRestrictedNetworkPermissionAppAllowedOnRestrictedNetworks() {
- mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
- new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+ public void testHasRestrictedNetworkPermissionUidAllowedOnRestrictedNetworks() {
+ mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(
+ new ArraySet<>(new Integer[] { MOCK_UID1 }));
assertTrue(hasRestrictedNetworkPermission(
PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1));
assertTrue(hasRestrictedNetworkPermission(
@@ -352,11 +352,11 @@
PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE1, MOCK_UID1, CONNECTIVITY_INTERNAL));
assertFalse(hasRestrictedNetworkPermission(
- PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1));
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID2));
assertFalse(hasRestrictedNetworkPermission(
- PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1, CHANGE_NETWORK_STATE));
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID2, CHANGE_NETWORK_STATE));
assertFalse(hasRestrictedNetworkPermission(
- PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID1, CONNECTIVITY_INTERNAL));
+ PARTITION_VENDOR, VERSION_Q, MOCK_PACKAGE2, MOCK_UID2, CONNECTIVITY_INTERNAL));
}
@@ -396,32 +396,32 @@
assertFalse(wouldBeCarryoverPackage(PARTITION_PRODUCT, VERSION_Q, MOCK_UID1));
}
- private boolean wouldBeAppAllowedOnRestrictedNetworks(String packageName) {
- final PackageInfo packageInfo = new PackageInfo();
- packageInfo.packageName = packageName;
- return mPermissionMonitor.isAppAllowedOnRestrictedNetworks(packageInfo);
+ private boolean wouldBeUidAllowedOnRestrictedNetworks(int uid) {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = uid;
+ return mPermissionMonitor.isUidAllowedOnRestrictedNetworks(applicationInfo);
}
@Test
public void testIsAppAllowedOnRestrictedNetworks() {
- mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(new ArraySet<>());
- assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
- assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+ mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(new ArraySet<>());
+ assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID1));
+ assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID2));
- mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
- new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
- assertTrue(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
- assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+ mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(
+ new ArraySet<>(new Integer[] { MOCK_UID1 }));
+ assertTrue(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID1));
+ assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID2));
- mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
- new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
- assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
- assertTrue(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+ mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(
+ new ArraySet<>(new Integer[] { MOCK_UID2 }));
+ assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID1));
+ assertTrue(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID2));
- mPermissionMonitor.updateAppsAllowedOnRestrictedNetworks(
- new ArraySet<>(new String[] { "com.android.test" }));
- assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE1));
- assertFalse(wouldBeAppAllowedOnRestrictedNetworks(MOCK_PACKAGE2));
+ mPermissionMonitor.updateUidsAllowedOnRestrictedNetworks(
+ new ArraySet<>(new Integer[] { 123 }));
+ assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID1));
+ assertFalse(wouldBeUidAllowedOnRestrictedNetworks(MOCK_UID2));
}
private void assertBackgroundPermission(boolean hasPermission, String name, int uid,
@@ -901,12 +901,12 @@
}
@Test
- public void testAppsAllowedOnRestrictedNetworksChanged() throws Exception {
+ public void testUidsAllowedOnRestrictedNetworksChanged() throws Exception {
final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService);
final ArgumentCaptor<ContentObserver> captor =
ArgumentCaptor.forClass(ContentObserver.class);
verify(mDeps, times(1)).registerContentObserver(any(),
- argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)),
+ argThat(uri -> uri.getEncodedPath().contains(UIDS_ALLOWED_ON_RESTRICTED_NETWORKS)),
anyBoolean(), captor.capture());
final ContentObserver contentObserver = captor.getValue();
@@ -924,24 +924,24 @@
when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
when(mPackageManager.getPackagesForUid(MOCK_UID2)).thenReturn(new String[]{MOCK_PACKAGE2});
- // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1
+ // MOCK_UID1 is listed in setting that allow to use restricted networks, MOCK_UID1
// should have SYSTEM permission.
- when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
- new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+ when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(
+ new ArraySet<>(new Integer[] { MOCK_UID1 }));
contentObserver.onChange(true /* selfChange */);
mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
- // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID2
+ // MOCK_UID2 is listed in setting that allow to use restricted networks, MOCK_UID2
// should have SYSTEM permission but MOCK_UID1 should revoke permission.
- when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
- new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
+ when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(
+ new ArraySet<>(new Integer[] { MOCK_UID2 }));
contentObserver.onChange(true /* selfChange */);
mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2});
mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
- // No app lists in setting, should revoke permission from all uids.
- when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+ // No uid lists in setting, should revoke permission from all uids.
+ when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
contentObserver.onChange(true /* selfChange */);
mNetdMonitor.expectNoPermission(
new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1, MOCK_UID2});
@@ -953,7 +953,7 @@
final ArgumentCaptor<ContentObserver> captor =
ArgumentCaptor.forClass(ContentObserver.class);
verify(mDeps, times(1)).registerContentObserver(any(),
- argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)),
+ argThat(uri -> uri.getEncodedPath().contains(UIDS_ALLOWED_ON_RESTRICTED_NETWORKS)),
anyBoolean(), captor.capture());
final ContentObserver contentObserver = captor.getValue();
@@ -974,22 +974,15 @@
addPackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1);
mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
- // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID1
+ // MOCK_UID1 is listed in setting that allow to use restricted networks, MOCK_UID1
// should upgrade to SYSTEM permission.
- when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
- new ArraySet<>(new String[] { MOCK_PACKAGE2 }));
- contentObserver.onChange(true /* selfChange */);
- mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
-
- // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1
- // should still have SYSTEM permission.
- when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(
- new ArraySet<>(new String[] { MOCK_PACKAGE1 }));
+ when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(
+ new ArraySet<>(new Integer[] { MOCK_UID1 }));
contentObserver.onChange(true /* selfChange */);
mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
// No app lists in setting, MOCK_UID1 should downgrade to NETWORK permission.
- when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
+ when(mDeps.getUidsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>());
contentObserver.onChange(true /* selfChange */);
mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
@@ -998,4 +991,4 @@
removePackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1);
mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1});
}
-}
\ No newline at end of file
+}