1 /* 2 * Copyright (C) 2023 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 17 package android.net.thread; 18 19 import static java.util.Objects.requireNonNull; 20 21 import android.Manifest.permission; 22 import android.annotation.CallbackExecutor; 23 import android.annotation.FlaggedApi; 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresPermission; 28 import android.annotation.Size; 29 import android.annotation.SystemApi; 30 import android.os.Binder; 31 import android.os.OutcomeReceiver; 32 import android.os.RemoteException; 33 import android.util.SparseIntArray; 34 35 import com.android.internal.annotations.GuardedBy; 36 import com.android.internal.annotations.VisibleForTesting; 37 38 import java.lang.annotation.Retention; 39 import java.lang.annotation.RetentionPolicy; 40 import java.time.Duration; 41 import java.util.HashMap; 42 import java.util.Map; 43 import java.util.concurrent.Executor; 44 45 /** 46 * Provides the primary APIs for controlling all aspects of a Thread network. 47 * 48 * <p>For example, join this device to a Thread network with given Thread Operational Dataset, or 49 * migrate an existing network. 50 * 51 * @hide 52 */ 53 @FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED) 54 @SystemApi 55 public final class ThreadNetworkController { 56 private static final String TAG = "ThreadNetworkController"; 57 58 /** The Thread stack is stopped. */ 59 public static final int DEVICE_ROLE_STOPPED = 0; 60 61 /** The device is not currently participating in a Thread network/partition. */ 62 public static final int DEVICE_ROLE_DETACHED = 1; 63 64 /** The device is a Thread Child. */ 65 public static final int DEVICE_ROLE_CHILD = 2; 66 67 /** The device is a Thread Router. */ 68 public static final int DEVICE_ROLE_ROUTER = 3; 69 70 /** The device is a Thread Leader. */ 71 public static final int DEVICE_ROLE_LEADER = 4; 72 73 /** The Thread radio is disabled. */ 74 public static final int STATE_DISABLED = 0; 75 76 /** The Thread radio is enabled. */ 77 public static final int STATE_ENABLED = 1; 78 79 /** The Thread radio is being disabled. */ 80 public static final int STATE_DISABLING = 2; 81 82 /** @hide */ 83 @Retention(RetentionPolicy.SOURCE) 84 @IntDef({ 85 DEVICE_ROLE_STOPPED, 86 DEVICE_ROLE_DETACHED, 87 DEVICE_ROLE_CHILD, 88 DEVICE_ROLE_ROUTER, 89 DEVICE_ROLE_LEADER 90 }) 91 public @interface DeviceRole {} 92 93 /** @hide */ 94 @Retention(RetentionPolicy.SOURCE) 95 @IntDef( 96 prefix = {"STATE_"}, 97 value = {STATE_DISABLED, STATE_ENABLED, STATE_DISABLING}) 98 public @interface EnabledState {} 99 100 /** Thread standard version 1.3. */ 101 public static final int THREAD_VERSION_1_3 = 4; 102 103 /** Minimum value of max power in unit of 0.01dBm. @hide */ 104 private static final int POWER_LIMITATION_MIN = -32768; 105 106 /** Maximum value of max power in unit of 0.01dBm. @hide */ 107 private static final int POWER_LIMITATION_MAX = 32767; 108 109 /** @hide */ 110 @Retention(RetentionPolicy.SOURCE) 111 @IntDef({THREAD_VERSION_1_3}) 112 public @interface ThreadVersion {} 113 114 private final IThreadNetworkController mControllerService; 115 116 private final Object mStateCallbackMapLock = new Object(); 117 118 @GuardedBy("mStateCallbackMapLock") 119 private final Map<StateCallback, StateCallbackProxy> mStateCallbackMap = new HashMap<>(); 120 121 private final Object mOpDatasetCallbackMapLock = new Object(); 122 123 @GuardedBy("mOpDatasetCallbackMapLock") 124 private final Map<OperationalDatasetCallback, OperationalDatasetCallbackProxy> 125 mOpDatasetCallbackMap = new HashMap<>(); 126 127 /** @hide */ ThreadNetworkController(@onNull IThreadNetworkController controllerService)128 public ThreadNetworkController(@NonNull IThreadNetworkController controllerService) { 129 requireNonNull(controllerService, "controllerService cannot be null"); 130 mControllerService = controllerService; 131 } 132 133 /** 134 * Enables/Disables the radio of this ThreadNetworkController. The requested enabled state will 135 * be persistent and survives device reboots. 136 * 137 * <p>When Thread is in {@code STATE_DISABLED}, {@link ThreadNetworkController} APIs which 138 * require the Thread radio will fail with error code {@link 139 * ThreadNetworkException#ERROR_THREAD_DISABLED}. When Thread is in {@code STATE_DISABLING}, 140 * {@link ThreadNetworkController} APIs that return a {@link ThreadNetworkException} will fail 141 * with error code {@link ThreadNetworkException#ERROR_BUSY}. 142 * 143 * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. It indicates 144 * the operation has completed. But there maybe subsequent calls to update the enabled state, 145 * callers of this method should use {@link #registerStateCallback} to subscribe to the Thread 146 * enabled state changes. 147 * 148 * <p>On failure, {@link OutcomeReceiver#onError} of {@code receiver} will be invoked with a 149 * specific error in {@link ThreadNetworkException#ERROR_}. 150 * 151 * @param enabled {@code true} for enabling Thread 152 * @param executor the executor to execute {@code receiver} 153 * @param receiver the receiver to receive result of this operation 154 */ 155 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") setEnabled( boolean enabled, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)156 public void setEnabled( 157 boolean enabled, 158 @NonNull @CallbackExecutor Executor executor, 159 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 160 try { 161 mControllerService.setEnabled(enabled, new OperationReceiverProxy(executor, receiver)); 162 } catch (RemoteException e) { 163 throw e.rethrowFromSystemServer(); 164 } 165 } 166 167 /** Returns the Thread version this device is operating on. */ 168 @ThreadVersion getThreadVersion()169 public int getThreadVersion() { 170 try { 171 return mControllerService.getThreadVersion(); 172 } catch (RemoteException e) { 173 throw e.rethrowFromSystemServer(); 174 } 175 } 176 177 /** 178 * Creates a new Active Operational Dataset with randomized parameters. 179 * 180 * <p>This method is the recommended way to create a randomized dataset which can be used with 181 * {@link #join} to securely join this device to the specified network . It's highly discouraged 182 * to change the randomly generated Extended PAN ID, Network Key or PSKc, as it will compromise 183 * the security of a Thread network. 184 * 185 * @throws IllegalArgumentException if length of the UTF-8 representation of {@code networkName} 186 * isn't in range of [{@link #LENGTH_MIN_NETWORK_NAME_BYTES}, {@link 187 * #LENGTH_MAX_NETWORK_NAME_BYTES}] 188 */ createRandomizedDataset( @onNull String networkName, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> receiver)189 public void createRandomizedDataset( 190 @NonNull String networkName, 191 @NonNull @CallbackExecutor Executor executor, 192 @NonNull OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> receiver) { 193 ActiveOperationalDataset.checkNetworkName(networkName); 194 requireNonNull(executor, "executor cannot be null"); 195 requireNonNull(receiver, "receiver cannot be null"); 196 try { 197 mControllerService.createRandomizedDataset( 198 networkName, new ActiveDatasetReceiverProxy(executor, receiver)); 199 } catch (RemoteException e) { 200 e.rethrowFromSystemServer(); 201 } 202 } 203 204 /** Returns {@code true} if {@code deviceRole} indicates an attached state. */ isAttached(@eviceRole int deviceRole)205 public static boolean isAttached(@DeviceRole int deviceRole) { 206 return deviceRole == DEVICE_ROLE_CHILD 207 || deviceRole == DEVICE_ROLE_ROUTER 208 || deviceRole == DEVICE_ROLE_LEADER; 209 } 210 211 /** 212 * Callback to receive notifications when the Thread network states are changed. 213 * 214 * <p>Applications which are interested in monitoring Thread network states should implement 215 * this interface and register the callback with {@link #registerStateCallback}. 216 */ 217 public interface StateCallback { 218 /** 219 * The Thread device role has changed. 220 * 221 * @param deviceRole the new Thread device role 222 */ onDeviceRoleChanged(@eviceRole int deviceRole)223 void onDeviceRoleChanged(@DeviceRole int deviceRole); 224 225 /** 226 * The Thread network partition ID has changed. 227 * 228 * @param partitionId the new Thread partition ID 229 */ onPartitionIdChanged(long partitionId)230 default void onPartitionIdChanged(long partitionId) {} 231 232 /** 233 * The Thread enabled state has changed. 234 * 235 * <p>The Thread enabled state can be set with {@link setEnabled}, it may also be updated by 236 * airplane mode or admin control. 237 * 238 * @param enabledState the new Thread enabled state 239 */ onThreadEnableStateChanged(@nabledState int enabledState)240 default void onThreadEnableStateChanged(@EnabledState int enabledState) {} 241 } 242 243 private static final class StateCallbackProxy extends IStateCallback.Stub { 244 private final Executor mExecutor; 245 private final StateCallback mCallback; 246 StateCallbackProxy(@allbackExecutor Executor executor, StateCallback callback)247 StateCallbackProxy(@CallbackExecutor Executor executor, StateCallback callback) { 248 mExecutor = executor; 249 mCallback = callback; 250 } 251 252 @Override onDeviceRoleChanged(@eviceRole int deviceRole)253 public void onDeviceRoleChanged(@DeviceRole int deviceRole) { 254 final long identity = Binder.clearCallingIdentity(); 255 try { 256 mExecutor.execute(() -> mCallback.onDeviceRoleChanged(deviceRole)); 257 } finally { 258 Binder.restoreCallingIdentity(identity); 259 } 260 } 261 262 @Override onPartitionIdChanged(long partitionId)263 public void onPartitionIdChanged(long partitionId) { 264 final long identity = Binder.clearCallingIdentity(); 265 try { 266 mExecutor.execute(() -> mCallback.onPartitionIdChanged(partitionId)); 267 } finally { 268 Binder.restoreCallingIdentity(identity); 269 } 270 } 271 272 @Override onThreadEnableStateChanged(@nabledState int enabled)273 public void onThreadEnableStateChanged(@EnabledState int enabled) { 274 final long identity = Binder.clearCallingIdentity(); 275 try { 276 mExecutor.execute(() -> mCallback.onThreadEnableStateChanged(enabled)); 277 } finally { 278 Binder.restoreCallingIdentity(identity); 279 } 280 } 281 } 282 283 /** 284 * Registers a callback to be called when Thread network states are changed. 285 * 286 * <p>Upon return of this method, methods of {@code callback} will be invoked immediately with 287 * existing states. 288 * 289 * @param executor the executor to execute the {@code callback} 290 * @param callback the callback to receive Thread network state changes 291 * @throws IllegalArgumentException if {@code callback} has already been registered 292 */ 293 @RequiresPermission(permission.ACCESS_NETWORK_STATE) registerStateCallback( @onNull @allbackExecutor Executor executor, @NonNull StateCallback callback)294 public void registerStateCallback( 295 @NonNull @CallbackExecutor Executor executor, @NonNull StateCallback callback) { 296 requireNonNull(executor, "executor cannot be null"); 297 requireNonNull(callback, "callback cannot be null"); 298 synchronized (mStateCallbackMapLock) { 299 if (mStateCallbackMap.containsKey(callback)) { 300 throw new IllegalArgumentException("callback has already been registered"); 301 } 302 StateCallbackProxy callbackProxy = new StateCallbackProxy(executor, callback); 303 mStateCallbackMap.put(callback, callbackProxy); 304 305 try { 306 mControllerService.registerStateCallback(callbackProxy); 307 } catch (RemoteException e) { 308 mStateCallbackMap.remove(callback); 309 e.rethrowFromSystemServer(); 310 } 311 } 312 } 313 314 /** 315 * Unregisters the Thread state changed callback. 316 * 317 * @param callback the callback which has been registered with {@link #registerStateCallback} 318 * @throws IllegalArgumentException if {@code callback} hasn't been registered 319 */ 320 @RequiresPermission(permission.ACCESS_NETWORK_STATE) unregisterStateCallback(@onNull StateCallback callback)321 public void unregisterStateCallback(@NonNull StateCallback callback) { 322 requireNonNull(callback, "callback cannot be null"); 323 synchronized (mStateCallbackMapLock) { 324 StateCallbackProxy callbackProxy = mStateCallbackMap.get(callback); 325 if (callbackProxy == null) { 326 throw new IllegalArgumentException("callback hasn't been registered"); 327 } 328 try { 329 mControllerService.unregisterStateCallback(callbackProxy); 330 mStateCallbackMap.remove(callback); 331 } catch (RemoteException e) { 332 e.rethrowFromSystemServer(); 333 } 334 } 335 } 336 337 /** 338 * Callback to receive notifications when the Thread Operational Datasets are changed. 339 * 340 * <p>Applications which are interested in monitoring Thread network datasets should implement 341 * this interface and register the callback with {@link #registerOperationalDatasetCallback}. 342 */ 343 public interface OperationalDatasetCallback { 344 /** 345 * Called when the Active Operational Dataset is changed. 346 * 347 * @param activeDataset the new Active Operational Dataset or {@code null} if the dataset is 348 * absent 349 */ onActiveOperationalDatasetChanged(@ullable ActiveOperationalDataset activeDataset)350 void onActiveOperationalDatasetChanged(@Nullable ActiveOperationalDataset activeDataset); 351 352 /** 353 * Called when the Pending Operational Dataset is changed. 354 * 355 * @param pendingDataset the new Pending Operational Dataset or {@code null} if the dataset 356 * has been committed and removed 357 */ onPendingOperationalDatasetChanged( @ullable PendingOperationalDataset pendingDataset)358 default void onPendingOperationalDatasetChanged( 359 @Nullable PendingOperationalDataset pendingDataset) {} 360 } 361 362 private static final class OperationalDatasetCallbackProxy 363 extends IOperationalDatasetCallback.Stub { 364 private final Executor mExecutor; 365 private final OperationalDatasetCallback mCallback; 366 OperationalDatasetCallbackProxy( @allbackExecutor Executor executor, OperationalDatasetCallback callback)367 OperationalDatasetCallbackProxy( 368 @CallbackExecutor Executor executor, OperationalDatasetCallback callback) { 369 mExecutor = executor; 370 mCallback = callback; 371 } 372 373 @Override onActiveOperationalDatasetChanged( @ullable ActiveOperationalDataset activeDataset)374 public void onActiveOperationalDatasetChanged( 375 @Nullable ActiveOperationalDataset activeDataset) { 376 final long identity = Binder.clearCallingIdentity(); 377 try { 378 mExecutor.execute(() -> mCallback.onActiveOperationalDatasetChanged(activeDataset)); 379 } finally { 380 Binder.restoreCallingIdentity(identity); 381 } 382 } 383 384 @Override onPendingOperationalDatasetChanged( @ullable PendingOperationalDataset pendingDataset)385 public void onPendingOperationalDatasetChanged( 386 @Nullable PendingOperationalDataset pendingDataset) { 387 final long identity = Binder.clearCallingIdentity(); 388 try { 389 mExecutor.execute( 390 () -> mCallback.onPendingOperationalDatasetChanged(pendingDataset)); 391 } finally { 392 Binder.restoreCallingIdentity(identity); 393 } 394 } 395 } 396 397 /** 398 * Registers a callback to be called when Thread Operational Datasets are changed. 399 * 400 * <p>Upon return of this method, methods of {@code callback} will be invoked immediately with 401 * existing Operational Datasets. 402 * 403 * @param executor the executor to execute {@code callback} 404 * @param callback the callback to receive Operational Dataset changes 405 * @throws IllegalArgumentException if {@code callback} has already been registered 406 */ 407 @RequiresPermission( 408 allOf = { 409 permission.ACCESS_NETWORK_STATE, 410 "android.permission.THREAD_NETWORK_PRIVILEGED" 411 }) registerOperationalDatasetCallback( @onNull @allbackExecutor Executor executor, @NonNull OperationalDatasetCallback callback)412 public void registerOperationalDatasetCallback( 413 @NonNull @CallbackExecutor Executor executor, 414 @NonNull OperationalDatasetCallback callback) { 415 requireNonNull(executor, "executor cannot be null"); 416 requireNonNull(callback, "callback cannot be null"); 417 synchronized (mOpDatasetCallbackMapLock) { 418 if (mOpDatasetCallbackMap.containsKey(callback)) { 419 throw new IllegalArgumentException("callback has already been registered"); 420 } 421 OperationalDatasetCallbackProxy callbackProxy = 422 new OperationalDatasetCallbackProxy(executor, callback); 423 mOpDatasetCallbackMap.put(callback, callbackProxy); 424 425 try { 426 mControllerService.registerOperationalDatasetCallback(callbackProxy); 427 } catch (RemoteException e) { 428 mOpDatasetCallbackMap.remove(callback); 429 e.rethrowFromSystemServer(); 430 } 431 } 432 } 433 434 /** 435 * Unregisters the Thread Operational Dataset callback. 436 * 437 * @param callback the callback which has been registered with {@link 438 * #registerOperationalDatasetCallback} 439 * @throws IllegalArgumentException if {@code callback} hasn't been registered 440 */ 441 @RequiresPermission( 442 allOf = { 443 permission.ACCESS_NETWORK_STATE, 444 "android.permission.THREAD_NETWORK_PRIVILEGED" 445 }) unregisterOperationalDatasetCallback(@onNull OperationalDatasetCallback callback)446 public void unregisterOperationalDatasetCallback(@NonNull OperationalDatasetCallback callback) { 447 requireNonNull(callback, "callback cannot be null"); 448 synchronized (mOpDatasetCallbackMapLock) { 449 OperationalDatasetCallbackProxy callbackProxy = mOpDatasetCallbackMap.get(callback); 450 if (callbackProxy == null) { 451 throw new IllegalArgumentException("callback hasn't been registered"); 452 } 453 try { 454 mControllerService.unregisterOperationalDatasetCallback(callbackProxy); 455 mOpDatasetCallbackMap.remove(callback); 456 } catch (RemoteException e) { 457 e.rethrowFromSystemServer(); 458 } 459 } 460 } 461 462 /** 463 * Joins to a Thread network with given Active Operational Dataset. 464 * 465 * <p>This method does nothing if this device has already joined to the same network specified 466 * by {@code activeDataset}. If this device has already joined to a different network, this 467 * device will first leave from that network and then join the new network. This method changes 468 * only this device and all other connected devices will stay in the old network. To change the 469 * network for all connected devices together, use {@link #scheduleMigration}. 470 * 471 * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called and the Dataset 472 * will be persisted on this device; this device will try to attach to the Thread network and 473 * the state changes can be observed by {@link #registerStateCallback}. On failure, {@link 474 * OutcomeReceiver#onError} of {@code receiver} will be invoked with a specific error: 475 * 476 * <ul> 477 * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_CHANNEL} {@code activeDataset} 478 * specifies a channel which is not supported in the current country or region; the {@code 479 * activeDataset} is rejected and not persisted so this device won't auto re-join the next 480 * time 481 * <li>{@link ThreadNetworkException#ERROR_ABORTED} this operation is aborted by another 482 * {@code join} or {@code leave} operation 483 * </ul> 484 * 485 * @param activeDataset the Active Operational Dataset represents the Thread network to join 486 * @param executor the executor to execute {@code receiver} 487 * @param receiver the receiver to receive result of this operation 488 */ 489 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") join( @onNull ActiveOperationalDataset activeDataset, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)490 public void join( 491 @NonNull ActiveOperationalDataset activeDataset, 492 @NonNull @CallbackExecutor Executor executor, 493 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 494 requireNonNull(activeDataset, "activeDataset cannot be null"); 495 requireNonNull(executor, "executor cannot be null"); 496 requireNonNull(receiver, "receiver cannot be null"); 497 try { 498 mControllerService.join(activeDataset, new OperationReceiverProxy(executor, receiver)); 499 } catch (RemoteException e) { 500 throw e.rethrowFromSystemServer(); 501 } 502 } 503 504 /** 505 * Schedules a network migration which moves all devices in the current connected network to a 506 * new network or updates parameters of the current connected network. 507 * 508 * <p>The migration doesn't happen immediately but is registered to the Leader device so that 509 * all devices in the current Thread network can be scheduled to apply the new dataset together. 510 * 511 * <p>On success, the Pending Dataset is successfully registered and persisted on the Leader and 512 * {@link OutcomeReceiver#onResult} of {@code receiver} will be called; Operational Dataset 513 * changes will be asynchronously delivered via {@link OperationalDatasetCallback} if a callback 514 * has been registered with {@link #registerOperationalDatasetCallback}. When failed, {@link 515 * OutcomeReceiver#onError} will be called with a specific error: 516 * 517 * <ul> 518 * <li>{@link ThreadNetworkException#ERROR_FAILED_PRECONDITION} the migration is rejected 519 * because this device is not attached 520 * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_CHANNEL} {@code pendingDataset} 521 * specifies a channel which is not supported in the current country or region; the {@code 522 * pendingDataset} is rejected and not persisted 523 * <li>{@link ThreadNetworkException#ERROR_REJECTED_BY_PEER} the Pending Dataset is rejected 524 * by the Leader device 525 * <li>{@link ThreadNetworkException#ERROR_BUSY} another {@code scheduleMigration} request is 526 * being processed 527 * <li>{@link ThreadNetworkException#ERROR_TIMEOUT} response from the Leader device hasn't 528 * been received before deadline 529 * </ul> 530 * 531 * <p>The Delay Timer of {@code pendingDataset} can vary from several minutes to a few days. 532 * It's important to select a proper value to safely migrate all devices in the network without 533 * leaving sleepy end devices orphaned. Apps are not suggested to specify the Delay Timer value 534 * if it's unclear how long it can take to propagate the {@code pendingDataset} to the whole 535 * network. Instead, use {@link Duration#ZERO} to use the default value suggested by the system. 536 * 537 * @param pendingDataset the Pending Operational Dataset 538 * @param executor the executor to execute {@code receiver} 539 * @param receiver the receiver to receive result of this operation 540 */ 541 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") scheduleMigration( @onNull PendingOperationalDataset pendingDataset, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)542 public void scheduleMigration( 543 @NonNull PendingOperationalDataset pendingDataset, 544 @NonNull @CallbackExecutor Executor executor, 545 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 546 requireNonNull(pendingDataset, "pendingDataset cannot be null"); 547 requireNonNull(executor, "executor cannot be null"); 548 requireNonNull(receiver, "receiver cannot be null"); 549 try { 550 mControllerService.scheduleMigration( 551 pendingDataset, new OperationReceiverProxy(executor, receiver)); 552 } catch (RemoteException e) { 553 throw e.rethrowFromSystemServer(); 554 } 555 } 556 557 /** 558 * Leaves from the Thread network. 559 * 560 * <p>This undoes a {@link join} operation. On success, this device is disconnected from the 561 * joined network and will not automatically join a network before {@link #join} is called 562 * again. Active and Pending Operational Dataset configured and persisted on this device will be 563 * removed too. 564 * 565 * @param executor the executor to execute {@code receiver} 566 * @param receiver the receiver to receive result of this operation 567 */ 568 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") leave( @onNull @allbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)569 public void leave( 570 @NonNull @CallbackExecutor Executor executor, 571 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 572 requireNonNull(executor, "executor cannot be null"); 573 requireNonNull(receiver, "receiver cannot be null"); 574 try { 575 mControllerService.leave(new OperationReceiverProxy(executor, receiver)); 576 } catch (RemoteException e) { 577 throw e.rethrowFromSystemServer(); 578 } 579 } 580 581 /** 582 * Sets to use a specified test network as the upstream. 583 * 584 * @param testNetworkInterfaceName The name of the test network interface. When it's null, 585 * forbids using test network as an upstream. 586 * @param executor the executor to execute {@code receiver} 587 * @param receiver the receiver to receive result of this operation 588 * @hide 589 */ 590 @VisibleForTesting 591 @RequiresPermission( 592 allOf = {"android.permission.THREAD_NETWORK_PRIVILEGED", permission.NETWORK_SETTINGS}) setTestNetworkAsUpstream( @ullable String testNetworkInterfaceName, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)593 public void setTestNetworkAsUpstream( 594 @Nullable String testNetworkInterfaceName, 595 @NonNull @CallbackExecutor Executor executor, 596 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 597 requireNonNull(executor, "executor cannot be null"); 598 requireNonNull(receiver, "receiver cannot be null"); 599 try { 600 mControllerService.setTestNetworkAsUpstream( 601 testNetworkInterfaceName, new OperationReceiverProxy(executor, receiver)); 602 } catch (RemoteException e) { 603 throw e.rethrowFromSystemServer(); 604 } 605 } 606 607 /** 608 * Sets max power of each channel. 609 * 610 * <p>If not set, the default max power is set by the Thread HAL service or the Thread radio 611 * chip firmware. 612 * 613 * <p>On success, the Pending Dataset is successfully registered and persisted on the Leader and 614 * {@link OutcomeReceiver#onResult} of {@code receiver} will be called; When failed, {@link 615 * OutcomeReceiver#onError} will be called with a specific error: 616 * 617 * <ul> 618 * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_OPERATION} the operation is no 619 * supported by the platform. 620 * </ul> 621 * 622 * @param channelMaxPowers SparseIntArray (key: channel, value: max power) consists of channel 623 * and corresponding max power. Valid channel values should be between {@link 624 * ActiveOperationalDataset#CHANNEL_MIN_24_GHZ} and {@link 625 * ActiveOperationalDataset#CHANNEL_MAX_24_GHZ}. The unit of the max power is 0.01dBm. Max 626 * power values should be between INT16_MIN (-32768) and INT16_MAX (32767). If the max power 627 * is set to INT16_MAX, the corresponding channel is not supported. 628 * @param executor the executor to execute {@code receiver}. 629 * @param receiver the receiver to receive the result of this operation. 630 * @throws IllegalArgumentException if the size of {@code channelMaxPowers} is smaller than 1, 631 * or invalid channel or max power is configured. 632 * @hide 633 */ 634 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") setChannelMaxPowers( @onNull @izemin = 1) SparseIntArray channelMaxPowers, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)635 public final void setChannelMaxPowers( 636 @NonNull @Size(min = 1) SparseIntArray channelMaxPowers, 637 @NonNull @CallbackExecutor Executor executor, 638 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 639 requireNonNull(channelMaxPowers, "channelMaxPowers cannot be null"); 640 requireNonNull(executor, "executor cannot be null"); 641 requireNonNull(receiver, "receiver cannot be null"); 642 643 if (channelMaxPowers.size() < 1) { 644 throw new IllegalArgumentException("channelMaxPowers cannot be empty"); 645 } 646 647 for (int i = 0; i < channelMaxPowers.size(); i++) { 648 int channel = channelMaxPowers.keyAt(i); 649 int maxPower = channelMaxPowers.get(channel); 650 651 if ((channel < ActiveOperationalDataset.CHANNEL_MIN_24_GHZ) 652 || (channel > ActiveOperationalDataset.CHANNEL_MAX_24_GHZ)) { 653 throw new IllegalArgumentException( 654 "Channel " 655 + channel 656 + " exceeds allowed range [" 657 + ActiveOperationalDataset.CHANNEL_MIN_24_GHZ 658 + ", " 659 + ActiveOperationalDataset.CHANNEL_MAX_24_GHZ 660 + "]"); 661 } 662 663 if ((maxPower < POWER_LIMITATION_MIN) || (maxPower > POWER_LIMITATION_MAX)) { 664 throw new IllegalArgumentException( 665 "Channel power ({channel: " 666 + channel 667 + ", maxPower: " 668 + maxPower 669 + "}) exceeds allowed range [" 670 + POWER_LIMITATION_MIN 671 + ", " 672 + POWER_LIMITATION_MAX 673 + "]"); 674 } 675 } 676 677 try { 678 mControllerService.setChannelMaxPowers( 679 toChannelMaxPowerArray(channelMaxPowers), 680 new OperationReceiverProxy(executor, receiver)); 681 } catch (RemoteException e) { 682 throw e.rethrowFromSystemServer(); 683 } 684 } 685 toChannelMaxPowerArray( @onNull SparseIntArray channelMaxPowers)686 private static ChannelMaxPower[] toChannelMaxPowerArray( 687 @NonNull SparseIntArray channelMaxPowers) { 688 final ChannelMaxPower[] powerArray = new ChannelMaxPower[channelMaxPowers.size()]; 689 690 for (int i = 0; i < channelMaxPowers.size(); i++) { 691 powerArray[i] = new ChannelMaxPower(); 692 powerArray[i].channel = channelMaxPowers.keyAt(i); 693 powerArray[i].maxPower = channelMaxPowers.get(powerArray[i].channel); 694 } 695 696 return powerArray; 697 } 698 propagateError( Executor executor, OutcomeReceiver<T, ThreadNetworkException> receiver, int errorCode, String errorMsg)699 private static <T> void propagateError( 700 Executor executor, 701 OutcomeReceiver<T, ThreadNetworkException> receiver, 702 int errorCode, 703 String errorMsg) { 704 final long identity = Binder.clearCallingIdentity(); 705 try { 706 executor.execute( 707 () -> receiver.onError(new ThreadNetworkException(errorCode, errorMsg))); 708 } finally { 709 Binder.restoreCallingIdentity(identity); 710 } 711 } 712 713 private static final class ActiveDatasetReceiverProxy 714 extends IActiveOperationalDatasetReceiver.Stub { 715 final Executor mExecutor; 716 final OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> mResultReceiver; 717 ActiveDatasetReceiverProxy( @allbackExecutor Executor executor, OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> resultReceiver)718 ActiveDatasetReceiverProxy( 719 @CallbackExecutor Executor executor, 720 OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> resultReceiver) { 721 this.mExecutor = executor; 722 this.mResultReceiver = resultReceiver; 723 } 724 725 @Override onSuccess(ActiveOperationalDataset dataset)726 public void onSuccess(ActiveOperationalDataset dataset) { 727 final long identity = Binder.clearCallingIdentity(); 728 try { 729 mExecutor.execute(() -> mResultReceiver.onResult(dataset)); 730 } finally { 731 Binder.restoreCallingIdentity(identity); 732 } 733 } 734 735 @Override onError(int errorCode, String errorMessage)736 public void onError(int errorCode, String errorMessage) { 737 propagateError(mExecutor, mResultReceiver, errorCode, errorMessage); 738 } 739 } 740 741 private static final class OperationReceiverProxy extends IOperationReceiver.Stub { 742 final Executor mExecutor; 743 final OutcomeReceiver<Void, ThreadNetworkException> mResultReceiver; 744 OperationReceiverProxy( @allbackExecutor Executor executor, OutcomeReceiver<Void, ThreadNetworkException> resultReceiver)745 OperationReceiverProxy( 746 @CallbackExecutor Executor executor, 747 OutcomeReceiver<Void, ThreadNetworkException> resultReceiver) { 748 this.mExecutor = executor; 749 this.mResultReceiver = resultReceiver; 750 } 751 752 @Override onSuccess()753 public void onSuccess() { 754 final long identity = Binder.clearCallingIdentity(); 755 try { 756 mExecutor.execute(() -> mResultReceiver.onResult(null)); 757 } finally { 758 Binder.restoreCallingIdentity(identity); 759 } 760 } 761 762 @Override onError(int errorCode, String errorMessage)763 public void onError(int errorCode, String errorMessage) { 764 propagateError(mExecutor, mResultReceiver, errorCode, errorMessage); 765 } 766 } 767 } 768