Merge "Adjust background restriction transition delays in tests" into main
diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml
index 4783f2b..02a9ce6 100644
--- a/service/ServiceConnectivityResources/res/values/config_thread.xml
+++ b/service/ServiceConnectivityResources/res/values/config_thread.xml
@@ -53,4 +53,10 @@
     UTF-8 bytes.
     -->
     <string translatable="false" name="config_thread_model_name">Thread Border Router</string>
+
+    <!-- Whether the Thread network will be managed by the Google Home ecosystem. When this value
+    is set, a TXT entry "vgh=0" or "vgh=1" will be added to the "_mehscop._udp" mDNS service
+    respectively (The TXT value is a string).
+    -->
+    <bool name="config_thread_managed_by_google_home">false</bool>
 </resources>
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
index d0cb9b8..1447ff8 100644
--- a/thread/service/java/com/android/server/thread/NsdPublisher.java
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -45,7 +45,6 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -615,9 +614,8 @@
             }
             List<DnsTxtAttribute> txtList = new ArrayList<>();
             for (Map.Entry<String, byte[]> entry : serviceInfo.getAttributes().entrySet()) {
-                DnsTxtAttribute attribute = new DnsTxtAttribute();
-                attribute.name = entry.getKey();
-                attribute.value = Arrays.copyOf(entry.getValue(), entry.getValue().length);
+                DnsTxtAttribute attribute =
+                        new DnsTxtAttribute(entry.getKey(), entry.getValue().clone());
                 txtList.add(attribute);
             }
             // TODO: b/329018320 - Use the serviceInfo.getExpirationTime to derive TTL.
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 737ec41..0c77dee 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -61,6 +61,8 @@
 import static com.android.server.thread.openthread.IOtDaemon.OT_STATE_ENABLED;
 import static com.android.server.thread.openthread.IOtDaemon.TUN_IF_NAME;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -117,6 +119,7 @@
 import com.android.server.connectivity.ConnectivityResources;
 import com.android.server.thread.openthread.BackboneRouterState;
 import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
+import com.android.server.thread.openthread.DnsTxtAttribute;
 import com.android.server.thread.openthread.IChannelMasksReceiver;
 import com.android.server.thread.openthread.IOtDaemon;
 import com.android.server.thread.openthread.IOtDaemonCallback;
@@ -130,7 +133,6 @@
 
 import java.io.IOException;
 import java.net.Inet6Address;
-import java.nio.charset.StandardCharsets;
 import java.security.SecureRandom;
 import java.time.Clock;
 import java.time.DateTimeException;
@@ -338,9 +340,11 @@
         final String modelName = resources.getString(R.string.config_thread_model_name);
         final String vendorName = resources.getString(R.string.config_thread_vendor_name);
         final String vendorOui = resources.getString(R.string.config_thread_vendor_oui);
