Merge "[Thread] initial Thread network service" into main

This commit is contained in:
Kangping Dong
2023-09-19 11:25:37 +00:00
committed by Gerrit Code Review
18 changed files with 705 additions and 0 deletions

View File

@@ -213,6 +213,7 @@ bootclasspath_fragment {
"android.net.http.apihelpers",
"android.net.netstats.provider",
"android.net.nsd",
"android.net.thread",
"android.net.wear",
],
},

View File

@@ -388,3 +388,16 @@ package android.net.nsd {
}
package android.net.thread {
public class ThreadNetworkController {
method public int getThreadVersion();
field public static final int THREAD_VERSION_1_3 = 4; // 0x4
}
public class ThreadNetworkManager {
method @NonNull public java.util.List<android.net.thread.ThreadNetworkController> getAllThreadNetworkControllers();
}
}

View File

@@ -24,6 +24,8 @@ import android.net.mdns.aidl.IMDns;
import android.net.nsd.INsdManager;
import android.net.nsd.MDnsManager;
import android.net.nsd.NsdManager;
import android.net.thread.IThreadNetworkManager;
import android.net.thread.ThreadNetworkManager;
/**
* Class for performing registration for Connectivity services which are exposed via updatable APIs
@@ -89,5 +91,14 @@ public final class ConnectivityFrameworkInitializerTiramisu {
return new MDnsManager(service);
}
);
SystemServiceRegistry.registerContextAwareService(
ThreadNetworkManager.SERVICE_NAME,
ThreadNetworkManager.class,
(context, serviceBinder) -> {
IThreadNetworkManager managerService =
IThreadNetworkManager.Stub.asInterface(serviceBinder);
return new ThreadNetworkManager(context, managerService);
});
}
}

View File

@@ -16,7 +16,10 @@
package com.android.server;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.thread.ThreadNetworkManager;
import android.util.Log;
import com.android.modules.utils.build.SdkLevel;
@@ -26,6 +29,7 @@ import com.android.server.ethernet.EthernetService;
import com.android.server.ethernet.EthernetServiceImpl;
import com.android.server.nearby.NearbyService;
import com.android.server.remoteauth.RemoteAuthService;
import com.android.server.thread.ThreadNetworkService;
/**
* Connectivity service initializer for core networking. This is called by system server to create
@@ -40,6 +44,7 @@ public final class ConnectivityServiceInitializer extends SystemService {
private final NearbyService mNearbyService;
private final EthernetServiceImpl mEthernetServiceImpl;
private final RemoteAuthService mRemoteAuthService;
private final ThreadNetworkService mThreadNetworkService;
public ConnectivityServiceInitializer(Context context) {
super(context);
@@ -52,6 +57,7 @@ public final class ConnectivityServiceInitializer extends SystemService {
mNsdService = createNsdService(context);
mNearbyService = createNearbyService(context);
mRemoteAuthService = createRemoteAuthService(context);
mThreadNetworkService = createThreadNetworkService(context);
}
@Override
@@ -93,6 +99,12 @@ public final class ConnectivityServiceInitializer extends SystemService {
publishBinderService(RemoteAuthService.SERVICE_NAME, mRemoteAuthService,
/* allowIsolated= */ false);
}
if (mThreadNetworkService != null) {
Log.i(TAG, "Registering " + ThreadNetworkManager.SERVICE_NAME);
publishBinderService(ThreadNetworkManager.SERVICE_NAME, mThreadNetworkService,
/* allowIsolated= */ false);
}
}
@Override
@@ -104,6 +116,10 @@ public final class ConnectivityServiceInitializer extends SystemService {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY && mEthernetServiceImpl != null) {
mEthernetServiceImpl.start();
}
if (mThreadNetworkService != null) {
mThreadNetworkService.onBootPhase(phase);
}
}
/**
@@ -171,4 +187,25 @@ public final class ConnectivityServiceInitializer extends SystemService {
}
return EthernetService.create(context);
}
/**
* Returns Thread network service instance if supported.
* Thread is supported if all of below are satisfied:
* 1. the FEATURE_THREAD_NETWORK is available
* 2. the SDK level is V+, or SDK level is U and the device is a TV
*/
@Nullable
private ThreadNetworkService createThreadNetworkService(final Context context) {
final PackageManager pm = context.getPackageManager();
if (!pm.hasSystemFeature(ThreadNetworkManager.FEATURE_NAME)) {
return null;
}
if (!SdkLevel.isAtLeastU()) {
return null;
}
if (!SdkLevel.isAtLeastV() && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
return null;
}
return new ThreadNetworkService(context);
}
}

9
thread/TEST_MAPPING Normal file
View File

