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.SuppressLint; 30 import android.annotation.SystemApi; 31 import android.os.Binder; 32 import android.os.OutcomeReceiver; 33 import android.os.RemoteException; 34 import android.util.SparseIntArray; 35 36 import com.android.internal.annotations.GuardedBy; 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.net.thread.flags.Flags; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.time.Duration; 43 import java.time.Instant; 44 import java.util.HashMap; 45 import java.util.Map; 46 import java.util.concurrent.Executor; 47 import java.util.function.Consumer; 48 49 /** 50 * Provides the primary APIs for controlling all aspects of a Thread network. 51 * 52 * <p>For example, join this device to a Thread network with given Thread Operational Dataset, or 53 * migrate an existing network. 54 * 55 * @hide 56 */ 57 @FlaggedApi(Flags.FLAG_THREAD_ENABLED) 58 @SystemApi 59 public final class ThreadNetworkController { 60 private static final String TAG = "ThreadNetworkController"; 61 62 /** The Thread stack is stopped. */ 63 public static final int DEVICE_ROLE_STOPPED = 0; 64 65 /** The device is not currently participating in a Thread network/partition. */ 66 public static final int DEVICE_ROLE_DETACHED = 1; 67 68 /** The device is a Thread Child. */ 69 public static final int DEVICE_ROLE_CHILD = 2; 70 71 /** The device is a Thread Router. */ 72 public static final int DEVICE_ROLE_ROUTER = 3; 73 74 /** The device is a Thread Leader. */ 75 public static final int DEVICE_ROLE_LEADER = 4; 76 77 /** The Thread radio is disabled. */ 78 public static final int STATE_DISABLED = 0; 79 80 /** The Thread radio is enabled. */ 81 public static final int STATE_ENABLED = 1; 82 83 /** The Thread radio is being disabled. */ 84 public static final int STATE_DISABLING = 2; 85 86 /** The ephemeral key mode is disabled. */ 87 @FlaggedApi(Flags.FLAG_EPSKC_ENABLED) 88 public static final int EPHEMERAL_KEY_DISABLED = 0; 89 90 /** 91 * The ephemeral key mode is enabled, an external commissioner candidate can use the ephemeral 92 * key to connect to this device and get Thread credential shared. 93 */ 94 @FlaggedApi(Flags.FLAG_EPSKC_ENABLED) 95 public static final int EPHEMERAL_KEY_ENABLED = 1; 96 97 /** 98 * The ephemeral key is in use. This state means there is already an active secure session 99 * connected to this device with the ephemeral key, it's not possible to use the ephemeral key 100 * for new connections in this state. 101 */ 102 @FlaggedApi(Flags.FLAG_EPSKC_ENABLED) 103 public static final int EPHEMERAL_KEY_IN_USE = 2; 104 105 /** @hide */ 106 @Retention(RetentionPolicy.SOURCE) 107 @IntDef({ 108 DEVICE_ROLE_STOPPED, 109 DEVICE_ROLE_DETACHED, 110 DEVICE_ROLE_CHILD, 111 DEVICE_ROLE_ROUTER, 112 DEVICE_ROLE_LEADER 113 }) 114 public @interface DeviceRole {} 115 116 /** @hide */ 117 @Retention(RetentionPolicy.SOURCE) 118 @IntDef( 119 prefix = {"STATE_"}, 120 value = {STATE_DISABLED, STATE_ENABLED, STATE_DISABLING}) 121 public @interface EnabledState {} 122 123 /** @hide */ 124 @Retention(RetentionPolicy.SOURCE) 125 @IntDef( 126 prefix = {"EPHEMERAL_KEY_"}, 127 value = {EPHEMERAL_KEY_DISABLED, EPHEMERAL_KEY_ENABLED, EPHEMERAL_KEY_IN_USE}) 128 public @interface EphemeralKeyState {} 129 130 /** Thread standard version 1.3. */ 131 public static final int THREAD_VERSION_1_3 = 4; 132 133 /** The value of max power to disable the Thread channel. */ 134 // This constant can never change. It has "max" in the name not because it indicates 135 // maximum power, but because it's passed to an API that sets the maximum power to 136 // disabled the Thread channel. 137 @SuppressLint("MinMaxConstant") 138 public static final int MAX_POWER_CHANNEL_DISABLED = Integer.MIN_VALUE; 139 140 /** The maximum lifetime of an ephemeral key. @hide */ 141 @NonNull private static final Duration EPHEMERAL_KEY_LIFETIME_MAX = Duration.ofMinutes(10); 142 143 /** @hide */ 144 @Retention(RetentionPolicy.SOURCE) 145 @IntDef({THREAD_VERSION_1_3}) 146 public @interface ThreadVersion {} 147 148 private final IThreadNetworkController mControllerService; 149 150 private final Object mStateCallbackMapLock = new Object(); 151 152 @GuardedBy("mStateCallbackMapLock") 153 private final Map<StateCallback, StateCallbackProxy> mStateCallbackMap = new HashMap<>(); 154 155 private final Object mOpDatasetCallbackMapLock = new Object(); 156 157 @GuardedBy("mOpDatasetCallbackMapLock") 158 private final Map<OperationalDatasetCallback, OperationalDatasetCallbackProxy> 159 mOpDatasetCallbackMap = new HashMap<>(); 160 161 private final Object mConfigurationCallbackMapLock = new Object(); 162 163 @GuardedBy("mConfigurationCallbackMapLock") 164 private final Map<Consumer<ThreadConfiguration>, ConfigurationCallbackProxy> 165 mConfigurationCallbackMap = new HashMap<>(); 166 167 /** @hide */ ThreadNetworkController(@onNull IThreadNetworkController controllerService)168 public ThreadNetworkController(@NonNull IThreadNetworkController controllerService) { 169 requireNonNull(controllerService, "controllerService cannot be null"); 170 mControllerService = controllerService; 171 } 172 173 /** 174 * Enables/Disables the radio of this ThreadNetworkController. The requested enabled state will 175 * be persistent and survives device reboots. 176 * 177 * <p>When Thread is in {@code STATE_DISABLED}, {@link ThreadNetworkController} APIs which 178 * require the Thread radio will fail with error code {@link 179 * ThreadNetworkException#ERROR_THREAD_DISABLED}. When Thread is in {@code STATE_DISABLING}, 180 * {@link ThreadNetworkController} APIs that return a {@link ThreadNetworkException} will fail 181 * with error code {@link ThreadNetworkException#ERROR_BUSY}. 182 * 183 * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. It indicates 184 * the operation has completed. But there maybe subsequent calls to update the enabled state, 185 * callers of this method should use {@link #registerStateCallback} to subscribe to the Thread 186 * enabled state changes. 187 * 188 * <p>On failure, {@link OutcomeReceiver#onError} of {@code receiver} will be invoked with a 189 * specific error in {@link ThreadNetworkException#ERROR_}. 190 * 191 * @param enabled {@code true} for enabling Thread 192 * @param executor the executor to execute {@code receiver} 193 * @param receiver the receiver to receive result of this operation 194 */ 195 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") setEnabled( boolean enabled, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)196 public void setEnabled( 197 boolean enabled, 198 @NonNull @CallbackExecutor Executor executor, 199 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 200 try { 201 mControllerService.setEnabled(enabled, new OperationReceiverProxy(executor, receiver)); 202 } catch (RemoteException e) { 203 throw e.rethrowFromSystemServer(); 204 } 205 } 206 207 /** Returns the maximum lifetime allowed when activating ephemeral key mode. */ 208 @FlaggedApi(Flags.FLAG_EPSKC_ENABLED) 209 @NonNull getMaxEphemeralKeyLifetime()210 public Duration getMaxEphemeralKeyLifetime() { 211 return EPHEMERAL_KEY_LIFETIME_MAX; 212 } 213 214 /** 215 * Activates ephemeral key mode with a given {@code lifetime}. The ephemeral key is a temporary, 216 * single-use numeric code that is used for Thread Administration Sharing. After activation, the 217 * mode may expire or get deactivated, caller to this method should subscribe to the ephemeral 218 * key state updates with {@link #registerStateCallback} to get notified when the ephemeral key 219 * state changes. 220 * 221 * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. The ephemeral 222 * key string contains a sequence of numeric digits 0-9 of user-input friendly length (typically 223 * 9). Subscribers to ephemeral key state updates with {@link #registerStateCallback} will be 224 * notified with a call to {@link #onEphemeralKeyStateChanged}. 225 * 226 * <p>On failure, {@link OutcomeReceiver#onError} of {@code receiver} will be invoked with a 227 * specific error: 228 * 229 * <ul> 230 * <li>{@link ThreadNetworkException#ERROR_FAILED_PRECONDITION} when this device is not a 231 * Border Router or not attached to Thread network 232 * <li>{@link ThreadNetworkException#ERROR_BUSY} when ephemeral key mode is already activated 233 * on the device, caller can recover from this error when the ephemeral key mode gets 234 * deactivated 235 * </ul> 236 * 237 * @param lifetime valid lifetime of the generated ephemeral key, should be larger than {@link 238 * Duration#ZERO} and at most the duration returned by {@link #getMaxEphemeralKeyLifetime}. 239 * @param executor the executor on which to execute {@code receiver} 240 * @param receiver the receiver to receive the result of this operation 241 * @throws IllegalArgumentException if the {@code lifetime} exceeds the allowed range 242 */ 243 @FlaggedApi(Flags.FLAG_EPSKC_ENABLED) 244 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") activateEphemeralKeyMode( @onNull Duration lifetime, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)245 public void activateEphemeralKeyMode( 246 @NonNull Duration lifetime, 247 @NonNull @CallbackExecutor Executor executor, 248 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 249 if (lifetime.compareTo(Duration.ZERO) <= 0 250 || lifetime.compareTo(EPHEMERAL_KEY_LIFETIME_MAX) > 0) { 251 throw new IllegalArgumentException( 252 "Invalid ephemeral key lifetime: the value must be in range of (0, " 253 + EPHEMERAL_KEY_LIFETIME_MAX 254 + "]"); 255 } 256 long lifetimeMillis = lifetime.toMillis(); 257 try { 258 mControllerService.activateEphemeralKeyMode( 259 lifetimeMillis, new OperationReceiverProxy(executor, receiver)); 260 } catch (RemoteException e) { 261 throw e.rethrowFromSystemServer(); 262 } 263 } 264 265 /** 266 * Deactivates ephemeral key mode. If there is an active connection with the ephemeral key, the 267 * connection will be terminated. 268 * 269 * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. The call will 270 * always succeed if the device is not in ephemeral key mode. It returns an error {@link 271 * ThreadNetworkException#ERROR_FAILED_PRECONDITION} if this device is not a Border Router. 272 * 273 * @param executor the executor to execute {@code receiver} 274 * @param receiver the receiver to receive the result of this operation 275 */ 276 @FlaggedApi(Flags.FLAG_EPSKC_ENABLED) 277 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") deactivateEphemeralKeyMode( @onNull @allbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)278 public void deactivateEphemeralKeyMode( 279 @NonNull @CallbackExecutor Executor executor, 280 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 281 try { 282 mControllerService.deactivateEphemeralKeyMode( 283 new OperationReceiverProxy(executor, receiver)); 284 } catch (RemoteException e) { 285 throw e.rethrowFromSystemServer(); 286 } 287 } 288 289 /** Returns the Thread version this device is operating on. */ 290 @ThreadVersion getThreadVersion()291 public int getThreadVersion() { 292 try { 293 return mControllerService.getThreadVersion(); 294 } catch (RemoteException e) { 295 throw e.rethrowFromSystemServer(); 296 } 297 } 298 299 /** 300 * Creates a new Active Operational Dataset with randomized parameters. 301 * 302 * <p>This method is the recommended way to create a randomized dataset which can be used with 303 * {@link #join} to securely join this device to the specified network . It's highly discouraged 304 * to change the randomly generated Extended PAN ID, Network Key or PSKc, as it will compromise 305 * the security of a Thread network. 306 * 307 * @throws IllegalArgumentException if length of the UTF-8 representation of {@code networkName} 308 * isn't in range of [{@link #LENGTH_MIN_NETWORK_NAME_BYTES}, {@link 309 * #LENGTH_MAX_NETWORK_NAME_BYTES}] 310 */ createRandomizedDataset( @onNull String networkName, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> receiver)311 public void createRandomizedDataset( 312 @NonNull String networkName, 313 @NonNull @CallbackExecutor Executor executor, 314 @NonNull OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> receiver) { 315 ActiveOperationalDataset.checkNetworkName(networkName); 316 requireNonNull(executor, "executor cannot be null"); 317 requireNonNull(receiver, "receiver cannot be null"); 318 try { 319 mControllerService.createRandomizedDataset( 320 networkName, new ActiveDatasetReceiverProxy(executor, receiver)); 321 } catch (RemoteException e) { 322 e.rethrowFromSystemServer(); 323 } 324 } 325 326 /** Returns {@code true} if {@code deviceRole} indicates an attached state. */ isAttached(@eviceRole int deviceRole)327 public static boolean isAttached(@DeviceRole int deviceRole) { 328 return deviceRole == DEVICE_ROLE_CHILD 329 || deviceRole == DEVICE_ROLE_ROUTER 330 || deviceRole == DEVICE_ROLE_LEADER; 331 } 332 333 /** 334 * Callback to receive notifications when the Thread network states are changed. 335 * 336 * <p>Applications which are interested in monitoring Thread network states should implement 337 * this interface and register the callback with {@link #registerStateCallback}. 338 */ 339 public interface StateCallback { 340 /** 341 * The Thread device role has changed. 342 * 343 * @param deviceRole the new Thread device role 344 */ onDeviceRoleChanged(@eviceRole int deviceRole)345 void onDeviceRoleChanged(@DeviceRole int deviceRole); 346 347 /** 348 * The Thread network partition ID has changed. 349 * 350 * @param partitionId the new Thread partition ID 351 */ onPartitionIdChanged(long partitionId)352 default void onPartitionIdChanged(long partitionId) {} 353 354 /** 355 * The Thread enabled state has changed. 356 * 357 * <p>The Thread enabled state can be set with {@link setEnabled}, it may also be updated by 358 * airplane mode or admin control. 359 * 360 * @param enabledState the new Thread enabled state 361 */ onThreadEnableStateChanged(@nabledState int enabledState)362 default void onThreadEnableStateChanged(@EnabledState int enabledState) {} 363 364 /** 365 * The ephemeral key state has changed. 366 * 367 * @param ephemeralKeyState the ephemeral key state 368 * @param ephemeralKey the ephemeral key string which contains a sequence of numeric digits 369 * 0-9 of user-input friendly length (typically 9), or {@code null} if {@code 370 * ephemeralKeyState} is {@link #EPHEMERAL_KEY_DISABLED} or the caller doesn't have the 371 * permission {@link android.permission.THREAD_NETWORK_PRIVILEGED} 372 * @param expiry a timestamp of when the ephemeral key will expire or {@code null} if {@code 373 * ephemeralKeyState} is {@link #EPHEMERAL_KEY_DISABLED} 374 */ 375 @FlaggedApi(Flags.FLAG_EPSKC_ENABLED) 376 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") onEphemeralKeyStateChanged( @phemeralKeyState int ephemeralKeyState, @Nullable String ephemeralKey, @Nullable Instant expiry)377 default void onEphemeralKeyStateChanged( 378 @EphemeralKeyState int ephemeralKeyState, 379 @Nullable String ephemeralKey, 380 @Nullable Instant expiry) {} 381 } 382 383 private static final class StateCallbackProxy extends IStateCallback.Stub { 384 private final Executor mExecutor; 385 private final StateCallback mCallback; 386 StateCallbackProxy(@allbackExecutor Executor executor, StateCallback callback)387 StateCallbackProxy(@CallbackExecutor Executor executor, StateCallback callback) { 388 mExecutor = executor; 389 mCallback = callback; 390 } 391 392 @Override onDeviceRoleChanged(@eviceRole int deviceRole)393 public void onDeviceRoleChanged(@DeviceRole int deviceRole) { 394 final long identity = Binder.clearCallingIdentity(); 395 try { 396 mExecutor.execute(() -> mCallback.onDeviceRoleChanged(deviceRole)); 397 } finally { 398 Binder.restoreCallingIdentity(identity); 399 } 400 } 401 402 @Override onPartitionIdChanged(long partitionId)403 public void onPartitionIdChanged(long partitionId) { 404 final long identity = Binder.clearCallingIdentity(); 405 try { 406 mExecutor.execute(() -> mCallback.onPartitionIdChanged(partitionId)); 407 } finally { 408 Binder.restoreCallingIdentity(identity); 409 } 410 } 411 412 @Override onThreadEnableStateChanged(@nabledState int enabled)413 public void onThreadEnableStateChanged(@EnabledState int enabled) { 414 final long identity = Binder.clearCallingIdentity(); 415 try { 416 mExecutor.execute(() -> mCallback.onThreadEnableStateChanged(enabled)); 417 } finally { 418 Binder.restoreCallingIdentity(identity); 419 } 420 } 421 422 @Override onEphemeralKeyStateChanged( @phemeralKeyState int ephemeralKeyState, String ephemeralKey, long lifetimeMillis)423 public void onEphemeralKeyStateChanged( 424 @EphemeralKeyState int ephemeralKeyState, 425 String ephemeralKey, 426 long lifetimeMillis) { 427 final long identity = Binder.clearCallingIdentity(); 428 final Instant expiry = 429 ephemeralKeyState == EPHEMERAL_KEY_DISABLED 430 ? null 431 : Instant.now().plusMillis(lifetimeMillis); 432 433 try { 434 mExecutor.execute( 435 () -> 436 mCallback.onEphemeralKeyStateChanged( 437 ephemeralKeyState, ephemeralKey, expiry)); 438 } finally { 439 Binder.restoreCallingIdentity(identity); 440 } 441 } 442 } 443 444 /** 445 * Registers a callback to be called when Thread network states are changed. 446 * 447 * <p>Upon return of this method, all methods of {@code callback} will be invoked immediately 448 * with existing states. The order of the invoked callbacks is not guaranteed. 449 * 450 * @param executor the executor to execute the {@code callback} 451 * @param callback the callback to receive Thread network state changes 452 * @throws IllegalArgumentException if {@code callback} has already been registered 453 */ 454 @RequiresPermission(permission.ACCESS_NETWORK_STATE) registerStateCallback( @onNull @allbackExecutor Executor executor, @NonNull StateCallback callback)455 public void registerStateCallback( 456 @NonNull @CallbackExecutor Executor executor, @NonNull StateCallback callback) { 457 requireNonNull(executor, "executor cannot be null"); 458 requireNonNull(callback, "callback cannot be null"); 459 synchronized (mStateCallbackMapLock) { 460 if (mStateCallbackMap.containsKey(callback)) { 461 throw new IllegalArgumentException("callback has already been registered"); 462 } 463 StateCallbackProxy callbackProxy = new StateCallbackProxy(executor, callback); 464 mStateCallbackMap.put(callback, callbackProxy); 465 466 try { 467 mControllerService.registerStateCallback(callbackProxy); 468 } catch (RemoteException e) { 469 mStateCallbackMap.remove(callback); 470 e.rethrowFromSystemServer(); 471 } 472 } 473 } 474 475 /** 476 * Unregisters the Thread state changed callback. 477 * 478 * @param callback the callback which has been registered with {@link #registerStateCallback} 479 * @throws IllegalArgumentException if {@code callback} hasn't been registered 480 */ 481 @RequiresPermission(permission.ACCESS_NETWORK_STATE) unregisterStateCallback(@onNull StateCallback callback)482 public void unregisterStateCallback(@NonNull StateCallback callback) { 483 requireNonNull(callback, "callback cannot be null"); 484 synchronized (mStateCallbackMapLock) { 485 StateCallbackProxy callbackProxy = mStateCallbackMap.get(callback); 486 if (callbackProxy == null) { 487 throw new IllegalArgumentException("callback hasn't been registered"); 488 } 489 try { 490 mControllerService.unregisterStateCallback(callbackProxy); 491 mStateCallbackMap.remove(callback); 492 } catch (RemoteException e) { 493 e.rethrowFromSystemServer(); 494 } 495 } 496 } 497 498 /** 499 * Callback to receive notifications when the Thread Operational Datasets are changed. 500 * 501 * <p>Applications which are interested in monitoring Thread network datasets should implement 502 * this interface and register the callback with {@link #registerOperationalDatasetCallback}. 503 */ 504 public interface OperationalDatasetCallback { 505 /** 506 * Called when the Active Operational Dataset is changed. 507 * 508 * @param activeDataset the new Active Operational Dataset or {@code null} if the dataset is 509 * absent 510 */ onActiveOperationalDatasetChanged(@ullable ActiveOperationalDataset activeDataset)511 void onActiveOperationalDatasetChanged(@Nullable ActiveOperationalDataset activeDataset); 512 513 /** 514 * Called when the Pending Operational Dataset is changed. 515 * 516 * @param pendingDataset the new Pending Operational Dataset or {@code null} if the dataset 517 * has been committed and removed 518 */ onPendingOperationalDatasetChanged( @ullable PendingOperationalDataset pendingDataset)519 default void onPendingOperationalDatasetChanged( 520 @Nullable PendingOperationalDataset pendingDataset) {} 521 } 522 523 private static final class OperationalDatasetCallbackProxy 524 extends IOperationalDatasetCallback.Stub { 525 private final Executor mExecutor; 526 private final OperationalDatasetCallback mCallback; 527 OperationalDatasetCallbackProxy( @allbackExecutor Executor executor, OperationalDatasetCallback callback)528 OperationalDatasetCallbackProxy( 529 @CallbackExecutor Executor executor, OperationalDatasetCallback callback) { 530 mExecutor = executor; 531 mCallback = callback; 532 } 533 534 @Override onActiveOperationalDatasetChanged( @ullable ActiveOperationalDataset activeDataset)535 public void onActiveOperationalDatasetChanged( 536 @Nullable ActiveOperationalDataset activeDataset) { 537 final long identity = Binder.clearCallingIdentity(); 538 try { 539 mExecutor.execute(() -> mCallback.onActiveOperationalDatasetChanged(activeDataset)); 540 } finally { 541 Binder.restoreCallingIdentity(identity); 542 } 543 } 544 545 @Override onPendingOperationalDatasetChanged( @ullable PendingOperationalDataset pendingDataset)546 public void onPendingOperationalDatasetChanged( 547 @Nullable PendingOperationalDataset pendingDataset) { 548 final long identity = Binder.clearCallingIdentity(); 549 try { 550 mExecutor.execute( 551 () -> mCallback.onPendingOperationalDatasetChanged(pendingDataset)); 552 } finally { 553 Binder.restoreCallingIdentity(identity); 554 } 555 } 556 } 557 558 /** 559 * Registers a callback to be called when Thread Operational Datasets are changed. 560 * 561 * <p>Upon return of this method, methods of {@code callback} will be invoked immediately with 562 * existing Operational Datasets. 563 * 564 * @param executor the executor to execute {@code callback} 565 * @param callback the callback to receive Operational Dataset changes 566 * @throws IllegalArgumentException if {@code callback} has already been registered 567 */ 568 @RequiresPermission( 569 allOf = { 570 permission.ACCESS_NETWORK_STATE, 571 "android.permission.THREAD_NETWORK_PRIVILEGED" 572 }) registerOperationalDatasetCallback( @onNull @allbackExecutor Executor executor, @NonNull OperationalDatasetCallback callback)573 public void registerOperationalDatasetCallback( 574 @NonNull @CallbackExecutor Executor executor, 575 @NonNull OperationalDatasetCallback callback) { 576 requireNonNull(executor, "executor cannot be null"); 577 requireNonNull(callback, "callback cannot be null"); 578 synchronized (mOpDatasetCallbackMapLock) { 579 if (mOpDatasetCallbackMap.containsKey(callback)) { 580 throw new IllegalArgumentException("callback has already been registered"); 581 } 582 OperationalDatasetCallbackProxy callbackProxy = 583 new OperationalDatasetCallbackProxy(executor, callback); 584 mOpDatasetCallbackMap.put(callback, callbackProxy); 585 586 try { 587 mControllerService.registerOperationalDatasetCallback(callbackProxy); 588 } catch (RemoteException e) { 589 mOpDatasetCallbackMap.remove(callback); 590 e.rethrowFromSystemServer(); 591 } 592 } 593 } 594 595 /** 596 * Unregisters the Thread Operational Dataset callback. 597 * 598 * @param callback the callback which has been registered with {@link 599 * #registerOperationalDatasetCallback} 600 * @throws IllegalArgumentException if {@code callback} hasn't been registered 601 */ 602 @RequiresPermission( 603 allOf = { 604 permission.ACCESS_NETWORK_STATE, 605 "android.permission.THREAD_NETWORK_PRIVILEGED" 606 }) unregisterOperationalDatasetCallback(@onNull OperationalDatasetCallback callback)607 public void unregisterOperationalDatasetCallback(@NonNull OperationalDatasetCallback callback) { 608 requireNonNull(callback, "callback cannot be null"); 609 synchronized (mOpDatasetCallbackMapLock) { 610 OperationalDatasetCallbackProxy callbackProxy = mOpDatasetCallbackMap.get(callback); 611 if (callbackProxy == null) { 612 throw new IllegalArgumentException("callback hasn't been registered"); 613 } 614 try { 615 mControllerService.unregisterOperationalDatasetCallback(callbackProxy); 616 mOpDatasetCallbackMap.remove(callback); 617 } catch (RemoteException e) { 618 e.rethrowFromSystemServer(); 619 } 620 } 621 } 622 623 /** 624 * Joins to a Thread network with given Active Operational Dataset. 625 * 626 * <p>This method does nothing if this device has already joined to the same network specified 627 * by {@code activeDataset}. If this device has already joined to a different network, this 628 * device will first leave from that network and then join the new network. This method changes 629 * only this device and all other connected devices will stay in the old network. To change the 630 * network for all connected devices together, use {@link #scheduleMigration}. 631 * 632 * <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called and the Dataset 633 * will be persisted on this device; this device will try to attach to the Thread network and 634 * the state changes can be observed by {@link #registerStateCallback}. On failure, {@link 635 * OutcomeReceiver#onError} of {@code receiver} will be invoked with a specific error: 636 * 637 * <ul> 638 * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_CHANNEL} {@code activeDataset} 639 * specifies a channel which is not supported in the current country or region; the {@code 640 * activeDataset} is rejected and not persisted so this device won't auto re-join the next 641 * time 642 * <li>{@link ThreadNetworkException#ERROR_ABORTED} this operation is aborted by another 643 * {@code join} or {@code leave} operation 644 * </ul> 645 * 646 * @param activeDataset the Active Operational Dataset represents the Thread network to join 647 * @param executor the executor to execute {@code receiver} 648 * @param receiver the receiver to receive result of this operation 649 */ 650 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") join( @onNull ActiveOperationalDataset activeDataset, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)651 public void join( 652 @NonNull ActiveOperationalDataset activeDataset, 653 @NonNull @CallbackExecutor Executor executor, 654 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 655 requireNonNull(activeDataset, "activeDataset cannot be null"); 656 requireNonNull(executor, "executor cannot be null"); 657 requireNonNull(receiver, "receiver cannot be null"); 658 try { 659 mControllerService.join(activeDataset, new OperationReceiverProxy(executor, receiver)); 660 } catch (RemoteException e) { 661 throw e.rethrowFromSystemServer(); 662 } 663 } 664 665 /** 666 * Schedules a network migration which moves all devices in the current connected network to a 667 * new network or updates parameters of the current connected network. 668 * 669 * <p>The migration doesn't happen immediately but is registered to the Leader device so that 670 * all devices in the current Thread network can be scheduled to apply the new dataset together. 671 * 672 * <p>On success, the Pending Dataset is successfully registered and persisted on the Leader and 673 * {@link OutcomeReceiver#onResult} of {@code receiver} will be called; Operational Dataset 674 * changes will be asynchronously delivered via {@link OperationalDatasetCallback} if a callback 675 * has been registered with {@link #registerOperationalDatasetCallback}. When failed, {@link 676 * OutcomeReceiver#onError} will be called with a specific error: 677 * 678 * <ul> 679 * <li>{@link ThreadNetworkException#ERROR_FAILED_PRECONDITION} the migration is rejected 680 * because this device is not attached 681 * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_CHANNEL} {@code pendingDataset} 682 * specifies a channel which is not supported in the current country or region; the {@code 683 * pendingDataset} is rejected and not persisted 684 * <li>{@link ThreadNetworkException#ERROR_REJECTED_BY_PEER} the Pending Dataset is rejected 685 * by the Leader device 686 * <li>{@link ThreadNetworkException#ERROR_BUSY} another {@code scheduleMigration} request is 687 * being processed 688 * <li>{@link ThreadNetworkException#ERROR_TIMEOUT} response from the Leader device hasn't 689 * been received before deadline 690 * </ul> 691 * 692 * <p>The Delay Timer of {@code pendingDataset} can vary from several minutes to a few days. 693 * It's important to select a proper value to safely migrate all devices in the network without 694 * leaving sleepy end devices orphaned. Apps are not suggested to specify the Delay Timer value 695 * if it's unclear how long it can take to propagate the {@code pendingDataset} to the whole 696 * network. Instead, use {@link Duration#ZERO} to use the default value suggested by the system. 697 * 698 * @param pendingDataset the Pending Operational Dataset 699 * @param executor the executor to execute {@code receiver} 700 * @param receiver the receiver to receive result of this operation 701 */ 702 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") scheduleMigration( @onNull PendingOperationalDataset pendingDataset, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)703 public void scheduleMigration( 704 @NonNull PendingOperationalDataset pendingDataset, 705 @NonNull @CallbackExecutor Executor executor, 706 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 707 requireNonNull(pendingDataset, "pendingDataset cannot be null"); 708 requireNonNull(executor, "executor cannot be null"); 709 requireNonNull(receiver, "receiver cannot be null"); 710 try { 711 mControllerService.scheduleMigration( 712 pendingDataset, new OperationReceiverProxy(executor, receiver)); 713 } catch (RemoteException e) { 714 throw e.rethrowFromSystemServer(); 715 } 716 } 717 718 /** 719 * Leaves from the Thread network. 720 * 721 * <p>This undoes a {@link join} operation. On success, this device is disconnected from the 722 * joined network and will not automatically join a network before {@link #join} is called 723 * again. Active and Pending Operational Dataset configured and persisted on this device will be 724 * removed too. 725 * 726 * @param executor the executor to execute {@code receiver} 727 * @param receiver the receiver to receive result of this operation 728 */ 729 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") leave( @onNull @allbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)730 public void leave( 731 @NonNull @CallbackExecutor Executor executor, 732 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 733 requireNonNull(executor, "executor cannot be null"); 734 requireNonNull(receiver, "receiver cannot be null"); 735 try { 736 mControllerService.leave(new OperationReceiverProxy(executor, receiver)); 737 } catch (RemoteException e) { 738 throw e.rethrowFromSystemServer(); 739 } 740 } 741 742 /** 743 * Configures the Thread features for this device. 744 * 745 * <p>This method sets the {@link ThreadConfiguration} for this device. On success, the {@link 746 * OutcomeReceiver#onResult} will be called, and the {@code configuration} will be applied and 747 * persisted to the device; the configuration changes can be observed by {@link 748 * #registerConfigurationCallback}. On failure, {@link OutcomeReceiver#onError} of {@code 749 * receiver} will be invoked with a specific error: 750 * 751 * <ul> 752 * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_FEATURE} the configuration enables a 753 * feature which is not supported by the platform. 754 * </ul> 755 * 756 * @param configuration the configuration to set 757 * @param executor the executor to execute {@code receiver} 758 * @param receiver the receiver to receive result of this operation 759 */ 760 @FlaggedApi(Flags.FLAG_SET_NAT64_CONFIGURATION_ENABLED) 761 @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED) setConfiguration( @onNull ThreadConfiguration configuration, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)762 public void setConfiguration( 763 @NonNull ThreadConfiguration configuration, 764 @NonNull @CallbackExecutor Executor executor, 765 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 766 requireNonNull(configuration, "Configuration cannot be null"); 767 requireNonNull(executor, "executor cannot be null"); 768 requireNonNull(receiver, "receiver cannot be null"); 769 try { 770 mControllerService.setConfiguration( 771 configuration, new OperationReceiverProxy(executor, receiver)); 772 } catch (RemoteException e) { 773 throw e.rethrowFromSystemServer(); 774 } 775 } 776 777 /** 778 * Registers a callback to be called when the configuration is changed. 779 * 780 * <p>Upon return of this method, {@code callback} will be invoked immediately with the current 781 * {@link ThreadConfiguration}. 782 * 783 * @param executor the executor to execute the {@code callback} 784 * @param callback the callback to receive Thread configuration changes 785 * @throws IllegalArgumentException if {@code callback} has already been registered 786 */ 787 @FlaggedApi(Flags.FLAG_CONFIGURATION_ENABLED) 788 @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED) registerConfigurationCallback( @onNull @allbackExecutor Executor executor, @NonNull Consumer<ThreadConfiguration> callback)789 public void registerConfigurationCallback( 790 @NonNull @CallbackExecutor Executor executor, 791 @NonNull Consumer<ThreadConfiguration> callback) { 792 requireNonNull(executor, "executor cannot be null"); 793 requireNonNull(callback, "callback cannot be null"); 794 synchronized (mConfigurationCallbackMapLock) { 795 if (mConfigurationCallbackMap.containsKey(callback)) { 796 throw new IllegalArgumentException("callback has already been registered"); 797 } 798 ConfigurationCallbackProxy callbackProxy = 799 new ConfigurationCallbackProxy(executor, callback); 800 mConfigurationCallbackMap.put(callback, callbackProxy); 801 try { 802 mControllerService.registerConfigurationCallback(callbackProxy); 803 } catch (RemoteException e) { 804 mConfigurationCallbackMap.remove(callback); 805 e.rethrowFromSystemServer(); 806 } 807 } 808 } 809 810 /** 811 * Unregisters the configuration callback. 812 * 813 * @param callback the callback which has been registered with {@link 814 * #registerConfigurationCallback} 815 * @throws IllegalArgumentException if {@code callback} hasn't been registered 816 */ 817 @FlaggedApi(Flags.FLAG_CONFIGURATION_ENABLED) 818 @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED) unregisterConfigurationCallback(@onNull Consumer<ThreadConfiguration> callback)819 public void unregisterConfigurationCallback(@NonNull Consumer<ThreadConfiguration> callback) { 820 requireNonNull(callback, "callback cannot be null"); 821 synchronized (mConfigurationCallbackMapLock) { 822 ConfigurationCallbackProxy callbackProxy = mConfigurationCallbackMap.get(callback); 823 if (callbackProxy == null) { 824 throw new IllegalArgumentException("callback hasn't been registered"); 825 } 826 try { 827 mControllerService.unregisterConfigurationCallback(callbackProxy); 828 mConfigurationCallbackMap.remove(callbackProxy.mConfigurationConsumer); 829 } catch (RemoteException e) { 830 e.rethrowFromSystemServer(); 831 } 832 } 833 } 834 835 /** 836 * Sets to use a specified test network as the upstream. 837 * 838 * @param testNetworkInterfaceName The name of the test network interface. When it's null, 839 * forbids using test network as an upstream. 840 * @param executor the executor to execute {@code receiver} 841 * @param receiver the receiver to receive result of this operation 842 * @hide 843 */ 844 @VisibleForTesting 845 @RequiresPermission( 846 allOf = {"android.permission.THREAD_NETWORK_PRIVILEGED", permission.NETWORK_SETTINGS}) setTestNetworkAsUpstream( @ullable String testNetworkInterfaceName, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)847 public void setTestNetworkAsUpstream( 848 @Nullable String testNetworkInterfaceName, 849 @NonNull @CallbackExecutor Executor executor, 850 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 851 requireNonNull(executor, "executor cannot be null"); 852 requireNonNull(receiver, "receiver cannot be null"); 853 try { 854 mControllerService.setTestNetworkAsUpstream( 855 testNetworkInterfaceName, new OperationReceiverProxy(executor, receiver)); 856 } catch (RemoteException e) { 857 throw e.rethrowFromSystemServer(); 858 } 859 } 860 861 /** 862 * Sets max power of each channel. 863 * 864 * <p>This method sets the max power for the given channel. The platform sets the actual output 865 * power to be less than or equal to the {@code channelMaxPowers} and as close as possible to 866 * the {@code channelMaxPowers}. 867 * 868 * <p>If not set, the default max power is set by the Thread HAL service or the Thread radio 869 * chip firmware. 870 * 871 * <p>On success, the Pending Dataset is successfully registered and persisted on the Leader and 872 * {@link OutcomeReceiver#onResult} of {@code receiver} will be called; When failed, {@link 873 * OutcomeReceiver#onError} will be called with a specific error: 874 * 875 * <ul> 876 * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_FEATURE} the feature is not supported 877 * by the platform. 878 * </ul> 879 * 880 * @param channelMaxPowers SparseIntArray (key: channel, value: max power) consists of channel 881 * and corresponding max power. Valid channel values should be between {@link 882 * ActiveOperationalDataset#CHANNEL_MIN_24_GHZ} and {@link 883 * ActiveOperationalDataset#CHANNEL_MAX_24_GHZ}. The unit of the max power is 0.01dBm. For 884 * example, 1000 means 0.01W and 2000 means 0.1W. If the power value of {@code 885 * channelMaxPowers} is lower than the minimum output power supported by the platform, the 886 * output power will be set to the minimum output power supported by the platform. If the 887 * power value of {@code channelMaxPowers} is higher than the maximum output power supported 888 * by the platform, the output power will be set to the maximum output power supported by 889 * the platform. If the power value of {@code channelMaxPowers} is set to {@link 890 * #MAX_POWER_CHANNEL_DISABLED}, the corresponding channel is disabled. 891 * @param executor the executor to execute {@code receiver}. 892 * @param receiver the receiver to receive the result of this operation. 893 * @throws IllegalArgumentException if the size of {@code channelMaxPowers} is smaller than 1, 894 * or invalid channel or max power is configured. 895 */ 896 @FlaggedApi(Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED) 897 @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") setChannelMaxPowers( @onNull @izemin = 1) SparseIntArray channelMaxPowers, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver)898 public final void setChannelMaxPowers( 899 @NonNull @Size(min = 1) SparseIntArray channelMaxPowers, 900 @NonNull @CallbackExecutor Executor executor, 901 @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) { 902 requireNonNull(channelMaxPowers, "channelMaxPowers cannot be null"); 903 requireNonNull(executor, "executor cannot be null"); 904 requireNonNull(receiver, "receiver cannot be null"); 905 906 if (channelMaxPowers.size() < 1) { 907 throw new IllegalArgumentException("channelMaxPowers cannot be empty"); 908 } 909 910 for (int i = 0; i < channelMaxPowers.size(); i++) { 911 int channel = channelMaxPowers.keyAt(i); 912 913 if ((channel < ActiveOperationalDataset.CHANNEL_MIN_24_GHZ) 914 || (channel > ActiveOperationalDataset.CHANNEL_MAX_24_GHZ)) { 915 throw new IllegalArgumentException( 916 "Channel " 917 + channel 918 + " exceeds allowed range [" 919 + ActiveOperationalDataset.CHANNEL_MIN_24_GHZ 920 + ", " 921 + ActiveOperationalDataset.CHANNEL_MAX_24_GHZ 922 + "]"); 923 } 924 } 925 926 try { 927 mControllerService.setChannelMaxPowers( 928 toChannelMaxPowerArray(channelMaxPowers), 929 new OperationReceiverProxy(executor, receiver)); 930 } catch (RemoteException e) { 931 throw e.rethrowFromSystemServer(); 932 } 933 } 934 toChannelMaxPowerArray( @onNull SparseIntArray channelMaxPowers)935 private static ChannelMaxPower[] toChannelMaxPowerArray( 936 @NonNull SparseIntArray channelMaxPowers) { 937 final ChannelMaxPower[] powerArray = new ChannelMaxPower[channelMaxPowers.size()]; 938 939 for (int i = 0; i < channelMaxPowers.size(); i++) { 940 powerArray[i] = new ChannelMaxPower(); 941 powerArray[i].channel = channelMaxPowers.keyAt(i); 942 powerArray[i].maxPower = channelMaxPowers.get(powerArray[i].channel); 943 } 944 945 return powerArray; 946 } 947 propagateError( Executor executor, OutcomeReceiver<T, ThreadNetworkException> receiver, int errorCode, String errorMsg)948 private static <T> void propagateError( 949 Executor executor, 950 OutcomeReceiver<T, ThreadNetworkException> receiver, 951 int errorCode, 952 String errorMsg) { 953 final long identity = Binder.clearCallingIdentity(); 954 try { 955 executor.execute( 956 () -> receiver.onError(new ThreadNetworkException(errorCode, errorMsg))); 957 } finally { 958 Binder.restoreCallingIdentity(identity); 959 } 960 } 961 962 private static final class ActiveDatasetReceiverProxy 963 extends IActiveOperationalDatasetReceiver.Stub { 964 final Executor mExecutor; 965 final OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> mResultReceiver; 966 ActiveDatasetReceiverProxy( @allbackExecutor Executor executor, OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> resultReceiver)967 ActiveDatasetReceiverProxy( 968 @CallbackExecutor Executor executor, 969 OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> resultReceiver) { 970 this.mExecutor = executor; 971 this.mResultReceiver = resultReceiver; 972 } 973 974 @Override onSuccess(ActiveOperationalDataset dataset)975 public void onSuccess(ActiveOperationalDataset dataset) { 976 final long identity = Binder.clearCallingIdentity(); 977 try { 978 mExecutor.execute(() -> mResultReceiver.onResult(dataset)); 979 } finally { 980 Binder.restoreCallingIdentity(identity); 981 } 982 } 983 984 @Override onError(int errorCode, String errorMessage)985 public void onError(int errorCode, String errorMessage) { 986 propagateError(mExecutor, mResultReceiver, errorCode, errorMessage); 987 } 988 } 989 990 private static final class OperationReceiverProxy extends IOperationReceiver.Stub { 991 final Executor mExecutor; 992 final OutcomeReceiver<Void, ThreadNetworkException> mResultReceiver; 993 OperationReceiverProxy( @allbackExecutor Executor executor, OutcomeReceiver<Void, ThreadNetworkException> resultReceiver)994 OperationReceiverProxy( 995 @CallbackExecutor Executor executor, 996 OutcomeReceiver<Void, ThreadNetworkException> resultReceiver) { 997 this.mExecutor = executor; 998 this.mResultReceiver = resultReceiver; 999 } 1000 1001 @Override onSuccess()1002 public void onSuccess() { 1003 final long identity = Binder.clearCallingIdentity(); 1004 try { 1005 mExecutor.execute(() -> mResultReceiver.onResult(null)); 1006 } finally { 1007 Binder.restoreCallingIdentity(identity); 1008 } 1009 } 1010 1011 @Override onError(int errorCode, String errorMessage)1012 public void onError(int errorCode, String errorMessage) { 1013 propagateError(mExecutor, mResultReceiver, errorCode, errorMessage); 1014 } 1015 } 1016 1017 private static final class ConfigurationCallbackProxy extends IConfigurationReceiver.Stub { 1018 final Executor mExecutor; 1019 final Consumer<ThreadConfiguration> mConfigurationConsumer; 1020 ConfigurationCallbackProxy( @allbackExecutor Executor executor, Consumer<ThreadConfiguration> ConfigurationConsumer)1021 ConfigurationCallbackProxy( 1022 @CallbackExecutor Executor executor, 1023 Consumer<ThreadConfiguration> ConfigurationConsumer) { 1024 this.mExecutor = executor; 1025 this.mConfigurationConsumer = ConfigurationConsumer; 1026 } 1027 1028 @Override onConfigurationChanged(ThreadConfiguration configuration)1029 public void onConfigurationChanged(ThreadConfiguration configuration) { 1030 final long identity = Binder.clearCallingIdentity(); 1031 try { 1032 mExecutor.execute(() -> mConfigurationConsumer.accept(configuration)); 1033 } finally { 1034 Binder.restoreCallingIdentity(identity); 1035 } 1036 } 1037 } 1038 } 1039