+        final boolean managedByGoogle =
+                resources.getBoolean(R.bool.config_thread_managed_by_google_home);
 
         if (!modelName.isEmpty()) {
-            if (modelName.getBytes(StandardCharsets.UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
+            if (modelName.getBytes(UTF_8).length > MAX_MODEL_NAME_UTF8_BYTES) {
                 throw new IllegalStateException(
                         "Model name is longer than "
                                 + MAX_MODEL_NAME_UTF8_BYTES
@@ -350,7 +354,7 @@
         }
 
         if (!vendorName.isEmpty()) {
-            if (vendorName.getBytes(StandardCharsets.UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
+            if (vendorName.getBytes(UTF_8).length > MAX_VENDOR_NAME_UTF8_BYTES) {
                 throw new IllegalStateException(
                         "Vendor name is longer than "
                                 + MAX_VENDOR_NAME_UTF8_BYTES
@@ -367,9 +371,21 @@
         meshcopTxts.modelName = modelName;
         meshcopTxts.vendorName = vendorName;
         meshcopTxts.vendorOui = HexEncoding.decode(vendorOui.replace("-", "").replace(":", ""));
+        meshcopTxts.nonStandardTxtEntries = List.of(makeManagedByGoogleTxtAttr(managedByGoogle));
+
         return meshcopTxts;
     }
 
+    /**
+     * Creates a DNS-SD TXT entry for indicating whether Thread on this device is managed by Google.
+     *
+     * @return TXT entry "vgh=1" if {@code managedByGoogle} is {@code true}; otherwise, "vgh=0"
+     */
+    private static DnsTxtAttribute makeManagedByGoogleTxtAttr(boolean managedByGoogle) {
+        final byte[] value = (managedByGoogle ? "1" : "0").getBytes(UTF_8);
+        return new DnsTxtAttribute("vgh", value);
+    }
+
     private void onOtDaemonDied() {
         checkOnHandlerThread();
         Log.w(TAG, "OT daemon is dead, clean up...");
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 11c4819..0e95703 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -45,7 +45,6 @@
 
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
-
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
 import android.content.Context;
@@ -859,6 +858,7 @@
         assertThat(txtMap.get("rv")).isNotNull();
         assertThat(txtMap.get("tv")).isNotNull();
         assertThat(txtMap.get("sb")).isNotNull();
+        assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
     }
 
     @Test
@@ -885,6 +885,7 @@
         assertThat(txtMap.get("tv")).isNotNull();
         assertThat(txtMap.get("sb")).isNotNull();
         assertThat(txtMap.get("id").length).isEqualTo(16);
+        assertThat(new String(txtMap.get("vgh"))).isIn(List.of("0", "1"));
     }
 
     @Test
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index d24059a..c0a8eea 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -17,7 +17,9 @@
 
 import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
 import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+
 import static com.google.common.io.BaseEncoding.base16;
+
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import android.net.InetAddresses;
@@ -26,7 +28,9 @@
 import android.net.thread.ActiveOperationalDataset;
 import android.os.Handler;
 import android.os.HandlerThread;
+
 import com.google.errorprone.annotations.FormatMethod;
+
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.IOException;
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
index 3cae84f..ae0bc80 100644
--- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -22,14 +22,15 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.hamcrest.MockitoHamcrest.argThat;
 
 import android.net.DnsResolver;
 import android.net.InetAddresses;
@@ -50,21 +51,23 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.net.InetAddress;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 
 /** Unit tests for {@link NsdPublisher}. */
 public final class NsdPublisherTest {
+    private static final DnsTxtAttribute TEST_TXT_ENTRY_1 =
+            new DnsTxtAttribute("key1", new byte[] {0x01, 0x02});
+    private static final DnsTxtAttribute TEST_TXT_ENTRY_2 =
+            new DnsTxtAttribute("key2", new byte[] {0x03});
+
     @Mock private NsdManager mMockNsdManager;
     @Mock private DnsResolver mMockDnsResolver;
 
@@ -87,19 +90,15 @@
     public void registerService_nsdManagerSucceeds_serviceRegistrationSucceeds() throws Exception {
         prepareTest();
 
-        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
-        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
         mNsdPublisher.registerService(
                 null,
                 "MyService",
                 "_test._tcp",
                 List.of("_subtype1", "_subtype2"),
                 12345,
-                List.of(txt1, txt2),
+                List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
                 mRegistrationReceiver,
                 16 /* listenerId */);
-
         mTestLooper.dispatchAll();
 
         ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@@ -126,11 +125,10 @@
         assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
         assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
         assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
-        assertThat(actualServiceInfo.getAttributes().get("key1"))
-                .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
-        assertThat(actualServiceInfo.getAttributes().get("key2"))
-                .isEqualTo(new byte[] {(byte) 0x03});
-
+        assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
+                .isEqualTo(TEST_TXT_ENTRY_1.value);
+        assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
+                .isEqualTo(TEST_TXT_ENTRY_2.value);
         verify(mRegistrationReceiver, times(1)).onSuccess();
     }
 
@@ -138,19 +136,15 @@
     public void registerService_nsdManagerFails_serviceRegistrationFails() throws Exception {
         prepareTest();
 
-        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
-        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
         mNsdPublisher.registerService(
                 null,
                 "MyService",
                 "_test._tcp",
                 List.of("_subtype1", "_subtype2"),
                 12345,
-                List.of(txt1, txt2),
+                List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
                 mRegistrationReceiver,
                 16 /* listenerId */);
-
         mTestLooper.dispatchAll();
 
         ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
@@ -177,21 +171,16 @@
         assertThat(actualServiceInfo.getSubtypes()).isEqualTo(Set.of("_subtype1", "_subtype2"));
         assertThat(actualServiceInfo.getPort()).isEqualTo(12345);
         assertThat(actualServiceInfo.getAttributes().size()).isEqualTo(2);
-        assertThat(actualServiceInfo.getAttributes().get("key1"))
-                .isEqualTo(new byte[] {(byte) 0x01, (byte) 0x02});
-        assertThat(actualServiceInfo.getAttributes().get("key2"))
-                .isEqualTo(new byte[] {(byte) 0x03});
-
+        assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_1.name))
+                .isEqualTo(TEST_TXT_ENTRY_1.value);
+        assertThat(actualServiceInfo.getAttributes().get(TEST_TXT_ENTRY_2.name))
+                .isEqualTo(TEST_TXT_ENTRY_2.value);
         verify(mRegistrationReceiver, times(1)).onError(FAILURE_INTERNAL_ERROR);
     }
 
     @Test
     public void registerService_nsdManagerThrows_serviceRegistrationFails() throws Exception {
         prepareTest();
-
-        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
-        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
         doThrow(new IllegalArgumentException("NsdManager fails"))
                 .when(mMockNsdManager)
                 .registerService(any(), anyInt(), any(Executor.class), any());
@@ -202,7 +191,7 @@
                 "_test._tcp",
                 List.of("_subtype1", "_subtype2"),
                 12345,
-                List.of(txt1, txt2),
+                List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
                 mRegistrationReceiver,
                 16 /* listenerId */);
         mTestLooper.dispatchAll();
@@ -215,16 +204,13 @@
             throws Exception {
         prepareTest();
 
-        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
-        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
         mNsdPublisher.registerService(
                 null,
                 "MyService",
                 "_test._tcp",
                 List.of("_subtype1", "_subtype2"),
                 12345,
-                List.of(txt1, txt2),
+                List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
                 mRegistrationReceiver,
                 16 /* listenerId */);
 
@@ -260,16 +246,13 @@
     public void unregisterService_nsdManagerFails_serviceUnregistrationFails() throws Exception {
         prepareTest();
 
-        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
-        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
         mNsdPublisher.registerService(
                 null,
                 "MyService",
                 "_test._tcp",
                 List.of("_subtype1", "_subtype2"),
                 12345,
-                List.of(txt1, txt2),
+                List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
                 mRegistrationReceiver,
                 16 /* listenerId */);
 
@@ -587,8 +570,8 @@
                 List.of(
                         InetAddress.parseNumericAddress("2001::1"),
                         InetAddress.parseNumericAddress("2001::2")));
-        serviceInfo.setAttribute("key1", new byte[] {(byte) 0x01, (byte) 0x02});
-        serviceInfo.setAttribute("key2", new byte[] {(byte) 0x03});
+        serviceInfo.setAttribute(TEST_TXT_ENTRY_1.name, TEST_TXT_ENTRY_1.value);
+        serviceInfo.setAttribute(TEST_TXT_ENTRY_2.name, TEST_TXT_ENTRY_2.value);
         serviceInfoCallbackArgumentCaptor.getValue().onServiceUpdated(serviceInfo);
         mTestLooper.dispatchAll();
 
@@ -599,11 +582,8 @@
                         eq("_test._tcp"),
                         eq(12345),
                         eq(List.of("2001::1", "2001::2")),
-                        argThat(
-                                new TxtMatcher(
-                                        List.of(
-                                                makeTxtAttribute("key1", List.of(0x01, 0x02)),
-                                                makeTxtAttribute("key2", List.of(0x03))))),
+                        (List<DnsTxtAttribute>)
+                                argThat(containsInAnyOrder(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2)),
                         anyInt());
     }
 
@@ -725,10 +705,6 @@
     @Test
     public void reset_unregisterAll() {
         prepareTest();
-
-        DnsTxtAttribute txt1 = makeTxtAttribute("key1", List.of(0x01, 0x02));
-        DnsTxtAttribute txt2 = makeTxtAttribute("key2", List.of(0x03));
-
         ArgumentCaptor<NsdServiceInfo> actualServiceInfoCaptor =
                 ArgumentCaptor.forClass(NsdServiceInfo.class);
         ArgumentCaptor<NsdManager.RegistrationListener> actualRegistrationListenerCaptor =
@@ -740,7 +716,7 @@
                 "_test._tcp",
                 List.of("_subtype1", "_subtype2"),
                 12345,
-                List.of(txt1, txt2),
+                List.of(TEST_TXT_ENTRY_1, TEST_TXT_ENTRY_2),
                 mRegistrationReceiver,
                 16 /* listenerId */);
         mTestLooper.dispatchAll();
@@ -814,19 +790,6 @@
         verify(spyNsdPublisher, times(1)).reset();
     }
 
-    private static DnsTxtAttribute makeTxtAttribute(String name, List<Integer> value) {
-        DnsTxtAttribute txtAttribute = new DnsTxtAttribute();
-
-        txtAttribute.name = name;
-        txtAttribute.value = new byte[value.size()];
-
-        for (int i = 0; i < value.size(); ++i) {
-            txtAttribute.value[i] = value.get(i).byteValue();
-        }
-
-        return txtAttribute;
-    }
-
     private static List<InetAddress> makeAddresses(String... addressStrings) {
         List<InetAddress> addresses = new ArrayList<>();
 
@@ -836,30 +799,6 @@
         return addresses;
     }
 
-    private static class TxtMatcher implements ArgumentMatcher<List<DnsTxtAttribute>> {
-        private final List<DnsTxtAttribute> mAttributes;
-
-        TxtMatcher(List<DnsTxtAttribute> attributes) {
-            mAttributes = attributes;
-        }
-
-        @Override
-        public boolean matches(List<DnsTxtAttribute> argument) {
-            if (argument.size() != mAttributes.size()) {
-                return false;
-            }
-            for (int i = 0; i < argument.size(); ++i) {
-                if (!Objects.equals(argument.get(i).name, mAttributes.get(i).name)) {
-                    return false;
-                }
-                if (!Arrays.equals(argument.get(i).value, mAttributes.get(i).value)) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
     // @Before and @Test run in different threads. NsdPublisher requires the jobs are run on the
     // thread looper, so TestLooper needs to be created inside each test case to install the
     // correct looper.
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 8f60783..2f58943 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -78,6 +78,7 @@
 import com.android.connectivity.resources.R;
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.connectivity.ConnectivityResources;
+import com.android.server.thread.openthread.DnsTxtAttribute;
 import com.android.server.thread.openthread.MeshcopTxtAttributes;
 import com.android.server.thread.openthread.testing.FakeOtDaemon;
 
@@ -94,6 +95,7 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
+import java.nio.charset.StandardCharsets;
 import java.time.Clock;
 import java.time.DateTimeException;
 import java.util.concurrent.CompletableFuture;
@@ -145,6 +147,7 @@
     private static final byte[] TEST_VENDOR_OUI_BYTES = new byte[] {(byte) 0xAC, (byte) 0xDE, 0x48};
     private static final String TEST_VENDOR_NAME = "test vendor";
     private static final String TEST_MODEL_NAME = "test model";
+    private static final boolean TEST_VGH_VALUE = false;
 
     @Mock private ConnectivityManager mMockConnectivityManager;
     @Mock private NetworkAgent mMockNetworkAgent;
@@ -197,6 +200,8 @@
                 .thenReturn(TEST_VENDOR_OUI);
         when(mResources.getString(eq(R.string.config_thread_model_name)))
                 .thenReturn(TEST_MODEL_NAME);
+        when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+                .thenReturn(TEST_VGH_VALUE);
 
         final AtomicFile storageFile = new AtomicFile(tempFolder.newFile("thread_settings.xml"));
         mPersistentSettings = new ThreadPersistentSettings(storageFile, mConnectivityResources);
@@ -232,13 +237,15 @@
     }
 
     @Test
-    public void initialize_vendorAndModelNameInResourcesAreSetToOtDaemon() throws Exception {
+    public void initialize_resourceOverlayValuesAreSetToOtDaemon() throws Exception {
         when(mResources.getString(eq(R.string.config_thread_vendor_name)))
                 .thenReturn(TEST_VENDOR_NAME);
         when(mResources.getString(eq(R.string.config_thread_vendor_oui)))
                 .thenReturn(TEST_VENDOR_OUI);
         when(mResources.getString(eq(R.string.config_thread_model_name)))
                 .thenReturn(TEST_MODEL_NAME);
+        when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+                .thenReturn(true);
 
         mService.initialize();
         mTestLooper.dispatchAll();
@@ -247,6 +254,20 @@
         assertThat(meshcopTxts.vendorName).isEqualTo(TEST_VENDOR_NAME);
         assertThat(meshcopTxts.vendorOui).isEqualTo(TEST_VENDOR_OUI_BYTES);
         assertThat(meshcopTxts.modelName).isEqualTo(TEST_MODEL_NAME);
+        assertThat(meshcopTxts.nonStandardTxtEntries)
+                .containsExactly(new DnsTxtAttribute("vgh", "1".getBytes(StandardCharsets.UTF_8)));
+    }
+
+    @Test
+    public void getMeshcopTxtAttributes_managedByGoogleIsFalse_vghIsZero() {
+        when(mResources.getBoolean(eq(R.bool.config_thread_managed_by_google_home)))
+                .thenReturn(false);
+
+        MeshcopTxtAttributes meshcopTxts =
+                ThreadNetworkControllerService.getMeshcopTxtAttributes(mResources);
+
+        assertThat(meshcopTxts.nonStandardTxtEntries)
+                .containsExactly(new DnsTxtAttribute("vgh", "0".getBytes(StandardCharsets.UTF_8)));
     }
 
     @Test