Merge "Add FlaggedAPI annotation to NSD offload APIs" into main
diff --git a/OWNERS b/OWNERS
index 988af41..b2176cc 100644
--- a/OWNERS
+++ b/OWNERS
@@ -3,4 +3,3 @@
file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking
per-file **IpSec* = file:platform/frameworks/base:main:/services/core/java/com/android/server/vcn/OWNERS
-per-file **Xfrm* = file:platform/frameworks/base:main:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 50d6c4b..5e9bbcb 100644
--- a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -18,6 +18,7 @@
import static android.system.OsConstants.AF_INET6;
import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOCK_RAW;
import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_SNDTIMEO;
@@ -38,12 +39,21 @@
import android.net.MacAddress;
import android.net.TrafficStats;
import android.net.util.SocketUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructTimeval;
import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.GuardedBy;
+import com.android.net.module.util.FdEventsReader;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.LlaOption;
@@ -103,6 +113,11 @@
private static final int DAY_IN_SECONDS = 86_400;
+ // Commands for IpServer to control RouterAdvertisementDaemon
+ private static final int CMD_START = 1;
+ private static final int CMD_STOP = 2;
+ private static final int CMD_BUILD_NEW_RA = 3;
+
private final InterfaceParams mInterface;
private final InetSocketAddress mAllNodes;
@@ -120,9 +135,13 @@
@GuardedBy("mLock")
private RaParams mRaParams;
+ // To be accessed only from RaMessageHandler
+ private RsPacketListener mRsPacketListener;
+
private volatile FileDescriptor mSocket;
private volatile MulticastTransmitter mMulticastTransmitter;
- private volatile UnicastResponder mUnicastResponder;
+ private volatile RaMessageHandler mRaMessageHandler;
+ private volatile HandlerThread mRaHandlerThread;
/** Encapsulate the RA parameters for RouterAdvertisementDaemon.*/
public static class RaParams {
@@ -244,6 +263,94 @@
}
}
+ private class RaMessageHandler extends Handler {
+ RaMessageHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_START:
+ mRsPacketListener = new RsPacketListener(this);
+ mRsPacketListener.start();
+ break;
+ case CMD_STOP:
+ if (mRsPacketListener != null) {
+ mRsPacketListener.stop();
+ mRsPacketListener = null;
+ }
+ break;
+ case CMD_BUILD_NEW_RA:
+ synchronized (mLock) {
+ // raInfo.first is deprecatedParams and raInfo.second is newParams.
+ final Pair<RaParams, RaParams> raInfo = (Pair<RaParams, RaParams>) msg.obj;
+ if (raInfo.first != null) {
+ mDeprecatedInfoTracker.putPrefixes(raInfo.first.prefixes);
+ mDeprecatedInfoTracker.putDnses(raInfo.first.dnses);
+ }
+
+ if (raInfo.second != null) {
+ // Process information that is no longer deprecated.
+ mDeprecatedInfoTracker.removePrefixes(raInfo.second.prefixes);
+ mDeprecatedInfoTracker.removeDnses(raInfo.second.dnses);
+ }
+ mRaParams = raInfo.second;
+ assembleRaLocked();
+ }
+
+ maybeNotifyMulticastTransmitter();
+ break;
+ default:
+ Log.e(TAG, "Unknown message, cmd = " + String.valueOf(msg.what));
+ break;
+ }
+ }
+ }
+
+ private class RsPacketListener extends FdEventsReader<RsPacketListener.RecvBuffer> {
+ private static final class RecvBuffer {
+ // The recycled buffer for receiving Router Solicitations from clients.
+ // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
+ // This is fine since currently only byte 0 is examined anyway.
+ final byte[] mBytes = new byte[IPV6_MIN_MTU];
+ final InetSocketAddress mSrcAddr = new InetSocketAddress(0);
+ }
+
+ RsPacketListener(@NonNull Handler handler) {
+ super(handler, new RecvBuffer());
+ }
+
+ @Override
+ protected int recvBufSize(@NonNull RecvBuffer buffer) {
+ return buffer.mBytes.length;
+ }
+
+ @Override
+ protected FileDescriptor createFd() {
+ return mSocket;
+ }
+
+ @Override
+ protected int readPacket(@NonNull FileDescriptor fd, @NonNull RecvBuffer buffer)
+ throws Exception {
+ return Os.recvfrom(
+ fd, buffer.mBytes, 0, buffer.mBytes.length, 0 /* flags */, buffer.mSrcAddr);
+ }
+
+ @Override
+ protected final void handlePacket(@NonNull RecvBuffer buffer, int length) {
+ // Do the least possible amount of validations.
+ if (buffer.mSrcAddr == null
+ || length <= 0
+ || buffer.mBytes[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) {
+ return;
+ }
+
+ maybeSendRA(buffer.mSrcAddr);
+ }
+ }
+
public RouterAdvertisementDaemon(InterfaceParams ifParams) {
mInterface = ifParams;
mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0);
@@ -252,48 +359,43 @@
/** Build new RA.*/
public void buildNewRa(RaParams deprecatedParams, RaParams newParams) {
- synchronized (mLock) {
- if (deprecatedParams != null) {
- mDeprecatedInfoTracker.putPrefixes(deprecatedParams.prefixes);
- mDeprecatedInfoTracker.putDnses(deprecatedParams.dnses);
- }
-
- if (newParams != null) {
- // Process information that is no longer deprecated.
- mDeprecatedInfoTracker.removePrefixes(newParams.prefixes);
- mDeprecatedInfoTracker.removeDnses(newParams.dnses);
- }
-
- mRaParams = newParams;
- assembleRaLocked();
- }
-
- maybeNotifyMulticastTransmitter();
+ final Pair<RaParams, RaParams> raInfo = new Pair<>(deprecatedParams, newParams);
+ sendMessage(CMD_BUILD_NEW_RA, raInfo);
}
/** Start router advertisement daemon. */
public boolean start() {
if (!createSocket()) {
+ Log.e(TAG, "Failed to start RouterAdvertisementDaemon.");
return false;
}
mMulticastTransmitter = new MulticastTransmitter();
mMulticastTransmitter.start();
- mUnicastResponder = new UnicastResponder();
- mUnicastResponder.start();
+ mRaHandlerThread = new HandlerThread(TAG);
+ mRaHandlerThread.start();
+ mRaMessageHandler = new RaMessageHandler(mRaHandlerThread.getLooper());
- return true;
+ return sendMessage(CMD_START);
}
/** Stop router advertisement daemon. */
public void stop() {
+ if (!sendMessage(CMD_STOP)) {
+ Log.e(TAG, "RouterAdvertisementDaemon has been stopped or was never started.");
+ return;
+ }
+
+ mRaHandlerThread.quitSafely();
+ mRaHandlerThread = null;
+ mRaMessageHandler = null;
+
closeSocket();
// Wake up mMulticastTransmitter thread to interrupt a potential 1 day sleep before
// the thread's termination.
maybeNotifyMulticastTransmitter();
mMulticastTransmitter = null;
- mUnicastResponder = null;
}
@GuardedBy("mLock")
@@ -503,7 +605,7 @@
final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_NEIGHBOR);
try {
- mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ mSocket = Os.socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6);
// Setting SNDTIMEO is purely for defensive purposes.
Os.setsockoptTimeval(
mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms));
@@ -565,34 +667,17 @@
}
}
- private final class UnicastResponder extends Thread {
- private final InetSocketAddress mSolicitor = new InetSocketAddress(0);
- // The recycled buffer for receiving Router Solicitations from clients.
- // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
- // This is fine since currently only byte 0 is examined anyway.
- private final byte[] mSolicitation = new byte[IPV6_MIN_MTU];
+ private boolean sendMessage(int cmd) {
+ return sendMessage(cmd, null);
+ }
- @Override
- public void run() {
- while (isSocketValid()) {
- try {
- // Blocking receive.
- final int rval = Os.recvfrom(
- mSocket, mSolicitation, 0, mSolicitation.length, 0, mSolicitor);
- // Do the least possible amount of validation.
- if (rval < 1 || mSolicitation[0] != asByte(ICMPV6_ROUTER_SOLICITATION)) {
- continue;
- }
- } catch (ErrnoException | SocketException e) {
- if (isSocketValid()) {
- Log.e(TAG, "recvfrom error: " + e);
- }
- continue;
- }
-
- maybeSendRA(mSolicitor);
- }
+ private boolean sendMessage(int cmd, @Nullable Object obj) {
+ if (mRaMessageHandler == null) {
+ return false;
}
+
+ return mRaMessageHandler.sendMessage(
+ Message.obtain(mRaMessageHandler, cmd, obj));
}
// TODO: Consider moving this to run on a provided Looper as a Handler,
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index 6c0ca82..6f3e865 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -79,6 +79,7 @@
private final TetheringConfiguration mConfig;
// keyed by downstream type(TetheringManager.TETHERING_*).
private final ArrayMap<AddressKey, LinkAddress> mCachedAddresses;
+ private final Random mRandom;
public PrivateAddressCoordinator(Context context, TetheringConfiguration config) {
mDownstreams = new ArraySet<>();
@@ -95,6 +96,7 @@
mTetheringPrefixes = new ArrayList<>(Arrays.asList(new IpPrefix("192.168.0.0/16"),
new IpPrefix("172.16.0.0/12"), new IpPrefix("10.0.0.0/8")));
+ mRandom = new Random();
}
/**
@@ -263,12 +265,13 @@
// is less than 127.0.0.0 = 0x7f000000 = 2130706432.
//
// Additionally, it makes debug output easier to read by making the numbers smaller.
- final int randomPrefixStart = getRandomInt() & ~prefixRangeMask & prefixMask;
+ final int randomInt = getRandomInt();
+ final int randomPrefixStart = randomInt & ~prefixRangeMask & prefixMask;
// A random offset within the prefix. Used to determine the local address once the prefix
// is selected. It does not result in an IPv4 address ending in .0, .1, or .255
- // For a PREFIX_LENGTH of 255, this is a number between 2 and 254.
- final int subAddress = getSanitizedSubAddr(~prefixMask);
+ // For a PREFIX_LENGTH of 24, this is a number between 2 and 254.
+ final int subAddress = getSanitizedSubAddr(randomInt, ~prefixMask);
// Find a prefix length PREFIX_LENGTH between randomPrefixStart and the end of the block,
// such that the prefix does not conflict with any upstream.
@@ -310,12 +313,12 @@
/** Get random int which could be used to generate random address. */
@VisibleForTesting
public int getRandomInt() {
- return (new Random()).nextInt();
+ return mRandom.nextInt();
}
/** Get random subAddress and avoid selecting x.x.x.0, x.x.x.1 and x.x.x.255 address. */
- private int getSanitizedSubAddr(final int subAddrMask) {
- final int randomSubAddr = getRandomInt() & subAddrMask;
+ private int getSanitizedSubAddr(final int randomInt, final int subAddrMask) {
+ final int randomSubAddr = randomInt & subAddrMask;
// If prefix length > 30, the selecting speace would be less than 4 which may be hard to
// avoid 3 consecutive address.
if (PREFIX_LENGTH > 30) return randomSubAddr;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 996ee11..da3b584 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -327,8 +327,10 @@
return mConfig;
}
});
- mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMainSM, mLog,
- TetherMainSM.EVENT_UPSTREAM_CALLBACK);
+ mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mHandler, mLog,
+ (what, obj) -> {
+ mTetherMainSM.sendMessage(TetherMainSM.EVENT_UPSTREAM_CALLBACK, what, 0, obj);
+ });
mForwardedDownstreams = new HashSet<>();
IntentFilter filter = new IntentFilter();
@@ -2732,84 +2734,73 @@
}
}
- private IpServer.Callback makeControlCallback() {
- return new IpServer.Callback() {
- @Override
- public void updateInterfaceState(IpServer who, int state, int lastError) {
- notifyInterfaceStateChange(who, state, lastError);
+ private class ControlCallback extends IpServer.Callback {
+ @Override
+ public void updateInterfaceState(IpServer who, int state, int lastError) {
+ final String iface = who.interfaceName();
+ final TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState != null && tetherState.ipServer.equals(who)) {
+ tetherState.lastState = state;
+ tetherState.lastError = lastError;
+ } else {
+ if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
}
- @Override
- public void updateLinkProperties(IpServer who, LinkProperties newLp) {
- notifyLinkPropertiesChanged(who, newLp);
- }
+ mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, lastError));
- @Override
- public void dhcpLeasesChanged() {
- maybeDhcpLeasesChanged();
+ // If TetherMainSM is in ErrorState, TetherMainSM stays there.
+ // Thus we give a chance for TetherMainSM to recover to InitialState
+ // by sending CMD_CLEAR_ERROR
+ if (lastError == TETHER_ERROR_INTERNAL_ERROR) {
+ mTetherMainSM.sendMessage(TetherMainSM.CMD_CLEAR_ERROR, who);
}
-
- @Override
- public void requestEnableTethering(int tetheringType, boolean enabled) {
- mTetherMainSM.sendMessage(TetherMainSM.EVENT_REQUEST_CHANGE_DOWNSTREAM,
- tetheringType, 0, enabled ? Boolean.TRUE : Boolean.FALSE);
+ int which;
+ switch (state) {
+ case IpServer.STATE_UNAVAILABLE:
+ case IpServer.STATE_AVAILABLE:
+ which = TetherMainSM.EVENT_IFACE_SERVING_STATE_INACTIVE;
+ break;
+ case IpServer.STATE_TETHERED:
+ case IpServer.STATE_LOCAL_ONLY:
+ which = TetherMainSM.EVENT_IFACE_SERVING_STATE_ACTIVE;
+ break;
+ default:
+ Log.wtf(TAG, "Unknown interface state: " + state);
+ return;
}
- };
- }
-
- // TODO: Move into TetherMainSM.
- private void notifyInterfaceStateChange(IpServer who, int state, int error) {
- final String iface = who.interfaceName();
- final TetherState tetherState = mTetherStates.get(iface);
- if (tetherState != null && tetherState.ipServer.equals(who)) {
- tetherState.lastState = state;
- tetherState.lastError = error;
- } else {
- if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
+ mTetherMainSM.sendMessage(which, state, 0, who);
+ sendTetherStateChangedBroadcast();
}
- mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error));
-
- // If TetherMainSM is in ErrorState, TetherMainSM stays there.
- // Thus we give a chance for TetherMainSM to recover to InitialState
- // by sending CMD_CLEAR_ERROR
- if (error == TETHER_ERROR_INTERNAL_ERROR) {
- mTetherMainSM.sendMessage(TetherMainSM.CMD_CLEAR_ERROR, who);
- }
- int which;
- switch (state) {
- case IpServer.STATE_UNAVAILABLE:
- case IpServer.STATE_AVAILABLE:
- which = TetherMainSM.EVENT_IFACE_SERVING_STATE_INACTIVE;
- break;
- case IpServer.STATE_TETHERED:
- case IpServer.STATE_LOCAL_ONLY:
- which = TetherMainSM.EVENT_IFACE_SERVING_STATE_ACTIVE;
- break;
- default:
- Log.wtf(TAG, "Unknown interface state: " + state);
+ @Override
+ public void updateLinkProperties(IpServer who, LinkProperties newLp) {
+ final String iface = who.interfaceName();
+ final int state;
+ final TetherState tetherState = mTetherStates.get(iface);
+ if (tetherState != null && tetherState.ipServer.equals(who)) {
+ state = tetherState.lastState;
+ } else {
+ mLog.log("got notification from stale iface " + iface);
return;
- }
- mTetherMainSM.sendMessage(which, state, 0, who);
- sendTetherStateChangedBroadcast();
- }
+ }
- private void notifyLinkPropertiesChanged(IpServer who, LinkProperties newLp) {
- final String iface = who.interfaceName();
- final int state;
- final TetherState tetherState = mTetherStates.get(iface);
- if (tetherState != null && tetherState.ipServer.equals(who)) {
- state = tetherState.lastState;
- } else {
- mLog.log("got notification from stale iface " + iface);
- return;
+ mLog.log(String.format(
+ "OBSERVED LinkProperties update iface=%s state=%s lp=%s",
+ iface, IpServer.getStateString(state), newLp));
+ final int which = TetherMainSM.EVENT_IFACE_UPDATE_LINKPROPERTIES;
+ mTetherMainSM.sendMessage(which, state, 0, newLp);
}
- mLog.log(String.format(
- "OBSERVED LinkProperties update iface=%s state=%s lp=%s",
- iface, IpServer.getStateString(state), newLp));
- final int which = TetherMainSM.EVENT_IFACE_UPDATE_LINKPROPERTIES;
- mTetherMainSM.sendMessage(which, state, 0, newLp);
+ @Override
+ public void dhcpLeasesChanged() {
+ maybeDhcpLeasesChanged();
+ }
+
+ @Override
+ public void requestEnableTethering(int tetheringType, boolean enabled) {
+ mTetherMainSM.sendMessage(TetherMainSM.EVENT_REQUEST_CHANGE_DOWNSTREAM,
+ tetheringType, 0, enabled ? Boolean.TRUE : Boolean.FALSE);
+ }
}
private boolean hasSystemFeature(final String feature) {
@@ -2851,7 +2842,7 @@
mLog.i("adding IpServer for: " + iface);
final TetherState tetherState = new TetherState(
new IpServer(iface, mHandler, interfaceType, mLog, mNetd, mBpfCoordinator,
- mRoutingCoordinator, makeControlCallback(), mConfig,
+ mRoutingCoordinator, new ControlCallback(), mConfig,
mPrivateAddressCoordinator, mTetheringMetrics,
mDeps.getIpServerDependencies()), isNcm);
mTetherStates.put(iface, tetherState);
@@ -2879,4 +2870,9 @@
} catch (RemoteException e) { }
});
}
+
+ @VisibleForTesting
+ public TetherMainSM getTetherMainSMForTesting() {
+ return mTetherMainSM;
+ }
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index c274165..d02e8e8 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -35,7 +35,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
-import com.android.internal.util.StateMachine;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
@@ -84,9 +83,9 @@
/**
* Get a reference to the UpstreamNetworkMonitor to be used by tethering.
*/
- public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
- SharedLog log, int what) {
- return new UpstreamNetworkMonitor(ctx, target, log, what);
+ public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, Handler h,
+ SharedLog log, UpstreamNetworkMonitor.EventListener listener) {
+ return new UpstreamNetworkMonitor(ctx, h, log, listener);
}
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index ac2aa7b..7a05d74 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -44,7 +44,6 @@
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.StateMachine;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.ConnectivityManagerShimImpl;
import com.android.networkstack.apishim.common.ConnectivityManagerShim;
@@ -111,9 +110,8 @@
private final Context mContext;
private final SharedLog mLog;
- private final StateMachine mTarget;
private final Handler mHandler;
- private final int mWhat;
+ private final EventListener mEventListener;
private final HashMap<Network, UpstreamNetworkState> mNetworkMap = new HashMap<>();
private HashSet<IpPrefix> mLocalPrefixes;
private ConnectivityManager mCM;
@@ -135,12 +133,11 @@
private Network mDefaultInternetNetwork;
private boolean mPreferTestNetworks;
- public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
+ public UpstreamNetworkMonitor(Context ctx, Handler h, SharedLog log, EventListener listener) {
mContext = ctx;
- mTarget = tgt;
- mHandler = mTarget.getHandler();
+ mHandler = h;
mLog = log.forSubComponent(TAG);
- mWhat = what;
+ mEventListener = listener;
mLocalPrefixes = new HashSet<>();
mIsDefaultCellularUpstream = false;
mCM = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -374,11 +371,12 @@
network, newNc));
}
- mNetworkMap.put(network, new UpstreamNetworkState(
- prev.linkProperties, newNc, network));
+ final UpstreamNetworkState uns =
+ new UpstreamNetworkState(prev.linkProperties, newNc, network);
+ mNetworkMap.put(network, uns);
// TODO: If sufficient information is available to select a more
// preferable upstream, do so now and notify the target.
- notifyTarget(EVENT_ON_CAPABILITIES, network);
+ mEventListener.onUpstreamEvent(EVENT_ON_CAPABILITIES, uns);
}
private @Nullable UpstreamNetworkState updateLinkProperties(@NonNull Network network,
@@ -411,7 +409,7 @@
private void handleLinkProp(Network network, LinkProperties newLp) {
final UpstreamNetworkState ns = updateLinkProperties(network, newLp);
if (ns != null) {
- notifyTarget(EVENT_ON_LINKPROPERTIES, ns);
+ mEventListener.onUpstreamEvent(EVENT_ON_LINKPROPERTIES, ns);
}
}
@@ -438,7 +436,7 @@
// preferable upstream, do so now and notify the target. Likewise,
// if the current upstream network is gone, notify the target of the
// fact that we now have no upstream at all.
- notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network));
+ mEventListener.onUpstreamEvent(EVENT_ON_LOST, mNetworkMap.remove(network));
}
private void maybeHandleNetworkSwitch(@NonNull Network network) {
@@ -456,14 +454,14 @@
// Default network changed. Update local data and notify tethering.
Log.d(TAG, "New default Internet network: " + network);
mDefaultInternetNetwork = network;
- notifyTarget(EVENT_DEFAULT_SWITCHED, ns);
+ mEventListener.onUpstreamEvent(EVENT_DEFAULT_SWITCHED, ns);
}
private void recomputeLocalPrefixes() {
final HashSet<IpPrefix> localPrefixes = allLocalPrefixes(mNetworkMap.values());
if (!mLocalPrefixes.equals(localPrefixes)) {
mLocalPrefixes = localPrefixes;
- notifyTarget(NOTIFY_LOCAL_PREFIXES, localPrefixes.clone());
+ mEventListener.onUpstreamEvent(NOTIFY_LOCAL_PREFIXES, localPrefixes.clone());
}
}
@@ -502,12 +500,13 @@
// onLinkPropertiesChanged right after this method and mDefaultInternetNetwork will
// be updated then.
//
- // Technically, not updating here isn't necessary, because the notifications to
- // Tethering sent by notifyTarget are messages sent to a state machine running on
- // the same thread as this method, and so cannot arrive until after this method has
- // returned. However, it is not a good idea to rely on that because fact that
- // Tethering uses multiple state machines running on the same thread is a major
- // source of race conditions and something that should be fixed.
+ // Technically, mDefaultInternetNetwork could be updated here, because the
+ // Callback#onChange implementation sends messages to the state machine running
+ // on the same thread as this method. If there is new default network change,
+ // the message cannot arrive until onLinkPropertiesChanged returns.
+ // However, it is not a good idea to rely on that because fact that Tethering uses
+ // multiple state machines running on the same thread is a major source of race
+ // conditions and something that should be fixed.
//
// TODO: is it correct that this code always updates EntitlementManager?
// This code runs when the default network connects or changes capabilities, but the
@@ -551,7 +550,7 @@
mIsDefaultCellularUpstream = false;
mEntitlementMgr.notifyUpstream(false);
Log.d(TAG, "Lost default Internet network: " + network);
- notifyTarget(EVENT_DEFAULT_SWITCHED, null);
+ mEventListener.onUpstreamEvent(EVENT_DEFAULT_SWITCHED, null);
return;
}
@@ -569,14 +568,6 @@
if (cb != null) cm().unregisterNetworkCallback(cb);
}
- private void notifyTarget(int which, Network network) {
- notifyTarget(which, mNetworkMap.get(network));
- }
-
- private void notifyTarget(int which, Object obj) {
- mTarget.sendMessage(mWhat, which, 0, obj);
- }
-
private static class TypeStatePair {
public int type = TYPE_NONE;
public UpstreamNetworkState ns = null;
@@ -698,4 +689,10 @@
public void setPreferTestNetworks(boolean prefer) {
mPreferTestNetworks = prefer;
}
+
+ /** An interface to notify upstream network changes. */
+ public interface EventListener {
+ /** Notify the client of some event */
+ void onUpstreamEvent(int what, Object obj);
+ }
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 01600b8..47ecf58 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -1280,9 +1280,9 @@
final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
final TetherDownstream6Key key = rule.makeTetherDownstream6Key();
- assertEquals(key.iif, mobileIfIndex);
- assertEquals(key.dstMac, MacAddress.ALL_ZEROS_ADDRESS); // rawip upstream
- assertTrue(Arrays.equals(key.neigh6, NEIGH_A.getAddress()));
+ assertEquals(mobileIfIndex, key.iif);
+ assertEquals(MacAddress.ALL_ZEROS_ADDRESS, key.dstMac); // rawip upstream
+ assertArrayEquals(NEIGH_A.getAddress(), key.neigh6);
// iif (4) + dstMac(6) + padding(2) + neigh6 (16) = 28.
assertEquals(28, key.writeToBytes().length);
}
@@ -1293,12 +1293,12 @@
final Ipv6DownstreamRule rule = buildTestDownstreamRule(mobileIfIndex, NEIGH_A, MAC_A);
final Tether6Value value = rule.makeTether6Value();
- assertEquals(value.oif, DOWNSTREAM_IFINDEX);
- assertEquals(value.ethDstMac, MAC_A);
- assertEquals(value.ethSrcMac, DOWNSTREAM_MAC);
- assertEquals(value.ethProto, ETH_P_IPV6);
- assertEquals(value.pmtu, NetworkStackConstants.ETHER_MTU);
- // oif (4) + ethDstMac (6) + ethSrcMac (6) + ethProto (2) + pmtu (2) = 20.
+ assertEquals(DOWNSTREAM_IFINDEX, value.oif);
+ assertEquals(MAC_A, value.ethDstMac);
+ assertEquals(DOWNSTREAM_MAC, value.ethSrcMac);
+ assertEquals(ETH_P_IPV6, value.ethProto);
+ assertEquals(NetworkStackConstants.ETHER_MTU, value.pmtu);
+ // oif (4) + ethDstMac (6) + ethSrcMac (6) + ethProto (2) + pmtu (2) = 20
assertEquals(20, value.writeToBytes().length);
}
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 6eba590..5877fc5 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -187,7 +187,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.CollectionUtils;
@@ -310,6 +309,7 @@
private BroadcastReceiver mBroadcastReceiver;
private Tethering mTethering;
private TestTetheringEventCallback mTetheringEventCallback;
+ private Tethering.TetherMainSM mTetherMainSM;
private PhoneStateListener mPhoneStateListener;
private InterfaceConfigurationParcel mInterfaceConfiguration;
private TetheringConfiguration mConfig;
@@ -319,6 +319,7 @@
private SoftApCallback mSoftApCallback;
private SoftApCallback mLocalOnlyHotspotCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+ private UpstreamNetworkMonitor.EventListener mEventListener;
private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
private TestConnectivityManager mCm;
@@ -432,7 +433,6 @@
}
public class MockTetheringDependencies extends TetheringDependencies {
- StateMachine mUpstreamNetworkMonitorSM;
ArrayList<IpServer> mAllDownstreams;
@Override
@@ -456,12 +456,12 @@
}
@Override
- public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx,
- StateMachine target, SharedLog log, int what) {
+ public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, Handler h,
+ SharedLog log, UpstreamNetworkMonitor.EventListener listener) {
// Use a real object instead of a mock so that some tests can use a real UNM and some
// can use a mock.
- mUpstreamNetworkMonitorSM = target;
- mUpstreamNetworkMonitor = spy(super.getUpstreamNetworkMonitor(ctx, target, log, what));
+ mEventListener = listener;
+ mUpstreamNetworkMonitor = spy(super.getUpstreamNetworkMonitor(ctx, h, log, listener));
return mUpstreamNetworkMonitor;
}
@@ -688,6 +688,7 @@
private void initTetheringOnTestThread() throws Exception {
mLooper = new TestLooper();
mTethering = new Tethering(mTetheringDependencies);
+ mTetherMainSM = mTethering.getTetherMainSMForTesting();
verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
verify(mNetd).registerUnsolicitedEventListener(any());
verifyDefaultNetworkRequestFiled();
@@ -1182,10 +1183,7 @@
initTetheringUpstream(upstreamState);
// Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES.
- mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
- Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
- UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
- 0,
+ mEventListener.onUpstreamEvent(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
upstreamState);
mLooper.dispatchAll();
@@ -2713,14 +2711,12 @@
@Test
public void testUpstreamNetworkChanged() throws Exception {
initTetheringOnTestThread();
- final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM)
- mTetheringDependencies.mUpstreamNetworkMonitorSM;
final InOrder inOrder = inOrder(mNotificationUpdater);
// Gain upstream.
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
initTetheringUpstream(upstreamState);
- stateMachine.chooseUpstreamType(true);
+ mTetherMainSM.chooseUpstreamType(true);
mTetheringEventCallback.expectUpstreamChanged(upstreamState.network);
inOrder.verify(mNotificationUpdater)
.onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
@@ -2728,7 +2724,7 @@
// Set the upstream with the same network ID but different object and the same capability.
final UpstreamNetworkState upstreamState2 = buildMobileIPv4UpstreamState();
initTetheringUpstream(upstreamState2);
- stateMachine.chooseUpstreamType(true);
+ mTetherMainSM.chooseUpstreamType(true);
// Expect that no upstream change event and capabilities changed event.
mTetheringEventCallback.assertNoUpstreamChangeCallback();
inOrder.verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
@@ -2738,17 +2734,17 @@
assertFalse(upstreamState3.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED));
upstreamState3.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
initTetheringUpstream(upstreamState3);
- stateMachine.chooseUpstreamType(true);
+ mTetherMainSM.chooseUpstreamType(true);
// Expect that no upstream change event and capabilities changed event.
mTetheringEventCallback.assertNoUpstreamChangeCallback();
- stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState3);
+ mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState3);
inOrder.verify(mNotificationUpdater)
.onUpstreamCapabilitiesChanged(upstreamState3.networkCapabilities);
// Lose upstream.
initTetheringUpstream(null);
- stateMachine.chooseUpstreamType(true);
+ mTetherMainSM.chooseUpstreamType(true);
mTetheringEventCallback.expectUpstreamChanged(NULL_NETWORK);
inOrder.verify(mNotificationUpdater).onUpstreamCapabilitiesChanged(null);
}
@@ -2756,17 +2752,15 @@
@Test
public void testUpstreamCapabilitiesChanged() throws Exception {
initTetheringOnTestThread();
- final Tethering.TetherMainSM stateMachine = (Tethering.TetherMainSM)
- mTetheringDependencies.mUpstreamNetworkMonitorSM;
final InOrder inOrder = inOrder(mNotificationUpdater);
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
initTetheringUpstream(upstreamState);
- stateMachine.chooseUpstreamType(true);
+ mTetherMainSM.chooseUpstreamType(true);
inOrder.verify(mNotificationUpdater)
.onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
- stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
+ mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
inOrder.verify(mNotificationUpdater)
.onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
@@ -2775,7 +2769,7 @@
// Expect that capability is changed with new capability VALIDATED.
assertFalse(upstreamState.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED));
upstreamState.networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
- stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
+ mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
inOrder.verify(mNotificationUpdater)
.onUpstreamCapabilitiesChanged(upstreamState.networkCapabilities);
@@ -2784,7 +2778,7 @@
final UpstreamNetworkState upstreamState2 = new UpstreamNetworkState(
upstreamState.linkProperties, upstreamState.networkCapabilities,
new Network(WIFI_NETID));
- stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2);
+ mTetherMainSM.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState2);
inOrder.verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
}
@@ -2907,11 +2901,7 @@
final String iface, final int transportType) {
final UpstreamNetworkState upstream = buildV4UpstreamState(ipv4Address, network, iface,
transportType);
- mTetheringDependencies.mUpstreamNetworkMonitorSM.sendMessage(
- Tethering.TetherMainSM.EVENT_UPSTREAM_CALLBACK,
- UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
- 0,
- upstream);
+ mEventListener.onUpstreamEvent(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, upstream);
mLooper.dispatchAll();
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
index e756bd3..045c0cb 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/UpstreamNetworkMonitorTest.java
@@ -30,10 +30,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -51,27 +53,23 @@
import android.net.NetworkRequest;
import android.os.Build;
import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.os.test.TestLooper;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.tethering.TestConnectivityManager.NetworkRequestInfo;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
-import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -84,8 +82,6 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UpstreamNetworkMonitorTest {
- private static final int EVENT_UNM_UPDATE = 1;
-
private static final boolean INCLUDES = true;
private static final boolean EXCLUDES = false;
@@ -102,12 +98,13 @@
@Mock private EntitlementManager mEntitleMgr;
@Mock private IConnectivityManager mCS;
@Mock private SharedLog mLog;
+ @Mock private UpstreamNetworkMonitor.EventListener mListener;
- private TestStateMachine mSM;
private TestConnectivityManager mCM;
private UpstreamNetworkMonitor mUNM;
private final TestLooper mLooper = new TestLooper();
+ private InOrder mCallbackOrder;
@Before public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -117,17 +114,11 @@
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
+ mCallbackOrder = inOrder(mListener);
mCM = spy(new TestConnectivityManager(mContext, mCS));
when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE))).thenReturn(mCM);
- mSM = new TestStateMachine(mLooper.getLooper());
- mUNM = new UpstreamNetworkMonitor(mContext, mSM, mLog, EVENT_UNM_UPDATE);
- }
-
- @After public void tearDown() throws Exception {
- if (mSM != null) {
- mSM.quit();
- mSM = null;
- }
+ mUNM = new UpstreamNetworkMonitor(mContext, new Handler(mLooper.getLooper()), mLog,
+ mListener);
}
@Test
@@ -603,14 +594,17 @@
mCM.makeDefaultNetwork(cellAgent);
mLooper.dispatchAll();
verifyCurrentLinkProperties(cellAgent);
- int messageIndex = mSM.messages.size() - 1;
+ verifyNotifyNetworkCapabilitiesChange(cellAgent.networkCapabilities);
+ verifyNotifyLinkPropertiesChange(cellLp);
+ verifyNotifyDefaultSwitch(cellAgent);
+ verifyNoMoreInteractions(mListener);
addLinkAddresses(cellLp, ipv6Addr1);
mCM.sendLinkProperties(cellAgent, false /* updateDefaultFirst */);
mLooper.dispatchAll();
verifyCurrentLinkProperties(cellAgent);
- verifyNotifyLinkPropertiesChange(messageIndex);
- messageIndex = mSM.messages.size() - 1;
+ verifyNotifyLinkPropertiesChange(cellLp);
+ verifyNoMoreInteractions(mListener);
removeLinkAddresses(cellLp, ipv6Addr1);
addLinkAddresses(cellLp, ipv6Addr2);
@@ -618,7 +612,8 @@
mLooper.dispatchAll();
assertEquals(cellAgent.linkProperties, mUNM.getCurrentPreferredUpstream().linkProperties);
verifyCurrentLinkProperties(cellAgent);
- verifyNotifyLinkPropertiesChange(messageIndex);
+ verifyNotifyLinkPropertiesChange(cellLp);
+ verifyNoMoreInteractions(mListener);
}
private void verifyCurrentLinkProperties(TestNetworkAgent agent) {
@@ -626,12 +621,33 @@
assertEquals(agent.linkProperties, mUNM.getCurrentPreferredUpstream().linkProperties);
}
- private void verifyNotifyLinkPropertiesChange(int lastMessageIndex) {
- assertEquals(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
- mSM.messages.get(++lastMessageIndex).arg1);
- assertEquals(UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES,
- mSM.messages.get(++lastMessageIndex).arg1);
- assertEquals(lastMessageIndex + 1, mSM.messages.size());
+ private void verifyNotifyNetworkCapabilitiesChange(final NetworkCapabilities cap) {
+ mCallbackOrder.verify(mListener).onUpstreamEvent(
+ eq(UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES),
+ argThat(uns -> uns instanceof UpstreamNetworkState
+ && cap.equals(((UpstreamNetworkState) uns).networkCapabilities)));
+
+ }
+
+ private void verifyNotifyLinkPropertiesChange(final LinkProperties lp) {
+ mCallbackOrder.verify(mListener).onUpstreamEvent(
+ eq(UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES),
+ argThat(uns -> uns instanceof UpstreamNetworkState
+ && lp.equals(((UpstreamNetworkState) uns).linkProperties)));
+
+ mCallbackOrder.verify(mListener).onUpstreamEvent(
+ eq(UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES), any());
+ }
+
+ private void verifyNotifyDefaultSwitch(TestNetworkAgent agent) {
+ mCallbackOrder.verify(mListener).onUpstreamEvent(
+ eq(UpstreamNetworkMonitor.EVENT_DEFAULT_SWITCHED),
+ argThat(uns ->
+ uns instanceof UpstreamNetworkState
+ && agent.networkId.equals(((UpstreamNetworkState) uns).network)
+ && agent.linkProperties.equals(((UpstreamNetworkState) uns).linkProperties)
+ && agent.networkCapabilities.equals(
+ ((UpstreamNetworkState) uns).networkCapabilities)));
}
private void addLinkAddresses(LinkProperties lp, String... addrs) {
@@ -673,33 +689,6 @@
return false;
}
- public static class TestStateMachine extends StateMachine {
- public final ArrayList<Message> messages = new ArrayList<>();
- private final State mLoggingState = new LoggingState();
-
- class LoggingState extends State {
- @Override public void enter() {
- messages.clear();
- }
-
- @Override public void exit() {
- messages.clear();
- }
-
- @Override public boolean processMessage(Message msg) {
- messages.add(msg);
- return true;
- }
- }
-
- public TestStateMachine(Looper looper) {
- super("UpstreamNetworkMonitor.TestStateMachine", looper);
- addState(mLoggingState);
- setInitialState(mLoggingState);
- super.start();
- }
- }
-
static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
final Set<String> expectedSet = new HashSet<>();
Collections.addAll(expectedSet, expected);
diff --git a/common/flags.aconfig b/common/flags.aconfig
index b85c2fe..ad78d62 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -15,13 +15,6 @@
}
flag {
- name: "nsd_expired_services_removal"
- namespace: "android_core_networking"
- description: "Remove expired services from MdnsServiceCache"
- bug: "304649384"
-}
-
-flag {
name: "set_data_saver_via_cm"
namespace: "android_core_networking"
description: "Set data saver through ConnectivityManager API"
@@ -41,3 +34,10 @@
description: "Block network access for apps in a low importance background state"
bug: "304347838"
}
+
+flag {
+ name: "register_nsd_offload_engine"
+ namespace: "android_core_networking"
+ description: "The flag controls the access for registerOffloadEngine API in NsdManager"
+ bug: "294777050"
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index f59bb52..bf01a9d 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -35,10 +35,10 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.ConnectivityThread;
import android.net.Network;
import android.net.NetworkRequest;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -643,10 +643,9 @@
*/
public NsdManager(Context context, INsdManager service) {
mContext = context;
-
- HandlerThread t = new HandlerThread("NsdManager");
- t.start();
- mHandler = new ServiceHandler(t.getLooper());
+ // Use a common singleton thread ConnectivityThread to be shared among all nsd tasks.
+ // Instead of launching separate threads to handle tasks from the various instances.
+ mHandler = new ServiceHandler(ConnectivityThread.getInstanceLooper());
try {
mService = service.connect(new NsdCallbackImpl(mHandler), CompatChanges.isChangeEnabled(
diff --git a/framework/Android.bp b/framework/Android.bp
index fab37e9..10acbd0 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -94,7 +94,9 @@
"framework-wifi.stubs.module_lib",
],
static_libs: [
- "mdns_aidl_interface-lateststable-java",
+ // Not using the latest stable version because all functions in the latest version of
+ // mdns_aidl_interface are deprecated.
+ "mdns_aidl_interface-V1-java",
"modules-utils-backgroundthread",
"modules-utils-build",
"modules-utils-preconditions",
@@ -266,7 +268,7 @@
":framework-connectivity-t-pre-jarjar{.jar}",
":framework-connectivity.stubs.module_lib{.jar}",
":framework-connectivity-t.stubs.module_lib{.jar}",
- ":framework-connectivity-flagged-apis{.jar}",
+ ":framework-connectivity-module-api-stubs-including-flagged{.jar}",
"jarjar-excludes.txt",
],
tools: [
@@ -279,7 +281,7 @@
"--prefix android.net.connectivity " +
"--apistubs $(location :framework-connectivity.stubs.module_lib{.jar}) " +
"--apistubs $(location :framework-connectivity-t.stubs.module_lib{.jar}) " +
- "--apistubs $(location :framework-connectivity-flagged-apis{.jar}) " +
+ "--apistubs $(location :framework-connectivity-module-api-stubs-including-flagged{.jar}) " +
// Make a ":"-separated list. There will be an extra ":" but empty items are ignored.
"--unsupportedapi $$(printf ':%s' $(locations :connectivity-hiddenapi-files)) " +
"--excludes $(location jarjar-excludes.txt) " +
@@ -292,7 +294,7 @@
}
droidstubs {
- name: "framework-connectivity-flagged-apis-droidstubs",
+ name: "framework-connectivity-module-api-stubs-including-flagged-droidstubs",
srcs: [
":framework-connectivity-sources",
":framework-connectivity-tiramisu-updatable-sources",
@@ -300,7 +302,6 @@
":framework-thread-sources",
],
flags: [
- "--show-annotation android.annotation.FlaggedApi",
"--show-for-stub-purposes-annotation android.annotation.SystemApi" +
"\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\)",
"--show-for-stub-purposes-annotation android.annotation.SystemApi" +
@@ -315,8 +316,8 @@
}
java_library {
- name: "framework-connectivity-flagged-apis",
- srcs: [":framework-connectivity-flagged-apis-droidstubs"],
+ name: "framework-connectivity-module-api-stubs-including-flagged",
+ srcs: [":framework-connectivity-module-api-stubs-including-flagged-droidstubs"],
}
// Library providing limited APIs within the connectivity module, so that R+ components like
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index a7a4059..2a9892f 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -76,23 +76,43 @@
}
static Status initPrograms(const char* cg2_path) {
+ if (!cg2_path) {
+ ALOGE("cg2_path is NULL");
+ return statusFromErrno(EINVAL, "cg2_path is NULL");
+ }
+
// This code was mainlined in T, so this should be trivially satisfied.
- if (!modules::sdklevel::IsAtLeastT()) abort();
+ if (!modules::sdklevel::IsAtLeastT()) {
+ ALOGE("S- platform is unsupported");
+ abort();
+ }
// S requires eBPF support which was only added in 4.9, so this should be satisfied.
- if (!bpf::isAtLeastKernelVersion(4, 9, 0)) abort();
+ if (!bpf::isAtLeastKernelVersion(4, 9, 0)) {
+ ALOGE("kernel version < 4.9.0 is unsupported");
+ abort();
+ }
// U bumps the kernel requirement up to 4.14
- if (modules::sdklevel::IsAtLeastU() && !bpf::isAtLeastKernelVersion(4, 14, 0)) abort();
+ if (modules::sdklevel::IsAtLeastU() && !bpf::isAtLeastKernelVersion(4, 14, 0)) {
+ ALOGE("U+ platform with kernel version < 4.14.0 is unsupported");
+ abort();
+ }
if (modules::sdklevel::IsAtLeastV()) {
// V bumps the kernel requirement up to 4.19
// see also: //system/netd/tests/kernel_test.cpp TestKernel419
- if (!bpf::isAtLeastKernelVersion(4, 19, 0)) abort();
+ if (!bpf::isAtLeastKernelVersion(4, 19, 0)) {
+ ALOGE("V+ platform with kernel version < 4.19.0 is unsupported");
+ abort();
+ }
// Technically already required by U, but only enforce on V+
// see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
- if (bpf::isKernel32Bit() && bpf::isAtLeastKernelVersion(5, 16, 0)) abort();
+ if (bpf::isKernel32Bit() && bpf::isAtLeastKernelVersion(5, 16, 0)) {
+ ALOGE("V+ platform with 32 bit kernel, version >= 5.16.0 is unsupported");
+ abort();
+ }
}
// Linux 6.1 is highest version supported by U, starting with V new kernels,
@@ -102,10 +122,16 @@
// it does not affect unprivileged apps, the 32-on-64 compatibility
// problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
// see also: //system/bpf/bpfloader/BpfLoader.cpp main()
- if (bpf::isUserspace32bit() && bpf::isAtLeastKernelVersion(6, 2, 0)) abort();
+ if (bpf::isUserspace32bit() && bpf::isAtLeastKernelVersion(6, 2, 0)) {
+ ALOGE("32 bit userspace with Kernel version >= 6.2.0 is unsupported");
+ abort();
+ }
// U mandates this mount point (though it should also be the case on T)
- if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) abort();
+ if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) {
+ ALOGE("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
+ abort();
+ }
unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
if (!cg_fd.ok()) {
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index ee5f25b..8cf6db7 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.Manifest.permission.DEVICE_POWER;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STACK;
import static android.net.ConnectivityManager.NETID_UNSET;
@@ -2104,11 +2105,17 @@
if (!SdkLevel.isAtLeastT()) {
throw new SecurityException("API is not available in before API level 33");
}
- // REGISTER_NSD_OFFLOAD_ENGINE was only added to the SDK in V, but may
- // be back ported to older builds: accept it as long as it's signature-protected
- if (PermissionUtils.checkAnyPermissionOf(context, REGISTER_NSD_OFFLOAD_ENGINE)
- && (SdkLevel.isAtLeastV() || PermissionUtils.isSystemSignaturePermission(
- context, REGISTER_NSD_OFFLOAD_ENGINE))) {
+
+ // REGISTER_NSD_OFFLOAD_ENGINE was only added to the SDK in V.
+ if (SdkLevel.isAtLeastV() && PermissionUtils.checkAnyPermissionOf(context,
+ REGISTER_NSD_OFFLOAD_ENGINE)) {
+ return;
+ }
+
+ // REGISTER_NSD_OFFLOAD_ENGINE cannot be backport to U. In U, check the DEVICE_POWER
+ // permission instead.
+ if (!SdkLevel.isAtLeastV() && SdkLevel.isAtLeastU()
+ && PermissionUtils.checkAnyPermissionOf(context, DEVICE_POWER)) {
return;
}
if (PermissionUtils.checkAnyPermissionOf(context, NETWORK_STACK,
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 01b8de7..48e86d8 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -48,7 +48,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.SharedLog;
@@ -238,18 +237,7 @@
mDeps = deps;
// Interface match regex.
- String ifaceMatchRegex = mDeps.getInterfaceRegexFromResource(mContext);
- // "*" is a magic string to indicate "pick the default".
- if (ifaceMatchRegex.equals("*")) {
- if (SdkLevel.isAtLeastU()) {
- // On U+, include both usb%d and eth%d interfaces.
- ifaceMatchRegex = "(usb|eth)\\d+";
- } else {
- // On T, include only eth%d interfaces.
- ifaceMatchRegex = "eth\\d+";
- }
- }
- mIfaceMatch = ifaceMatchRegex;
+ mIfaceMatch = mDeps.getInterfaceRegexFromResource(mContext);
// Read default Ethernet interface configuration from resources
final String[] interfaceConfigs = mDeps.getInterfaceConfigFromResource(context);
diff --git a/service/Android.bp b/service/Android.bp
index 76741bc..e2dab9e 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -185,7 +185,7 @@
"androidx.annotation_annotation",
"connectivity-net-module-utils-bpf",
"connectivity_native_aidl_interface-lateststable-java",
- "dnsresolver_aidl_interface-V12-java",
+ "dnsresolver_aidl_interface-V13-java",
"modules-utils-shell-command-handler",
"net-utils-device-common",
"net-utils-device-common-ip",
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 045d707f..f30abc6 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -194,11 +194,8 @@
-->
</string-array>
- <!-- Regex of wired ethernet ifaces. Network interfaces that match this regex will be tracked
- by ethernet service.
- If set to "*", ethernet service uses "(eth|usb)\\d+" on Android U+ and eth\\d+ on
- Android T. -->
- <string translatable="false" name="config_ethernet_iface_regex">*</string>
+ <!-- Regex of wired ethernet ifaces -->
+ <string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
<!-- Ignores Wi-Fi validation failures after roam.
If validation fails on a Wi-Fi network after a roam to a new BSSID,
diff --git a/service/ServiceConnectivityResources/res/values/strings.xml b/service/ServiceConnectivityResources/res/values/strings.xml
index b2fa5f5..246155e 100644
--- a/service/ServiceConnectivityResources/res/values/strings.xml
+++ b/service/ServiceConnectivityResources/res/values/strings.xml
@@ -29,6 +29,15 @@
<!-- A notification is shown when a captive portal network is detected. This is the notification's message. -->
<string name="network_available_sign_in_detailed"><xliff:g id="network_ssid">%1$s</xliff:g></string>
+ <!-- A notification is shown when the system detected no internet access on a mobile network, possibly because the user is out of data, and a webpage is available to get Internet access (possibly by topping up or getting a subscription). This is the notification's title. -->
+ <string name="mobile_network_available_no_internet">No internet</string>
+
+ <!-- A notification is shown when the system detected no internet access on a mobile network, possibly because the user is out of data, and a webpage is available to get Internet access (possibly by topping up or getting a subscription). This is the notification's message. -->
+ <string name="mobile_network_available_no_internet_detailed">You may be out of data from <xliff:g id="network_carrier" example="Android Mobile">%1$s</xliff:g>. Tap for options.</string>
+
+ <!-- A notification is shown when the system detected no internet access on a mobile network, possibly because the user is out of data, and a webpage is available to get Internet access (possibly by topping up or getting a subscription). This is the notification's message when the carrier is unknown. -->
+ <string name="mobile_network_available_no_internet_detailed_unknown_carrier">You may be out of data. Tap for options.</string>
+
<!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's title. -->
<string name="wifi_no_internet"><xliff:g id="network_ssid" example="GoogleGuest">%1$s</xliff:g> has no internet access</string>
diff --git a/service/src/com/android/server/BpfLoaderRcUtils.java b/service/src/com/android/server/BpfLoaderRcUtils.java
new file mode 100644
index 0000000..1b6ee55
--- /dev/null
+++ b/service/src/com/android/server/BpfLoaderRcUtils.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * BpfRcUtils is responsible for comparing the bpf loader rc file.
+ *
+ * {@hide}
+ */
+public class BpfLoaderRcUtils {
+ public static final String TAG = BpfLoaderRcUtils.class.getSimpleName();
+
+ private static final List<String> BPF_LOADER_RC_S_T = List.of(
+ "service bpfloader /system/bin/bpfloader",
+ "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+ "rlimit memlock 1073741824 1073741824",
+ "oneshot",
+ "reboot_on_failure reboot,bpfloader-failed",
+ "updatable"
+ );
+
+ private static final List<String> BPF_LOADER_RC_U = List.of(
+ "service bpfloader /system/bin/bpfloader",
+ "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+ "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+ "user root",
+ "rlimit memlock 1073741824 1073741824",
+ "oneshot",
+ "reboot_on_failure reboot,bpfloader-failed",
+ "updatable"
+ );
+
+ private static final List<String> BPF_LOADER_RC_UQPR2 = List.of(
+ "service bpfloader /system/bin/netbpfload",
+ "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+ "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+ "user root",
+ "rlimit memlock 1073741824 1073741824",
+ "oneshot",
+ "reboot_on_failure reboot,bpfloader-failed",
+ "updatable"
+ );
+
+
+ private static final String BPF_LOADER_RC_FILE_PATH = "/etc/init/bpfloader.rc";
+ private static final String NET_BPF_LOAD_RC_FILE_PATH = "/etc/init/netbpfload.rc";
+
+ private BpfLoaderRcUtils() {
+ }
+
+ /**
+ * Load the bpf rc file content from the input stream.
+ */
+ @VisibleForTesting
+ public static List<String> loadExistingBpfRcFile(@NonNull InputStream inputStream) {
+ List<String> contents = new ArrayList<>();
+ boolean bpfSectionFound = false;
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ line = line.trim();
+ if (line.isEmpty()) {
+ continue;
+ }
+ if (line.startsWith("#")) {
+ continue;
+ }
+ // If bpf service section was found and new service or action section start. The
+ // read should stop.
+ if (bpfSectionFound && (line.startsWith("service ") || (line.startsWith("on ")))) {
+ break;
+ }
+ if (line.startsWith("service bpfloader ")) {
+ bpfSectionFound = true;
+ }
+ if (bpfSectionFound) {
+ contents.add(line);
+ }
+ }
+ } catch (IOException e) {
+ Log.wtf("read input stream failed.", e);
+ contents.clear();
+ return contents;
+ }
+ return contents;
+ }
+
+ /**
+ * Check the bpfLoader rc file on the system image matches any of the template files.
+ */
+ public static boolean checkBpfLoaderRc() {
+ File bpfRcFile = new File(BPF_LOADER_RC_FILE_PATH);
+ if (!bpfRcFile.exists()) {
+ if (SdkLevel.isAtLeastU()) {
+ bpfRcFile = new File(NET_BPF_LOAD_RC_FILE_PATH);
+ }
+ if (!bpfRcFile.exists()) {
+ Log.wtf(TAG,
+ "neither " + BPF_LOADER_RC_FILE_PATH + " nor " + NET_BPF_LOAD_RC_FILE_PATH
+ + " exist.");
+ return false;
+ }
+ // Check bpf rc file in U QPR2
+ return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_UQPR2);
+ }
+
+ if (SdkLevel.isAtLeastU()) {
+ // Check bpf rc file in U
+ return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_U);
+ }
+ // Check bpf rc file in S/T
+ return compareBpfLoaderRc(bpfRcFile, BPF_LOADER_RC_S_T);
+ }
+
+ private static boolean compareBpfLoaderRc(@NonNull File bpfRcFile,
+ @NonNull List<String> template) {
+ try {
+ List<String> actualContent = loadExistingBpfRcFile(new FileInputStream(bpfRcFile));
+ if (!actualContent.equals(template)) {
+ Log.wtf(TAG, "BPF rc file is not same as the template files " + actualContent);
+ return false;
+ }
+ } catch (FileNotFoundException e) {
+ Log.wtf(bpfRcFile.getPath() + " doesn't exist.", e);
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 8f29078..9268da5 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1876,6 +1876,10 @@
activityManager.registerUidFrozenStateChangedCallback(
(Runnable r) -> r.run(), frozenStateChangedCallback);
}
+
+ if (mDeps.isFeatureNotChickenedOut(mContext, LOG_BPF_RC)) {
+ mHandler.post(BpfLoaderRcUtils::checkBpfLoaderRc);
+ }
}
/**
@@ -3335,6 +3339,8 @@
public static final String ALLOW_SYSUI_CONNECTIVITY_REPORTS =
"allow_sysui_connectivity_reports";
+ public static final String LOG_BPF_RC = "log_bpf_rc_force_disable";
+
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
@@ -4784,7 +4790,7 @@
// If the Private DNS mode is opportunistic, reprogram the DNS servers
// in order to restart a validation pass from within netd.
final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig();
- if (cfg.useTls && TextUtils.isEmpty(cfg.hostname)) {
+ if (cfg.inOpportunisticMode()) {
updateDnses(nai.linkProperties, null, nai.network.getNetId());
}
}
diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java
index 894bcc4..8e6854a 100644
--- a/service/src/com/android/server/connectivity/DnsManager.java
+++ b/service/src/com/android/server/connectivity/DnsManager.java
@@ -302,7 +302,7 @@
final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
PRIVATE_DNS_OFF);
- final boolean useTls = privateDnsCfg.useTls;
+ final boolean useTls = privateDnsCfg.mode != PRIVATE_DNS_MODE_OFF;
final PrivateDnsValidationStatuses statuses =
useTls ? mPrivateDnsValidationMap.get(netId) : null;
final boolean validated = (null != statuses) && statuses.hasValidatedServer();
@@ -370,7 +370,7 @@
// networks like IMS.
final PrivateDnsConfig privateDnsCfg = mPrivateDnsMap.getOrDefault(netId,
PRIVATE_DNS_OFF);
- final boolean useTls = privateDnsCfg.useTls;
+ final boolean useTls = privateDnsCfg.mode != PRIVATE_DNS_MODE_OFF;
final boolean strictMode = privateDnsCfg.inStrictMode();
paramsParcel.netId = netId;
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index bc13592..7707122 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -243,7 +243,7 @@
details = r.getString(R.string.network_available_sign_in_detailed, name);
break;
case TRANSPORT_CELLULAR:
- title = r.getString(R.string.network_available_sign_in, 0);
+ title = r.getString(R.string.mobile_network_available_no_internet);
// TODO: Change this to pull from NetworkInfo once a printable
// name has been added to it
NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
@@ -252,8 +252,16 @@
subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
}
- details = mTelephonyManager.createForSubscriptionId(subId)
+ final String operatorName = mTelephonyManager.createForSubscriptionId(subId)
.getNetworkOperatorName();
+ if (TextUtils.isEmpty(operatorName)) {
+ details = r.getString(R.string
+ .mobile_network_available_no_internet_detailed_unknown_carrier);
+ } else {
+ details = r.getString(
+ R.string.mobile_network_available_no_internet_detailed,
+ operatorName);
+ }
break;
default:
title = r.getString(R.string.network_available_sign_in, 0);
diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java
index c473444..d94c8dc 100644
--- a/service/src/com/android/server/connectivity/NetworkRanker.java
+++ b/service/src/com/android/server/connectivity/NetworkRanker.java
@@ -17,8 +17,6 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -223,19 +221,6 @@
}
/**
- * Returns whether the scorable has any of the PRIORITIZE_* capabilities.
- *
- * These capabilities code for customer slices, and a network that has one is a customer slice.
- */
- private boolean hasPrioritizedCapability(@NonNull final Scoreable nai) {
- final NetworkCapabilities caps = nai.getCapsNoCopy();
- final long anyPrioritizeCapability =
- (1L << NET_CAPABILITY_PRIORITIZE_LATENCY)
- | (1L << NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
- return 0 != (caps.getCapabilitiesInternal() & anyPrioritizeCapability);
- }
-
- /**
* Get the best network among a list of candidates according to policy.
* @param candidates the candidates
* @param currentSatisfier the current satisfier, or null if none
@@ -339,12 +324,6 @@
// change from the previous result. If there were, it's guaranteed candidates.size() > 0
// because accepted.size() > 0 above.
- // If any network is not a slice with prioritized bandwidth or latency, don't choose one
- // that is.
- partitionInto(candidates, nai -> !hasPrioritizedCapability(nai), accepted, rejected);
- if (accepted.size() == 1) return accepted.get(0);
- if (accepted.size() > 0 && rejected.size() > 0) candidates = new ArrayList<>(accepted);
-
// 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) {
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 6325b46..9f1debc 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -191,7 +191,7 @@
java_library {
name: "net-utils-device-common-netlink",
srcs: [
- "device/com/android/net/module/util/netlink/*.java",
+ "device/com/android/net/module/util/netlink/**/*.java",
],
sdk_version: "module_current",
min_sdk_version: "30",
diff --git a/staticlibs/device/com/android/net/module/util/netlink/IpSecXfrmNetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/IpSecXfrmNetlinkMessage.java
deleted file mode 100644
index 8ad784b..0000000
--- a/staticlibs/device/com/android/net/module/util/netlink/IpSecXfrmNetlinkMessage.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.net.module.util.netlink;
-
-import androidx.annotation.NonNull;
-
-/** Base calss for XFRM netlink messages */
-// Developer notes: The Linux kernel includes a number of XFRM structs that are not standard netlink
-// attributes (e.g., xfrm_usersa_id). These structs are unlikely to change size, so this XFRM
-// netlink message implementation assumes their sizes will remain stable. If any non-attribute
-// struct size changes, it should be caught by CTS and then developers should add
-// kernel-version-based behvaiours.
-public abstract class IpSecXfrmNetlinkMessage extends NetlinkMessage {
- // TODO: STOPSHIP: b/308011229 Remove it when OsConstants.IPPROTO_ESP is exposed
- public static final int IPPROTO_ESP = 50;
-
- public IpSecXfrmNetlinkMessage(@NonNull StructNlMsgHdr header) {
- super(header);
- }
-
- // TODO: Add the support for parsing messages
-}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
index 9e1e26e..111e0ba 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
@@ -16,12 +16,17 @@
package com.android.net.module.util.netlink;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.NETLINK_XFRM;
+
import android.system.OsConstants;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage;
+
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
/**
* NetlinkMessage base class for other, more specific netlink message types.
@@ -75,6 +80,8 @@
parsed = parseInetDiagMessage(nlmsghdr, byteBuffer);
} else if (nlFamily == OsConstants.NETLINK_NETFILTER) {
parsed = parseNfMessage(nlmsghdr, byteBuffer);
+ } else if (nlFamily == NETLINK_XFRM) {
+ parsed = parseXfrmMessage(nlmsghdr, byteBuffer);
} else {
parsed = null;
}
@@ -168,4 +175,19 @@
default: return null;
}
}
+
+ @Nullable
+ private static NetlinkMessage parseXfrmMessage(
+ @NonNull final StructNlMsgHdr nlmsghdr, @NonNull final ByteBuffer byteBuffer) {
+ return (NetlinkMessage) XfrmNetlinkMessage.parseXfrmInternal(nlmsghdr, byteBuffer);
+ }
+
+ /** A convenient method to create a ByteBuffer for encoding a new message */
+ protected static ByteBuffer newNlMsgByteBuffer(int payloadLen) {
+ final int length = StructNlMsgHdr.STRUCT_SIZE + payloadLen;
+ final ByteBuffer byteBuffer = ByteBuffer.allocate(length);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ return byteBuffer;
+ }
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
index 5052cb8..ff37639 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java
@@ -128,6 +128,14 @@
nlmsg_pid = 0;
}
+ public StructNlMsgHdr(int payloadLen, short type, short flags, int seq) {
+ nlmsg_len = StructNlMsgHdr.STRUCT_SIZE + payloadLen;
+ nlmsg_type = type;
+ nlmsg_flags = flags;
+ nlmsg_seq = seq;
+ nlmsg_pid = 0;
+ }
+
/**
* Write netlink message header to ByteBuffer.
*/
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/OWNERS b/staticlibs/device/com/android/net/module/util/netlink/xfrm/OWNERS
new file mode 100644
index 0000000..fca70aa
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 685852
+file:platform/frameworks/base:main:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/staticlibs/device/com/android/net/module/util/netlink/IpSecStructXfrmAddressT.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmAddressT.java
similarity index 86%
rename from staticlibs/device/com/android/net/module/util/netlink/IpSecStructXfrmAddressT.java
rename to staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmAddressT.java
index 4c19887..cef1f56 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/IpSecStructXfrmAddressT.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmAddressT.java
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.net.module.util.netlink;
+package com.android.net.module.util.netlink.xfrm;
+
+import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN;
import android.system.OsConstants;
@@ -41,21 +43,19 @@
*
* @hide
*/
-public class IpSecStructXfrmAddressT extends Struct {
- private static final int IPV4_ADDRESS_LEN = 4;
-
+public class StructXfrmAddressT extends Struct {
public static final int STRUCT_SIZE = 16;
@Field(order = 0, type = Type.ByteArray, arraysize = STRUCT_SIZE)
public final byte[] address;
// Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
- public IpSecStructXfrmAddressT(@NonNull byte[] address) {
+ public StructXfrmAddressT(@NonNull final byte[] address) {
this.address = address.clone();
}
// Constructor to build a new message
- public IpSecStructXfrmAddressT(@NonNull InetAddress inetAddress) {
+ public StructXfrmAddressT(@NonNull final InetAddress inetAddress) {
this.address = new byte[STRUCT_SIZE];
final byte[] addressBytes = inetAddress.getAddress();
System.arraycopy(addressBytes, 0, address, 0, addressBytes.length);
@@ -67,7 +67,7 @@
if (family == OsConstants.AF_INET6) {
addressBytes = this.address;
} else if (family == OsConstants.AF_INET) {
- addressBytes = new byte[IPV4_ADDRESS_LEN];
+ addressBytes = new byte[IPV4_ADDR_LEN];
System.arraycopy(this.address, 0, addressBytes, 0, addressBytes.length);
} else {
throw new IllegalArgumentException("Invalid IP family " + family);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmId.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmId.java
new file mode 100644
index 0000000..bdcdcd8
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmId.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+
+import java.net.InetAddress;
+
+/**
+ * Struct xfrm_id
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <pre>
+ * struct xfrm_id {
+ * xfrm_address_t daddr;
+ * __be32 spi;
+ * __u8 proto;
+ * };
+ * </pre>
+ *
+ * @hide
+ */
+public class StructXfrmId extends Struct {
+ public static final int STRUCT_SIZE = 24;
+
+ @Field(order = 0, type = Type.ByteArray, arraysize = 16)
+ public final byte[] nestedStructDAddr;
+
+ @Field(order = 1, type = Type.UBE32)
+ public final long spi;
+
+ @Field(order = 2, type = Type.U8, padding = 3)
+ public final short proto;
+
+ @Computed private final StructXfrmAddressT mDestXfrmAddressT;
+
+ // Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
+ public StructXfrmId(@NonNull final byte[] nestedStructDAddr, long spi, short proto) {
+ this.nestedStructDAddr = nestedStructDAddr.clone();
+ this.spi = spi;
+ this.proto = proto;
+
+ mDestXfrmAddressT = new StructXfrmAddressT(this.nestedStructDAddr);
+ }
+
+ // Constructor to build a new message
+ public StructXfrmId(@NonNull final InetAddress destAddress, long spi, short proto) {
+ this(new StructXfrmAddressT(destAddress).writeToBytes(), spi, proto);
+ }
+
+ /** Return the destination address */
+ public InetAddress getDestAddress(int family) {
+ return mDestXfrmAddressT.getAddress(family);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCfg.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCfg.java
new file mode 100644
index 0000000..12f68c8
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCfg.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_INF;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+
+import java.math.BigInteger;
+
+/**
+ * Struct xfrm_lifetime_cfg
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <pre>
+ * struct xfrm_lifetime_cfg {
+ * __u64 soft_byte_limit;
+ * __u64 hard_byte_limit;
+ * __u64 soft_packet_limit;
+ * __u64 hard_packet_limit;
+ * __u64 soft_add_expires_seconds;
+ * __u64 hard_add_expires_seconds;
+ * __u64 soft_use_expires_seconds;
+ * __u64 hard_use_expires_seconds;
+ * };
+ * </pre>
+ *
+ * @hide
+ */
+public class StructXfrmLifetimeCfg extends Struct {
+ public static final int STRUCT_SIZE = 64;
+
+ @Field(order = 0, type = Type.U64)
+ public final BigInteger softByteLimit;
+
+ @Field(order = 1, type = Type.U64)
+ public final BigInteger hardByteLimit;
+
+ @Field(order = 2, type = Type.U64)
+ public final BigInteger softPacketLimit;
+
+ @Field(order = 3, type = Type.U64)
+ public final BigInteger hardPacketLimit;
+
+ @Field(order = 4, type = Type.U64)
+ public final BigInteger softAddExpiresSeconds;
+
+ @Field(order = 5, type = Type.U64)
+ public final BigInteger hardAddExpiresSeconds;
+
+ @Field(order = 6, type = Type.U64)
+ public final BigInteger softUseExpiresSeconds;
+
+ @Field(order = 7, type = Type.U64)
+ public final BigInteger hardUseExpiresSeconds;
+
+ // Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
+ public StructXfrmLifetimeCfg(
+ @NonNull final BigInteger softByteLimit,
+ @NonNull final BigInteger hardByteLimit,
+ @NonNull final BigInteger softPacketLimit,
+ @NonNull final BigInteger hardPacketLimit,
+ @NonNull final BigInteger softAddExpiresSeconds,
+ @NonNull final BigInteger hardAddExpiresSeconds,
+ @NonNull final BigInteger softUseExpiresSeconds,
+ @NonNull final BigInteger hardUseExpiresSeconds) {
+ this.softByteLimit = softByteLimit;
+ this.hardByteLimit = hardByteLimit;
+ this.softPacketLimit = softPacketLimit;
+ this.hardPacketLimit = hardPacketLimit;
+ this.softAddExpiresSeconds = softAddExpiresSeconds;
+ this.hardAddExpiresSeconds = hardAddExpiresSeconds;
+ this.softUseExpiresSeconds = softUseExpiresSeconds;
+ this.hardUseExpiresSeconds = hardUseExpiresSeconds;
+ }
+
+ // Constructor to build a new message
+ public StructXfrmLifetimeCfg() {
+ this(
+ XFRM_INF,
+ XFRM_INF,
+ XFRM_INF,
+ XFRM_INF,
+ BigInteger.ZERO,
+ BigInteger.ZERO,
+ BigInteger.ZERO,
+ BigInteger.ZERO);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCur.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCur.java
new file mode 100644
index 0000000..6a539c7
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCur.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+
+import java.math.BigInteger;
+
+/**
+ * Struct xfrm_lifetime_cur
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <pre>
+ * struct xfrm_lifetime_cur {
+ * __u64 bytes;
+ * __u64 packets;
+ * __u64 add_time;
+ * __u64 use_time;
+ * };
+ * </pre>
+ *
+ * @hide
+ */
+public class StructXfrmLifetimeCur extends Struct {
+ public static final int STRUCT_SIZE = 32;
+
+ @Field(order = 0, type = Type.U64)
+ public final BigInteger bytes;
+
+ @Field(order = 1, type = Type.U64)
+ public final BigInteger packets;
+
+ @Field(order = 2, type = Type.U64)
+ public final BigInteger addTime;
+
+ @Field(order = 3, type = Type.U64)
+ public final BigInteger useTime;
+
+ public StructXfrmLifetimeCur(
+ @NonNull final BigInteger bytes,
+ @NonNull final BigInteger packets,
+ @NonNull final BigInteger addTime,
+ @NonNull final BigInteger useTime) {
+ this.bytes = bytes;
+ this.packets = packets;
+ this.addTime = addTime;
+ this.useTime = useTime;
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmSelector.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmSelector.java
new file mode 100644
index 0000000..7bd2ca1
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmSelector.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * Struct xfrm_selector
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <pre>
+ * struct xfrm_selector {
+ * xfrm_address_t daddr;
+ * xfrm_address_t saddr;
+ * __be16 dport;
+ * __be16 dport_mask;
+ * __be16 sport;
+ * __be16 sport_mask;
+ * __u16 family;
+ * __u8 prefixlen_d;
+ * __u8 prefixlen_s;
+ * __u8 proto;
+ * int ifindex;
+ * __kernel_uid32_t user;
+ * };
+ * </pre>
+ *
+ * @hide
+ */
+public class StructXfrmSelector extends Struct {
+ public static final int STRUCT_SIZE = 56;
+
+ @Field(order = 0, type = Type.ByteArray, arraysize = 16)
+ public final byte[] nestedStructDAddr;
+
+ @Field(order = 1, type = Type.ByteArray, arraysize = 16)
+ public final byte[] nestedStructSAddr;
+
+ @Field(order = 2, type = Type.UBE16)
+ public final int dPort;
+
+ @Field(order = 3, type = Type.UBE16)
+ public final int dPortMask;
+
+ @Field(order = 4, type = Type.UBE16)
+ public final int sPort;
+
+ @Field(order = 5, type = Type.UBE16)
+ public final int sPortMask;
+
+ @Field(order = 6, type = Type.U16)
+ public final int selectorFamily;
+
+ @Field(order = 7, type = Type.U8, padding = 1)
+ public final short prefixlenD;
+
+ @Field(order = 8, type = Type.U8, padding = 1)
+ public final short prefixlenS;
+
+ @Field(order = 9, type = Type.U8, padding = 1)
+ public final short proto;
+
+ @Field(order = 10, type = Type.S32)
+ public final int ifIndex;
+
+ @Field(order = 11, type = Type.S32)
+ public final int user;
+
+ // Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
+ public StructXfrmSelector(
+ @NonNull final byte[] nestedStructDAddr,
+ @NonNull final byte[] nestedStructSAddr,
+ int dPort,
+ int dPortMask,
+ int sPort,
+ int sPortMask,
+ int selectorFamily,
+ short prefixlenD,
+ short prefixlenS,
+ short proto,
+ int ifIndex,
+ int user) {
+ this.nestedStructDAddr = nestedStructDAddr.clone();
+ this.nestedStructSAddr = nestedStructSAddr.clone();
+ this.dPort = dPort;
+ this.dPortMask = dPortMask;
+ this.sPort = sPort;
+ this.sPortMask = sPortMask;
+ this.selectorFamily = selectorFamily;
+ this.prefixlenD = prefixlenD;
+ this.prefixlenS = prefixlenS;
+ this.proto = proto;
+ this.ifIndex = ifIndex;
+ this.user = user;
+ }
+
+ // Constructor to build a new message
+ public StructXfrmSelector(int selectorFamily) {
+ this(
+ new byte[StructXfrmAddressT.STRUCT_SIZE],
+ new byte[StructXfrmAddressT.STRUCT_SIZE],
+ 0 /* dPort */,
+ 0 /* dPortMask */,
+ 0 /* sPort */,
+ 0 /* sPortMask */,
+ selectorFamily,
+ (short) 0 /* prefixlenD */,
+ (short) 0 /* prefixlenS */,
+ (short) 0 /* proto */,
+ 0 /* ifIndex */,
+ 0 /* user */);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/IpSecStructXfrmUsersaId.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaId.java
similarity index 76%
rename from staticlibs/device/com/android/net/module/util/netlink/IpSecStructXfrmUsersaId.java
rename to staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaId.java
index 6f7b656..5ebc69c 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/IpSecStructXfrmUsersaId.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaId.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.net.module.util.netlink;
+package com.android.net.module.util.netlink.xfrm;
import androidx.annotation.NonNull;
@@ -40,7 +40,7 @@
*
* @hide
*/
-public class IpSecStructXfrmUsersaId extends Struct {
+public class StructXfrmUsersaId extends Struct {
public static final int STRUCT_SIZE = 24;
@Field(order = 0, type = Type.ByteArray, arraysize = 16)
@@ -55,23 +55,23 @@
@Field(order = 3, type = Type.U8, padding = 1)
public final short proto;
- @Computed private final IpSecStructXfrmAddressT mDestXfrmAddressT;
+ @Computed private final StructXfrmAddressT mDestXfrmAddressT;
// Constructor that allows Strutc.parse(Class<T>, ByteBuffer) to work
- public IpSecStructXfrmUsersaId(
- @NonNull byte[] nestedStructDAddr, long spi, int family, short proto) {
+ public StructXfrmUsersaId(
+ @NonNull final byte[] nestedStructDAddr, long spi, int family, short proto) {
this.nestedStructDAddr = nestedStructDAddr.clone();
this.spi = spi;
this.family = family;
this.proto = proto;
- mDestXfrmAddressT = new IpSecStructXfrmAddressT(this.nestedStructDAddr);
+ mDestXfrmAddressT = new StructXfrmAddressT(this.nestedStructDAddr);
}
// Constructor to build a new message
- public IpSecStructXfrmUsersaId(
- @NonNull InetAddress destAddress, long spi, int family, short proto) {
- this(new IpSecStructXfrmAddressT(destAddress).writeToBytes(), spi, family, proto);
+ public StructXfrmUsersaId(
+ @NonNull final InetAddress destAddress, long spi, int family, short proto) {
+ this(new StructXfrmAddressT(destAddress).writeToBytes(), spi, family, proto);
}
/** Return the destination address */
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessage.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessage.java
new file mode 100644
index 0000000..680a7ca
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessage.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_MSG_GETSA;
+
+import android.system.OsConstants;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+/**
+ * An XfrmNetlinkMessage subclass for XFRM_MSG_GETSA messages.
+ *
+ * <p>see include/uapi/linux/xfrm.h
+ *
+ * <p>XFRM_MSG_GETSA syntax
+ *
+ * <ul>
+ * <li>TLV: xfrm_usersa_id
+ * <li>Optional Attributes: XFRMA_MARK, XFRMA_SRCADDR
+ * </ul>
+ *
+ * @hide
+ */
+public class XfrmNetlinkGetSaMessage extends XfrmNetlinkMessage {
+ @NonNull private final StructXfrmUsersaId mXfrmUsersaId;
+
+ private XfrmNetlinkGetSaMessage(
+ @NonNull final StructNlMsgHdr header, @NonNull final StructXfrmUsersaId xfrmUsersaId) {
+ super(header);
+ mXfrmUsersaId = xfrmUsersaId;
+ }
+
+ private XfrmNetlinkGetSaMessage(
+ @NonNull final StructNlMsgHdr header,
+ @NonNull final InetAddress destAddress,
+ long spi,
+ short proto) {
+ super(header);
+
+ final int family =
+ destAddress instanceof Inet4Address ? OsConstants.AF_INET : OsConstants.AF_INET6;
+ mXfrmUsersaId = new StructXfrmUsersaId(destAddress, spi, family, proto);
+ }
+
+ @Override
+ protected void packPayload(@NonNull final ByteBuffer byteBuffer) {
+ mXfrmUsersaId.writeToByteBuffer(byteBuffer);
+ }
+
+ /**
+ * Parse XFRM_MSG_GETSA message from ByteBuffer.
+ *
+ * <p>This method should be called from NetlinkMessage#parse(ByteBuffer, int) for generic
+ * message validation and processing
+ *
+ * @param nlmsghdr netlink message header.
+ * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. MUST be
+ * host order
+ */
+ @Nullable
+ static XfrmNetlinkGetSaMessage parseInternal(
+ @NonNull final StructNlMsgHdr nlmsghdr, @NonNull final ByteBuffer byteBuffer) {
+ final StructXfrmUsersaId xfrmUsersaId = Struct.parse(StructXfrmUsersaId.class, byteBuffer);
+ if (xfrmUsersaId == null) {
+ return null;
+ }
+
+ // Attributes not supported. Don't bother handling them.
+
+ return new XfrmNetlinkGetSaMessage(nlmsghdr, xfrmUsersaId);
+ }
+
+ /** A convenient method to create a XFRM_MSG_GETSA message. */
+ public static byte[] newXfrmNetlinkGetSaMessage(
+ @NonNull final InetAddress destAddress, long spi, short proto) {
+ final int payloadLen = StructXfrmUsersaId.STRUCT_SIZE;
+
+ final StructNlMsgHdr nlmsghdr =
+ new StructNlMsgHdr(payloadLen, XFRM_MSG_GETSA, NLM_F_REQUEST, 0);
+ final XfrmNetlinkGetSaMessage message =
+ new XfrmNetlinkGetSaMessage(nlmsghdr, destAddress, spi, proto);
+
+ final ByteBuffer byteBuffer = newNlMsgByteBuffer(payloadLen);
+ message.pack(byteBuffer);
+
+ return byteBuffer.array();
+ }
+
+ public StructXfrmUsersaId getStructXfrmUsersaId() {
+ return mXfrmUsersaId;
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
new file mode 100644
index 0000000..9773cd6
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.StructNlMsgHdr;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+/** Base calss for XFRM netlink messages */
+// Developer notes: The Linux kernel includes a number of XFRM structs that are not standard netlink
+// attributes (e.g., xfrm_usersa_id). These structs are unlikely to change size, so this XFRM
+// netlink message implementation assumes their sizes will remain stable. If any non-attribute
+// struct size changes, it should be caught by CTS and then developers should add
+// kernel-version-based behvaiours.
+public abstract class XfrmNetlinkMessage extends NetlinkMessage {
+ // TODO: b/312498032 Remove it when OsConstants.IPPROTO_ESP is stable
+ public static final int IPPROTO_ESP = 50;
+ // TODO: b/312498032 Remove it when OsConstants.NETLINK_XFRM is stable
+ public static final int NETLINK_XFRM = 6;
+
+ /* see include/uapi/linux/xfrm.h */
+ public static final short XFRM_MSG_NEWSA = 16;
+ public static final short XFRM_MSG_GETSA = 18;
+
+ public static final BigInteger XFRM_INF = new BigInteger("FFFFFFFFFFFFFFFF", 16);
+
+ public XfrmNetlinkMessage(@NonNull final StructNlMsgHdr header) {
+ super(header);
+ }
+
+ /**
+ * Parse XFRM message from ByteBuffer.
+ *
+ * <p>This method should be called from NetlinkMessage#parse(ByteBuffer, int) for generic
+ * message validation and processing
+ *
+ * @param nlmsghdr netlink message header.
+ * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. MUST be
+ * host order
+ */
+ @Nullable
+ public static XfrmNetlinkMessage parseXfrmInternal(
+ @NonNull final StructNlMsgHdr nlmsghdr, @NonNull final ByteBuffer byteBuffer) {
+ switch (nlmsghdr.nlmsg_type) {
+ case XFRM_MSG_GETSA:
+ return XfrmNetlinkGetSaMessage.parseInternal(nlmsghdr, byteBuffer);
+ default:
+ return null;
+ }
+ }
+
+ protected abstract void packPayload(@NonNull final ByteBuffer byteBuffer);
+
+ /** Write a XFRM message to {@link ByteBuffer}. */
+ public void pack(@NonNull final ByteBuffer byteBuffer) {
+ getHeader().pack(byteBuffer);
+ packPayload(byteBuffer);
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
index d5b4c90..8315b8f 100644
--- a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
@@ -21,15 +21,12 @@
import static android.Manifest.permission.NETWORK_STACK;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
import android.os.Binder;
import java.io.PrintWriter;
@@ -57,25 +54,6 @@
}
/**
- * Return true if the permission has system signature.
- */
- public static boolean isSystemSignaturePermission(@NonNull Context context,
- @NonNull String permission) {
- try {
- PermissionInfo permissionInfo = context.getPackageManager().getPermissionInfo(
- permission, 0 /* flags */);
- if (permissionInfo == null) {
- return false;
- }
- return "android".equals(permissionInfo.packageName)
- && permissionInfo.getProtection() == PROTECTION_SIGNATURE;
- } catch (PackageManager.NameNotFoundException ignored) {
- // Ignored the NameNotFoundException and return false
- }
- return false;
- }
-
- /**
* Return true if the context has one of give permission that is allowed
* for a particular process and user ID running in the system.
*/
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 56565ed..637a938 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -225,19 +225,6 @@
}
-java_library {
- name: "mdns_aidl_interface-lateststable-java",
- sdk_version: "module_current",
- min_sdk_version: "30",
- static_libs: [
- "mdns_aidl_interface-V1-java",
- ],
- apex_available: [
- "//apex_available:platform",
- "com.android.tethering",
- ],
-}
-
aidl_interface {
name: "mdns_aidl_interface",
local_include_dir: "binder",
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
index 028308b..c5a91a4 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
@@ -16,15 +16,12 @@
package com.android.net.module.util
-import android.Manifest.permission.INTERNET
-import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.NETWORK_STACK
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PERMISSION_DENIED
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
-import android.os.Build
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.net.module.util.PermissionUtils.checkAnyPermissionOf
@@ -144,27 +141,4 @@
Assert.fail("Exception should have not been thrown with system feature enabled")
}
}
-
- @Test
- @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
- fun testIsSystemSignaturePermission() {
- assertTrue(
- PermissionUtils.isSystemSignaturePermission(
- context,
- NETWORK_SETTINGS
- )
- )
- assertFalse(
- PermissionUtils
- .isSystemSignaturePermission(context, PERMISSION_MAINLINE_NETWORK_STACK)
- )
- assertFalse(
- PermissionUtils
- .isSystemSignaturePermission(context, "test_permission")
- )
- assertFalse(
- PermissionUtils
- .isSystemSignaturePermission(context, INTERNET)
- )
- }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/OWNERS b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/OWNERS
new file mode 100644
index 0000000..fca70aa
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 685852
+file:platform/frameworks/base:main:/services/core/java/com/android/server/vcn/OWNERS
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmIdTest.java
similarity index 71%
copy from staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java
copy to staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmIdTest.java
index 4266f68..c9741cf 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmIdTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.net.module.util.netlink;
+package com.android.net.module.util.netlink.xfrm;
-import static com.android.net.module.util.netlink.IpSecXfrmNetlinkMessage.IPPROTO_ESP;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -38,22 +38,19 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class IpSecStructXfrmUsersaIdTest {
+public class StructXfrmIdTest {
private static final String EXPECTED_HEX_STRING =
- "C0000201000000000000000000000000" + "7768440002003200";
+ "C0000201000000000000000000000000" + "53FA0FDD32000000";
private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
-
private static final InetAddress DEST_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1");
- private static final long SPI = 0x77684400;
- private static final int FAMILY = OsConstants.AF_INET;
+ private static final long SPI = 0x53fa0fdd;
private static final short PROTO = IPPROTO_ESP;
@Test
public void testEncode() throws Exception {
- final IpSecStructXfrmUsersaId struct =
- new IpSecStructXfrmUsersaId(DEST_ADDRESS, SPI, FAMILY, PROTO);
+ final StructXfrmId struct = new StructXfrmId(DEST_ADDRESS, SPI, PROTO);
- ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
+ final ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
buffer.order(ByteOrder.nativeOrder());
struct.writeToByteBuffer(buffer);
@@ -64,13 +61,10 @@
public void testDecode() throws Exception {
final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
buffer.order(ByteOrder.nativeOrder());
+ final StructXfrmId struct = StructXfrmId.parse(StructXfrmId.class, buffer);
- final IpSecStructXfrmUsersaId struct =
- IpSecStructXfrmUsersaId.parse(IpSecStructXfrmUsersaId.class, buffer);
-
- assertEquals(DEST_ADDRESS, struct.getDestAddress());
+ assertEquals(DEST_ADDRESS, struct.getDestAddress(OsConstants.AF_INET));
assertEquals(SPI, struct.spi);
- assertEquals(FAMILY, struct.family);
assertEquals(PROTO, struct.proto);
}
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCfgTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCfgTest.java
new file mode 100644
index 0000000..69360f6
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCfgTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.XFRM_INF;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructXfrmLifetimeCfgTest {
+ private static final String EXPECTED_HEX_STRING =
+ "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
+ + "00000000000000000000000000000000"
+ + "00000000000000000000000000000000";
+ private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
+
+ @Test
+ public void testEncode() throws Exception {
+ final StructXfrmLifetimeCfg struct = new StructXfrmLifetimeCfg();
+
+ final ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
+ buffer.order(ByteOrder.nativeOrder());
+ struct.writeToByteBuffer(buffer);
+
+ assertArrayEquals(EXPECTED_HEX, buffer.array());
+ }
+
+ @Test
+ public void testDecode() throws Exception {
+ final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
+ buffer.order(ByteOrder.nativeOrder());
+ final StructXfrmLifetimeCfg struct =
+ StructXfrmLifetimeCfg.parse(StructXfrmLifetimeCfg.class, buffer);
+
+ assertEquals(XFRM_INF, struct.softByteLimit);
+ assertEquals(XFRM_INF, struct.hardByteLimit);
+ assertEquals(XFRM_INF, struct.softPacketLimit);
+ assertEquals(XFRM_INF, struct.hardPacketLimit);
+ assertEquals(BigInteger.ZERO, struct.softAddExpiresSeconds);
+ assertEquals(BigInteger.ZERO, struct.hardAddExpiresSeconds);
+ assertEquals(BigInteger.ZERO, struct.softUseExpiresSeconds);
+ assertEquals(BigInteger.ZERO, struct.hardUseExpiresSeconds);
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCurTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCurTest.java
new file mode 100644
index 0000000..008c922
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmLifetimeCurTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Calendar;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructXfrmLifetimeCurTest {
+ private static final String EXPECTED_HEX_STRING =
+ "00000000000000000000000000000000" + "8CFE4265000000000000000000000000";
+ private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
+ private static final BigInteger ADD_TIME;
+
+ static {
+ final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
+ cal.set(2023, Calendar.NOVEMBER, 2, 1, 42, 36);
+ final long timestampSeconds = TimeUnit.MILLISECONDS.toSeconds(cal.getTimeInMillis());
+ ADD_TIME = BigInteger.valueOf(timestampSeconds);
+ }
+
+ @Test
+ public void testEncode() throws Exception {
+ final StructXfrmLifetimeCur struct =
+ new StructXfrmLifetimeCur(
+ BigInteger.ZERO, BigInteger.ZERO, ADD_TIME, BigInteger.ZERO);
+
+ final ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
+ buffer.order(ByteOrder.nativeOrder());
+ struct.writeToByteBuffer(buffer);
+
+ assertArrayEquals(EXPECTED_HEX, buffer.array());
+ }
+
+ @Test
+ public void testDecode() throws Exception {
+ final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
+ buffer.order(ByteOrder.nativeOrder());
+ final StructXfrmLifetimeCur struct =
+ StructXfrmLifetimeCur.parse(StructXfrmLifetimeCur.class, buffer);
+
+ assertEquals(BigInteger.ZERO, struct.bytes);
+ assertEquals(BigInteger.ZERO, struct.packets);
+ assertEquals(ADD_TIME, struct.addTime);
+ assertEquals(BigInteger.ZERO, struct.useTime);
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmSelectorTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmSelectorTest.java
new file mode 100644
index 0000000..99f3b2a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmSelectorTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.netlink.xfrm;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.HexDump;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructXfrmSelectorTest {
+ private static final String EXPECTED_HEX_STRING =
+ "00000000000000000000000000000000"
+ + "00000000000000000000000000000000"
+ + "00000000000000000200000000000000"
+ + "0000000000000000";
+ private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
+
+ private static final byte[] XFRM_ADDRESS_T_ANY_BYTES = new byte[16];
+ private static final int FAMILY = OsConstants.AF_INET;
+
+ @Test
+ public void testEncode() throws Exception {
+ final StructXfrmSelector struct = new StructXfrmSelector(FAMILY);
+
+ final ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
+ buffer.order(ByteOrder.nativeOrder());
+ struct.writeToByteBuffer(buffer);
+
+ assertArrayEquals(EXPECTED_HEX, buffer.array());
+ }
+
+ @Test
+ public void testDecode() throws Exception {
+ final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
+ buffer.order(ByteOrder.nativeOrder());
+ final StructXfrmSelector struct =
+ StructXfrmSelector.parse(StructXfrmSelector.class, buffer);
+
+ assertArrayEquals(XFRM_ADDRESS_T_ANY_BYTES, struct.nestedStructDAddr);
+ assertArrayEquals(XFRM_ADDRESS_T_ANY_BYTES, struct.nestedStructSAddr);
+ assertEquals(0, struct.dPort);
+ assertEquals(0, struct.dPortMask);
+ assertEquals(0, struct.sPort);
+ assertEquals(0, struct.sPortMask);
+ assertEquals(FAMILY, struct.selectorFamily);
+ assertEquals(0, struct.prefixlenD);
+ assertEquals(0, struct.prefixlenS);
+ assertEquals(0, struct.proto);
+ assertEquals(0, struct.ifIndex);
+ assertEquals(0, struct.user);
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
similarity index 81%
rename from staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java
rename to staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
index 4266f68..b659f62 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-package com.android.net.module.util.netlink;
+package com.android.net.module.util.netlink.xfrm;
-import static com.android.net.module.util.netlink.IpSecXfrmNetlinkMessage.IPPROTO_ESP;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -38,7 +38,7 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class IpSecStructXfrmUsersaIdTest {
+public class StructXfrmUsersaIdTest {
private static final String EXPECTED_HEX_STRING =
"C0000201000000000000000000000000" + "7768440002003200";
private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
@@ -50,10 +50,9 @@
@Test
public void testEncode() throws Exception {
- final IpSecStructXfrmUsersaId struct =
- new IpSecStructXfrmUsersaId(DEST_ADDRESS, SPI, FAMILY, PROTO);
+ final StructXfrmUsersaId struct = new StructXfrmUsersaId(DEST_ADDRESS, SPI, FAMILY, PROTO);
- ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
+ final ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
buffer.order(ByteOrder.nativeOrder());
struct.writeToByteBuffer(buffer);
@@ -65,8 +64,8 @@
final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
buffer.order(ByteOrder.nativeOrder());
- final IpSecStructXfrmUsersaId struct =
- IpSecStructXfrmUsersaId.parse(IpSecStructXfrmUsersaId.class, buffer);
+ final StructXfrmUsersaId struct =
+ StructXfrmUsersaId.parse(StructXfrmUsersaId.class, buffer);
assertEquals(DEST_ADDRESS, struct.getDestAddress());
assertEquals(SPI, struct.spi);
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java
similarity index 68%
copy from staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java
copy to staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java
index 4266f68..0ab36e7 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/IpSecStructXfrmUsersaIdTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.net.module.util.netlink;
+package com.android.net.module.util.netlink.xfrm;
-import static com.android.net.module.util.netlink.IpSecXfrmNetlinkMessage.IPPROTO_ESP;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.IPPROTO_ESP;
+import static com.android.net.module.util.netlink.xfrm.XfrmNetlinkMessage.NETLINK_XFRM;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -28,6 +29,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.HexDump;
+import com.android.net.module.util.netlink.NetlinkMessage;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,11 +40,12 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class IpSecStructXfrmUsersaIdTest {
+public class XfrmNetlinkGetSaMessageTest {
private static final String EXPECTED_HEX_STRING =
- "C0000201000000000000000000000000" + "7768440002003200";
+ "28000000120001000000000000000000"
+ + "C0000201000000000000000000000000"
+ + "7768440002003200";
private static final byte[] EXPECTED_HEX = HexDump.hexStringToByteArray(EXPECTED_HEX_STRING);
-
private static final InetAddress DEST_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1");
private static final long SPI = 0x77684400;
private static final int FAMILY = OsConstants.AF_INET;
@@ -50,27 +53,23 @@
@Test
public void testEncode() throws Exception {
- final IpSecStructXfrmUsersaId struct =
- new IpSecStructXfrmUsersaId(DEST_ADDRESS, SPI, FAMILY, PROTO);
-
- ByteBuffer buffer = ByteBuffer.allocate(EXPECTED_HEX.length);
- buffer.order(ByteOrder.nativeOrder());
- struct.writeToByteBuffer(buffer);
-
- assertArrayEquals(EXPECTED_HEX, buffer.array());
+ final byte[] result =
+ XfrmNetlinkGetSaMessage.newXfrmNetlinkGetSaMessage(DEST_ADDRESS, SPI, PROTO);
+ assertArrayEquals(EXPECTED_HEX, result);
}
@Test
public void testDecode() throws Exception {
final ByteBuffer buffer = ByteBuffer.wrap(EXPECTED_HEX);
buffer.order(ByteOrder.nativeOrder());
-
- final IpSecStructXfrmUsersaId struct =
- IpSecStructXfrmUsersaId.parse(IpSecStructXfrmUsersaId.class, buffer);
+ final XfrmNetlinkGetSaMessage message =
+ (XfrmNetlinkGetSaMessage) NetlinkMessage.parse(buffer, NETLINK_XFRM);
+ final StructXfrmUsersaId struct = message.getStructXfrmUsersaId();
assertEquals(DEST_ADDRESS, struct.getDestAddress());
assertEquals(SPI, struct.spi);
assertEquals(FAMILY, struct.family);
assertEquals(PROTO, struct.proto);
+ assertEquals(0, buffer.remaining());
}
}
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index 90b7875..0ffe81e 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -36,6 +36,7 @@
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="force-skip-system-props" value="true" />
<option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+ <option name="set-global-setting" key="low_power_standby_enabled" value="0" />
</target_preparer>
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 778f0c5..58f6d58 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -278,10 +278,8 @@
private static final int MIN_KEEPALIVE_INTERVAL = 10;
private static final int NETWORK_CALLBACK_TIMEOUT_MS = 30_000;
- // Timeout for waiting network to be validated. Set the timeout to 30s, which is more than
- // DNS timeout.
- // TODO(b/252972908): reset the original timer when aosp/2188755 is ramped up.
- private static final int LISTEN_ACTIVITY_TIMEOUT_MS = 30_000;
+ // Timeout for waiting network to be validated.
+ private static final int LISTEN_ACTIVITY_TIMEOUT_MS = 5_000;
private static final int NO_CALLBACK_TIMEOUT_MS = 100;
private static final int NETWORK_REQUEST_TIMEOUT_MS = 3000;
private static final int SOCKET_TIMEOUT_MS = 100;
diff --git a/tests/cts/net/src/android/net/cts/DnsResolverTest.java b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
index 308aead..9ff0f2f 100644
--- a/tests/cts/net/src/android/net/cts/DnsResolverTest.java
+++ b/tests/cts/net/src/android/net/cts/DnsResolverTest.java
@@ -860,4 +860,9 @@
assertEquals(DnsResolver.ERROR_SYSTEM, e.code);
}
}
+
+ @Test
+ public void testNoRawBinderAccess() {
+ assertNull(mContext.getSystemService("dnsresolver"));
+ }
}
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 9b082a4..496d163 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -255,7 +255,12 @@
na.connect()
testCallback.expectAvailableThenValidatedCallbacks(na.network, TEST_TIMEOUT_MS)
- assertEquals(2, nsInstrumentation.getRequestUrls().size)
+ val requestedSize = nsInstrumentation.getRequestUrls().size
+ if (requestedSize == 2 || (requestedSize == 1 &&
+ nsInstrumentation.getRequestUrls()[0] == httpsProbeUrl)) {
+ return
+ }
+ fail("Unexpected request urls: ${nsInstrumentation.getRequestUrls()}")
}
@Test
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 0965193..550a9ee 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -51,6 +51,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
diff --git a/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt b/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
new file mode 100644
index 0000000..2cf6b17
--- /dev/null
+++ b/tests/unit/java/com/android/server/BpfLoaderRcUtilsTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server
+
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@IgnoreUpTo(Build.VERSION_CODES.S)
+class BpfLoaderRcUtilsTest {
+ @Test
+ fun testLoadExistingBpfRcFile() {
+
+ val inputString = """
+ service a
+ # test comment
+ service bpfloader /system/bin/bpfloader
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
+ user root
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ # comment æ¼¢å—
+ reboot_on_failure reboot,bpfloader-failed
+ updatable
+
+ #test comment
+ on b
+ oneshot
+ # test comment
+ """.trimIndent()
+ val expectedResult = listOf(
+ "service bpfloader /system/bin/bpfloader",
+ "capabilities CHOWN SYS_ADMIN NET_ADMIN",
+ "group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system",
+ "user root",
+ "rlimit memlock 1073741824 1073741824",
+ "oneshot",
+ "reboot_on_failure reboot,bpfloader-failed",
+ "updatable"
+ )
+
+ assertEquals(expectedResult,
+ BpfLoaderRcUtils.loadExistingBpfRcFile(inputString.byteInputStream()))
+ }
+
+ @Test
+ fun testCheckBpfRcFile() {
+ assertTrue(BpfLoaderRcUtils.checkBpfLoaderRc())
+ }
+}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 9fad766..8f5fd7c 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -161,6 +161,7 @@
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
import static com.android.server.ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS;
import static com.android.server.ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION;
+import static com.android.server.ConnectivityService.LOG_BPF_RC;
import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM;
@@ -2157,6 +2158,8 @@
switch (name) {
case ALLOW_SYSUI_CONNECTIVITY_REPORTS:
return true;
+ case LOG_BPF_RC:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 771edb2..ad87d28 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.Manifest.permission.DEVICE_POWER;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.Manifest.permission.NETWORK_STACK;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
@@ -24,7 +25,6 @@
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -75,7 +75,6 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
import android.net.INetd;
import android.net.Network;
import android.net.mdns.aidl.DiscoveryInfo;
@@ -145,6 +144,7 @@
// TODOs:
// - test client can send requests and receive replies
// - test NSD_ON ENABLE/DISABLED listening
+@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -169,6 +169,8 @@
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule
+ public TestRule ignoreRule = new DevSdkIgnoreRule();
@Mock Context mContext;
@Mock PackageManager mPackageManager;
@Mock ContentResolver mResolver;
@@ -1696,8 +1698,8 @@
@Test
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
- public void testRegisterOffloadEngine_checkPermission()
- throws PackageManager.NameNotFoundException {
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void testRegisterOffloadEngine_checkPermission_V() {
final NsdManager client = connectClient(mService);
final OffloadEngine offloadEngine = mock(OffloadEngine.class);
doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(NETWORK_STACK);
@@ -1707,17 +1709,41 @@
doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
REGISTER_NSD_OFFLOAD_ENGINE);
- PermissionInfo permissionInfo = new PermissionInfo("");
- permissionInfo.packageName = "android";
- permissionInfo.protectionLevel = PROTECTION_SIGNATURE;
- doReturn(permissionInfo).when(mPackageManager).getPermissionInfo(
- REGISTER_NSD_OFFLOAD_ENGINE, 0);
- client.registerOffloadEngine("iface1", OffloadEngine.OFFLOAD_TYPE_REPLY,
- OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK,
- Runnable::run, offloadEngine);
- client.unregisterOffloadEngine(offloadEngine);
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
+ REGISTER_NSD_OFFLOAD_ENGINE);
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(DEVICE_POWER);
+ assertThrows(SecurityException.class,
+ () -> client.registerOffloadEngine("iface1", OffloadEngine.OFFLOAD_TYPE_REPLY,
+ OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK, Runnable::run,
+ offloadEngine));
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
+ REGISTER_NSD_OFFLOAD_ENGINE);
+ final OffloadEngine offloadEngine2 = mock(OffloadEngine.class);
+ client.registerOffloadEngine("iface2", OffloadEngine.OFFLOAD_TYPE_REPLY,
+ OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK, Runnable::run,
+ offloadEngine2);
+ client.unregisterOffloadEngine(offloadEngine2);
+ }
- // TODO: add checks to test the packageName other than android
+ @Test
+ @EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
+ @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+ public void testRegisterOffloadEngine_checkPermission_U() {
+ final NsdManager client = connectClient(mService);
+ final OffloadEngine offloadEngine = mock(OffloadEngine.class);
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(NETWORK_STACK);
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(
+ PERMISSION_MAINLINE_NETWORK_STACK);
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(NETWORK_SETTINGS);
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(
+ REGISTER_NSD_OFFLOAD_ENGINE);
+
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(DEVICE_POWER);
+ client.registerOffloadEngine("iface2", OffloadEngine.OFFLOAD_TYPE_REPLY,
+ OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK, Runnable::run,
+ offloadEngine);
+ client.unregisterOffloadEngine(offloadEngine);
}
diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
index 545ed16..44512bb 100644
--- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java
@@ -19,6 +19,7 @@
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
+import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_SPECIFIER;
import static android.net.NetworkCapabilities.MAX_TRANSPORT;
@@ -140,7 +141,8 @@
assertResolverOptionsEquals(actual.resolverOptions, expected.resolverOptions);
assertContainsExactly(actual.transportTypes, expected.transportTypes);
assertEquals(actual.meteredNetwork, expected.meteredNetwork);
- assertFieldCountEquals(17, ResolverParamsParcel.class);
+ assertEquals(actual.dohParams, expected.dohParams);
+ assertFieldCountEquals(18, ResolverParamsParcel.class);
}
@Before
@@ -328,14 +330,14 @@
public void testOverrideDefaultMode() throws Exception {
// Hard-coded default is opportunistic mode.
final PrivateDnsConfig cfgAuto = DnsManager.getPrivateDnsConfig(mCtx);
- assertTrue(cfgAuto.useTls);
+ assertEquals(PRIVATE_DNS_MODE_OPPORTUNISTIC, cfgAuto.mode);
assertEquals("", cfgAuto.hostname);
assertEquals(new InetAddress[0], cfgAuto.ips);
// Pretend a gservices push sets the default to "off".
ConnectivitySettingsManager.setPrivateDnsDefaultMode(mCtx, PRIVATE_DNS_MODE_OFF);
final PrivateDnsConfig cfgOff = DnsManager.getPrivateDnsConfig(mCtx);
- assertFalse(cfgOff.useTls);
+ assertEquals(PRIVATE_DNS_MODE_OFF, cfgOff.mode);
assertEquals("", cfgOff.hostname);
assertEquals(new InetAddress[0], cfgOff.ips);
@@ -343,7 +345,7 @@
ConnectivitySettingsManager.setPrivateDnsMode(mCtx, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
ConnectivitySettingsManager.setPrivateDnsHostname(mCtx, "strictmode.com");
final PrivateDnsConfig cfgStrict = DnsManager.getPrivateDnsConfig(mCtx);
- assertTrue(cfgStrict.useTls);
+ assertEquals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, cfgStrict.mode);
assertEquals("strictmode.com", cfgStrict.hostname);
assertEquals(new InetAddress[0], cfgStrict.ips);
}
@@ -381,6 +383,7 @@
expectedParams.transportTypes = TEST_TRANSPORT_TYPES;
expectedParams.resolverOptions = null;
expectedParams.meteredNetwork = true;
+ expectedParams.dohParams = null;
assertResolverParamsEquals(actualParams, expectedParams);
}
@@ -417,7 +420,7 @@
// The PrivateDnsConfig map is empty, so the default PRIVATE_DNS_OFF is returned.
PrivateDnsConfig privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
- assertFalse(privateDnsCfg.useTls);
+ assertEquals(PRIVATE_DNS_MODE_OFF, privateDnsCfg.mode);
assertEquals("", privateDnsCfg.hostname);
assertEquals(new InetAddress[0], privateDnsCfg.ips);
@@ -429,7 +432,7 @@
VALIDATION_RESULT_SUCCESS));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
- assertTrue(privateDnsCfg.useTls);
+ assertEquals(PRIVATE_DNS_MODE_OPPORTUNISTIC, privateDnsCfg.mode);
assertEquals("", privateDnsCfg.hostname);
assertEquals(new InetAddress[0], privateDnsCfg.ips);
@@ -437,14 +440,14 @@
mDnsManager.updatePrivateDns(network, new PrivateDnsConfig(tlsName, tlsAddrs));
mDnsManager.updatePrivateDnsStatus(TEST_NETID, lp);
privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
- assertTrue(privateDnsCfg.useTls);
+ assertEquals(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, privateDnsCfg.mode);
assertEquals(tlsName, privateDnsCfg.hostname);
assertEquals(tlsAddrs, privateDnsCfg.ips);
// The network is removed, so the PrivateDnsConfig map becomes empty again.
mDnsManager.removeNetwork(network);
privateDnsCfg = mDnsManager.getPrivateDnsConfig(network);
- assertFalse(privateDnsCfg.useTls);
+ assertEquals(PRIVATE_DNS_MODE_OFF, privateDnsCfg.mode);
assertEquals("", privateDnsCfg.hostname);
assertEquals(new InetAddress[0], privateDnsCfg.ips);
}
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index b319c30..7121ed4 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -54,6 +54,7 @@
import android.content.res.Resources;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
@@ -107,12 +108,16 @@
private static final long TEST_TIMEOUT_MS = 10_000L;
private static final long UI_AUTOMATOR_WAIT_TIME_MILLIS = TEST_TIMEOUT_MS;
- static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
- static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
- static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
+ private static final int TEST_SUB_ID = 43;
+ private static final String TEST_OPERATOR_NAME = "Test Operator";
+ private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
+ private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
+ private static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
static {
CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ CELL_CAPABILITIES.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUB_ID).build());
WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
@@ -149,6 +154,7 @@
@Mock DisplayMetrics mDisplayMetrics;
@Mock PackageManager mPm;
@Mock TelephonyManager mTelephonyManager;
+ @Mock TelephonyManager mTestSubIdTelephonyManager;
@Mock NotificationManager mNotificationManager;
@Mock NetworkAgentInfo mWifiNai;
@Mock NetworkAgentInfo mCellNai;
@@ -170,18 +176,21 @@
mVpnNai.networkInfo = mNetworkInfo;
mDisplayMetrics.density = 2.275f;
doReturn(true).when(mVpnNai).isVPN();
- when(mCtx.getResources()).thenReturn(mResources);
- when(mCtx.getPackageManager()).thenReturn(mPm);
- when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo());
+ doReturn(mResources).when(mCtx).getResources();
+ doReturn(mPm).when(mCtx).getPackageManager();
+ doReturn(new ApplicationInfo()).when(mCtx).getApplicationInfo();
final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mCtx));
doReturn(UserHandle.ALL).when(asUserCtx).getUser();
- when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx);
- when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
- .thenReturn(mNotificationManager);
- when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO);
+ doReturn(asUserCtx).when(mCtx).createContextAsUser(eq(UserHandle.ALL), anyInt());
+ doReturn(mNotificationManager).when(mCtx)
+ .getSystemService(eq(Context.NOTIFICATION_SERVICE));
+ doReturn(TEST_EXTRA_INFO).when(mNetworkInfo).getExtraInfo();
ConnectivityResources.setResourcesContextForTest(mCtx);
- when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B);
- when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+ doReturn(0xFF607D8B).when(mResources).getColor(anyInt(), any());
+ doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
+ doReturn(mTestSubIdTelephonyManager).when(mTelephonyManager)
+ .createForSubscriptionId(TEST_SUB_ID);
+ doReturn(TEST_OPERATOR_NAME).when(mTestSubIdTelephonyManager).getNetworkOperatorName();
// Come up with some credible-looking transport names. The actual values do not matter.
String[] transportNames = new String[NetworkCapabilities.MAX_TRANSPORT + 1];
@@ -532,4 +541,44 @@
R.string.wifi_no_internet, TEST_EXTRA_INFO,
R.string.wifi_no_internet_detailed);
}
+
+ private void runTelephonySignInNotificationTest(String testTitle, String testContents) {
+ final int id = 101;
+ final String tag = NetworkNotificationManager.tagFor(id);
+ mManager.showNotification(id, SIGN_IN, mCellNai, null, null, false);
+
+ final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class);
+ verify(mNotificationManager).notify(eq(tag), eq(SIGN_IN.eventId), noteCaptor.capture());
+ final Bundle noteExtras = noteCaptor.getValue().extras;
+ assertEquals(testTitle, noteExtras.getString(Notification.EXTRA_TITLE));
+ assertEquals(testContents, noteExtras.getString(Notification.EXTRA_TEXT));
+ }
+
+ @Test
+ public void testTelephonySignInNotification() {
+ final String testTitle = "Telephony no internet title";
+ final String testContents = "Add data for " + TEST_OPERATOR_NAME;
+ // The test does not use real resources as they are in the ConnectivityResources package,
+ // which is tricky to use (requires resolving the package, QUERY_ALL_PACKAGES permission).
+ doReturn(testTitle).when(mResources).getString(
+ R.string.mobile_network_available_no_internet);
+ doReturn(testContents).when(mResources).getString(
+ R.string.mobile_network_available_no_internet_detailed, TEST_OPERATOR_NAME);
+
+ runTelephonySignInNotificationTest(testTitle, testContents);
+ }
+
+ @Test
+ public void testTelephonySignInNotification_NoOperator() {
+ doReturn("").when(mTestSubIdTelephonyManager).getNetworkOperatorName();
+
+ final String testTitle = "Telephony no internet title";
+ final String testContents = "Add data";
+ doReturn(testTitle).when(mResources).getString(
+ R.string.mobile_network_available_no_internet);
+ doReturn(testContents).when(mResources).getString(
+ R.string.mobile_network_available_no_internet_detailed_unknown_carrier);
+
+ runTelephonySignInNotificationTest(testTitle, testContents);
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
index 87f7369..1e3f389 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -18,12 +18,9 @@
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL as NET_CAP_PORTAL
-import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET as NET_CAP_INTERNET
-import android.net.NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH as NET_CAP_PRIO_BW
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkScore.KEEP_CONNECTED_NONE
-import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY
import android.net.NetworkScore.POLICY_EXITING as EXITING
import android.net.NetworkScore.POLICY_TRANSPORT_PRIMARY as PRIMARY
import android.net.NetworkScore.POLICY_YIELD_TO_BAD_WIFI as YIELD_TO_BAD_WIFI
@@ -53,8 +50,8 @@
class NetworkRankerTest(private val activelyPreferBadWifi: Boolean) {
private val mRanker = NetworkRanker(NetworkRanker.Configuration(activelyPreferBadWifi))
- private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities) :
- NetworkRanker.Scoreable {
+ private class TestScore(private val sc: FullScore, private val nc: NetworkCapabilities)
+ : NetworkRanker.Scoreable {
override fun getScore() = sc
override fun getCapsNoCopy(): NetworkCapabilities = nc
}
@@ -199,41 +196,4 @@
val badExitingWifi = TestScore(score(EVER_EVALUATED, EVER_VALIDATED, EXITING), CAPS_WIFI)
assertEquals(cell, rank(cell, badExitingWifi))
}
-
- @Test
- fun testValidatedPolicyStrongerThanSlice() {
- val unvalidatedNonslice = TestScore(score(EVER_EVALUATED),
- caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
- val slice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
- caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
- assertEquals(slice, rank(slice, unvalidatedNonslice))
- }
-
- @Test
- fun testPrimaryPolicyStrongerThanSlice() {
- val nonslice = TestScore(score(EVER_EVALUATED),
- caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
- val primarySlice = TestScore(score(EVER_EVALUATED, POLICY_TRANSPORT_PRIMARY),
- caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
- assertEquals(primarySlice, rank(nonslice, primarySlice))
- }
-
- @Test
- fun testPreferNonSlices() {
- // Slices lose to non-slices for general ranking
- val nonslice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
- caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
- val slice = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
- caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
- assertEquals(nonslice, rank(slice, nonslice))
- }
-
- @Test
- fun testSlicePolicyStrongerThanTransport() {
- val nonSliceCell = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
- caps(TRANSPORT_CELLULAR, NET_CAP_INTERNET))
- val sliceWifi = TestScore(score(EVER_EVALUATED, IS_VALIDATED),
- caps(TRANSPORT_WIFI, NET_CAP_INTERNET, NET_CAP_PRIO_BW))
- assertEquals(nonSliceCell, rank(nonSliceCell, sliceWifi))
- }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
index 331a5b6..5251e2a 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsDiscoveryManagerTests.java
@@ -56,6 +56,7 @@
import java.util.concurrent.ScheduledExecutorService;
/** Tests for {@link MdnsDiscoveryManager}. */
+@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class MdnsDiscoveryManagerTests {
@@ -134,9 +135,10 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
if (thread != null) {
thread.quitSafely();
+ thread.join();
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
index 3cea5cb..b040ab6 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
@@ -49,6 +49,7 @@
private const val TEST_ELAPSED_REALTIME_MS = 123L
private const val DEFAULT_TTL_TIME_MS = 120000L
+@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class MdnsServiceCacheTest {
@@ -104,6 +105,7 @@
@After
fun tearDown() {
thread.quitSafely()
+ thread.join()
}
private fun makeFlags(isExpiredServicesRemovalEnabled: Boolean = false) =
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 26a3796..7a2e4bf 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -87,6 +87,7 @@
import java.util.stream.Stream;
/** Tests for {@link MdnsServiceTypeClient}. */
+@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class MdnsServiceTypeClientTests {
@@ -230,9 +231,10 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
if (thread != null) {
thread.quitSafely();
+ thread.join();
}
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt
index c62a081..3e1dab8 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitorTest.kt
@@ -27,6 +27,7 @@
private val LINKADDRV4 = LinkAddress("192.0.2.0/24")
private val IFACE_IDX = 32
+@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
internal class SocketNetlinkMonitorTest {
@@ -43,6 +44,7 @@
@After
fun tearDown() {
thread.quitSafely()
+ thread.join()
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index f4c62c5..958c4f2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -132,6 +132,7 @@
it[ConnectivityService.KEY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
+ it[ConnectivityService.LOG_BPF_RC] = true
}
fun enableFeature(f: String) = enabledFeatures.set(f, true)
fun disableFeature(f: String) = enabledFeatures.set(f, false)
diff --git a/thread/demoapp/Android.bp b/thread/demoapp/Android.bp
new file mode 100644
index 0000000..da7a5f8
--- /dev/null
+++ b/thread/demoapp/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "ThreadNetworkDemoApp",
+ srcs: ["java/**/*.java"],
+ min_sdk_version: "34",
+ resource_dirs: ["res"],
+ static_libs: [
+ "androidx-constraintlayout_constraintlayout",
+ "androidx.appcompat_appcompat",
+ "androidx.navigation_navigation-common",
+ "androidx.navigation_navigation-fragment",
+ "androidx.navigation_navigation-ui",
+ "com.google.android.material_material",
+ "guava",
+ ],
+ libs: [
+ "framework-connectivity-t",
+ ],
+ certificate: "platform",
+ privileged: true,
+ platform_apis: true,
+}
diff --git a/thread/demoapp/AndroidManifest.xml b/thread/demoapp/AndroidManifest.xml
new file mode 100644
index 0000000..c31bb71
--- /dev/null
+++ b/thread/demoapp/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.threadnetwork.demoapp">
+
+ <uses-sdk android:minSdkVersion="34" android:targetSdkVersion="35"/>
+ <uses-feature android:name="android.hardware.threadnetwork" android:required="true" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED" />
+
+ <application
+ android:label="ThreadNetworkDemoApp"
+ android:theme="@style/Theme.ThreadNetworkDemoApp"
+ android:icon="@mipmap/ic_launcher"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:testOnly="true">
+ <activity android:name=".MainActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/thread/demoapp/java/com/android/threadnetwork/demoapp/ConnectivityToolsFragment.java b/thread/demoapp/java/com/android/threadnetwork/demoapp/ConnectivityToolsFragment.java
new file mode 100644
index 0000000..6f616eb
--- /dev/null
+++ b/thread/demoapp/java/com/android/threadnetwork/demoapp/ConnectivityToolsFragment.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2023 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.threadnetwork.demoapp;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+
+import com.android.threadnetwork.demoapp.concurrent.BackgroundExecutorProvider;
+
+import com.google.android.material.switchmaterial.SwitchMaterial;
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.common.io.CharStreams;
+import com.google.common.net.InetAddresses;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+public final class ConnectivityToolsFragment extends Fragment {
+ private static final String TAG = "ConnectivityTools";
+
+ // This is a mirror of NetworkCapabilities#NET_CAPABILITY_LOCAL_NETWORK which is @hide for now
+ private static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
+
+ private static final Duration PING_TIMEOUT = Duration.ofSeconds(10L);
+ private static final Duration UDP_TIMEOUT = Duration.ofSeconds(10L);
+ private final ListeningScheduledExecutorService mBackgroundExecutor =
+ BackgroundExecutorProvider.getBackgroundExecutor();
+ private final ArrayList<String> mServerIpCandidates = new ArrayList<>();
+ private final ArrayList<String> mServerPortCandidates = new ArrayList<>();
+ private Executor mMainExecutor;
+
+ private ListenableFuture<String> mPingFuture;
+ private ListenableFuture<String> mUdpFuture;
+ private ArrayAdapter<String> mPingServerIpAdapter;
+ private ArrayAdapter<String> mUdpServerIpAdapter;
+ private ArrayAdapter<String> mUdpServerPortAdapter;
+
+ private Network mThreadNetwork;
+ private boolean mBindThreadNetwork = false;
+
+ private void subscribeToThreadNetwork() {
+ ConnectivityManager cm = getActivity().getSystemService(ConnectivityManager.class);
+ cm.registerNetworkCallback(
+ new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ mThreadNetwork = network;
+ }
+
+ @Override
+ public void onLost(Network network) {
+ mThreadNetwork = network;
+ }
+ },
+ new Handler(Looper.myLooper()));
+ }
+
+ private static String getPingCommand(String serverIp) {
+ try {
+ InetAddress serverAddress = InetAddresses.forString(serverIp);
+ return (serverAddress instanceof Inet6Address)
+ ? "/system/bin/ping6"
+ : "/system/bin/ping";
+ } catch (IllegalArgumentException e) {
+ // The ping command can handle the illegal argument and output error message
+ return "/system/bin/ping6";
+ }
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.connectivity_tools_fragment, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ mMainExecutor = ContextCompat.getMainExecutor(getActivity());
+
+ subscribeToThreadNetwork();
+
+ AutoCompleteTextView pingServerIpText = view.findViewById(R.id.ping_server_ip_address_text);
+ mPingServerIpAdapter =
+ new ArrayAdapter<String>(
+ getActivity(), R.layout.list_server_ip_address_view, mServerIpCandidates);
+ pingServerIpText.setAdapter(mPingServerIpAdapter);
+ TextView pingOutputText = view.findViewById(R.id.ping_output_text);
+ Button pingButton = view.findViewById(R.id.ping_button);
+
+ pingButton.setOnClickListener(
+ v -> {
+ if (mPingFuture != null) {
+ mPingFuture.cancel(/* mayInterruptIfRunning= */ true);
+ mPingFuture = null;
+ }
+
+ String serverIp = pingServerIpText.getText().toString().strip();
+ updateServerIpCandidates(serverIp);
+ pingOutputText.setText("Sending ping message to " + serverIp + "\n");
+
+ mPingFuture = sendPing(serverIp);
+ Futures.addCallback(
+ mPingFuture,
+ new FutureCallback<String>() {
+ @Override
+ public void onSuccess(String result) {
+ pingOutputText.append(result + "\n");
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ if (t instanceof CancellationException) {
+ // Ignore the cancellation error
+ return;
+ }
+ pingOutputText.append("Failed: " + t.getMessage() + "\n");
+ }
+ },
+ mMainExecutor);
+ });
+
+ AutoCompleteTextView udpServerIpText = view.findViewById(R.id.udp_server_ip_address_text);
+ mUdpServerIpAdapter =
+ new ArrayAdapter<String>(
+ getActivity(), R.layout.list_server_ip_address_view, mServerIpCandidates);
+ udpServerIpText.setAdapter(mUdpServerIpAdapter);
+ AutoCompleteTextView udpServerPortText = view.findViewById(R.id.udp_server_port_text);
+ mUdpServerPortAdapter =
+ new ArrayAdapter<String>(
+ getActivity(), R.layout.list_server_port_view, mServerPortCandidates);
+ udpServerPortText.setAdapter(mUdpServerPortAdapter);
+ TextInputEditText udpMsgText = view.findViewById(R.id.udp_message_text);
+ TextView udpOutputText = view.findViewById(R.id.udp_output_text);
+
+ SwitchMaterial switchBindThreadNetwork = view.findViewById(R.id.switch_bind_thread_network);
+ switchBindThreadNetwork.setChecked(mBindThreadNetwork);
+ switchBindThreadNetwork.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ if (isChecked) {
+ Log.i(TAG, "Binding to the Thread network");
+
+ if (mThreadNetwork == null) {
+ Log.e(TAG, "Thread network is not available");
+ Toast.makeText(
+ getActivity().getApplicationContext(),
+ "Thread network is not available",
+ Toast.LENGTH_LONG);
+ switchBindThreadNetwork.setChecked(false);
+ } else {
+ mBindThreadNetwork = true;
+ }
+ } else {
+ mBindThreadNetwork = false;
+ }
+ });
+
+ Button sendUdpButton = view.findViewById(R.id.send_udp_button);
+ sendUdpButton.setOnClickListener(
+ v -> {
+ if (mUdpFuture != null) {
+ mUdpFuture.cancel(/* mayInterruptIfRunning= */ true);
+ mUdpFuture = null;
+ }
+
+ String serverIp = udpServerIpText.getText().toString().strip();
+ String serverPort = udpServerPortText.getText().toString().strip();
+ String udpMsg = udpMsgText.getText().toString().strip();
+ updateServerIpCandidates(serverIp);
+ updateServerPortCandidates(serverPort);
+ udpOutputText.setText(
+ String.format(
+ "Sending UDP message \"%s\" to [%s]:%s",
+ udpMsg, serverIp, serverPort));
+
+ mUdpFuture = sendUdpMessage(serverIp, serverPort, udpMsg);
+ Futures.addCallback(
+ mUdpFuture,
+ new FutureCallback<String>() {
+ @Override
+ public void onSuccess(String result) {
+ udpOutputText.append("\n" + result);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ if (t instanceof CancellationException) {
+ // Ignore the cancellation error
+ return;
+ }
+ udpOutputText.append("\nFailed: " + t.getMessage());
+ }
+ },
+ mMainExecutor);
+ });
+ }
+
+ private void updateServerIpCandidates(String newServerIp) {
+ if (!mServerIpCandidates.contains(newServerIp)) {
+ mServerIpCandidates.add(0, newServerIp);
+ mPingServerIpAdapter.notifyDataSetChanged();
+ mUdpServerIpAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private void updateServerPortCandidates(String newServerPort) {
+ if (!mServerPortCandidates.contains(newServerPort)) {
+ mServerPortCandidates.add(0, newServerPort);
+ mUdpServerPortAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private ListenableFuture<String> sendPing(String serverIp) {
+ return FluentFuture.from(Futures.submit(() -> doSendPing(serverIp), mBackgroundExecutor))
+ .withTimeout(PING_TIMEOUT.getSeconds(), TimeUnit.SECONDS, mBackgroundExecutor);
+ }
+
+ private String doSendPing(String serverIp) throws IOException {
+ String pingCommand = getPingCommand(serverIp);
+ Process process =
+ new ProcessBuilder()
+ .command(pingCommand, "-c 1", serverIp)
+ .redirectErrorStream(true)
+ .start();
+
+ return CharStreams.toString(new InputStreamReader(process.getInputStream()));
+ }
+
+ private ListenableFuture<String> sendUdpMessage(
+ String serverIp, String serverPort, String msg) {
+ return FluentFuture.from(
+ Futures.submit(
+ () -> doSendUdpMessage(serverIp, serverPort, msg),
+ mBackgroundExecutor))
+ .withTimeout(UDP_TIMEOUT.getSeconds(), TimeUnit.SECONDS, mBackgroundExecutor);
+ }
+
+ private String doSendUdpMessage(String serverIp, String serverPort, String msg)
+ throws IOException {
+ SocketAddress serverAddr = new InetSocketAddress(serverIp, Integer.parseInt(serverPort));
+
+ try (DatagramSocket socket = new DatagramSocket()) {
+ if (mBindThreadNetwork && mThreadNetwork != null) {
+ mThreadNetwork.bindSocket(socket);
+ Log.i(TAG, "Successfully bind the socket to the Thread network");
+ }
+
+ socket.connect(serverAddr);
+ Log.d(TAG, "connected " + serverAddr);
+
+ byte[] msgBytes = msg.getBytes();
+ DatagramPacket packet = new DatagramPacket(msgBytes, msgBytes.length);
+
+ Log.d(TAG, String.format("Sending message to server %s: %s", serverAddr, msg));
+ socket.send(packet);
+ Log.d(TAG, "Send done");
+
+ Log.d(TAG, "Waiting for server reply");
+ socket.receive(packet);
+ return new String(packet.getData(), packet.getOffset(), packet.getLength(), UTF_8);
+ }
+ }
+}
diff --git a/thread/demoapp/java/com/android/threadnetwork/demoapp/MainActivity.java b/thread/demoapp/java/com/android/threadnetwork/demoapp/MainActivity.java
new file mode 100644
index 0000000..ef97a6c
--- /dev/null
+++ b/thread/demoapp/java/com/android/threadnetwork/demoapp/MainActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.threadnetwork.demoapp;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.drawerlayout.widget.DrawerLayout;
+import androidx.navigation.NavController;
+import androidx.navigation.fragment.NavHostFragment;
+import androidx.navigation.ui.AppBarConfiguration;
+import androidx.navigation.ui.NavigationUI;
+
+import com.google.android.material.navigation.NavigationView;
+
+public final class MainActivity extends AppCompatActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+
+ NavHostFragment navHostFragment =
+ (NavHostFragment)
+ getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
+
+ NavController navController = navHostFragment.getNavController();
+
+ DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
+ Toolbar topAppBar = findViewById(R.id.top_app_bar);
+ AppBarConfiguration appBarConfig =
+ new AppBarConfiguration.Builder(navController.getGraph())
+ .setOpenableLayout(drawerLayout)
+ .build();
+
+ NavigationUI.setupWithNavController(topAppBar, navController, appBarConfig);
+
+ NavigationView navView = findViewById(R.id.nav_view);
+ NavigationUI.setupWithNavController(navView, navController);
+ }
+
+ @Override
+ protected void onActivityResult(int request, int result, Intent data) {
+ super.onActivityResult(request, result, data);
+ }
+}
diff --git a/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java b/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java
new file mode 100644
index 0000000..e95feaf
--- /dev/null
+++ b/thread/demoapp/java/com/android/threadnetwork/demoapp/ThreadNetworkSettingsFragment.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2023 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.threadnetwork.demoapp;
+
+import static com.google.common.io.BaseEncoding.base16;
+
+import android.net.ConnectivityManager;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.RouteInfo;
+import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadNetworkController;
+import android.net.thread.ThreadNetworkException;
+import android.net.thread.ThreadNetworkManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.Executor;
+
+public final class ThreadNetworkSettingsFragment extends Fragment {
+ private static final String TAG = "ThreadNetworkSettings";
+
+ // This is a mirror of NetworkCapabilities#NET_CAPABILITY_LOCAL_NETWORK which is @hide for now
+ private static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
+
+ private ThreadNetworkController mThreadController;
+ private TextView mTextState;
+ private TextView mTextNetworkInfo;
+ private TextView mMigrateNetworkState;
+ private Executor mMainExecutor;
+
+ private int mDeviceRole;
+ private long mPartitionId;
+ private ActiveOperationalDataset mActiveDataset;
+
+ private static final byte[] DEFAULT_ACTIVE_DATASET_TLVS =
+ base16().lowerCase()
+ .decode(
+ "0e080000000000010000000300001235060004001fffe00208dae21bccb8c321c40708fdc376ead74396bb0510c52f56cd2d38a9eb7a716954f8efd939030f4f70656e5468726561642d646231390102db190410fcb737e6fd6bb1b0fed524a4496363110c0402a0f7f8");
+ private static final ActiveOperationalDataset DEFAULT_ACTIVE_DATASET =
+ ActiveOperationalDataset.fromThreadTlvs(DEFAULT_ACTIVE_DATASET_TLVS);
+
+ private static String deviceRoleToString(int mDeviceRole) {
+ switch (mDeviceRole) {
+ case ThreadNetworkController.DEVICE_ROLE_STOPPED:
+ return "Stopped";
+ case ThreadNetworkController.DEVICE_ROLE_DETACHED:
+ return "Detached";
+ case ThreadNetworkController.DEVICE_ROLE_CHILD:
+ return "Child";
+ case ThreadNetworkController.DEVICE_ROLE_ROUTER:
+ return "Router";
+ case ThreadNetworkController.DEVICE_ROLE_LEADER:
+ return "Leader";
+ default:
+ return "Unknown";
+ }
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.thread_network_settings_fragment, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ ConnectivityManager cm = getActivity().getSystemService(ConnectivityManager.class);
+ cm.registerNetworkCallback(
+ new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .build(),
+ new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ Log.i(TAG, "New Thread network is available");
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(
+ Network network, LinkProperties linkProperties) {
+ updateNetworkInfo(linkProperties);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ Log.i(TAG, "Thread network " + network + " is lost");
+ updateNetworkInfo(null /* linkProperties */);
+ }
+ },
+ new Handler(Looper.myLooper()));
+
+ mMainExecutor = ContextCompat.getMainExecutor(getActivity());
+ ThreadNetworkManager threadManager =
+ getActivity().getSystemService(ThreadNetworkManager.class);
+ if (threadManager != null) {
+ mThreadController = threadManager.getAllThreadNetworkControllers().get(0);
+ mThreadController.registerStateCallback(
+ mMainExecutor,
+ new ThreadNetworkController.StateCallback() {
+ @Override
+ public void onDeviceRoleChanged(int mDeviceRole) {
+ ThreadNetworkSettingsFragment.this.mDeviceRole = mDeviceRole;
+ updateState();
+ }
+
+ @Override
+ public void onPartitionIdChanged(long mPartitionId) {
+ ThreadNetworkSettingsFragment.this.mPartitionId = mPartitionId;
+ updateState();
+ }
+ });
+ mThreadController.registerOperationalDatasetCallback(
+ mMainExecutor,
+ newActiveDataset -> {
+ this.mActiveDataset = newActiveDataset;
+ updateState();
+ });
+ }
+
+ mTextState = (TextView) view.findViewById(R.id.text_state);
+ mTextNetworkInfo = (TextView) view.findViewById(R.id.text_network_info);
+
+ if (mThreadController == null) {
+ mTextState.setText("Thread not supported!");
+ return;
+ }
+
+ ((Button) view.findViewById(R.id.button_join_network)).setOnClickListener(v -> doJoin());
+ ((Button) view.findViewById(R.id.button_leave_network)).setOnClickListener(v -> doLeave());
+
+ mMigrateNetworkState = view.findViewById(R.id.text_migrate_network_state);
+ ((Button) view.findViewById(R.id.button_migrate_network))
+ .setOnClickListener(v -> doMigration());
+
+ updateState();
+ }
+
+ private void doJoin() {
+ mThreadController.join(
+ DEFAULT_ACTIVE_DATASET,
+ mMainExecutor,
+ new OutcomeReceiver<Void, ThreadNetworkException>() {
+ @Override
+ public void onError(ThreadNetworkException error) {
+ Log.e(TAG, "Failed to join network " + DEFAULT_ACTIVE_DATASET, error);
+ }
+
+ @Override
+ public void onResult(Void v) {
+ Log.i(TAG, "Successfully Joined");
+ }
+ });
+ }
+
+ private void doLeave() {
+ mThreadController.leave(
+ mMainExecutor,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onError(ThreadNetworkException error) {
+ Log.e(TAG, "Failed to leave network " + DEFAULT_ACTIVE_DATASET, error);
+ }
+
+ @Override
+ public void onResult(Void v) {
+ Log.i(TAG, "Successfully Left");
+ }
+ });
+ }
+
+ private void doMigration() {
+ var newActiveDataset =
+ new ActiveOperationalDataset.Builder(DEFAULT_ACTIVE_DATASET)
+ .setNetworkName("NewThreadNet")
+ .setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(Instant.now()))
+ .build();
+ var pendingDataset =
+ new PendingOperationalDataset(
+ newActiveDataset,
+ OperationalDatasetTimestamp.fromInstant(Instant.now()),
+ Duration.ofSeconds(30));
+ mThreadController.scheduleMigration(
+ pendingDataset,
+ mMainExecutor,
+ new OutcomeReceiver<Void, ThreadNetworkException>() {
+ @Override
+ public void onResult(Void v) {
+ mMigrateNetworkState.setText(
+ "Scheduled migration to network \"NewThreadNet\" in 30s");
+ // TODO: update Pending Dataset state
+ }
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ mMigrateNetworkState.setText(
+ "Failed to schedule migration: " + e.getMessage());
+ }
+ });
+ }
+
+ private void updateState() {
+ Log.i(
+ TAG,
+ String.format(
+ "Updating Thread states (mDeviceRole: %s)",
+ deviceRoleToString(mDeviceRole)));
+
+ String state =
+ String.format(
+ "Role %s\n"
+ + "Partition ID %d\n"
+ + "Network Name %s\n"
+ + "Extended PAN ID %s",
+ deviceRoleToString(mDeviceRole),
+ mPartitionId,
+ mActiveDataset != null ? mActiveDataset.getNetworkName() : null,
+ mActiveDataset != null
+ ? base16().encode(mActiveDataset.getExtendedPanId())
+ : null);
+ mTextState.setText(state);
+ }
+
+ private void updateNetworkInfo(LinkProperties linProperties) {
+ if (linProperties == null) {
+ mTextNetworkInfo.setText("");
+ return;
+ }
+
+ StringBuilder sb = new StringBuilder("Interface name:\n");
+ sb.append(linProperties.getInterfaceName() + "\n");
+ sb.append("Addresses:\n");
+ for (LinkAddress la : linProperties.getLinkAddresses()) {
+ sb.append(la + "\n");
+ }
+ sb.append("Routes:\n");
+ for (RouteInfo route : linProperties.getRoutes()) {
+ sb.append(route + "\n");
+ }
+ mTextNetworkInfo.setText(sb.toString());
+ }
+}
diff --git a/thread/demoapp/java/com/android/threadnetwork/demoapp/concurrent/BackgroundExecutorProvider.java b/thread/demoapp/java/com/android/threadnetwork/demoapp/concurrent/BackgroundExecutorProvider.java
new file mode 100644
index 0000000..d05ba73
--- /dev/null
+++ b/thread/demoapp/java/com/android/threadnetwork/demoapp/concurrent/BackgroundExecutorProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.threadnetwork.demoapp.concurrent;
+
+import androidx.annotation.GuardedBy;
+
+import com.google.common.util.concurrent.ListeningScheduledExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.util.concurrent.Executors;
+
+/** Provides executors for executing tasks in background. */
+public final class BackgroundExecutorProvider {
+ private static final int CONCURRENCY = 4;
+
+ @GuardedBy("BackgroundExecutorProvider.class")
+ private static ListeningScheduledExecutorService backgroundExecutor;
+
+ private BackgroundExecutorProvider() {}
+
+ public static synchronized ListeningScheduledExecutorService getBackgroundExecutor() {
+ if (backgroundExecutor == null) {
+ backgroundExecutor =
+ MoreExecutors.listeningDecorator(
+ Executors.newScheduledThreadPool(/* maxConcurrency= */ CONCURRENCY));
+ }
+ return backgroundExecutor;
+ }
+}
diff --git a/thread/demoapp/res/drawable/ic_launcher_foreground.xml b/thread/demoapp/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..4dd8163
--- /dev/null
+++ b/thread/demoapp/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <group android:scaleX="0.0612"
+ android:scaleY="0.0612"
+ android:translateX="23.4"
+ android:translateY="23.683332">
+ <path
+ android:pathData="M0,0h1000v1000h-1000z"
+ android:fillColor="#00FFCEC7"/>
+ <path
+ android:pathData="m630.6,954.5l-113.5,0l0,-567.2l-170.5,0c-50.6,0 -92,41.2 -92,91.9c0,50.6 41.4,91.8 92,91.8l0,113.5c-113.3,0 -205.5,-92.1 -205.5,-205.4c0,-113.3 92.2,-205.5 205.5,-205.5l170.5,0l0,-57.5c0,-94.2 76.7,-171 171.1,-171c94.2,0 170.8,76.7 170.8,171c0,94.2 -76.6,171 -170.8,171l-57.6,0l0,567.2zM630.6,273.9l57.6,0c31.7,0 57.3,-25.8 57.3,-57.5c0,-31.7 -25.7,-57.5 -57.3,-57.5c-31.8,0 -57.6,25.8 -57.6,57.5l0,57.5z"
+ android:strokeLineJoin="miter"
+ android:strokeWidth="0"
+ android:fillColor="#000000"
+ android:fillType="nonZero"
+ android:strokeColor="#00000000"
+ android:strokeLineCap="butt"/>
+ </group>
+</vector>
diff --git a/thread/demoapp/res/drawable/ic_menu_24dp.xml b/thread/demoapp/res/drawable/ic_menu_24dp.xml
new file mode 100644
index 0000000..8a4cf80
--- /dev/null
+++ b/thread/demoapp/res/drawable/ic_menu_24dp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/>
+</vector>
diff --git a/thread/demoapp/res/drawable/ic_thread_wordmark.xml b/thread/demoapp/res/drawable/ic_thread_wordmark.xml
new file mode 100644
index 0000000..babaf54
--- /dev/null
+++ b/thread/demoapp/res/drawable/ic_thread_wordmark.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="167dp"
+ android:height="31dp"
+ android:viewportWidth="167"
+ android:viewportHeight="31">
+ <path
+ android:pathData="m32.413,7.977 l3.806,0 0,9.561 11.48,0 0,-9.561 3.837,0 0,22.957 -3.837,0 0,-9.558 -11.48,0 0,9.558 -3.806,0 0,-22.957z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="m76.761,30.934 l-4.432,-7.641 -6.483,0 0,7.641 -3.807,0 0,-22.957 11.48,0c2.095,0 3.894,0.75 5.392,2.246 1.501,1.504 2.249,3.298 2.249,5.392 0,1.591 -0.453,3.034 -1.356,4.335 -0.885,1.279 -2.006,2.193 -3.376,2.747l4.732,8.236 -4.4,0zM73.519,11.812l-7.673,0 0,7.645 7.673,0c1.034,0 1.928,-0.379 2.678,-1.124 0.75,-0.752 1.126,-1.657 1.126,-2.717 0,-1.034 -0.376,-1.926 -1.126,-2.678C75.448,12.188 74.554,11.812 73.519,11.812Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="m106.945,7.977 l0,3.835 -11.478,0 0,5.757 11.478,0 0,3.807 -11.478,0 0,5.722 11.478,0 0,3.836 -15.277,0 0,-22.957 15.277,0z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="m132.325,27.08 l-10.586,0 -1.958,3.854 -4.283,0 11.517,-23.519 11.522,23.519 -4.283,0 -1.928,-3.854zM123.627,23.267 L130.404,23.267 127.014,16.013 123.627,23.267z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="m146.606,7.977 l7.638,0c1.569,0 3.044,0.304 4.436,0.907 1.387,0.609 2.608,1.437 3.656,2.485 1.047,1.047 1.869,2.266 2.479,3.653 0.609,1.391 0.909,2.866 0.909,4.435 0,1.563 -0.299,3.041 -0.909,4.432 -0.61,1.391 -1.425,2.608 -2.464,3.654 -1.037,1.05 -2.256,1.874 -3.656,2.48 -1.401,0.607 -2.882,0.91 -4.451,0.91l-7.638,0 0,-22.956zM154.244,27.098c1.06,0 2.054,-0.199 2.978,-0.599 0.925,-0.394 1.737,-0.945 2.432,-1.654 0.696,-0.702 1.241,-1.521 1.638,-2.446 0.397,-0.925 0.597,-1.907 0.597,-2.942 0,-1.037 -0.201,-2.02 -0.597,-2.948 -0.397,-0.925 -0.946,-1.737 -1.651,-2.447 -0.709,-0.703 -1.524,-1.256 -2.45,-1.653 -0.925,-0.397 -1.907,-0.597 -2.948,-0.597l-3.834,0 0,15.286 3.834,0z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="m16.491,30.934 l-3.828,0 0,-19.128 -5.749,0c-1.705,0 -3.102,1.391 -3.102,3.1 0,1.706 1.397,3.097 3.102,3.097l0,3.83c-3.821,0 -6.931,-3.106 -6.931,-6.926 0,-3.822 3.111,-6.929 6.931,-6.929l5.749,0 0,-1.938c0,-3.179 2.587,-5.766 5.77,-5.766 3.175,0 5.76,2.588 5.76,5.766 0,3.179 -2.584,5.766 -5.76,5.766l-1.942,0 0,19.128zM16.491,7.977 L18.433,7.977c1.069,0 1.934,-0.869 1.934,-1.938 0,-1.069 -0.865,-1.938 -1.934,-1.938 -1.072,0 -1.942,0.869 -1.942,1.938l0,1.938z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/thread/demoapp/res/layout/connectivity_tools_fragment.xml b/thread/demoapp/res/layout/connectivity_tools_fragment.xml
new file mode 100644
index 0000000..a1aa0d4
--- /dev/null
+++ b/thread/demoapp/res/layout/connectivity_tools_fragment.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".ConnectivityToolsFragment" >
+
+<LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp"
+ android:paddingBottom="16dp"
+ android:orientation="vertical">
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/ping_server_ip_address_layout"
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:hint="Server IP Address">
+ <AutoCompleteTextView
+ android:id="@+id/ping_server_ip_address_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="fdde:ad00:beef::ff:fe00:7400"
+ android:textSize="14sp"/>
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <Button
+ android:id="@+id/ping_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:text="Ping"
+ android:textSize="20dp"/>
+
+ <TextView
+ android:id="@+id/ping_output_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:scrollbars="vertical"
+ android:textIsSelectable="true"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:orientation="horizontal" >
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/udp_server_ip_address_layout"
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:hint="Server IP Address">
+ <AutoCompleteTextView
+ android:id="@+id/udp_server_ip_address_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="fdde:ad00:beef::ff:fe00:7400"
+ android:textSize="14sp"/>
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/udp_server_port_layout"
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="2dp"
+ android:hint="Server Port">
+ <AutoCompleteTextView
+ android:id="@+id/udp_server_port_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inputType="number"
+ android:text="12345"
+ android:textSize="14sp"/>
+ </com.google.android.material.textfield.TextInputLayout>
+ </LinearLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/udp_message_layout"
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:hint="UDP Message">
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/udp_message_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Hello Thread!"
+ android:textSize="14sp"/>
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.switchmaterial.SwitchMaterial
+ android:id="@+id/switch_bind_thread_network"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:checked="true"
+ android:text="Bind to Thread network" />
+
+ <Button
+ android:id="@+id/send_udp_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:text="Send UDP Message"
+ android:textSize="20dp"/>
+
+ <TextView
+ android:id="@+id/udp_output_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:scrollbars="vertical"
+ android:textIsSelectable="true"/>
+</LinearLayout>
+</ScrollView>
diff --git a/thread/demoapp/res/layout/list_server_ip_address_view.xml b/thread/demoapp/res/layout/list_server_ip_address_view.xml
new file mode 100644
index 0000000..1a8f02e
--- /dev/null
+++ b/thread/demoapp/res/layout/list_server_ip_address_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="?attr/textAppearanceBody2"
+ />
diff --git a/thread/demoapp/res/layout/list_server_port_view.xml b/thread/demoapp/res/layout/list_server_port_view.xml
new file mode 100644
index 0000000..1a8f02e
--- /dev/null
+++ b/thread/demoapp/res/layout/list_server_port_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="?attr/textAppearanceBody2"
+ />
diff --git a/thread/demoapp/res/layout/main_activity.xml b/thread/demoapp/res/layout/main_activity.xml
new file mode 100644
index 0000000..12072e5
--- /dev/null
+++ b/thread/demoapp/res/layout/main_activity.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<androidx.drawerlayout.widget.DrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".MainActivity">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <com.google.android.material.appbar.MaterialToolbar
+ android:id="@+id/top_app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:navigationIcon="@drawable/ic_menu_24dp" />
+
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <androidx.fragment.app.FragmentContainerView
+ android:id="@+id/nav_host_fragment"
+ android:name="androidx.navigation.fragment.NavHostFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:defaultNavHost="true"
+ app:navGraph="@navigation/nav_graph" />
+
+ </LinearLayout>
+
+ <com.google.android.material.navigation.NavigationView
+ android:id="@+id/nav_view"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:padding="16dp"
+ android:layout_gravity="start"
+ android:fitsSystemWindows="true"
+ app:headerLayout="@layout/nav_header"
+ app:menu="@menu/nav_menu" />
+
+</androidx.drawerlayout.widget.DrawerLayout>
diff --git a/thread/demoapp/res/layout/nav_header.xml b/thread/demoapp/res/layout/nav_header.xml
new file mode 100644
index 0000000..b91fb9c
--- /dev/null
+++ b/thread/demoapp/res/layout/nav_header.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="bottom"
+ android:orientation="vertical" >
+
+ <ImageView
+ android:id="@+id/nav_header_image"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/nav_header_vertical_spacing"
+ android:src="@drawable/ic_thread_wordmark" />
+</LinearLayout>
diff --git a/thread/demoapp/res/layout/thread_network_settings_fragment.xml b/thread/demoapp/res/layout/thread_network_settings_fragment.xml
new file mode 100644
index 0000000..cae46a3
--- /dev/null
+++ b/thread/demoapp/res/layout/thread_network_settings_fragment.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp"
+ android:orientation="vertical"
+ tools:context=".ThreadNetworkSettingsFragment" >
+
+ <Button android:id="@+id/button_join_network"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Join Network" />
+ <Button android:id="@+id/button_leave_network"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Leave Network" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="16dp"
+ android:textStyle="bold"
+ android:text="State" />
+ <TextView
+ android:id="@+id/text_state"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12dp"
+ android:typeface="monospace" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:textSize="16dp"
+ android:textStyle="bold"
+ android:text="Network Info" />
+ <TextView
+ android:id="@+id/text_network_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12dp" />
+
+ <Button android:id="@+id/button_migrate_network"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Migrate Network" />
+ <TextView
+ android:id="@+id/text_migrate_network_state"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12dp" />
+</LinearLayout>
diff --git a/thread/demoapp/res/menu/nav_menu.xml b/thread/demoapp/res/menu/nav_menu.xml
new file mode 100644
index 0000000..8d036c2
--- /dev/null
+++ b/thread/demoapp/res/menu/nav_menu.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/thread_network_settings"
+ android:title="Thread Network Settings" />
+ <item
+ android:id="@+id/connectivity_tools"
+ android:title="Connectivity Tools" />
+</menu>
diff --git a/thread/demoapp/res/mipmap-anydpi-v26/ic_launcher.xml b/thread/demoapp/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..b111e91
--- /dev/null
+++ b/thread/demoapp/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+ <background android:drawable="@color/white"/>
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
diff --git a/thread/demoapp/res/mipmap-anydpi-v26/ic_launcher_round.xml b/thread/demoapp/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..b111e91
--- /dev/null
+++ b/thread/demoapp/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
+ <background android:drawable="@color/white"/>
+ <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+</adaptive-icon>
diff --git a/thread/demoapp/res/mipmap-hdpi/ic_launcher.png b/thread/demoapp/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..94e778f
--- /dev/null
+++ b/thread/demoapp/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-hdpi/ic_launcher_round.png b/thread/demoapp/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..074a671
--- /dev/null
+++ b/thread/demoapp/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-mdpi/ic_launcher.png b/thread/demoapp/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..674e51f
--- /dev/null
+++ b/thread/demoapp/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-mdpi/ic_launcher_round.png b/thread/demoapp/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4e35c29
--- /dev/null
+++ b/thread/demoapp/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-xhdpi/ic_launcher.png b/thread/demoapp/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..2ee5d92
--- /dev/null
+++ b/thread/demoapp/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-xhdpi/ic_launcher_round.png b/thread/demoapp/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..78a3b7d
--- /dev/null
+++ b/thread/demoapp/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-xxhdpi/ic_launcher.png b/thread/demoapp/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..ffb6261
--- /dev/null
+++ b/thread/demoapp/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-xxhdpi/ic_launcher_round.png b/thread/demoapp/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..80fa037
--- /dev/null
+++ b/thread/demoapp/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-xxxhdpi/ic_launcher.png b/thread/demoapp/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..5ca1bfe
--- /dev/null
+++ b/thread/demoapp/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/thread/demoapp/res/mipmap-xxxhdpi/ic_launcher_round.png b/thread/demoapp/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..2fd92e3
--- /dev/null
+++ b/thread/demoapp/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/thread/demoapp/res/navigation/nav_graph.xml b/thread/demoapp/res/navigation/nav_graph.xml
new file mode 100644
index 0000000..472d1bb
--- /dev/null
+++ b/thread/demoapp/res/navigation/nav_graph.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/nav_graph"
+ app:startDestination="@+id/thread_network_settings" >
+ <fragment
+ android:id="@+id/thread_network_settings"
+ android:name=".ThreadNetworkSettingsFragment"
+ android:label="Thread Network Settings"
+ tools:layout="@layout/thread_network_settings_fragment">
+ </fragment>
+
+ <fragment
+ android:id="@+id/connectivity_tools"
+ android:name=".ConnectivityToolsFragment"
+ android:label="Connectivity Tools"
+ tools:layout="@layout/connectivity_tools_fragment">
+ </fragment>
+</navigation>
diff --git a/thread/demoapp/res/values/colors.xml b/thread/demoapp/res/values/colors.xml
new file mode 100644
index 0000000..6a65937
--- /dev/null
+++ b/thread/demoapp/res/values/colors.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources>
diff --git a/thread/demoapp/res/values/dimens.xml b/thread/demoapp/res/values/dimens.xml
new file mode 100644
index 0000000..5165951
--- /dev/null
+++ b/thread/demoapp/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<resources>
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+ <dimen name="nav_header_vertical_spacing">8dp</dimen>
+ <dimen name="nav_header_height">176dp</dimen>
+ <dimen name="fab_margin">16dp</dimen>
+</resources>
diff --git a/thread/demoapp/res/values/themes.xml b/thread/demoapp/res/values/themes.xml
new file mode 100644
index 0000000..9cb3403
--- /dev/null
+++ b/thread/demoapp/res/values/themes.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!-- Base application theme. -->
+ <style name="Theme.ThreadNetworkDemoApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
+ <!-- Primary brand color. -->
+ <item name="colorPrimary">@color/purple_500</item>
+ <item name="colorPrimaryVariant">@color/purple_700</item>
+ <item name="colorOnPrimary">@color/white</item>
+ <!-- Secondary brand color. -->
+ <item name="colorSecondary">@color/teal_200</item>
+ <item name="colorSecondaryVariant">@color/teal_700</item>
+ <item name="colorOnSecondary">@color/black</item>
+ <!-- Status bar color. -->
+ <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
+ <!-- Customize your theme here. -->
+ </style>
+</resources>
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index 51e4d88..89dcd39 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -16,7 +16,6 @@
package android.net.thread;
-import android.net.Network;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IActiveOperationalDatasetReceiver;
import android.net.thread.IOperationalDatasetCallback;
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index 5c5fda9..34b0b06 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.os.Binder;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
@@ -98,7 +99,8 @@
private final Map<OperationalDatasetCallback, OperationalDatasetCallbackProxy>
mOpDatasetCallbackMap = new HashMap<>();
- ThreadNetworkController(@NonNull IThreadNetworkController controllerService) {
+ /** @hide */
+ public ThreadNetworkController(@NonNull IThreadNetworkController controllerService) {
requireNonNull(controllerService, "controllerService cannot be null");
mControllerService = controllerService;
}
@@ -180,12 +182,22 @@
@Override
public void onDeviceRoleChanged(@DeviceRole int deviceRole) {
- mExecutor.execute(() -> mCallback.onDeviceRoleChanged(deviceRole));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onDeviceRoleChanged(deviceRole));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onPartitionIdChanged(long partitionId) {
- mExecutor.execute(() -> mCallback.onPartitionIdChanged(partitionId));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onPartitionIdChanged(partitionId));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@@ -282,13 +294,24 @@
@Override
public void onActiveOperationalDatasetChanged(
@Nullable ActiveOperationalDataset activeDataset) {
- mExecutor.execute(() -> mCallback.onActiveOperationalDatasetChanged(activeDataset));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onActiveOperationalDatasetChanged(activeDataset));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onPendingOperationalDatasetChanged(
@Nullable PendingOperationalDataset pendingDataset) {
- mExecutor.execute(() -> mCallback.onPendingOperationalDatasetChanged(pendingDataset));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallback.onPendingOperationalDatasetChanged(pendingDataset));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@@ -481,7 +504,13 @@
OutcomeReceiver<T, ThreadNetworkException> receiver,
int errorCode,
String errorMsg) {
- executor.execute(() -> receiver.onError(new ThreadNetworkException(errorCode, errorMsg)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(
+ () -> receiver.onError(new ThreadNetworkException(errorCode, errorMsg)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
private static final class ActiveDatasetReceiverProxy
@@ -498,7 +527,12 @@
@Override
public void onSuccess(ActiveOperationalDataset dataset) {
- mExecutor.execute(() -> mResultReceiver.onResult(dataset));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mResultReceiver.onResult(dataset));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
@@ -520,7 +554,12 @@
@Override
public void onSuccess() {
- mExecutor.execute(() -> mResultReceiver.onResult(null));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mResultReceiver.onResult(null));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 6862398..3cf31e5 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -45,7 +45,7 @@
libs: [
"android.test.base",
"android.test.runner",
- "framework-connectivity-flagged-apis"
+ "framework-connectivity-module-api-stubs-including-flagged"
],
// Test coverage system runs on different devices. Need to
// compile for all architectures.
diff --git a/thread/tests/unit/Android.bp b/thread/tests/unit/Android.bp
index 5863673..8092693 100644
--- a/thread/tests/unit/Android.bp
+++ b/thread/tests/unit/Android.bp
@@ -33,11 +33,11 @@
static_libs: [
"androidx.test.ext.junit",
"compatibility-device-util-axt",
- "ctstestrunner-axt",
"framework-connectivity-pre-jarjar",
"framework-connectivity-t-pre-jarjar",
"guava",
"guava-android-testlib",
+ "mockito-target-minus-junit4",
"net-tests-utils",
"truth",
],
@@ -45,6 +45,7 @@
"android.test.base",
"android.test.runner",
],
+ jarjar_rules: ":connectivity-jarjar-rules",
// Test coverage system runs on different devices. Need to
// compile for all architectures.
compile_multilib: "both",
diff --git a/thread/tests/unit/AndroidTest.xml b/thread/tests/unit/AndroidTest.xml
index 663ff74..597c6a8 100644
--- a/thread/tests/unit/AndroidTest.xml
+++ b/thread/tests/unit/AndroidTest.xml
@@ -27,6 +27,7 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.thread.unittests" />
+ <option name="hidden-api-checks" value="false"/>
<!-- Ignores tests introduced by guava-android-testlib -->
<option name="exclude-annotation" value="org.junit.Ignore"/>
</test>
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
new file mode 100644
index 0000000..2f120b2
--- /dev/null
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2023 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.thread;
+
+import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
+import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
+import static android.os.Process.SYSTEM_UID;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+
+import android.net.thread.IActiveOperationalDatasetReceiver;
+import android.net.thread.IOperationReceiver;
+import android.net.thread.IOperationalDatasetCallback;
+import android.net.thread.IStateCallback;
+import android.net.thread.IThreadNetworkController;
+import android.net.thread.ThreadNetworkController.OperationalDatasetCallback;
+import android.net.thread.ThreadNetworkController.StateCallback;
+import android.os.Binder;
+import android.os.OutcomeReceiver;
+import android.os.Process;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.time.Duration;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/** Unit tests for {@link ThreadNetworkController}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ThreadNetworkControllerTest {
+
+ @Mock private IThreadNetworkController mMockService;
+ private ThreadNetworkController mController;
+
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset new":
+ // Active Timestamp: 1
+ // Channel: 19
+ // Channel Mask: 0x07FFF800
+ // Ext PAN ID: ACC214689BC40BDF
+ // Mesh Local Prefix: fd64:db12:25f4:7e0b::/64
+ // Network Key: F26B3153760F519A63BAFDDFFC80D2AF
+ // Network Name: OpenThread-d9a0
+ // PAN ID: 0xD9A0
+ // PSKc: A245479C836D551B9CA557F7B9D351B4
+ // Security Policy: 672 onrcb
+ private static final byte[] DEFAULT_DATASET_TLVS =
+ base16().decode(
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8");
+
+ private static final ActiveOperationalDataset DEFAULT_DATASET =
+ ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = new ThreadNetworkController(mMockService);
+ }
+
+ private static void setBinderUid(int uid) {
+ // TODO: generally, it's not a good practice to depend on the implementation detail to set
+ // a custom UID, but Connectivity, Wifi, UWB and etc modules are using this trick. Maybe
+ // define a interface (e.b. CallerIdentityInjector) for easier mocking.
+ Binder.restoreCallingIdentity((((long) uid) << 32) | Binder.getCallingPid());
+ }
+
+ private static IStateCallback getStateCallback(InvocationOnMock invocation) {
+ return (IStateCallback) invocation.getArguments()[0];
+ }
+
+ private static IOperationReceiver getOperationReceiver(InvocationOnMock invocation) {
+ return (IOperationReceiver) invocation.getArguments()[0];
+ }
+
+ private static IOperationReceiver getJoinReceiver(InvocationOnMock invocation) {
+ return (IOperationReceiver) invocation.getArguments()[1];
+ }
+
+ private static IOperationReceiver getScheduleMigrationReceiver(InvocationOnMock invocation) {
+ return (IOperationReceiver) invocation.getArguments()[1];
+ }
+
+ private static IActiveOperationalDatasetReceiver getCreateDatasetReceiver(
+ InvocationOnMock invocation) {
+ return (IActiveOperationalDatasetReceiver) invocation.getArguments()[1];
+ }
+
+ private static IOperationalDatasetCallback getOperationalDatasetCallback(
+ InvocationOnMock invocation) {
+ return (IOperationalDatasetCallback) invocation.getArguments()[0];
+ }
+
+ @Test
+ public void registerStateCallback_callbackIsInvokedWithCallingAppIdentity() throws Exception {
+ setBinderUid(SYSTEM_UID);
+ doAnswer(
+ invoke -> {
+ getStateCallback(invoke).onDeviceRoleChanged(DEVICE_ROLE_CHILD);
+ return null;
+ })
+ .when(mMockService)
+ .registerStateCallback(any(IStateCallback.class));
+ AtomicInteger callbackUid = new AtomicInteger(0);
+ StateCallback callback = state -> callbackUid.set(Binder.getCallingUid());
+
+ try {
+ mController.registerStateCallback(Runnable::run, callback);
+
+ assertThat(callbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(callbackUid.get()).isEqualTo(Process.myUid());
+ } finally {
+ mController.unregisterStateCallback(callback);
+ }
+ }
+
+ @Test
+ public void registerOperationalDatasetCallback_callbackIsInvokedWithCallingAppIdentity()
+ throws Exception {
+ setBinderUid(SYSTEM_UID);
+ doAnswer(
+ invoke -> {
+ getOperationalDatasetCallback(invoke)
+ .onActiveOperationalDatasetChanged(null);
+ getOperationalDatasetCallback(invoke)
+ .onPendingOperationalDatasetChanged(null);
+ return null;
+ })
+ .when(mMockService)
+ .registerOperationalDatasetCallback(any(IOperationalDatasetCallback.class));
+ AtomicInteger activeCallbackUid = new AtomicInteger(0);
+ AtomicInteger pendingCallbackUid = new AtomicInteger(0);
+ OperationalDatasetCallback callback =
+ new OperationalDatasetCallback() {
+ @Override
+ public void onActiveOperationalDatasetChanged(
+ ActiveOperationalDataset dataset) {
+ activeCallbackUid.set(Binder.getCallingUid());
+ }
+
+ @Override
+ public void onPendingOperationalDatasetChanged(
+ PendingOperationalDataset dataset) {
+ pendingCallbackUid.set(Binder.getCallingUid());
+ }
+ };
+
+ try {
+ mController.registerOperationalDatasetCallback(Runnable::run, callback);
+
+ assertThat(activeCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(activeCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(pendingCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(pendingCallbackUid.get()).isEqualTo(Process.myUid());
+ } finally {
+ mController.unregisterOperationalDatasetCallback(callback);
+ }
+ }
+
+ @Test
+ public void createRandomizedDataset_callbackIsInvokedWithCallingAppIdentity() throws Exception {
+ setBinderUid(SYSTEM_UID);
+ AtomicInteger successCallbackUid = new AtomicInteger(0);
+ AtomicInteger errorCallbackUid = new AtomicInteger(0);
+
+ doAnswer(
+ invoke -> {
+ getCreateDatasetReceiver(invoke).onSuccess(DEFAULT_DATASET);
+ return null;
+ })
+ .when(mMockService)
+ .createRandomizedDataset(anyString(), any(IActiveOperationalDatasetReceiver.class));
+ mController.createRandomizedDataset(
+ "TestNet",
+ Runnable::run,
+ dataset -> successCallbackUid.set(Binder.getCallingUid()));
+ doAnswer(
+ invoke -> {
+ getCreateDatasetReceiver(invoke).onError(ERROR_UNSUPPORTED_CHANNEL, "");
+ return null;
+ })
+ .when(mMockService)
+ .createRandomizedDataset(anyString(), any(IActiveOperationalDatasetReceiver.class));
+ mController.createRandomizedDataset(
+ "TestNet",
+ Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(ActiveOperationalDataset dataset) {}
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ errorCallbackUid.set(Binder.getCallingUid());
+ }
+ });
+
+ assertThat(successCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(successCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
+ }
+
+ @Test
+ public void join_callbackIsInvokedWithCallingAppIdentity() throws Exception {
+ setBinderUid(SYSTEM_UID);
+ AtomicInteger successCallbackUid = new AtomicInteger(0);
+ AtomicInteger errorCallbackUid = new AtomicInteger(0);
+
+ doAnswer(
+ invoke -> {
+ getJoinReceiver(invoke).onSuccess();
+ return null;
+ })
+ .when(mMockService)
+ .join(any(ActiveOperationalDataset.class), any(IOperationReceiver.class));
+ mController.join(
+ DEFAULT_DATASET,
+ Runnable::run,
+ v -> successCallbackUid.set(Binder.getCallingUid()));
+ doAnswer(
+ invoke -> {
+ getJoinReceiver(invoke).onError(ERROR_UNAVAILABLE, "");
+ return null;
+ })
+ .when(mMockService)
+ .join(any(ActiveOperationalDataset.class), any(IOperationReceiver.class));
+ mController.join(
+ DEFAULT_DATASET,
+ Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Void unused) {}
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ errorCallbackUid.set(Binder.getCallingUid());
+ }
+ });
+
+ assertThat(successCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(successCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
+ }
+
+ @Test
+ public void scheduleMigration_callbackIsInvokedWithCallingAppIdentity() throws Exception {
+ setBinderUid(SYSTEM_UID);
+ final PendingOperationalDataset pendingDataset =
+ new PendingOperationalDataset(
+ DEFAULT_DATASET,
+ new OperationalDatasetTimestamp(100, 0, false),
+ Duration.ZERO);
+ AtomicInteger successCallbackUid = new AtomicInteger(0);
+ AtomicInteger errorCallbackUid = new AtomicInteger(0);
+
+ doAnswer(
+ invoke -> {
+ getScheduleMigrationReceiver(invoke).onSuccess();
+ return null;
+ })
+ .when(mMockService)
+ .scheduleMigration(
+ any(PendingOperationalDataset.class), any(IOperationReceiver.class));
+ mController.scheduleMigration(
+ pendingDataset, Runnable::run, v -> successCallbackUid.set(Binder.getCallingUid()));
+ doAnswer(
+ invoke -> {
+ getScheduleMigrationReceiver(invoke).onError(ERROR_UNAVAILABLE, "");
+ return null;
+ })
+ .when(mMockService)
+ .scheduleMigration(
+ any(PendingOperationalDataset.class), any(IOperationReceiver.class));
+ mController.scheduleMigration(
+ pendingDataset,
+ Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Void unused) {}
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ errorCallbackUid.set(Binder.getCallingUid());
+ }
+ });
+
+ assertThat(successCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(successCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
+ }
+
+ @Test
+ public void leave_callbackIsInvokedWithCallingAppIdentity() throws Exception {
+ setBinderUid(SYSTEM_UID);
+ AtomicInteger successCallbackUid = new AtomicInteger(0);
+ AtomicInteger errorCallbackUid = new AtomicInteger(0);
+
+ doAnswer(
+ invoke -> {
+ getOperationReceiver(invoke).onSuccess();
+ return null;
+ })
+ .when(mMockService)
+ .leave(any(IOperationReceiver.class));
+ mController.leave(Runnable::run, v -> successCallbackUid.set(Binder.getCallingUid()));
+ doAnswer(
+ invoke -> {
+ getOperationReceiver(invoke).onError(ERROR_UNAVAILABLE, "");
+ return null;
+ })
+ .when(mMockService)
+ .leave(any(IOperationReceiver.class));
+ mController.leave(
+ Runnable::run,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Void unused) {}
+
+ @Override
+ public void onError(ThreadNetworkException e) {
+ errorCallbackUid.set(Binder.getCallingUid());
+ }
+ });
+
+ assertThat(successCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(successCallbackUid.get()).isEqualTo(Process.myUid());
+ assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
+ }
+}