@@ -0,0 +1,9 @@
{
// TODO (b/297729075): graduate this test to presubmit once it meets the SLO requirements.
// See go/test-mapping-slo-guide
"postsubmit": [
{
"name": "CtsThreadNetworkTestCases"
}
]
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2023, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.thread;
/**
* Interface for communicating with ThreadNetworkControllerService.
* @hide
*/
interface IThreadNetworkController {
int getThreadVersion();
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2023, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.thread;
import android.net.thread.IThreadNetworkController;
/**
* Interface for communicating with ThreadNetworkService.
* @hide
*/
interface IThreadNetworkManager {
List<IThreadNetworkController> getAllThreadNetworkControllers();
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.thread;
import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.RemoteException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Provides the primary API for controlling all aspects of a Thread network.
*
* @hide
*/
@SystemApi
public class ThreadNetworkController {
/** Thread standard version 1.3. */
public static final int THREAD_VERSION_1_3 = 4;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({THREAD_VERSION_1_3})
public @interface ThreadVersion {}
private final IThreadNetworkController mControllerService;
ThreadNetworkController(@NonNull IThreadNetworkController controllerService) {
requireNonNull(controllerService, "controllerService cannot be null");
mControllerService = controllerService;
}
/** Returns the Thread version this device is operating on. */
@ThreadVersion
public int getThreadVersion() {
try {
return mControllerService.getThreadVersion();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.thread;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
import com.android.net.module.util.CollectionUtils;
import java.util.Collections;
import java.util.List;
/**
* Provides the primary API for managing app aspects of Thread network connectivity.
*
* @hide
*/
@SystemApi
@SystemService(ThreadNetworkManager.SERVICE_NAME)
public class ThreadNetworkManager {
/**
* This value tracks {@link Context#THREAD_NETWORK_SERVICE}.
*
* <p>This is needed because at the time this service is created, it needs to support both
* Android U and V but {@link Context#THREAD_NETWORK_SERVICE} Is only available on the V branch.
*
* <p>Note that this is not added to NetworkStack ConstantsShim because we need this constant in
* the framework library while ConstantsShim is only linked against the service library.
*
* @hide
*/
public static final String SERVICE_NAME = "thread_network";
/**
* This value tracks {@link PackageManager#FEATURE_THREAD_NETWORK}.
*
* <p>This is needed because at the time this service is created, it needs to support both
* Android U and V but {@link PackageManager#FEATURE_THREAD_NETWORK} Is only available on the V
* branch.
*
* <p>Note that this is not added to NetworkStack COnstantsShim because we need this constant in
* the framework library while ConstantsShim is only linked against the service library.
*
* @hide
*/
public static final String FEATURE_NAME = "android.hardware.thread_network";
@NonNull private final Context mContext;
@NonNull private final List<ThreadNetworkController> mUnmodifiableControllerServices;
/**
* Creates a new ThreadNetworkManager instance.
*
* @hide
*/
public ThreadNetworkManager(
@NonNull Context context, @NonNull IThreadNetworkManager managerService) {
this(context, makeControllers(managerService));
}
private static List<ThreadNetworkController> makeControllers(
@NonNull IThreadNetworkManager managerService) {
requireNonNull(managerService, "managerService cannot be null");
List<IThreadNetworkController> controllerServices;
try {
controllerServices = managerService.getAllThreadNetworkControllers();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return Collections.emptyList();
}
return CollectionUtils.map(controllerServices, ThreadNetworkController::new);
}
private ThreadNetworkManager(
@NonNull Context context, @NonNull List<ThreadNetworkController> controllerServices) {
mContext = context;
mUnmodifiableControllerServices = Collections.unmodifiableList(controllerServices);
}
/** Returns the {@link ThreadNetworkController} object of all Thread networks. */
@NonNull
public List<ThreadNetworkController> getAllThreadNetworkControllers() {
return mUnmodifiableControllerServices;
}
}

View File

@@ -32,5 +32,11 @@ java_library {
// (service-connectivity is only used on 31+) and use 31 here
min_sdk_version: "30",
srcs: [":service-thread-sources"],
libs: [
"framework-connectivity-t-pre-jarjar",
],
static_libs: [
"net-utils-device-common",
],
apex_available: ["com.android.tethering"],
}

View File

@@ -0,0 +1,31 @@
/*
* 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.thread;
import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
import android.net.thread.IThreadNetworkController;
import android.net.thread.ThreadNetworkController;
/** Implementation of the {@link ThreadNetworkController} API. */
public final class ThreadNetworkControllerService extends IThreadNetworkController.Stub {
@Override
public int getThreadVersion() {
return THREAD_VERSION_1_3;
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.thread;
import android.content.Context;
import android.net.thread.IThreadNetworkController;
import android.net.thread.IThreadNetworkManager;
import com.android.server.SystemService;
import java.util.Collections;
import java.util.List;
/**
* Implementation of the Thread network service. This is the entry point of Android Thread feature.
*/
public class ThreadNetworkService extends IThreadNetworkManager.Stub {
private final ThreadNetworkControllerService mControllerService;
/** Creates a new {@link ThreadNetworkService} object. */
public ThreadNetworkService(Context context) {
this(context, new ThreadNetworkControllerService());
}
private ThreadNetworkService(
Context context, ThreadNetworkControllerService controllerService) {
mControllerService = controllerService;
}
/**
* Called by the service initializer.
*
* @see com.android.server.SystemService#onBootPhase
*/
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
// TODO: initialize ThreadNetworkManagerService
}
}
@Override
public List<IThreadNetworkController> getAllThreadNetworkControllers() {
return Collections.singletonList(mControllerService);
}
}

View File

@@ -0,0 +1,50 @@
//
// 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_test {
name: "CtsThreadNetworkTestCases",
defaults: ["cts_defaults"],
min_sdk_version: "33",
sdk_version: "test_current",
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
srcs: [
"src/**/*.java",
],
test_suites: [
"cts",
"general-tests",
"mts-tethering",
],
static_libs: [
"androidx.test.ext.junit",
"compatibility-device-util-axt",
"ctstestrunner-axt",
"net-tests-utils",
"truth-prebuilt",
],
libs: [
"android.test.base",
"android.test.runner",
],
// Test coverage system runs on different devices. Need to
// compile for all architectures.
compile_multilib: "both",
}

View File

@@ -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.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.net.thread.cts">
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.net.thread.cts"
android:label="CTS tests for android.net.thread" />
</manifest>

View File

@@ -0,0 +1,51 @@
<?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.
-->
<configuration description="Config for Thread network CTS test cases">
<option name="test-tag" value="CtsThreadNetworkTestCases" />
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<!--
Only run tests if the device under test is SDK version 33 (Android 13) or above.
The Thread feature is only available on V+ and U+ TV devices but this test module
needs run on T+ because there are testcases which verifies that Thread service
is not support on T or T-.
-->
<object type="module_controller"
class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
<!-- Run tests in MTS only if the Tethering Mainline module is installed. -->
<object type="module_controller"
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
<!-- Install test -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="CtsThreadNetworkTestCases.apk" />
<option name="check-min-sdk" value="true" />
<option name="cleanup-apks" value="true" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.net.thread.cts" />
</test>
</configuration>

3
thread/tests/cts/OWNERS Normal file
View File

@@ -0,0 +1,3 @@
# Bug component: 1203089
include platform/packages/modules/Connectivity:main:/thread/OWNERS

View File

@@ -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 android.net.thread.cts;
import static android.net.thread.ThreadNetworkController.THREAD_VERSION_1_3;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeNotNull;
import android.content.Context;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkManager;
import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
/** CTS tests for {@link ThreadNetworkController}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU) // Thread is available on only U+
public class ThreadNetworkControllerTest {
@Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private ThreadNetworkManager mManager;
@Before
public void setUp() {
mManager = mContext.getSystemService(ThreadNetworkManager.class);
// TODO: we will also need it in tearDown(), it's better to have a Rule to skip
// tests if a feature is not available.
assumeNotNull(mManager);
}
private List<ThreadNetworkController> getAllControllers() {
return mManager.getAllThreadNetworkControllers();
}
@Test
public void getThreadVersion_returnsAtLeastThreadVersion1P3() {
for (ThreadNetworkController controller : getAllControllers()) {
assertThat(controller.getThreadVersion()).isAtLeast(THREAD_VERSION_1_3);
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.thread.cts;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeNotNull;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkManager;
import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
/** Tests for {@link ThreadNetworkManager}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ThreadNetworkManagerTest {
@Rule public DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
private final Context mContext = ApplicationProvider.getApplicationContext();
private final PackageManager mPackageManager = mContext.getPackageManager();
private ThreadNetworkManager mManager;
@Before
public void setUp() {
mManager = mContext.getSystemService(ThreadNetworkManager.class);
}
@Test
@IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
public void getManager_onTOrLower_returnsNull() {
assertThat(mManager).isNull();
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void getManager_hasThreadFeatureOnVOrHigher_returnsNonNull() {
assumeTrue(mPackageManager.hasSystemFeature("android.hardware.thread_network"));
assertThat(mManager).isNotNull();
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
@IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void getManager_onUButNotTv_returnsNull() {
assumeFalse(mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
assertThat(mManager).isNull();
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
@IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void getManager_onUAndTv_returnsNonNull() {
assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
assertThat(mManager).isNotNull();
}
@Test
public void getManager_noThreadFeature_returnsNull() {
assumeFalse(mPackageManager.hasSystemFeature("android.hardware.thread_network"));
assertThat(mManager).isNull();
}
@Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void getAllThreadNetworkControllers_managerIsNotNull_returnsNotEmptyList() {
assumeNotNull(mManager);
List<ThreadNetworkController> controllers = mManager.getAllThreadNetworkControllers();
assertThat(controllers).isNotEmpty();
}
}