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.ThreadNetworkController;
33 import android.net.thread.ThreadNetworkController.StateCallback;
34 import android.net.thread.ThreadNetworkException;
35 import android.net.thread.ThreadNetworkManager;
36 import android.os.OutcomeReceiver;
37 
38 import java.time.Duration;
39 import java.util.List;
40 import java.util.concurrent.CompletableFuture;
41 import java.util.concurrent.ExecutionException;
42 import java.util.concurrent.TimeoutException;
43 
44 /** A helper class which provides synchronous API wrappers for {@link ThreadNetworkController}. */
45 public final class ThreadNetworkControllerWrapper {
46     public static final Duration JOIN_TIMEOUT = Duration.ofSeconds(10);
47     public static final Duration LEAVE_TIMEOUT = Duration.ofSeconds(2);
48     private static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
49     private static final Duration SET_ENABLED_TIMEOUT = Duration.ofSeconds(2);
50 
51     private final ThreadNetworkController mController;
52 
53     /**
54      * Returns a new {@link ThreadNetworkControllerWrapper} instance or {@code null} if Thread
55      * feature is not supported on this device.
56      */
57     @Nullable
newInstance(Context context)58     public static ThreadNetworkControllerWrapper newInstance(Context context) {
59         final ThreadNetworkManager manager = context.getSystemService(ThreadNetworkManager.class);
60         if (manager == null) {
61             return null;
62         }
63         return new ThreadNetworkControllerWrapper(manager.getAllThreadNetworkControllers().get(0));
64     }
65 
ThreadNetworkControllerWrapper(ThreadNetworkController controller)66     private ThreadNetworkControllerWrapper(ThreadNetworkController controller) {
67         mController = controller;
68     }
69 
70     /**
71      * Returns the Thread enabled state.
72      *
73      * <p>The value can be one of {@code ThreadNetworkController#STATE_*}.
74      */
getEnabledState()75     public final int getEnabledState()
76             throws InterruptedException, ExecutionException, TimeoutException {
77         CompletableFuture<Integer> future = new CompletableFuture<>();
78         StateCallback callback =
79                 new StateCallback() {
80                     @Override
81                     public void onThreadEnableStateChanged(int enabledState) {
82                         future.complete(enabledState);
83                     }
84 
85                     @Override
86                     public void onDeviceRoleChanged(int deviceRole) {}
87                 };
88 
89         runAsShell(
90                 ACCESS_NETWORK_STATE,
91                 () -> mController.registerStateCallback(directExecutor(), callback));
92         try {
93             return future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
94         } finally {
95             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
96         }
97     }
98 
99     /**
100      * Returns the Thread device role.
101      *
102      * <p>The value can be one of {@code ThreadNetworkController#DEVICE_ROLE_*}.
103      */
getDeviceRole()104     public final int getDeviceRole()
105             throws InterruptedException, ExecutionException, TimeoutException {
106         CompletableFuture<Integer> future = new CompletableFuture<>();
107         StateCallback callback = future::complete;
108 
109         runAsShell(
110                 ACCESS_NETWORK_STATE,
111                 () -> mController.registerStateCallback(directExecutor(), callback));
112         try {
113             return future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
114         } finally {
115             runAsShell(ACCESS_NETWORK_STATE, () -> mController.unregisterStateCallback(callback));
116         }
117     }
118 
119     /** An synchronous variant of {@link ThreadNetworkController#setEnabled}. */
setEnabledAndWait(boolean enabled)120     public void setEnabledAndWait(boolean enabled)
121             throws InterruptedException, ExecutionException, TimeoutException {
122         CompletableFuture<Void> future = new CompletableFuture<>();
123         runAsShell(
124                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
125                 () ->
126                         mController.setEnabled(
127                                 enabled, directExecutor(), newOutcomeReceiver(future)));
128         future.get(SET_ENABLED_TIMEOUT.toSeconds(), SECONDS);
129     }
130 
131     /** Joins the given network and wait for this device to become attached. */
joinAndWait(ActiveOperationalDataset activeDataset)132     public void joinAndWait(ActiveOperationalDataset activeDataset)
133             throws InterruptedException, ExecutionException, TimeoutException {
134         CompletableFuture<Void> future = new CompletableFuture<>();
135         runAsShell(
136                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
137                 () ->
138                         mController.join(
139                                 activeDataset, directExecutor(), newOutcomeReceiver(future)));
140         future.get(JOIN_TIMEOUT.toSeconds(), SECONDS);
141     }
142 
143     /** An synchronous variant of {@link ThreadNetworkController#leave}. */
leaveAndWait()144     public void leaveAndWait() throws InterruptedException, ExecutionException, TimeoutException {
145         CompletableFuture<Void> future = new CompletableFuture<>();
146         runAsShell(
147                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
148                 () -> mController.leave(directExecutor(), future::complete));
149         future.get(LEAVE_TIMEOUT.toSeconds(), SECONDS);
150     }
151 
152     /** Waits for the device role to become {@code deviceRole}. */
waitForRole(int deviceRole, Duration timeout)153     public int waitForRole(int deviceRole, Duration timeout)
154             throws InterruptedException, ExecutionException, TimeoutException {
155         return waitForRoleAnyOf(List.of(deviceRole), timeout);
156     }
157 
158     /** Waits for the device role to become one of the values specified in {@code deviceRoles}. */
waitForRoleAnyOf(List<Integer> deviceRoles, Duration timeout)159     public int waitForRoleAnyOf(List<Integer> deviceRoles, Duration timeout)
160             throws InterruptedException, ExecutionException, TimeoutException {
161         CompletableFuture<Integer> future = new CompletableFuture<>();
162         ThreadNetworkController.StateCallback callback =
163                 newRole -> {
164                     if (deviceRoles.contains(newRole)) {
165                         future.complete(newRole);
166                     }
167                 };
168 
169         runAsShell(
170                 ACCESS_NETWORK_STATE,
171                 () -> mController.registerStateCallback(directExecutor(), callback));
172 
173         try {
174             return future.get(timeout.toSeconds(), SECONDS);
175         } finally {
176             mController.unregisterStateCallback(callback);
177         }
178     }
179 
180     /** An synchronous variant of {@link ThreadNetworkController#setTestNetworkAsUpstream}. */
setTestNetworkAsUpstreamAndWait(@ullable String networkInterfaceName)181     public void setTestNetworkAsUpstreamAndWait(@Nullable String networkInterfaceName)
182             throws InterruptedException, ExecutionException, TimeoutException {
183         CompletableFuture<Void> future = new CompletableFuture<>();
184         runAsShell(
185                 PERMISSION_THREAD_NETWORK_PRIVILEGED,
186                 NETWORK_SETTINGS,
187                 () -> {
188                     mController.setTestNetworkAsUpstream(
189                             networkInterfaceName, directExecutor(), future::complete);
190                 });
191         future.get(CALLBACK_TIMEOUT.toSeconds(), SECONDS);
192     }
193 
newOutcomeReceiver( CompletableFuture<V> future)194     private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
195             CompletableFuture<V> future) {
196         return new OutcomeReceiver<V, ThreadNetworkException>() {
197             @Override
198             public void onResult(V result) {
199                 future.complete(result);
200             }
201 
202             @Override
203             public void onError(ThreadNetworkException e) {
204                 future.completeExceptionally(e);
205             }
206         };
207     }
208 }
209