From 5fc74b9fd8a31d6703ba760fcafe9bcfb4b99b1e Mon Sep 17 00:00:00 2001 From: Handa Wang Date: Thu, 20 Jun 2024 09:44:49 +0000 Subject: [PATCH] [Thread] Make ThreadConfiguraiton a system API This CL adds 3 things as @SystemApi: - The ThreadConfiguration class except the Builder nested class. - The ThreadNetworkController#registerConfigurationCallback method. - The ThreadNetworkController#unregisterConfigurationCallback method. Bug: 342519412 Test: CtsThreadNetworkTestCases Change-Id: I8d3e48b73d471af1515f251451a1259a4bde69af --- framework-t/api/system-current.txt | 10 + .../net/thread/ThreadConfiguration.java | 42 ++-- .../net/thread/ThreadNetworkController.java | 12 +- .../thread/ThreadPersistentSettings.java | 4 +- thread/tests/cts/Android.bp | 6 +- .../thread/cts/ThreadConfigurationTest.java | 95 ++++++++ .../cts/ThreadNetworkControllerTest.java | 217 ++++++++++++++++++ .../ThreadNetworkControllerServiceTest.java | 4 +- .../thread/ThreadPersistentSettingsTest.java | 10 +- 9 files changed, 364 insertions(+), 36 deletions(-) create mode 100644 thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt index 1f1953cf53..52de1a39e2 100644 --- a/framework-t/api/system-current.txt +++ b/framework-t/api/system-current.txt @@ -497,16 +497,26 @@ package android.net.thread { field @NonNull public static final android.os.Parcelable.Creator CREATOR; } + @FlaggedApi("com.android.net.thread.flags.configuration_enabled") public final class ThreadConfiguration implements android.os.Parcelable { + method public int describeContents(); + method public boolean isDhcpv6PdEnabled(); + method public boolean isNat64Enabled(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + @FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ThreadNetworkController { method public void createRandomizedDataset(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method public int getThreadVersion(); method public static boolean isAttached(int); method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void join(@NonNull android.net.thread.ActiveOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void leave(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void registerConfigurationCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback); method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); + method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void unregisterConfigurationCallback(@NonNull java.util.function.Consumer); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback); method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback); field public static final int DEVICE_ROLE_CHILD = 2; // 0x2 diff --git a/thread/framework/java/android/net/thread/ThreadConfiguration.java b/thread/framework/java/android/net/thread/ThreadConfiguration.java index e09b3a6d88..be2632c274 100644 --- a/thread/framework/java/android/net/thread/ThreadConfiguration.java +++ b/thread/framework/java/android/net/thread/ThreadConfiguration.java @@ -15,7 +15,9 @@ */ package android.net.thread; +import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -37,19 +39,19 @@ import java.util.Objects; * @see ThreadNetworkController#unregisterConfigurationCallback * @hide */ -// @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED) -// @SystemApi +@FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED) +@SystemApi public final class ThreadConfiguration implements Parcelable { private final boolean mNat64Enabled; - private final boolean mDhcp6PdEnabled; + private final boolean mDhcpv6PdEnabled; private ThreadConfiguration(Builder builder) { - this(builder.mNat64Enabled, builder.mDhcp6PdEnabled); + this(builder.mNat64Enabled, builder.mDhcpv6PdEnabled); } - private ThreadConfiguration(boolean nat64Enabled, boolean dhcp6PdEnabled) { + private ThreadConfiguration(boolean nat64Enabled, boolean dhcpv6PdEnabled) { this.mNat64Enabled = nat64Enabled; - this.mDhcp6PdEnabled = dhcp6PdEnabled; + this.mDhcpv6PdEnabled = dhcpv6PdEnabled; } /** Returns {@code true} if NAT64 is enabled. */ @@ -58,8 +60,8 @@ public final class ThreadConfiguration implements Parcelable { } /** Returns {@code true} if DHCPv6 Prefix Delegation is enabled. */ - public boolean isDhcp6PdEnabled() { - return mDhcp6PdEnabled; + public boolean isDhcpv6PdEnabled() { + return mDhcpv6PdEnabled; } @Override @@ -71,13 +73,13 @@ public final class ThreadConfiguration implements Parcelable { } else { ThreadConfiguration otherConfig = (ThreadConfiguration) other; return mNat64Enabled == otherConfig.mNat64Enabled - && mDhcp6PdEnabled == otherConfig.mDhcp6PdEnabled; + && mDhcpv6PdEnabled == otherConfig.mDhcpv6PdEnabled; } } @Override public int hashCode() { - return Objects.hash(mNat64Enabled, mDhcp6PdEnabled); + return Objects.hash(mNat64Enabled, mDhcpv6PdEnabled); } @Override @@ -85,7 +87,7 @@ public final class ThreadConfiguration implements Parcelable { StringBuilder sb = new StringBuilder(); sb.append('{'); sb.append("Nat64Enabled=").append(mNat64Enabled); - sb.append(", Dhcp6PdEnabled=").append(mDhcp6PdEnabled); + sb.append(", Dhcpv6PdEnabled=").append(mDhcpv6PdEnabled); sb.append('}'); return sb.toString(); } @@ -98,7 +100,7 @@ public final class ThreadConfiguration implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeBoolean(mNat64Enabled); - dest.writeBoolean(mDhcp6PdEnabled); + dest.writeBoolean(mDhcpv6PdEnabled); } public static final @NonNull Creator CREATOR = @@ -107,7 +109,7 @@ public final class ThreadConfiguration implements Parcelable { public ThreadConfiguration createFromParcel(Parcel in) { ThreadConfiguration.Builder builder = new ThreadConfiguration.Builder(); builder.setNat64Enabled(in.readBoolean()); - builder.setDhcp6PdEnabled(in.readBoolean()); + builder.setDhcpv6PdEnabled(in.readBoolean()); return builder.build(); } @@ -117,10 +119,14 @@ public final class ThreadConfiguration implements Parcelable { } }; - /** The builder for creating {@link ThreadConfiguration} objects. */ + /** + * The builder for creating {@link ThreadConfiguration} objects. + * + * @hide + */ public static final class Builder { private boolean mNat64Enabled = false; - private boolean mDhcp6PdEnabled = false; + private boolean mDhcpv6PdEnabled = false; /** Creates a new {@link Builder} object with all features disabled. */ public Builder() {} @@ -134,7 +140,7 @@ public final class ThreadConfiguration implements Parcelable { Objects.requireNonNull(config); mNat64Enabled = config.mNat64Enabled; - mDhcp6PdEnabled = config.mDhcp6PdEnabled; + mDhcpv6PdEnabled = config.mDhcpv6PdEnabled; } /** @@ -156,8 +162,8 @@ public final class ThreadConfiguration implements Parcelable { * IPv6. */ @NonNull - public Builder setDhcp6PdEnabled(boolean enabled) { - this.mDhcp6PdEnabled = enabled; + public Builder setDhcpv6PdEnabled(boolean enabled) { + this.mDhcpv6PdEnabled = enabled; return this; } diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java index 30b3d6a6a0..b4e581c94a 100644 --- a/thread/framework/java/android/net/thread/ThreadNetworkController.java +++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java @@ -619,16 +619,15 @@ public final class ThreadNetworkController { /** * Registers a callback to be called when the configuration is changed. * - *

