Merge "Rename getXXX to makeXXX in TetheringDependencies" 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..528991f 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();
}
/**
@@ -187,7 +189,10 @@
return cachedAddress;
}
- for (IpPrefix prefixRange : mTetheringPrefixes) {
+ final int prefixIndex = getStartedPrefixIndex();
+ for (int i = 0; i < mTetheringPrefixes.size(); i++) {
+ final IpPrefix prefixRange = mTetheringPrefixes.get(
+ (prefixIndex + i) % mTetheringPrefixes.size());
final LinkAddress newAddress = chooseDownstreamAddress(prefixRange);
if (newAddress != null) {
mDownstreams.add(ipServer);
@@ -200,6 +205,28 @@
return null;
}
+ private int getStartedPrefixIndex() {
+ if (!mConfig.isRandomPrefixBaseEnabled()) return 0;
+
+ final int random = getRandomInt() & 0xffffff;
+ // This is to select the starting prefix range (/8, /12, or /16) instead of the actual
+ // LinkAddress. To avoid complex operations in the selection logic and make the selected
+ // rate approximate consistency with that /8 is around 2^4 times of /12 and /12 is around
+ // 2^4 times of /16, we simply define a map between the value and the prefix value like
+ // this:
+ //
+ // Value 0 ~ 0xffff (65536/16777216 = 0.39%) -> 192.168.0.0/16
+ // Value 0x10000 ~ 0xfffff (983040/16777216 = 5.86%) -> 172.16.0.0/12
+ // Value 0x100000 ~ 0xffffff (15728640/16777216 = 93.7%) -> 10.0.0.0/8
+ if (random > 0xfffff) {
+ return 2;
+ } else if (random > 0xffff) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
private int getPrefixBaseAddress(final IpPrefix prefix) {
return inet4AddressToIntHTH((Inet4Address) prefix.getAddress());
}
@@ -263,12 +290,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 +338,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/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 502fee8..0678525 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -130,6 +130,8 @@
public static final String TETHER_ENABLE_WEAR_TETHERING =
"tether_enable_wear_tethering";
+ public static final String TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION =
+ "tether_force_random_prefix_base_selection";
/**
* Default value that used to periodic polls tether offload stats from tethering offload HAL
* to make the data warnings work.
@@ -171,6 +173,7 @@
private final int mP2pLeasesSubnetPrefixLength;
private final boolean mEnableWearTethering;
+ private final boolean mRandomPrefixBase;
private final int mUsbTetheringFunction;
protected final ContentResolver mContentResolver;
@@ -288,6 +291,8 @@
mEnableWearTethering = shouldEnableWearTethering(ctx);
+ mRandomPrefixBase = mDeps.isFeatureEnabled(ctx, TETHER_FORCE_RANDOM_PREFIX_BASE_SELECTION);
+
configLog.log(toString());
}
@@ -376,6 +381,10 @@
return mEnableWearTethering;
}
+ public boolean isRandomPrefixBaseEnabled() {
+ return mRandomPrefixBase;
+ }
+
/** Does the dumping.*/
public void dump(PrintWriter pw) {
pw.print("activeDataSubId: ");
@@ -426,6 +435,9 @@
pw.print("mUsbTetheringFunction: ");
pw.println(isUsingNcm() ? "NCM" : "RNDIS");
+
+ pw.print("mRandomPrefixBase: ");
+ pw.println(mRandomPrefixBase);
}
/** Returns the string representation of this object.*/
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index 6ebd6ae..2298a1a 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -104,6 +105,7 @@
when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mConnectivityMgr);
when(mConnectivityMgr.getAllNetworks()).thenReturn(mAllNetworks);
when(mConfig.shouldEnableWifiP2pDedicatedIp()).thenReturn(false);
+ when(mConfig.isRandomPrefixBaseEnabled()).thenReturn(false);
setUpIpServers();
mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
}
@@ -572,4 +574,41 @@
assertEquals("Wrong local hotspot prefix: ", new LinkAddress("192.168.134.5/24"),
localHotspotAddress);
}
+
+ @Test
+ public void testStartedPrefixRange() throws Exception {
+ when(mConfig.isRandomPrefixBaseEnabled()).thenReturn(true);
+
+ startedPrefixBaseTest("192.168.0.0/16", 0);
+
+ startedPrefixBaseTest("192.168.0.0/16", 1);
+
+ startedPrefixBaseTest("192.168.0.0/16", 0xffff);
+
+ startedPrefixBaseTest("172.16.0.0/12", 0x10000);
+
+ startedPrefixBaseTest("172.16.0.0/12", 0x11111);
+
+ startedPrefixBaseTest("172.16.0.0/12", 0xfffff);
+
+ startedPrefixBaseTest("10.0.0.0/8", 0x100000);
+
+ startedPrefixBaseTest("10.0.0.0/8", 0x1fffff);
+
+ startedPrefixBaseTest("10.0.0.0/8", 0xffffff);
+
+ startedPrefixBaseTest("192.168.0.0/16", 0x1000000);
+ }
+
+ private void startedPrefixBaseTest(final String expected, final int randomIntForPrefixBase)
+ throws Exception {
+ mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext, mConfig));
+ when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomIntForPrefixBase);
+ final LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
+ CONNECTIVITY_SCOPE_GLOBAL, false /* useLastAddress */);
+ final IpPrefix prefixBase = new IpPrefix(expected);
+ assertTrue(address + " is not part of " + prefixBase,
+ prefixBase.containsPrefix(asIpPrefix(address)));
+
+ }
}
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/api/system-current.txt b/framework-t/api/system-current.txt
index 05cf9e8..3513573 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -378,11 +378,11 @@
package android.net.nsd {
public final class NsdManager {
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void registerOffloadEngine(@NonNull String, long, long, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.OffloadEngine);
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void unregisterOffloadEngine(@NonNull android.net.nsd.OffloadEngine);
+ method @FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void registerOffloadEngine(@NonNull String, long, long, @NonNull java.util.concurrent.Executor, @NonNull android.net.nsd.OffloadEngine);
+ method @FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void unregisterOffloadEngine(@NonNull android.net.nsd.OffloadEngine);
}
- public interface OffloadEngine {
+ @FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") public interface OffloadEngine {
method public void onOffloadServiceRemoved(@NonNull android.net.nsd.OffloadServiceInfo);
method public void onOffloadServiceUpdated(@NonNull android.net.nsd.OffloadServiceInfo);
field public static final int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1; // 0x1
@@ -391,7 +391,7 @@
field public static final int OFFLOAD_TYPE_REPLY = 1; // 0x1
}
- public final class OffloadServiceInfo implements android.os.Parcelable {
+ @FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api") public final class OffloadServiceInfo implements android.os.Parcelable {
ctor public OffloadServiceInfo(@NonNull android.net.nsd.OffloadServiceInfo.Key, @NonNull java.util.List<java.lang.String>, @NonNull String, @Nullable byte[], @IntRange(from=0, to=java.lang.Integer.MAX_VALUE) int, long);
method public int describeContents();
method @NonNull public String getHostname();
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index dae8914..bf01a9d 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -22,6 +22,7 @@
import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND;
import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -143,6 +144,14 @@
private static final String TAG = NsdManager.class.getSimpleName();
private static final boolean DBG = false;
+ // TODO : remove this class when udc-mainline-prod is abandoned and android.net.flags.Flags is
+ // available here
+ /** @hide */
+ public static class Flags {
+ static final String REGISTER_NSD_OFFLOAD_ENGINE_API =
+ "com.android.net.flags.register_nsd_offload_engine_api";
+ }
+
/**
* Broadcast intent action to indicate whether network service discovery is
* enabled or disabled. An extra {@link #EXTRA_NSD_STATE} provides the state
@@ -365,6 +374,7 @@
*
* @hide
*/
+ @FlaggedApi(NsdManager.Flags.REGISTER_NSD_OFFLOAD_ENGINE_API)
@SystemApi
@RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
NETWORK_STACK})
@@ -402,6 +412,7 @@
*
* @hide
*/
+ @FlaggedApi(NsdManager.Flags.REGISTER_NSD_OFFLOAD_ENGINE_API)
@SystemApi
@RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK,
NETWORK_STACK})
diff --git a/framework-t/src/android/net/nsd/OffloadEngine.java b/framework-t/src/android/net/nsd/OffloadEngine.java
index b566b13..9015985 100644
--- a/framework-t/src/android/net/nsd/OffloadEngine.java
+++ b/framework-t/src/android/net/nsd/OffloadEngine.java
@@ -16,6 +16,7 @@
package android.net.nsd;
+import android.annotation.FlaggedApi;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -33,6 +34,7 @@
*
* @hide
*/
+@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")
@SystemApi
public interface OffloadEngine {
/**
diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
index d5dbf19..98dc83a 100644
--- a/framework-t/src/android/net/nsd/OffloadServiceInfo.java
+++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java
@@ -16,6 +16,7 @@
package android.net.nsd;
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -39,6 +40,7 @@
*
* @hide
*/
+@FlaggedApi("com.android.net.flags.register_nsd_offload_engine_api")
@SystemApi
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public final class OffloadServiceInfo implements Parcelable {
diff --git a/framework/Android.bp b/framework/Android.bp
index 73a1ed0..10acbd0 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -268,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: [
@@ -281,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) " +
@@ -294,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",
@@ -302,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" +
@@ -317,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/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 4e9087c..574ab2f 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -756,11 +756,20 @@
}
final ConnectivityManager cm = (ConnectivityManager) mInitialConfiguration.context
.getSystemService(Context.CONNECTIVITY_SERVICE);
- mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
- new NetworkInfo(mInitialConfiguration.info),
- mInitialConfiguration.properties, mInitialConfiguration.capabilities,
- mInitialConfiguration.localNetworkConfig, mInitialConfiguration.score,
- mInitialConfiguration.config, providerId);
+ if (mInitialConfiguration.localNetworkConfig == null) {
+ // Call registerNetworkAgent without localNetworkConfig argument to pass
+ // android.net.cts.NetworkAgentTest#testAgentStartsInConnecting in old cts
+ mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
+ new NetworkInfo(mInitialConfiguration.info),
+ mInitialConfiguration.properties, mInitialConfiguration.capabilities,
+ mInitialConfiguration.score, mInitialConfiguration.config, providerId);
+ } else {
+ mNetwork = cm.registerNetworkAgent(new NetworkAgentBinder(mHandler),
+ new NetworkInfo(mInitialConfiguration.info),
+ mInitialConfiguration.properties, mInitialConfiguration.capabilities,
+ mInitialConfiguration.localNetworkConfig, mInitialConfiguration.score,
+ mInitialConfiguration.config, providerId);
+ }
mInitialConfiguration = null; // All this memory can now be GC'd
}
return mNetwork;
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index a7a4059..a00c363 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -53,14 +53,10 @@
bpf_attach_type type) {
unique_fd cgroupProg(retrieveProgram(programPath));
if (!cgroupProg.ok()) {
- const int err = errno;
- ALOGE("Failed to get program from %s: %s", programPath, strerror(err));
- return statusFromErrno(err, "cgroup program get failed");
+ return statusFromErrno(errno, fmt::format("Failed to get program from {}", programPath));
}
if (android::bpf::attachProgram(type, cgroupProg, cgroupFd)) {
- const int err = errno;
- ALOGE("Program from %s attach failed: %s", programPath, strerror(err));
- return statusFromErrno(err, "program attach failed");
+ return statusFromErrno(errno, fmt::format("Program {} attach failed", programPath));
}
return netdutils::status::ok;
}
@@ -68,31 +64,39 @@
static Status checkProgramAccessible(const char* programPath) {
unique_fd prog(retrieveProgram(programPath));
if (!prog.ok()) {
- const int err = errno;
- ALOGE("Failed to get program from %s: %s", programPath, strerror(err));
- return statusFromErrno(err, "program retrieve failed");
+ return statusFromErrno(errno, fmt::format("Failed to get program from {}", programPath));
}
return netdutils::status::ok;
}
static Status initPrograms(const char* cg2_path) {
+ if (!cg2_path) return Status("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()) return Status("S- platform is unsupported");
// 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)) {
+ return Status("kernel version < 4.9.0 is unsupported");
+ }
// 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)) {
+ return Status("U+ platform with kernel version < 4.14.0 is unsupported");
+ }
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)) {
+ return Status("V+ platform with kernel version < 4.19.0 is unsupported");
+ }
// 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)) {
+ return Status("V+ platform with 32 bit kernel, version >= 5.16.0 is unsupported");
+ }
}
// Linux 6.1 is highest version supported by U, starting with V new kernels,
@@ -102,10 +106,14 @@
// 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)) {
+ return Status("32 bit userspace with Kernel version >= 6.2.0 is unsupported");
+ }
// 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")) {
+ return Status("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
+ }
unique_fd cg_fd(open(cg2_path, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
if (!cg_fd.ok()) {
diff --git a/netd/NetdUpdatable.cpp b/netd/NetdUpdatable.cpp
index 41b1fdb..8b9e5a7 100644
--- a/netd/NetdUpdatable.cpp
+++ b/netd/NetdUpdatable.cpp
@@ -31,7 +31,7 @@
android::netdutils::Status ret = sBpfHandler.init(cg2_path);
if (!android::netdutils::isOk(ret)) {
- LOG(ERROR) << __func__ << ": BPF handler init failed";
+ LOG(ERROR) << __func__ << ": Failed. " << ret.code() << " " << ret.msg();
return -ret.code();
}
return 0;
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index ee5f25b..2640332 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;
@@ -90,6 +91,7 @@
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.ExecutorProvider;
import com.android.server.connectivity.mdns.MdnsAdvertiser;
+import com.android.server.connectivity.mdns.MdnsAdvertisingOptions;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
import com.android.server.connectivity.mdns.MdnsFeatureFlags;
import com.android.server.connectivity.mdns.MdnsInterfaceSocket;
@@ -849,7 +851,9 @@
// service type would generate service instance names like
// Name._subtype._sub._type._tcp, which is incorrect
// (it should be Name._type._tcp).
- mAdvertiser.addService(transactionId, serviceInfo, typeSubtype.second);
+ mAdvertiser.addOrUpdateService(transactionId, serviceInfo,
+ typeSubtype.second,
+ MdnsAdvertisingOptions.newBuilder().build());
storeAdvertiserRequestMap(clientRequestId, transactionId, clientInfo,
serviceInfo.getNetwork());
} else {
@@ -2104,11 +2108,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/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index 1582fb6..c4d3338 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -32,6 +32,7 @@
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
@@ -122,17 +123,22 @@
return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
- int numQuestions = 0;
+ final List<MdnsRecord> questions = new ArrayList<>();
if (sendDiscoveryQueries) {
- numQuestions++; // Base service type
- if (!subtypes.isEmpty()) {
- numQuestions += subtypes.size();
+ // Base service type
+ questions.add(new MdnsPointerRecord(serviceTypeLabels, expectUnicastResponse));
+ for (String subtype : subtypes) {
+ final String[] labels = new String[serviceTypeLabels.length + 2];
+ labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
+ labels[1] = MdnsConstants.SUBTYPE_LABEL;
+ System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
+
+ questions.add(new MdnsPointerRecord(labels, expectUnicastResponse));
}
}
// List of (name, type) to query
- final ArrayList<Pair<String[], Integer>> missingKnownAnswerRecords = new ArrayList<>();
final long now = clock.elapsedRealtime();
for (MdnsResponse response : servicesToResolve) {
final String[] serviceName = response.getServiceName();
@@ -142,13 +148,13 @@
boolean renewSrv = !response.hasServiceRecord() || MdnsUtils.isRecordRenewalNeeded(
response.getServiceRecord(), now);
if (renewSrv && renewTxt) {
- missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_ANY));
+ questions.add(new MdnsAnyRecord(serviceName, expectUnicastResponse));
} else {
if (renewTxt) {
- missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
+ questions.add(new MdnsTextRecord(serviceName, expectUnicastResponse));
}
if (renewSrv) {
- missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
+ questions.add(new MdnsServiceRecord(serviceName, expectUnicastResponse));
// The hostname is not yet known, so queries for address records will be
// sent the next time the EnqueueMdnsQueryCallable is enqueued if the reply
// does not contain them. In practice, advertisers should include the
@@ -157,46 +163,27 @@
} else if (!response.hasInet4AddressRecord()
&& !response.hasInet6AddressRecord()) {
final String[] host = response.getServiceRecord().getServiceHost();
- missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));
- missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_AAAA));
+ questions.add(new MdnsInetAddressRecord(
+ host, MdnsRecord.TYPE_A, expectUnicastResponse));
+ questions.add(new MdnsInetAddressRecord(
+ host, MdnsRecord.TYPE_AAAA, expectUnicastResponse));
}
}
}
- numQuestions += missingKnownAnswerRecords.size();
- if (numQuestions == 0) {
+ if (questions.size() == 0) {
// No query to send
return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
}
- // Header.
- packetWriter.writeUInt16(transactionId); // transaction ID
- packetWriter.writeUInt16(MdnsConstants.FLAGS_QUERY); // flags
- packetWriter.writeUInt16(numQuestions); // number of questions
- packetWriter.writeUInt16(0); // number of answers (not yet known; will be written later)
- packetWriter.writeUInt16(0); // number of authority entries
- packetWriter.writeUInt16(0); // number of additional records
-
- // Question(s) for missing records on known answers
- for (Pair<String[], Integer> question : missingKnownAnswerRecords) {
- writeQuestion(question.first, question.second);
- }
-
- // Question(s) for discovering other services with the type. There will be one question
- // for each (fqdn+subtype, recordType) combination, as well as one for each (fqdn,
- // recordType) combination.
- if (sendDiscoveryQueries) {
- for (String subtype : subtypes) {
- String[] labels = new String[serviceTypeLabels.length + 2];
- labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
- labels[1] = MdnsConstants.SUBTYPE_LABEL;
- System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
-
- writeQuestion(labels, MdnsRecord.TYPE_PTR);
- }
- writeQuestion(serviceTypeLabels, MdnsRecord.TYPE_PTR);
- }
-
+ final MdnsPacket queryPacket = new MdnsPacket(
+ transactionId,
+ MdnsConstants.FLAGS_QUERY,
+ questions,
+ Collections.emptyList(), /* answers */
+ Collections.emptyList(), /* authorityRecords */
+ Collections.emptyList() /* additionalRecords */);
+ MdnsUtils.writeMdnsPacket(packetWriter, queryPacket);
sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT);
for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
sendPacketToIpv4AndIpv6(requestSender, emulatorPort);
@@ -209,14 +196,6 @@
}
}
- private void writeQuestion(String[] labels, int type) throws IOException {
- packetWriter.writeLabels(labels);
- packetWriter.writeUInt16(type);
- packetWriter.writeUInt16(
- MdnsConstants.QCLASS_INTERNET
- | (expectUnicastResponse ? MdnsConstants.QCLASS_UNICAST : 0));
- }
-
private void sendPacket(MdnsSocketClientBase requestSender, InetSocketAddress address)
throws IOException {
DatagramPacket packet = packetWriter.getPacket(address);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
index 28e3924..fc0e11b 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java
@@ -43,6 +43,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
@@ -342,16 +343,16 @@
}
/**
- * Add a service.
+ * Add a service to advertise.
*
* Conflicts must be checked via {@link #getConflictingService} before attempting to add.
*/
- void addService(int id, Registration registration) {
+ void addService(int id, @NonNull Registration registration) {
mPendingRegistrations.put(id, registration);
for (int i = 0; i < mAdvertisers.size(); i++) {
try {
- mAdvertisers.valueAt(i).addService(
- id, registration.getServiceInfo(), registration.getSubtype());
+ mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo(),
+ registration.getSubtype());
} catch (NameConflictException e) {
mSharedLog.wtf("Name conflict adding services that should have unique names",
e);
@@ -359,6 +360,17 @@
}
}
+ /**
+ * Update an already registered service.
+ * The caller is expected to check that the service being updated doesn't change its name
+ */
+ void updateService(int id, @NonNull Registration registration) {
+ mPendingRegistrations.put(id, registration);
+ for (int i = 0; i < mAdvertisers.size(); i++) {
+ mAdvertisers.valueAt(i).updateService(id, registration.getSubtype());
+ }
+ }
+
void removeService(int id) {
mPendingRegistrations.remove(id);
for (int i = 0; i < mAdvertisers.size(); i++) {
@@ -474,7 +486,8 @@
@NonNull
private NsdServiceInfo mServiceInfo;
@Nullable
- private final String mSubtype;
+ private String mSubtype;
+
int mConflictDuringProbingCount;
int mConflictAfterProbingCount;
@@ -485,6 +498,22 @@
}
/**
+ * Matches between the NsdServiceInfo in the Registration and the provided argument.
+ */
+ public boolean matches(@Nullable NsdServiceInfo newInfo) {
+ return Objects.equals(newInfo.getServiceName(), mOriginalName) && Objects.equals(
+ newInfo.getServiceType(), mServiceInfo.getServiceType()) && Objects.equals(
+ newInfo.getNetwork(), mServiceInfo.getNetwork());
+ }
+
+ /**
+ * Update subType for the registration.
+ */
+ public void updateSubtype(@Nullable String subtype) {
+ this.mSubtype = subtype;
+ }
+
+ /**
* Update the registration to use a different service name, after a conflict was found.
*
* @param newInfo New service info to use.
@@ -632,42 +661,68 @@
}
/**
- * Add a service to advertise.
+ * Add or update a service to advertise.
+ *
* @param id A unique ID for the service.
* @param service The service info to advertise.
* @param subtype An optional subtype to advertise the service with.
+ * @param advertisingOptions The advertising options.
*/
- public void addService(int id, NsdServiceInfo service, @Nullable String subtype) {
+ public void addOrUpdateService(int id, NsdServiceInfo service, @Nullable String subtype,
+ MdnsAdvertisingOptions advertisingOptions) {
checkThread();
- if (mRegistrations.get(id) != null) {
- mSharedLog.e("Adding duplicate registration for " + service);
- // TODO (b/264986328): add a more specific error code
- mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
- return;
- }
-
- mSharedLog.i("Adding service " + service + " with ID " + id + " and subtype " + subtype);
-
+ final Registration existingRegistration = mRegistrations.get(id);
final Network network = service.getNetwork();
- final Registration registration = new Registration(service, subtype);
- final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
- if (network == null) {
- // If registering on all networks, no advertiser must have conflicts
- checkConflictFilter = (net, adv) -> true;
- } else {
- // If registering on one network, the matching network advertiser and the one for all
- // networks must not have conflicts
- checkConflictFilter = (net, adv) -> net == null || network.equals(net);
- }
+ Registration registration;
+ if (advertisingOptions.isOnlyUpdate()) {
+ if (existingRegistration == null) {
+ mSharedLog.e("Update non existing registration for " + service);
+ mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
+ return;
+ }
+ if (!(existingRegistration.matches(service))) {
+ mSharedLog.e("Update request can only update subType, serviceInfo: " + service
+ + ", existing serviceInfo: " + existingRegistration.getServiceInfo());
+ mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
+ return;
- updateRegistrationUntilNoConflict(checkConflictFilter, registration);
+ }
+ mSharedLog.i("Update service " + service + " with ID " + id + " and subtype " + subtype
+ + " advertisingOptions " + advertisingOptions);
+ registration = existingRegistration;
+ registration.updateSubtype(subtype);
+ } else {
+ if (existingRegistration != null) {
+ mSharedLog.e("Adding duplicate registration for " + service);
+ // TODO (b/264986328): add a more specific error code
+ mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
+ return;
+ }
+ mSharedLog.i("Adding service " + service + " with ID " + id + " and subtype " + subtype
+ + " advertisingOptions " + advertisingOptions);
+ registration = new Registration(service, subtype);
+ final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
+ if (network == null) {
+ // If registering on all networks, no advertiser must have conflicts
+ checkConflictFilter = (net, adv) -> true;
+ } else {
+ // If registering on one network, the matching network advertiser and the one
+ // for all networks must not have conflicts
+ checkConflictFilter = (net, adv) -> net == null || network.equals(net);
+ }
+ updateRegistrationUntilNoConflict(checkConflictFilter, registration);
+ }
InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.get(network);
if (advertiser == null) {
advertiser = new InterfaceAdvertiserRequest(network);
mAdvertiserRequests.put(network, advertiser);
}
- advertiser.addService(id, registration);
+ if (advertisingOptions.isOnlyUpdate()) {
+ advertiser.updateService(id, registration);
+ } else {
+ advertiser.addService(id, registration);
+ }
mRegistrations.put(id, registration);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
new file mode 100644
index 0000000..e7a6ca7
--- /dev/null
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertisingOptions.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.mdns;
+
+/**
+ * API configuration parameters for advertising the mDNS service.
+ *
+ * <p>Use {@link MdnsAdvertisingOptions.Builder} to create {@link MdnsAdvertisingOptions}.
+ *
+ * @hide
+ */
+public class MdnsAdvertisingOptions {
+
+ private static MdnsAdvertisingOptions sDefaultOptions;
+ private final boolean mIsOnlyUpdate;
+
+ /**
+ * Parcelable constructs for a {@link MdnsAdvertisingOptions}.
+ */
+ MdnsAdvertisingOptions(
+ boolean isOnlyUpdate) {
+ this.mIsOnlyUpdate = isOnlyUpdate;
+ }
+
+ /**
+ * Returns a {@link Builder} for {@link MdnsAdvertisingOptions}.
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns a default search options.
+ */
+ public static synchronized MdnsAdvertisingOptions getDefaultOptions() {
+ if (sDefaultOptions == null) {
+ sDefaultOptions = newBuilder().build();
+ }
+ return sDefaultOptions;
+ }
+
+ /**
+ * @return {@code true} if the advertising request is an update request.
+ */
+ public boolean isOnlyUpdate() {
+ return mIsOnlyUpdate;
+ }
+
+ @Override
+ public String toString() {
+ return "MdnsAdvertisingOptions{" + "mIsOnlyUpdate=" + mIsOnlyUpdate + '}';
+ }
+
+ /**
+ * A builder to create {@link MdnsAdvertisingOptions}.
+ */
+ public static final class Builder {
+ private boolean mIsOnlyUpdate = false;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets if the advertising request is an update request.
+ */
+ public Builder setIsOnlyUpdate(boolean isOnlyUpdate) {
+ this.mIsOnlyUpdate = isOnlyUpdate;
+ return this;
+ }
+
+ /**
+ * Builds a {@link MdnsAdvertisingOptions} with the arguments supplied to this builder.
+ */
+ public MdnsAdvertisingOptions build() {
+ return new MdnsAdvertisingOptions(mIsOnlyUpdate);
+ }
+ }
+}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
index 973fd96..4399f2d 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInetAddressRecord.java
@@ -60,6 +60,12 @@
super(name, type, reader, isQuestion);
}
+ public MdnsInetAddressRecord(String[] name, int type, boolean isUnicast) {
+ super(name, type,
+ MdnsConstants.QCLASS_INTERNET | (isUnicast ? MdnsConstants.QCLASS_UNICAST : 0),
+ 0L /* receiptTimeMillis */, false /* cacheFlush */, 0L /* ttlMillis */);
+ }
+
public MdnsInetAddressRecord(String[] name, long receiptTimeMillis, boolean cacheFlush,
long ttlMillis, InetAddress address) {
super(name, address instanceof Inet4Address ? TYPE_A : TYPE_AAAA,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
index 62c37ad..463df63 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java
@@ -229,6 +229,18 @@
}
/**
+ * Update an already registered service without sending exit/re-announcement packet.
+ *
+ * @param id An exiting service id
+ * @param subtype A new subtype
+ */
+ public void updateService(int id, @Nullable String subtype) {
+ // The current implementation is intended to be used in cases where subtypes don't get
+ // announced.
+ mRecordRepository.updateService(id, subtype);
+ }
+
+ /**
* Start advertising a service.
*
* @throws NameConflictException There is already a service being advertised with that name.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
index 41cc380..e5c90a4 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsPointerRecord.java
@@ -39,6 +39,12 @@
super(name, TYPE_PTR, reader, isQuestion);
}
+ public MdnsPointerRecord(String[] name, boolean isUnicast) {
+ super(name, TYPE_PTR,
+ MdnsConstants.QCLASS_INTERNET | (isUnicast ? MdnsConstants.QCLASS_UNICAST : 0),
+ 0L /* receiptTimeMillis */, false /* cacheFlush */, 0L /* ttlMillis */);
+ }
+
public MdnsPointerRecord(String[] name, long receiptTimeMillis, boolean cacheFlush,
long ttlMillis, String[] pointer) {
super(name, TYPE_PTR, MdnsConstants.QCLASS_INTERNET, receiptTimeMillis, cacheFlush,
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index e34778f..48ece68 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -167,7 +167,7 @@
/**
* Whether the service is sending exit announcements and will be destroyed soon.
*/
- public boolean exiting = false;
+ public boolean exiting;
/**
* The replied query packet count of this service.
@@ -185,13 +185,20 @@
private boolean isProbing;
/**
+ * Create a ServiceRegistration with only update the subType
+ */
+ ServiceRegistration withSubtype(String newSubType) {
+ return new ServiceRegistration(srvRecord.record.getServiceHost(), serviceInfo,
+ newSubType, repliedServiceCount, sentPacketCount, exiting, isProbing);
+ }
+
+
+ /**
* Create a ServiceRegistration for dns-sd service registration (RFC6763).
- *
- * @param deviceHostname Hostname of the device (for the interface used)
- * @param serviceInfo Service to advertise
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
- @Nullable String subtype, int repliedServiceCount, int sentPacketCount) {
+ @Nullable String subtype, int repliedServiceCount, int sentPacketCount,
+ boolean exiting, boolean isProbing) {
this.serviceInfo = serviceInfo;
this.subtype = subtype;
@@ -266,7 +273,20 @@
this.allRecords = Collections.unmodifiableList(allRecords);
this.repliedServiceCount = repliedServiceCount;
this.sentPacketCount = sentPacketCount;
- this.isProbing = true;
+ this.isProbing = isProbing;
+ this.exiting = exiting;
+ }
+
+ /**
+ * Create a ServiceRegistration for dns-sd service registration (RFC6763).
+ *
+ * @param deviceHostname Hostname of the device (for the interface used)
+ * @param serviceInfo Service to advertise
+ */
+ ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
+ @Nullable String subtype, int repliedServiceCount, int sentPacketCount) {
+ this(deviceHostname, serviceInfo, subtype, repliedServiceCount, sentPacketCount,
+ false /* exiting */, true /* isProbing */);
}
void setProbing(boolean probing) {
@@ -305,6 +325,24 @@
}
/**
+ * Update a service that already registered in the repository.
+ *
+ * @param serviceId An existing service ID.
+ * @param subtype A new subtype
+ * @return
+ */
+ public void updateService(int serviceId, @Nullable String subtype) {
+ final ServiceRegistration existingRegistration = mServices.get(serviceId);
+ if (existingRegistration == null) {
+ throw new IllegalArgumentException(
+ "Service ID must already exist for an update request: " + serviceId);
+ }
+ final ServiceRegistration updatedRegistration = existingRegistration.withSubtype(
+ subtype);
+ mServices.put(serviceId, updatedRegistration);
+ }
+
+ /**
* Add a service to the repository.
*
* This may remove/replace any existing service that used the name added but is exiting.
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
index 4d407be..0d6a9ec 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceRecord.java
@@ -49,6 +49,12 @@
super(name, TYPE_SRV, reader, isQuestion);
}
+ public MdnsServiceRecord(String[] name, boolean isUnicast) {
+ super(name, TYPE_SRV,
+ MdnsConstants.QCLASS_INTERNET | (isUnicast ? MdnsConstants.QCLASS_UNICAST : 0),
+ 0L /* receiptTimeMillis */, false /* cacheFlush */, 0L /* ttlMillis */);
+ }
+
public MdnsServiceRecord(String[] name, long receiptTimeMillis, boolean cacheFlush,
long ttlMillis, int servicePriority, int serviceWeight, int servicePort,
String[] serviceHost) {
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
index cf6c8ac..92cf324 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
@@ -42,6 +42,12 @@
super(name, TYPE_TXT, reader, isQuestion);
}
+ public MdnsTextRecord(String[] name, boolean isUnicast) {
+ super(name, TYPE_TXT,
+ MdnsConstants.QCLASS_INTERNET | (isUnicast ? MdnsConstants.QCLASS_UNICAST : 0),
+ 0L /* receiptTimeMillis */, false /* cacheFlush */, 0L /* ttlMillis */);
+ }
+
public MdnsTextRecord(String[] name, long receiptTimeMillis, boolean cacheFlush, long ttlMillis,
List<TextEntry> entries) {
super(name, TYPE_TXT, MdnsConstants.QCLASS_INTERNET, receiptTimeMillis, cacheFlush,
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index 4d79f9d..1482ebb 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -183,13 +183,10 @@
}
/**
- * Create a raw DNS packet.
+ * Write the mdns packet from given MdnsPacket.
*/
- public static byte[] createRawDnsPacket(@NonNull byte[] packetCreationBuffer,
- @NonNull MdnsPacket packet) throws IOException {
- // TODO: support packets over size (send in multiple packets with TC bit set)
- final MdnsPacketWriter writer = new MdnsPacketWriter(packetCreationBuffer);
-
+ public static void writeMdnsPacket(@NonNull MdnsPacketWriter writer, @NonNull MdnsPacket packet)
+ throws IOException {
writer.writeUInt16(packet.transactionId); // Transaction ID (advertisement: 0)
writer.writeUInt16(packet.flags); // Response, authoritative (rfc6762 18.4)
writer.writeUInt16(packet.questions.size()); // questions count
@@ -210,6 +207,16 @@
for (MdnsRecord record : packet.additionalRecords) {
record.write(writer, 0L);
}
+ }
+
+ /**
+ * Create a raw DNS packet.
+ */
+ public static byte[] createRawDnsPacket(@NonNull byte[] packetCreationBuffer,
+ @NonNull MdnsPacket packet) throws IOException {
+ // TODO: support packets over size (send in multiple packets with TC bit set)
+ final MdnsPacketWriter writer = new MdnsPacketWriter(packetCreationBuffer);
+ writeMdnsPacket(writer, packet);
final int len = writer.getWritePosition();
return Arrays.copyOfRange(packetCreationBuffer, 0, len);
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/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/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/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/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
index ee34e57..9773cd6 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/xfrm/XfrmNetlinkMessage.java
@@ -17,10 +17,14 @@
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
@@ -28,12 +32,47 @@
// 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: STOPSHIP: b/308011229 Remove it when OsConstants.IPPROTO_ESP is exposed
+ // 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);
}
- // TODO: Add the support for parsing messages
+ /**
+ * 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/libnetdutils/include/netdutils/Status.h b/staticlibs/netd/libnetdutils/include/netdutils/Status.h
index 7b0bd47..34f3bb2 100644
--- a/staticlibs/netd/libnetdutils/include/netdutils/Status.h
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Status.h
@@ -41,6 +41,9 @@
// Constructs an error Status, |code| must be non-zero.
Status(int code, std::string msg) : mCode(code), mMsg(std::move(msg)) { assert(!ok()); }
+ // Constructs an error Status with message. Error |code| is unspecified.
+ explicit Status(std::string msg) : Status(std::numeric_limits<int>::max(), std::move(msg)) {}
+
Status(android::base::Result<void> result)
: mCode(result.ok() ? 0 : static_cast<int>(result.error().code())),
mMsg(result.ok() ? "" : result.error().message()) {}
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/xfrm/StructXfrmIdTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmIdTest.java
new file mode 100644
index 0000000..c9741cf
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmIdTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.IPPROTO_ESP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+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.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StructXfrmIdTest {
+ private static final String EXPECTED_HEX_STRING =
+ "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 = 0x53fa0fdd;
+ private static final short PROTO = IPPROTO_ESP;
+
+ @Test
+ public void testEncode() throws Exception {
+ final StructXfrmId struct = new StructXfrmId(DEST_ADDRESS, SPI, PROTO);
+
+ 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 StructXfrmId struct = StructXfrmId.parse(StructXfrmId.class, buffer);
+
+ assertEquals(DEST_ADDRESS, struct.getDestAddress(OsConstants.AF_INET));
+ assertEquals(SPI, struct.spi);
+ 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/xfrm/StructXfrmUsersaIdTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
index 52fd591..b659f62 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/StructXfrmUsersaIdTest.java
@@ -52,7 +52,7 @@
public void testEncode() throws Exception {
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);
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java
new file mode 100644
index 0000000..0ab36e7
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/xfrm/XfrmNetlinkGetSaMessageTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.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;
+
+import android.net.InetAddresses;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+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;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class XfrmNetlinkGetSaMessageTest {
+ private static final String EXPECTED_HEX_STRING =
+ "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;
+ private static final short PROTO = IPPROTO_ESP;
+
+ @Test
+ public void testEncode() throws Exception {
+ 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 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/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 225408c..c7d6555 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -131,17 +131,6 @@
import com.android.testutils.assertThrows
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.argThat
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.timeout
-import org.mockito.Mockito.verify
import java.io.Closeable
import java.io.IOException
import java.net.DatagramSocket
@@ -160,6 +149,17 @@
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.fail
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
private const val TAG = "NetworkAgentTest"
// This test doesn't really have a constraint on how fast the methods should return. If it's
@@ -951,7 +951,6 @@
argThat<NetworkInfo> { it.detailedState == NetworkInfo.DetailedState.CONNECTING },
any(LinkProperties::class.java),
any(NetworkCapabilities::class.java),
- any(), // LocalNetworkConfig TODO : specify when it's public
any(NetworkScore::class.java),
any(NetworkAgentConfig::class.java),
eq(NetworkProvider.ID_NONE))
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/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 ffc8aa1..32014c2 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;
@@ -170,6 +169,8 @@
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
+ @Rule
+ public TestRule ignoreRule = new DevSdkIgnoreRule();
@Mock Context mContext;
@Mock PackageManager mPackageManager;
@Mock ContentResolver mResolver;
@@ -1114,9 +1115,9 @@
final RegistrationListener regListener = mock(RegistrationListener.class);
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
- verify(mAdvertiser).addService(anyInt(), argThat(s ->
+ verify(mAdvertiser).addOrUpdateService(anyInt(), argThat(s ->
"Instance".equals(s.getServiceName())
- && SERVICE_TYPE.equals(s.getServiceType())), eq("_subtype"));
+ && SERVICE_TYPE.equals(s.getServiceType())), eq("_subtype"), any());
final DiscoveryListener discListener = mock(DiscoveryListener.class);
client.discoverServices(typeWithSubtype, PROTOCOL, network, Runnable::run, discListener);
@@ -1221,8 +1222,8 @@
waitForIdle();
final ArgumentCaptor<Integer> serviceIdCaptor = ArgumentCaptor.forClass(Integer.class);
- verify(mAdvertiser).addService(serviceIdCaptor.capture(),
- argThat(info -> matches(info, regInfo)), eq(null) /* subtype */);
+ verify(mAdvertiser).addOrUpdateService(serviceIdCaptor.capture(),
+ argThat(info -> matches(info, regInfo)), eq(null) /* subtype */, any());
client.unregisterService(regListenerWithoutFeature);
waitForIdle();
@@ -1281,10 +1282,10 @@
waitForIdle();
// The advertiser is enabled for _type2 but not _type1
- verify(mAdvertiser, never()).addService(
- anyInt(), argThat(info -> matches(info, service1)), eq(null) /* subtype */);
- verify(mAdvertiser).addService(
- anyInt(), argThat(info -> matches(info, service2)), eq(null) /* subtype */);
+ verify(mAdvertiser, never()).addOrUpdateService(anyInt(),
+ argThat(info -> matches(info, service1)), eq(null) /* subtype */, any());
+ verify(mAdvertiser).addOrUpdateService(anyInt(), argThat(info -> matches(info, service2)),
+ eq(null) /* subtype */, any());
}
@Test
@@ -1308,8 +1309,8 @@
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
- verify(mAdvertiser).addService(idCaptor.capture(), argThat(info ->
- matches(info, regInfo)), eq(null) /* subtype */);
+ verify(mAdvertiser).addOrUpdateService(idCaptor.capture(), argThat(info ->
+ matches(info, regInfo)), eq(null) /* subtype */, any());
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
@@ -1357,7 +1358,7 @@
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
- verify(mAdvertiser, never()).addService(anyInt(), any(), any());
+ verify(mAdvertiser, never()).addOrUpdateService(anyInt(), any(), any(), any());
verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
@@ -1386,9 +1387,9 @@
waitForIdle();
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
// Service name is truncated to 63 characters
- verify(mAdvertiser).addService(idCaptor.capture(),
+ verify(mAdvertiser).addOrUpdateService(idCaptor.capture(),
argThat(info -> info.getServiceName().equals("a".repeat(63))),
- eq(null) /* subtype */);
+ eq(null) /* subtype */, any());
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
@@ -1478,7 +1479,7 @@
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
- verify(mAdvertiser).addService(anyInt(), any(), any());
+ verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any(), any());
// Verify the discovery uses MdnsDiscoveryManager
final DiscoveryListener discListener = mock(DiscoveryListener.class);
@@ -1511,7 +1512,7 @@
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
- verify(mAdvertiser).addService(anyInt(), any(), any());
+ verify(mAdvertiser).addOrUpdateService(anyInt(), any(), any(), any());
final Network wifiNetwork1 = new Network(123);
final Network wifiNetwork2 = new Network(124);
@@ -1697,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);
@@ -1708,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 afb9abd..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;
@@ -329,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);
@@ -344,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);
}
@@ -419,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);
@@ -431,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);
@@ -439,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/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index ea2228e..46e9e45 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -118,7 +118,6 @@
import android.net.IpSecTunnelInterfaceResponse;
import android.net.LinkAddress;
import android.net.LinkProperties;
-import android.net.LocalSocket;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
@@ -195,10 +194,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.io.BufferedWriter;
-import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.net.Inet4Address;
@@ -209,13 +205,11 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -3178,20 +3172,7 @@
// Make it public and un-final so as to spy it
public class TestDeps extends Vpn.Dependencies {
- public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
- public final CompletableFuture<String[]> mtpdArgs = new CompletableFuture();
- public final File mStateFile;
-
- private final HashMap<String, Boolean> mRunningServices = new HashMap<>();
-
- TestDeps() {
- try {
- mStateFile = File.createTempFile("vpnTest", ".tmp");
- mStateFile.deleteOnExit();
- } catch (final IOException e) {
- throw new RuntimeException(e);
- }
- }
+ TestDeps() {}
@Override
public boolean isCallerSystem() {
@@ -3199,89 +3180,11 @@
}
@Override
- public void startService(final String serviceName) {
- mRunningServices.put(serviceName, true);
- }
-
- @Override
- public void stopService(final String serviceName) {
- mRunningServices.put(serviceName, false);
- }
-
- @Override
- public boolean isServiceRunning(final String serviceName) {
- return mRunningServices.getOrDefault(serviceName, false);
- }
-
- @Override
- public boolean isServiceStopped(final String serviceName) {
- return !isServiceRunning(serviceName);
- }
-
- @Override
- public File getStateFile() {
- return mStateFile;
- }
-
- @Override
public PendingIntent getIntentForStatusPanel(Context context) {
return null;
}
@Override
- public void sendArgumentsToDaemon(
- final String daemon, final LocalSocket socket, final String[] arguments,
- final Vpn.RetryScheduler interruptChecker) throws IOException {
- if ("racoon".equals(daemon)) {
- racoonArgs.complete(arguments);
- } else if ("mtpd".equals(daemon)) {
- writeStateFile(arguments);
- mtpdArgs.complete(arguments);
- } else {
- throw new UnsupportedOperationException("Unsupported daemon : " + daemon);
- }
- }
-
- private void writeStateFile(final String[] arguments) throws IOException {
- mStateFile.delete();
- mStateFile.createNewFile();
- mStateFile.deleteOnExit();
- final BufferedWriter writer = new BufferedWriter(
- new FileWriter(mStateFile, false /* append */));
- writer.write(EGRESS_IFACE);
- writer.write("\n");
- // addresses
- writer.write("10.0.0.1/24\n");
- // routes
- writer.write("192.168.6.0/24\n");
- // dns servers
- writer.write("192.168.6.1\n");
- // search domains
- writer.write("vpn.searchdomains.com\n");
- // endpoint - intentionally empty
- writer.write("\n");
- writer.flush();
- writer.close();
- }
-
- @Override
- @NonNull
- public InetAddress resolve(final String endpoint) {
- try {
- // If a numeric IP address, return it.
- return InetAddress.parseNumericAddress(endpoint);
- } catch (IllegalArgumentException e) {
- // Otherwise, return some token IP to test for.
- return InetAddress.parseNumericAddress("5.6.7.8");
- }
- }
-
- @Override
- public boolean isInterfacePresent(final Vpn vpn, final String iface) {
- return true;
- }
-
- @Override
public ParcelFileDescriptor adoptFd(Vpn vpn, int mtu) {
return new ParcelFileDescriptor(new FileDescriptor());
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
index a86f923..f0cb6df 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt
@@ -19,6 +19,7 @@
import android.net.InetAddresses.parseNumericAddress
import android.net.LinkAddress
import android.net.Network
+import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.net.nsd.OffloadEngine
import android.net.nsd.OffloadServiceInfo
@@ -71,6 +72,7 @@
private val TEST_INTERFACE2 = "test_iface2"
private val TEST_OFFLOAD_PACKET1 = byteArrayOf(0x01, 0x02, 0x03)
private val TEST_OFFLOAD_PACKET2 = byteArrayOf(0x02, 0x03, 0x04)
+private val DEFAULT_ADVERTISING_OPTION = MdnsAdvertisingOptions.getDefaultOptions()
private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
port = 12345
@@ -186,7 +188,8 @@
fun testAddService_OneNetwork() {
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
- postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
+ null /* subtype */, DEFAULT_ADVERTISING_OPTION) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
@@ -247,7 +250,8 @@
fun testAddService_AllNetworks() {
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
- postSync { advertiser.addService(SERVICE_ID_1, ALL_NETWORKS_SERVICE, TEST_SUBTYPE) }
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
+ TEST_SUBTYPE, DEFAULT_ADVERTISING_OPTION) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(ALL_NETWORKS_SERVICE.network),
@@ -318,24 +322,27 @@
fun testAddService_Conflicts() {
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
- postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
+ null /* subtype */, DEFAULT_ADVERTISING_OPTION) }
val oneNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), oneNetSocketCbCaptor.capture())
val oneNetSocketCb = oneNetSocketCbCaptor.value
// Register a service with the same name on all networks (name conflict)
- postSync { advertiser.addService(SERVICE_ID_2, ALL_NETWORKS_SERVICE, null /* subtype */) }
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_2, ALL_NETWORKS_SERVICE,
+ null /* subtype */, DEFAULT_ADVERTISING_OPTION) }
val allNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(null), allNetSocketCbCaptor.capture())
val allNetSocketCb = allNetSocketCbCaptor.value
- postSync { advertiser.addService(LONG_SERVICE_ID_1, LONG_SERVICE_1, null /* subtype */) }
- postSync { advertiser.addService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE,
- null /* subtype */) }
+ postSync { advertiser.addOrUpdateService(LONG_SERVICE_ID_1, LONG_SERVICE_1,
+ null /* subtype */, DEFAULT_ADVERTISING_OPTION) }
+ postSync { advertiser.addOrUpdateService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE,
+ null /* subtype */, DEFAULT_ADVERTISING_OPTION) }
- postSync { advertiser.addService(CASE_INSENSITIVE_TEST_SERVICE_ID, ALL_NETWORKS_SERVICE_2,
- null /* subtype */) }
+ postSync { advertiser.addOrUpdateService(CASE_INSENSITIVE_TEST_SERVICE_ID,
+ ALL_NETWORKS_SERVICE_2, null /* subtype */, DEFAULT_ADVERTISING_OPTION) }
// Callbacks for matching network and all networks both get the socket
postSync {
@@ -400,11 +407,51 @@
}
@Test
+ fun testAddOrUpdateService_Updates() {
+ val advertiser =
+ MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE,
+ null /* subtype */, DEFAULT_ADVERTISING_OPTION) }
+
+ val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
+ verify(socketProvider).requestSocket(eq(null), socketCbCaptor.capture())
+
+ val socketCb = socketCbCaptor.value
+ postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_1, mockSocket1, listOf(TEST_LINKADDR)) }
+
+ verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
+ argThat { it.matches(ALL_NETWORKS_SERVICE) }, eq(null))
+
+ val updateOptions = MdnsAdvertisingOptions.newBuilder().setIsOnlyUpdate(true).build()
+
+ // Update with serviceId that is not registered yet should fail
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_2, ALL_NETWORKS_SERVICE, TEST_SUBTYPE,
+ updateOptions) }
+ verify(cb).onRegisterServiceFailed(SERVICE_ID_2, NsdManager.FAILURE_INTERNAL_ERROR)
+
+ // Update service with different NsdServiceInfo should fail
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1, TEST_SUBTYPE,
+ updateOptions) }
+ verify(cb).onRegisterServiceFailed(SERVICE_ID_1, NsdManager.FAILURE_INTERNAL_ERROR)
+
+ // Update service with same NsdServiceInfo but different subType should succeed
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, ALL_NETWORKS_SERVICE, TEST_SUBTYPE,
+ updateOptions) }
+ verify(mockInterfaceAdvertiser1).updateService(eq(SERVICE_ID_1), eq(TEST_SUBTYPE))
+
+ // Newly created MdnsInterfaceAdvertiser will get addService() call.
+ postSync { socketCb.onSocketCreated(TEST_SOCKETKEY_2, mockSocket2, listOf(TEST_LINKADDR2)) }
+ verify(mockInterfaceAdvertiser2).addService(eq(SERVICE_ID_1),
+ argThat { it.matches(ALL_NETWORKS_SERVICE) }, eq(TEST_SUBTYPE))
+ }
+
+ @Test
fun testRemoveService_whenAllServiceRemoved_thenUpdateHostName() {
val advertiser =
MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog, flags)
verify(mockDeps, times(1)).generateHostname()
- postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
+ postSync { advertiser.addOrUpdateService(SERVICE_ID_1, SERVICE_1,
+ null /* subtype */, DEFAULT_ADVERTISING_OPTION) }
postSync { advertiser.removeService(SERVICE_ID_1) }
verify(mockDeps, times(2)).generateHostname()
}
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/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
index db41a6a..f85d71d 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt
@@ -48,6 +48,7 @@
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@@ -59,6 +60,7 @@
private val TEST_HOSTNAME = arrayOf("Android_test", "local")
private const val TEST_SERVICE_ID_1 = 42
+private const val TEST_SERVICE_ID_DUPLICATE = 43
private val TEST_SERVICE_1 = NsdServiceInfo().apply {
serviceType = "_testservice._tcp"
serviceName = "MyTestService"
@@ -272,6 +274,28 @@
verify(prober).restartForConflict(mockProbingInfo)
}
+ @Test
+ fun testReplaceExitingService() {
+ doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository)
+ .addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
+ val subType = "_sub"
+ advertiser.addService(TEST_SERVICE_ID_DUPLICATE, TEST_SERVICE_1, subType)
+ verify(repository).addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
+ verify(announcer).stop(TEST_SERVICE_ID_DUPLICATE)
+ verify(prober).startProbing(any())
+ }
+
+ @Test
+ fun testUpdateExistingService() {
+ doReturn(TEST_SERVICE_ID_DUPLICATE).`when`(repository)
+ .addService(eq(TEST_SERVICE_ID_DUPLICATE), any(), any())
+ val subType = "_sub"
+ advertiser.updateService(TEST_SERVICE_ID_DUPLICATE, subType)
+ verify(repository).updateService(eq(TEST_SERVICE_ID_DUPLICATE), any())
+ verify(announcer, never()).stop(TEST_SERVICE_ID_DUPLICATE)
+ verify(prober, never()).startProbing(any())
+ }
+
private fun addServiceAndFinishProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
AnnouncementInfo {
val testProbingInfo = mock(ProbingInfo::class.java)
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index f26f7e1..582e7b1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -129,7 +129,7 @@
@Test
fun testAddAndConflicts() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
- repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
assertFailsWith(NameConflictException::class) {
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* subtype */)
}
@@ -139,6 +139,45 @@
}
@Test
+ fun testAddAndUpdates() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
+ repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+ assertFailsWith(IllegalArgumentException::class) {
+ repository.updateService(TEST_SERVICE_ID_2, null /* subtype */)
+ }
+
+ repository.updateService(TEST_SERVICE_ID_1, TEST_SUBTYPE)
+
+ val queriedName = arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local")
+ val questions = listOf(MdnsPointerRecord(queriedName,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ // TTL and data is empty for a question
+ 0L /* ttlMillis */,
+ null /* pointer */))
+ val query = MdnsPacket(0 /* flags */, questions, listOf() /* answers */,
+ listOf() /* authorityRecords */, listOf() /* additionalRecords */)
+ val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val reply = repository.getReply(query, src)
+
+ assertNotNull(reply)
+
+ // TTLs as per RFC6762 10.
+ val longTtl = 4_500_000L
+ val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+
+ assertEquals(listOf(
+ MdnsPointerRecord(
+ queriedName,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ longTtl,
+ serviceName),
+ ), reply.answers)
+ }
+
+ @Test
fun testInvalidReuseOfServiceId() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
@@ -758,7 +797,7 @@
private fun MdnsRecordRepository.initWithService(
serviceId: Int,
serviceInfo: NsdServiceInfo,
- subtype: String? = null
+ subtype: String? = null,
): AnnouncementInfo {
updateAddresses(TEST_ADDRESSES)
addService(serviceId, serviceInfo, subtype)
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/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.