1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.net.thread.utils;
17 
18 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
19 import static android.Manifest.permission.NETWORK_SETTINGS;
20 import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
21 import static android.net.thread.utils.IntegrationTestUtils.CALLBACK_TIMEOUT;
22 
23 import static com.android.testutils.TestPermissionUtil.runAsShell;
24 
25 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
26 
27 import static java.util.concurrent.TimeUnit.SECONDS;
28 
29 import android.annotation.Nullable;
30 import android.content.Context;
31 import android.net.thread.ActiveOperationalDataset;
32 import android.net.thread.ThreadConfiguration;
33 import android.net.thread.ThreadNetworkController;
34 import android.net.thread.ThreadNetworkController.StateCallback;
35 import android.net.thread.ThreadNetworkException;
36 import android.net.thread.ThreadNetworkManager;
37 import android.os.OutcomeReceiver;
38 
39 import java.time.Duration;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.concurrent.CompletableFuture;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.TimeoutException;
45 import java.util.function.Consumer;
46 
47 /** A helper class which provides synchronous API wrappers for {@link ThreadNetworkController}. */
48 public final class ThreadNetworkControllerWrapper {
49     public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(10);
50     public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
51     private static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
52     private static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
53     private static final Duration CONFIG_TIMEOUT = Duration.ofSeconds(1);
54 
55     private final ThreadNetworkController mController;
56 
57     private final List<Integer> mDeviceRoleUpdates = new ArrayList<>();
58     @Nullable private StateCallback mStateCallback;
59 
60     /**
61      * Returns a new {@link ThreadNetworkControllerWrapper} instance or {@code null} if Thread
62      * feature is not supported on this device.
63      */
64     @Nullable
newInstance(Context context)65     public static ThreadNetworkControllerWrapper newInstance(Context context) {
66         final ThreadNetworkManager manager = context.getSystemService(ThreadNetworkManager.class);
67         if (manager == null) {
68             return null;
69         }
70         return new ThreadNetworkControllerWrapper(manager.getAllThreadNetworkControllers().get(0));
71     }
72 
ThreadNetworkControllerWrapper(ThreadNetworkController controller)73     private ThreadNetworkControllerWrapper(ThreadNetworkController controller) {
74         mController = controller;
75     }
76 
77     /**
78      * Returns the underlying {@link ThreadNetworkController} object or {@code null} if the current
79      * platform doesn't support it.
80      */
81     @Nullable
get()82     public ThreadNetworkController get() {
83         return mController;
84     }
85 
86     /**
87      * Returns the Thread enabled state.
88      *
89      * <p>The value can be one of {@code ThreadNetworkController#STATE_*}.
90      */
getEnabledState()91     public final int getEnabledState()
92             throws InterruptedException, ExecutionException, TimeoutException {
93         CompletableFuture<Integer> future = new CompletableFuture<>();
94         StateCallback callback =
95                 new StateCallback() {
96                     @Override
97                     public void onThreadEnableStateChanged(int enabledState) {
98                         future.complete(enabledState);
99                     }
100 
101                     @Override
102                     public void onDeviceRoleChanged(int deviceRole) {}
103                 };
104 
105         runAsShell(
106                 ACCESS_NETWORK_STATE,
107                 () -> mController.registerStateCallback(directExecutor(), callback));
108         try {
109             return future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
110         } finally {
111             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
112         }
113     }
114 
115     /**
116      * Returns the Thread device role.
117      *
118      * <p>The value can be one of {@code ThreadNetworkController#DEVICE_ROLE_*}.
119      */
getDeviceRole()120     public final int getDeviceRole()
121             throws InterruptedException, ExecutionException, TimeoutException {
122         CompletableFuture<Integer> future = new CompletableFuture<>();
123         StateCallback callback = future::complete;
124 
125         runAsShell(
126                 ACCESS_NETWORK_STATE,
127                 () -> mController.registerStateCallback(directExecutor(), callback));
128         try {
129             return future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
130         } finally {
131             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
132         }
133     }
134 
135     /** An synchronous variant of {@link ThreadNetworkController#setEnabled}. */
setEnabledAndWait(boolean enabled)136     public void setEnabledAndWait(boolean enabled)
137             throws InterruptedException, ExecutionException, TimeoutException {
138         CompletableFuture<Void> future = new CompletableFuture<>();
139         runAsShell(
140                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
141                 () ->
142                         mController.setEnabled(
143                                 enabled, directExecutor(), newOutcomeReceiver(future)));
144         future.get(SET_ENABLED_TIMEOUT.toSeconds(), SECONDS);
145     }
146 
147     /** Joins the given network and wait for this device to become attached. */
joinAndWait(ActiveOperationalDataset activeDataset)148     public void joinAndWait(ActiveOperationalDataset activeDataset)
149             throws InterruptedException, ExecutionException, TimeoutException {
150         CompletableFuture<Void> future = new CompletableFuture<>();
151         runAsShell(
152                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
153                 () ->
154                         mController.join(
155                                 activeDataset, directExecutor(), newOutcomeReceiver(future)));
156         future.get(JOIN_TIMEOUT.toSeconds(), SECONDS);
157     }
158 
159     /** An synchronous variant of {@link ThreadNetworkController#leave}. */
leaveAndWait()160     public void leaveAndWait() throws InterruptedException, ExecutionException, TimeoutException {
161         CompletableFuture<Void> future = new CompletableFuture<>();
162         runAsShell(
163                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
164                 () -> mController.leave(directExecutor(), future::complete));
165         future.get(LEAVE_TIMEOUT.toSeconds(), SECONDS);
166     }
167 
168     /** Waits for the device role to become {@code deviceRole}. */
waitForRole(int deviceRole, Duration timeout)169     public int waitForRole(int deviceRole, Duration timeout)
170             throws InterruptedException, ExecutionException, TimeoutException {
171         return waitForRoleAnyOf(List.of(deviceRole), timeout);
172     }
173 
174     /** Waits for the device role to become one of the values specified in {@code deviceRoles}. */
waitForRoleAnyOf(List<Integer> deviceRoles, Duration timeout)175     public int waitForRoleAnyOf(List<Integer> deviceRoles, Duration timeout)
176             throws InterruptedException, ExecutionException, TimeoutException {
177         CompletableFuture<Integer> future = new CompletableFuture<>();
178         ThreadNetworkController.StateCallback callback =
179                 newRole -> {
180                     if (deviceRoles.contains(newRole)) {
181                         future.complete(newRole);
182                     }
183                 };
184 
185         runAsShell(
186                 ACCESS_NETWORK_STATE,
187                 () -> mController.registerStateCallback(directExecutor(), callback));
188 
189         try {
190             return future.get(timeout.toSeconds(), SECONDS);
191         } finally {
192             mController.unregisterStateCallback(callback);
193         }
194     }
195 
196     /** An synchronous variant of {@link ThreadNetworkController#setTestNetworkAsUpstream}. */
setTestNetworkAsUpstreamAndWait(@ullable String networkInterfaceName)197     public void setTestNetworkAsUpstreamAndWait(@Nullable String networkInterfaceName)
198             throws InterruptedException, ExecutionException, TimeoutException {
199         CompletableFuture<Void> future = new CompletableFuture<>();
200         runAsShell(
201                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
202                 NETWORK_SETTINGS,
203                 () -> {
204                     mController.setTestNetworkAsUpstream(
205                             networkInterfaceName, directExecutor(), future::complete);
206                 });
207         future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
208     }
209 
getConfiguration()210     public ThreadConfiguration getConfiguration() throws Exception {
211         CompletableFuture<ThreadConfiguration> future = new CompletableFuture<>();
212         Consumer<ThreadConfiguration> callback = future::complete;
213         runAsShell(
214                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
215                 () -> mController.registerConfigurationCallback(directExecutor(), callback));
216         future.get(CONFIG_TIMEOUT.toSeconds(), SECONDS);
217         runAsShell(
218                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
219                 () -> mController.unregisterConfigurationCallback(callback));
220         return future.getNow(null);
221     }
222 
setConfigurationAndWait(ThreadConfiguration config)223     public void setConfigurationAndWait(ThreadConfiguration config) throws Exception {
224         CompletableFuture<Void> future = new CompletableFuture<>();
225         runAsShell(
226                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
227                 () ->
228                         mController.setConfiguration(
229                                 config, directExecutor(), newOutcomeReceiver(future)));
230         future.get(CONFIG_TIMEOUT.toSeconds(), SECONDS);
231     }
232 
setNat64EnabledAndWait(boolean enabled)233     public void setNat64EnabledAndWait(boolean enabled) throws Exception {
234         final ThreadConfiguration config = getConfiguration();
235         final ThreadConfiguration newConfig =
236                 new ThreadConfiguration.Builder(config).setNat64Enabled(enabled).build();
237         setConfigurationAndWait(newConfig);
238     }
239 
newOutcomeReceiver( CompletableFuture<V> future)240     private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
241             CompletableFuture<V> future) {
242         return new OutcomeReceiver<V, ThreadNetworkException>() {
243             @Override
244             public void onResult(V result) {
245                 future.complete(result);
246             }
247 
248             @Override
249             public void onError(ThreadNetworkException e) {
250                 future.completeExceptionally(e);
251             }
252         };
253     }
254 }
255