Upon return of this method, {@code callback} will be invoked immediately with the new + *

Upon return of this method, {@code callback} will be invoked immediately with the current * {@link ThreadConfiguration}. * * @param executor the executor to execute the {@code callback} * @param callback the callback to receive Thread configuration changes * @throws IllegalArgumentException if {@code callback} has already been registered - * @hide */ - // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED) - // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED) + @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED) + @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED) public void registerConfigurationCallback( @NonNull @CallbackExecutor Executor executor, @NonNull Consumer callback) { @@ -656,10 +655,9 @@ public final class ThreadNetworkController { * @param callback the callback which has been registered with {@link * #registerConfigurationCallback} * @throws IllegalArgumentException if {@code callback} hasn't been registered - * @hide */ - // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED) - // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED) + @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED) + @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED) public void unregisterConfigurationCallback(@NonNull Consumer callback) { requireNonNull(callback, "callback cannot be null"); synchronized (mConfigurationCallbackMapLock) { diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java index 747cc96ecd..7c4c72d837 100644 --- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java +++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java @@ -197,7 +197,7 @@ public class ThreadPersistentSettings { return false; } putObject(CONFIG_NAT64_ENABLED.key, configuration.isNat64Enabled()); - putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcp6PdEnabled()); + putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcpv6PdEnabled()); writeToStoreFile(); return true; } @@ -206,7 +206,7 @@ public class ThreadPersistentSettings { public ThreadConfiguration getConfiguration() { return new ThreadConfiguration.Builder() .setNat64Enabled(get(CONFIG_NAT64_ENABLED)) - .setDhcp6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED)) + .setDhcpv6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED)) .build(); } diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp index c1cf0a09ee..6db7c9c452 100644 --- a/thread/tests/cts/Android.bp +++ b/thread/tests/cts/Android.bp @@ -21,9 +21,11 @@ package { android_test { name: "CtsThreadNetworkTestCases", - defaults: ["cts_defaults"], + defaults: [ + "cts_defaults", + "framework-connectivity-test-defaults", + ], min_sdk_version: "33", - sdk_version: "test_current", manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", srcs: [ diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java new file mode 100644 index 0000000000..386412efab --- /dev/null +++ b/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.thread.cts; + +import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.thread.ThreadConfiguration; +import android.net.thread.utils.ThreadFeatureCheckerRule; +import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature; + +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +/** Tests for {@link ThreadConfiguration}. */ +@SmallTest +@RequiresThreadFeature +@RunWith(Parameterized.class) +public final class ThreadConfigurationTest { + @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule(); + + public final boolean mIsNat64Enabled; + public final boolean mIsDhcpv6PdEnabled; + + @Parameterized.Parameters + public static Collection configArguments() { + return Arrays.asList( + new Object[][] { + {false, false}, // All disabled + {true, false}, // NAT64 enabled + {false, true}, // DHCP6-PD enabled + {true, true}, // All enabled + }); + } + + public ThreadConfigurationTest(boolean isNat64Enabled, boolean isDhcpv6PdEnabled) { + mIsNat64Enabled = isNat64Enabled; + mIsDhcpv6PdEnabled = isDhcpv6PdEnabled; + } + + @Test + public void parcelable_parcelingIsLossLess() { + ThreadConfiguration config = + new ThreadConfiguration.Builder() + .setNat64Enabled(mIsNat64Enabled) + .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled) + .build(); + assertParcelingIsLossless(config); + } + + @Test + public void builder_correctValuesAreSet() { + ThreadConfiguration config = + new ThreadConfiguration.Builder() + .setNat64Enabled(mIsNat64Enabled) + .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled) + .build(); + + assertThat(config.isNat64Enabled()).isEqualTo(mIsNat64Enabled); + assertThat(config.isDhcpv6PdEnabled()).isEqualTo(mIsDhcpv6PdEnabled); + } + + @Test + public void builderConstructor_configsAreEqual() { + ThreadConfiguration config1 = + new ThreadConfiguration.Builder() + .setNat64Enabled(mIsNat64Enabled) + .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled) + .build(); + ThreadConfiguration config2 = new ThreadConfiguration.Builder(config1).build(); + assertThat(config1).isEqualTo(config2); + } +} 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 11c4819b7e..1a101b6daa 100644 --- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java +++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java @@ -58,6 +58,7 @@ import android.net.nsd.NsdServiceInfo; import android.net.thread.ActiveOperationalDataset; import android.net.thread.OperationalDatasetTimestamp; import android.net.thread.PendingOperationalDataset; +import android.net.thread.ThreadConfiguration; import android.net.thread.ThreadNetworkController; import android.net.thread.ThreadNetworkController.OperationalDatasetCallback; import android.net.thread.ThreadNetworkController.StateCallback; @@ -95,9 +96,11 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; import java.util.function.Predicate; /** CTS tests for {@link ThreadNetworkController}. */ @@ -110,11 +113,14 @@ public class ThreadNetworkControllerTest { private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000; private static final int CALLBACK_TIMEOUT_MILLIS = 1_000; private static final int ENABLED_TIMEOUT_MILLIS = 2_000; + private static final int SET_CONFIGURATION_TIMEOUT_MILLIS = 1_000; private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000; private static final int SERVICE_LOST_TIMEOUT_MILLIS = 20_000; private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp"; private static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED"; + private static final ThreadConfiguration DEFAULT_CONFIG = + new ThreadConfiguration.Builder().build(); @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule(); @@ -127,6 +133,9 @@ public class ThreadNetworkControllerTest { private HandlerThread mHandlerThread; private TapTestNetworkTracker mTestNetworkTracker; + private final List> mConfigurationCallbacksToCleanUp = + new ArrayList<>(); + @Before public void setUp() throws Exception { mController = @@ -141,6 +150,7 @@ public class ThreadNetworkControllerTest { mHandlerThread.start(); setEnabledAndWait(mController, true); + setConfigurationAndWait(mController, DEFAULT_CONFIG); } @After @@ -148,6 +158,18 @@ public class ThreadNetworkControllerTest { dropAllPermissions(); leaveAndWait(mController); tearDownTestNetwork(); + setConfigurationAndWait(mController, DEFAULT_CONFIG); + for (Consumer configurationCallback : + mConfigurationCallbacksToCleanUp) { + try { + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> mController.unregisterConfigurationCallback(configurationCallback)); + } catch (IllegalArgumentException e) { + // Ignore the exception when the callback is not registered. + } + } + mConfigurationCallbacksToCleanUp.clear(); } @Test @@ -832,6 +854,152 @@ public class ThreadNetworkControllerTest { NET_CAPABILITY_TRUSTED); } + @Test + public void setConfiguration_null_throwsNullPointerException() throws Exception { + CompletableFuture setConfigFuture = new CompletableFuture<>(); + assertThrows( + NullPointerException.class, + () -> + mController.setConfiguration( + null, mExecutor, newOutcomeReceiver(setConfigFuture))); + } + + @Test + public void setConfiguration_noPermissions_throwsSecurityException() throws Exception { + ThreadConfiguration configuration = + new ThreadConfiguration.Builder().setNat64Enabled(true).build(); + CompletableFuture setConfigFuture = new CompletableFuture<>(); + assertThrows( + SecurityException.class, + () -> { + mController.setConfiguration( + configuration, mExecutor, newOutcomeReceiver(setConfigFuture)); + }); + } + + @Test + public void registerConfigurationCallback_permissionsGranted_returnsCurrentStatus() + throws Exception { + CompletableFuture getConfigFuture = new CompletableFuture<>(); + Consumer callback = getConfigFuture::complete; + + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> registerConfigurationCallback(mController, mExecutor, callback)); + assertThat(getConfigFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS)) + .isEqualTo(DEFAULT_CONFIG); + } + + @Test + public void registerConfigurationCallback_noPermissions_throwsSecurityException() + throws Exception { + dropAllPermissions(); + + assertThrows( + SecurityException.class, + () -> registerConfigurationCallback(mController, mExecutor, config -> {})); + } + + @Test + public void registerConfigurationCallback_returnsUpdatedConfigurations() throws Exception { + CompletableFuture setFuture1 = new CompletableFuture<>(); + CompletableFuture setFuture2 = new CompletableFuture<>(); + ConfigurationListener listener = new ConfigurationListener(mController); + ThreadConfiguration config1 = + new ThreadConfiguration.Builder() + .setNat64Enabled(true) + .setDhcpv6PdEnabled(true) + .build(); + ThreadConfiguration config2 = + new ThreadConfiguration.Builder() + .setNat64Enabled(false) + .setDhcpv6PdEnabled(true) + .build(); + + try { + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> + mController.setConfiguration( + config1, mExecutor, newOutcomeReceiver(setFuture1))); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> + mController.setConfiguration( + config2, mExecutor, newOutcomeReceiver(setFuture2))); + setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); + setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS); + + listener.expectConfiguration(DEFAULT_CONFIG); + listener.expectConfiguration(config1); + listener.expectConfiguration(config2); + listener.expectNoMoreConfiguration(); + } finally { + listener.unregisterConfigurationCallback(); + } + } + + @Test + public void registerConfigurationCallback_alreadyRegistered_throwsIllegalArgumentException() + throws Exception { + grantPermissions(THREAD_NETWORK_PRIVILEGED); + + Consumer callback = config -> {}; + registerConfigurationCallback(mController, mExecutor, callback); + + assertThrows( + IllegalArgumentException.class, + () -> registerConfigurationCallback(mController, mExecutor, callback)); + } + + @Test + public void unregisterConfigurationCallback_noPermissions_throwsSecurityException() + throws Exception { + Consumer callback = config -> {}; + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> registerConfigurationCallback(mController, mExecutor, callback)); + + assertThrows( + SecurityException.class, + () -> mController.unregisterConfigurationCallback(callback)); + } + + @Test + public void unregisterConfigurationCallback_callbackRegistered_success() throws Exception { + Consumer callback = config -> {}; + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> { + registerConfigurationCallback(mController, mExecutor, callback); + mController.unregisterConfigurationCallback(callback); + }); + } + + @Test + public void + unregisterConfigurationCallback_callbackNotRegistered_throwsIllegalArgumentException() + throws Exception { + Consumer callback = config -> {}; + assertThrows( + IllegalArgumentException.class, + () -> mController.unregisterConfigurationCallback(callback)); + } + + @Test + public void unregisterConfigurationCallback_alreadyUnregistered_throwsIllegalArgumentException() + throws Exception { + grantPermissions(THREAD_NETWORK_PRIVILEGED); + + Consumer callback = config -> {}; + registerConfigurationCallback(mController, mExecutor, callback); + mController.unregisterConfigurationCallback(callback); + + assertThrows( + IllegalArgumentException.class, + () -> mController.unregisterConfigurationCallback(callback)); + } + private void grantPermissions(String... permissions) { for (String permission : permissions) { mGrantedPermissions.add(permission); @@ -1038,6 +1206,35 @@ public class ThreadNetworkControllerTest { } } + private class ConfigurationListener { + private ArrayTrackRecord mConfigurations = new ArrayTrackRecord<>(); + private final ArrayTrackRecord.ReadHead mReadHead = + mConfigurations.newReadHead(); + ThreadNetworkController mController; + Consumer mCallback = (config) -> mConfigurations.add(config); + + ConfigurationListener(ThreadNetworkController controller) { + this.mController = controller; + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> controller.registerConfigurationCallback(mExecutor, mCallback)); + } + + public void expectConfiguration(ThreadConfiguration config) { + assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, c -> c.equals(config))).isNotNull(); + } + + public void expectNoMoreConfiguration() { + assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, c -> true)).isNull(); + } + + public void unregisterConfigurationCallback() { + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> mController.unregisterConfigurationCallback(mCallback)); + } + } + private int booleanToEnabledState(boolean enabled) { return enabled ? STATE_ENABLED : STATE_DISABLED; } @@ -1052,6 +1249,18 @@ public class ThreadNetworkControllerTest { waitForEnabledState(controller, booleanToEnabledState(enabled)); } + private void setConfigurationAndWait( + ThreadNetworkController controller, ThreadConfiguration configuration) + throws Exception { + CompletableFuture setFuture = new CompletableFuture<>(); + runAsShell( + THREAD_NETWORK_PRIVILEGED, + () -> + controller.setConfiguration( + configuration, mExecutor, newOutcomeReceiver(setFuture))); + setFuture.get(SET_CONFIGURATION_TIMEOUT_MILLIS, MILLISECONDS); + } + private CompletableFuture joinRandomizedDataset( ThreadNetworkController controller, String networkName) throws Exception { ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller); @@ -1118,6 +1327,14 @@ public class ThreadNetworkControllerTest { }; } + private void registerConfigurationCallback( + ThreadNetworkController controller, + Executor executor, + Consumer callback) { + controller.registerConfigurationCallback(executor, callback); + mConfigurationCallbacksToCleanUp.add(callback); + } + private static void assertDoesNotThrow(ThrowingRunnable runnable) { try { runnable.run(); 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 eaf11b156d..a5dc25a303 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java @@ -716,12 +716,12 @@ public final class ThreadNetworkControllerServiceTest { ThreadConfiguration config1 = new ThreadConfiguration.Builder() .setNat64Enabled(false) - .setDhcp6PdEnabled(false) + .setDhcpv6PdEnabled(false) .build(); ThreadConfiguration config2 = new ThreadConfiguration.Builder() .setNat64Enabled(true) - .setDhcp6PdEnabled(true) + .setDhcpv6PdEnabled(true) .build(); ThreadConfiguration config3 = new ThreadConfiguration.Builder(config2).build(); // Same as config2 diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java index c932ac8f0e..ba489d9b1f 100644 --- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java @@ -152,7 +152,7 @@ public class ThreadPersistentSettingsTest { ThreadConfiguration configuration = new ThreadConfiguration.Builder() .setNat64Enabled(true) - .setDhcp6PdEnabled(true) + .setDhcpv6PdEnabled(true) .build(); mThreadPersistentSettings.putConfiguration(configuration); @@ -164,13 +164,13 @@ public class ThreadPersistentSettingsTest { ThreadConfiguration configuration1 = new ThreadConfiguration.Builder() .setNat64Enabled(false) - .setDhcp6PdEnabled(false) + .setDhcpv6PdEnabled(false) .build(); mThreadPersistentSettings.putConfiguration(configuration1); ThreadConfiguration configuration2 = new ThreadConfiguration.Builder() .setNat64Enabled(true) - .setDhcp6PdEnabled(true) + .setDhcpv6PdEnabled(true) .build(); assertThat(mThreadPersistentSettings.putConfiguration(configuration2)).isTrue(); @@ -188,9 +188,9 @@ public class ThreadPersistentSettingsTest { } @Test - public void putConfiguration_dhcp6PdEnabled_valuesUpdatedAndPersisted() throws Exception { + public void putConfiguration_dhcpv6PdEnabled_valuesUpdatedAndPersisted() throws Exception { ThreadConfiguration configuration = - new ThreadConfiguration.Builder().setDhcp6PdEnabled(true).build(); + new ThreadConfiguration.Builder().setDhcpv6PdEnabled(true).build(); mThreadPersistentSettings.putConfiguration(configuration); assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);