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