script-astra/Android/Sdk/sources/android-35/android/net/thread/ThreadNetworkController.java
localadmin 4380f00a78 init
2025-01-20 18:15:20 +03:00

768 lines
32 KiB
Java

/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.thread;
import static java.util.Objects.requireNonNull;
import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.Size;
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Provides the primary APIs for controlling all aspects of a Thread network.
*
* <p>For example, join this device to a Thread network with given Thread Operational Dataset, or
* migrate an existing network.
*
* @hide
*/
@FlaggedApi(ThreadNetworkFlags.FLAG_THREAD_ENABLED)
@SystemApi
public final class ThreadNetworkController {
private static final String TAG = "ThreadNetworkController";
/** The Thread stack is stopped. */
public static final int DEVICE_ROLE_STOPPED = 0;
/** The device is not currently participating in a Thread network/partition. */
public static final int DEVICE_ROLE_DETACHED = 1;
/** The device is a Thread Child. */
public static final int DEVICE_ROLE_CHILD = 2;
/** The device is a Thread Router. */
public static final int DEVICE_ROLE_ROUTER = 3;
/** The device is a Thread Leader. */
public static final int DEVICE_ROLE_LEADER = 4;
/** The Thread radio is disabled. */
public static final int STATE_DISABLED = 0;
/** The Thread radio is enabled. */
public static final int STATE_ENABLED = 1;
/** The Thread radio is being disabled. */
public static final int STATE_DISABLING = 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({
DEVICE_ROLE_STOPPED,
DEVICE_ROLE_DETACHED,
DEVICE_ROLE_CHILD,
DEVICE_ROLE_ROUTER,
DEVICE_ROLE_LEADER
})
public @interface DeviceRole {}
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = {"STATE_"},
value = {STATE_DISABLED, STATE_ENABLED, STATE_DISABLING})
public @interface EnabledState {}
/** Thread standard version 1.3. */
public static final int THREAD_VERSION_1_3 = 4;
/** Minimum value of max power in unit of 0.01dBm. @hide */
private static final int POWER_LIMITATION_MIN = -32768;
/** Maximum value of max power in unit of 0.01dBm. @hide */
private static final int POWER_LIMITATION_MAX = 32767;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef({THREAD_VERSION_1_3})
public @interface ThreadVersion {}
private final IThreadNetworkController mControllerService;
private final Object mStateCallbackMapLock = new Object();
@GuardedBy("mStateCallbackMapLock")
private final Map<StateCallback, StateCallbackProxy> mStateCallbackMap = new HashMap<>();
private final Object mOpDatasetCallbackMapLock = new Object();
@GuardedBy("mOpDatasetCallbackMapLock")
private final Map<OperationalDatasetCallback, OperationalDatasetCallbackProxy>
mOpDatasetCallbackMap = new HashMap<>();
/** @hide */
public ThreadNetworkController(@NonNull IThreadNetworkController controllerService) {
requireNonNull(controllerService, "controllerService cannot be null");
mControllerService = controllerService;
}
/**
* Enables/Disables the radio of this ThreadNetworkController. The requested enabled state will
* be persistent and survives device reboots.
*
* <p>When Thread is in {@code STATE_DISABLED}, {@link ThreadNetworkController} APIs which
* require the Thread radio will fail with error code {@link
* ThreadNetworkException#ERROR_THREAD_DISABLED}. When Thread is in {@code STATE_DISABLING},
* {@link ThreadNetworkController} APIs that return a {@link ThreadNetworkException} will fail
* with error code {@link ThreadNetworkException#ERROR_BUSY}.
*
* <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called. It indicates
* the operation has completed. But there maybe subsequent calls to update the enabled state,
* callers of this method should use {@link #registerStateCallback} to subscribe to the Thread
* enabled state changes.
*
* <p>On failure, {@link OutcomeReceiver#onError} of {@code receiver} will be invoked with a
* specific error in {@link ThreadNetworkException#ERROR_}.
*
* @param enabled {@code true} for enabling Thread
* @param executor the executor to execute {@code receiver}
* @param receiver the receiver to receive result of this operation
*/
@RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
public void setEnabled(
boolean enabled,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
try {
mControllerService.setEnabled(enabled, new OperationReceiverProxy(executor, receiver));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** Returns the Thread version this device is operating on. */
@ThreadVersion
public int getThreadVersion() {
try {
return mControllerService.getThreadVersion();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Creates a new Active Operational Dataset with randomized parameters.
*
* <p>This method is the recommended way to create a randomized dataset which can be used with
* {@link #join} to securely join this device to the specified network . It's highly discouraged
* to change the randomly generated Extended PAN ID, Network Key or PSKc, as it will compromise
* the security of a Thread network.
*
* @throws IllegalArgumentException if length of the UTF-8 representation of {@code networkName}
* isn't in range of [{@link #LENGTH_MIN_NETWORK_NAME_BYTES}, {@link
* #LENGTH_MAX_NETWORK_NAME_BYTES}]
*/
public void createRandomizedDataset(
@NonNull String networkName,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> receiver) {
ActiveOperationalDataset.checkNetworkName(networkName);
requireNonNull(executor, "executor cannot be null");
requireNonNull(receiver, "receiver cannot be null");
try {
mControllerService.createRandomizedDataset(
networkName, new ActiveDatasetReceiverProxy(executor, receiver));
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
/** Returns {@code true} if {@code deviceRole} indicates an attached state. */
public static boolean isAttached(@DeviceRole int deviceRole) {
return deviceRole == DEVICE_ROLE_CHILD
|| deviceRole == DEVICE_ROLE_ROUTER
|| deviceRole == DEVICE_ROLE_LEADER;
}
/**
* Callback to receive notifications when the Thread network states are changed.
*
* <p>Applications which are interested in monitoring Thread network states should implement
* this interface and register the callback with {@link #registerStateCallback}.
*/
public interface StateCallback {
/**
* The Thread device role has changed.
*
* @param deviceRole the new Thread device role
*/
void onDeviceRoleChanged(@DeviceRole int deviceRole);
/**
* The Thread network partition ID has changed.
*
* @param partitionId the new Thread partition ID
*/
default void onPartitionIdChanged(long partitionId) {}
/**
* The Thread enabled state has changed.
*
* <p>The Thread enabled state can be set with {@link setEnabled}, it may also be updated by
* airplane mode or admin control.
*
* @param enabledState the new Thread enabled state
*/
default void onThreadEnableStateChanged(@EnabledState int enabledState) {}
}
private static final class StateCallbackProxy extends IStateCallback.Stub {
private final Executor mExecutor;
private final StateCallback mCallback;
StateCallbackProxy(@CallbackExecutor Executor executor, StateCallback callback) {
mExecutor = executor;
mCallback = callback;
}
@Override
public void onDeviceRoleChanged(@DeviceRole int deviceRole) {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mCallback.onDeviceRoleChanged(deviceRole));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onPartitionIdChanged(long partitionId) {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mCallback.onPartitionIdChanged(partitionId));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onThreadEnableStateChanged(@EnabledState int enabled) {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mCallback.onThreadEnableStateChanged(enabled));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
/**
* Registers a callback to be called when Thread network states are changed.
*
* <p>Upon return of this method, methods of {@code callback} will be invoked immediately with
* existing states.
*
* @param executor the executor to execute the {@code callback}
* @param callback the callback to receive Thread network state changes
* @throws IllegalArgumentException if {@code callback} has already been registered
*/
@RequiresPermission(permission.ACCESS_NETWORK_STATE)
public void registerStateCallback(
@NonNull @CallbackExecutor Executor executor, @NonNull StateCallback callback) {
requireNonNull(executor, "executor cannot be null");
requireNonNull(callback, "callback cannot be null");
synchronized (mStateCallbackMapLock) {
if (mStateCallbackMap.containsKey(callback)) {
throw new IllegalArgumentException("callback has already been registered");
}
StateCallbackProxy callbackProxy = new StateCallbackProxy(executor, callback);
mStateCallbackMap.put(callback, callbackProxy);
try {
mControllerService.registerStateCallback(callbackProxy);
} catch (RemoteException e) {
mStateCallbackMap.remove(callback);
e.rethrowFromSystemServer();
}
}
}
/**
* Unregisters the Thread state changed callback.
*
* @param callback the callback which has been registered with {@link #registerStateCallback}
* @throws IllegalArgumentException if {@code callback} hasn't been registered
*/
@RequiresPermission(permission.ACCESS_NETWORK_STATE)
public void unregisterStateCallback(@NonNull StateCallback callback) {
requireNonNull(callback, "callback cannot be null");
synchronized (mStateCallbackMapLock) {
StateCallbackProxy callbackProxy = mStateCallbackMap.get(callback);
if (callbackProxy == null) {
throw new IllegalArgumentException("callback hasn't been registered");
}
try {
mControllerService.unregisterStateCallback(callbackProxy);
mStateCallbackMap.remove(callback);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
}
/**
* Callback to receive notifications when the Thread Operational Datasets are changed.
*
* <p>Applications which are interested in monitoring Thread network datasets should implement
* this interface and register the callback with {@link #registerOperationalDatasetCallback}.
*/
public interface OperationalDatasetCallback {
/**
* Called when the Active Operational Dataset is changed.
*
* @param activeDataset the new Active Operational Dataset or {@code null} if the dataset is
* absent
*/
void onActiveOperationalDatasetChanged(@Nullable ActiveOperationalDataset activeDataset);
/**
* Called when the Pending Operational Dataset is changed.
*
* @param pendingDataset the new Pending Operational Dataset or {@code null} if the dataset
* has been committed and removed
*/
default void onPendingOperationalDatasetChanged(
@Nullable PendingOperationalDataset pendingDataset) {}
}
private static final class OperationalDatasetCallbackProxy
extends IOperationalDatasetCallback.Stub {
private final Executor mExecutor;
private final OperationalDatasetCallback mCallback;
OperationalDatasetCallbackProxy(
@CallbackExecutor Executor executor, OperationalDatasetCallback callback) {
mExecutor = executor;
mCallback = callback;
}
@Override
public void onActiveOperationalDatasetChanged(
@Nullable ActiveOperationalDataset activeDataset) {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mCallback.onActiveOperationalDatasetChanged(activeDataset));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onPendingOperationalDatasetChanged(
@Nullable PendingOperationalDataset pendingDataset) {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(
() -> mCallback.onPendingOperationalDatasetChanged(pendingDataset));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
/**
* Registers a callback to be called when Thread Operational Datasets are changed.
*
* <p>Upon return of this method, methods of {@code callback} will be invoked immediately with
* existing Operational Datasets.
*
* @param executor the executor to execute {@code callback}
* @param callback the callback to receive Operational Dataset changes
* @throws IllegalArgumentException if {@code callback} has already been registered
*/
@RequiresPermission(
allOf = {
permission.ACCESS_NETWORK_STATE,
"android.permission.THREAD_NETWORK_PRIVILEGED"
})
public void registerOperationalDatasetCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull OperationalDatasetCallback callback) {
requireNonNull(executor, "executor cannot be null");
requireNonNull(callback, "callback cannot be null");
synchronized (mOpDatasetCallbackMapLock) {
if (mOpDatasetCallbackMap.containsKey(callback)) {
throw new IllegalArgumentException("callback has already been registered");
}
OperationalDatasetCallbackProxy callbackProxy =
new OperationalDatasetCallbackProxy(executor, callback);
mOpDatasetCallbackMap.put(callback, callbackProxy);
try {
mControllerService.registerOperationalDatasetCallback(callbackProxy);
} catch (RemoteException e) {
mOpDatasetCallbackMap.remove(callback);
e.rethrowFromSystemServer();
}
}
}
/**
* Unregisters the Thread Operational Dataset callback.
*
* @param callback the callback which has been registered with {@link
* #registerOperationalDatasetCallback}
* @throws IllegalArgumentException if {@code callback} hasn't been registered
*/
@RequiresPermission(
allOf = {
permission.ACCESS_NETWORK_STATE,
"android.permission.THREAD_NETWORK_PRIVILEGED"
})
public void unregisterOperationalDatasetCallback(@NonNull OperationalDatasetCallback callback) {
requireNonNull(callback, "callback cannot be null");
synchronized (mOpDatasetCallbackMapLock) {
OperationalDatasetCallbackProxy callbackProxy = mOpDatasetCallbackMap.get(callback);
if (callbackProxy == null) {
throw new IllegalArgumentException("callback hasn't been registered");
}
try {
mControllerService.unregisterOperationalDatasetCallback(callbackProxy);
mOpDatasetCallbackMap.remove(callback);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
}
/**
* Joins to a Thread network with given Active Operational Dataset.
*
* <p>This method does nothing if this device has already joined to the same network specified
* by {@code activeDataset}. If this device has already joined to a different network, this
* device will first leave from that network and then join the new network. This method changes
* only this device and all other connected devices will stay in the old network. To change the
* network for all connected devices together, use {@link #scheduleMigration}.
*
* <p>On success, {@link OutcomeReceiver#onResult} of {@code receiver} is called and the Dataset
* will be persisted on this device; this device will try to attach to the Thread network and
* the state changes can be observed by {@link #registerStateCallback}. On failure, {@link
* OutcomeReceiver#onError} of {@code receiver} will be invoked with a specific error:
*
* <ul>
* <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_CHANNEL} {@code activeDataset}
* specifies a channel which is not supported in the current country or region; the {@code
* activeDataset} is rejected and not persisted so this device won't auto re-join the next
* time
* <li>{@link ThreadNetworkException#ERROR_ABORTED} this operation is aborted by another
* {@code join} or {@code leave} operation
* </ul>
*
* @param activeDataset the Active Operational Dataset represents the Thread network to join
* @param executor the executor to execute {@code receiver}
* @param receiver the receiver to receive result of this operation
*/
@RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
public void join(
@NonNull ActiveOperationalDataset activeDataset,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
requireNonNull(activeDataset, "activeDataset cannot be null");
requireNonNull(executor, "executor cannot be null");
requireNonNull(receiver, "receiver cannot be null");
try {
mControllerService.join(activeDataset, new OperationReceiverProxy(executor, receiver));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Schedules a network migration which moves all devices in the current connected network to a
* new network or updates parameters of the current connected network.
*
* <p>The migration doesn't happen immediately but is registered to the Leader device so that
* all devices in the current Thread network can be scheduled to apply the new dataset together.
*
* <p>On success, the Pending Dataset is successfully registered and persisted on the Leader and
* {@link OutcomeReceiver#onResult} of {@code receiver} will be called; Operational Dataset
* changes will be asynchronously delivered via {@link OperationalDatasetCallback} if a callback
* has been registered with {@link #registerOperationalDatasetCallback}. When failed, {@link
* OutcomeReceiver#onError} will be called with a specific error:
*
* <ul>
* <li>{@link ThreadNetworkException#ERROR_FAILED_PRECONDITION} the migration is rejected
* because this device is not attached
* <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_CHANNEL} {@code pendingDataset}
* specifies a channel which is not supported in the current country or region; the {@code
* pendingDataset} is rejected and not persisted
* <li>{@link ThreadNetworkException#ERROR_REJECTED_BY_PEER} the Pending Dataset is rejected
* by the Leader device
* <li>{@link ThreadNetworkException#ERROR_BUSY} another {@code scheduleMigration} request is
* being processed
* <li>{@link ThreadNetworkException#ERROR_TIMEOUT} response from the Leader device hasn't
* been received before deadline
* </ul>
*
* <p>The Delay Timer of {@code pendingDataset} can vary from several minutes to a few days.
* It's important to select a proper value to safely migrate all devices in the network without
* leaving sleepy end devices orphaned. Apps are not suggested to specify the Delay Timer value
* if it's unclear how long it can take to propagate the {@code pendingDataset} to the whole
* network. Instead, use {@link Duration#ZERO} to use the default value suggested by the system.
*
* @param pendingDataset the Pending Operational Dataset
* @param executor the executor to execute {@code receiver}
* @param receiver the receiver to receive result of this operation
*/
@RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
public void scheduleMigration(
@NonNull PendingOperationalDataset pendingDataset,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
requireNonNull(pendingDataset, "pendingDataset cannot be null");
requireNonNull(executor, "executor cannot be null");
requireNonNull(receiver, "receiver cannot be null");
try {
mControllerService.scheduleMigration(
pendingDataset, new OperationReceiverProxy(executor, receiver));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Leaves from the Thread network.
*
* <p>This undoes a {@link join} operation. On success, this device is disconnected from the
* joined network and will not automatically join a network before {@link #join} is called
* again. Active and Pending Operational Dataset configured and persisted on this device will be
* removed too.
*
* @param executor the executor to execute {@code receiver}
* @param receiver the receiver to receive result of this operation
*/
@RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
public void leave(
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
requireNonNull(executor, "executor cannot be null");
requireNonNull(receiver, "receiver cannot be null");
try {
mControllerService.leave(new OperationReceiverProxy(executor, receiver));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Sets to use a specified test network as the upstream.
*
* @param testNetworkInterfaceName The name of the test network interface. When it's null,
* forbids using test network as an upstream.
* @param executor the executor to execute {@code receiver}
* @param receiver the receiver to receive result of this operation
* @hide
*/
@VisibleForTesting
@RequiresPermission(
allOf = {"android.permission.THREAD_NETWORK_PRIVILEGED", permission.NETWORK_SETTINGS})
public void setTestNetworkAsUpstream(
@Nullable String testNetworkInterfaceName,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
requireNonNull(executor, "executor cannot be null");
requireNonNull(receiver, "receiver cannot be null");
try {
mControllerService.setTestNetworkAsUpstream(
testNetworkInterfaceName, new OperationReceiverProxy(executor, receiver));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Sets max power of each channel.
*
* <p>If not set, the default max power is set by the Thread HAL service or the Thread radio
* chip firmware.
*
* <p>On success, the Pending Dataset is successfully registered and persisted on the Leader and
* {@link OutcomeReceiver#onResult} of {@code receiver} will be called; When failed, {@link
* OutcomeReceiver#onError} will be called with a specific error:
*
* <ul>
* <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_OPERATION} the operation is no
* supported by the platform.
* </ul>
*
* @param channelMaxPowers SparseIntArray (key: channel, value: max power) consists of channel
* and corresponding max power. Valid channel values should be between {@link
* ActiveOperationalDataset#CHANNEL_MIN_24_GHZ} and {@link
* ActiveOperationalDataset#CHANNEL_MAX_24_GHZ}. The unit of the max power is 0.01dBm. Max
* power values should be between INT16_MIN (-32768) and INT16_MAX (32767). If the max power
* is set to INT16_MAX, the corresponding channel is not supported.
* @param executor the executor to execute {@code receiver}.
* @param receiver the receiver to receive the result of this operation.
* @throws IllegalArgumentException if the size of {@code channelMaxPowers} is smaller than 1,
* or invalid channel or max power is configured.
* @hide
*/
@RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
public final void setChannelMaxPowers(
@NonNull @Size(min = 1) SparseIntArray channelMaxPowers,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
requireNonNull(channelMaxPowers, "channelMaxPowers cannot be null");
requireNonNull(executor, "executor cannot be null");
requireNonNull(receiver, "receiver cannot be null");
if (channelMaxPowers.size() < 1) {
throw new IllegalArgumentException("channelMaxPowers cannot be empty");
}
for (int i = 0; i < channelMaxPowers.size(); i++) {
int channel = channelMaxPowers.keyAt(i);
int maxPower = channelMaxPowers.get(channel);
if ((channel < ActiveOperationalDataset.CHANNEL_MIN_24_GHZ)
|| (channel > ActiveOperationalDataset.CHANNEL_MAX_24_GHZ)) {
throw new IllegalArgumentException(
"Channel "
+ channel
+ " exceeds allowed range ["
+ ActiveOperationalDataset.CHANNEL_MIN_24_GHZ
+ ", "
+ ActiveOperationalDataset.CHANNEL_MAX_24_GHZ
+ "]");
}
if ((maxPower < POWER_LIMITATION_MIN) || (maxPower > POWER_LIMITATION_MAX)) {
throw new IllegalArgumentException(
"Channel power ({channel: "
+ channel
+ ", maxPower: "
+ maxPower
+ "}) exceeds allowed range ["
+ POWER_LIMITATION_MIN
+ ", "
+ POWER_LIMITATION_MAX
+ "]");
}
}
try {
mControllerService.setChannelMaxPowers(
toChannelMaxPowerArray(channelMaxPowers),
new OperationReceiverProxy(executor, receiver));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private static ChannelMaxPower[] toChannelMaxPowerArray(
@NonNull SparseIntArray channelMaxPowers) {
final ChannelMaxPower[] powerArray = new ChannelMaxPower[channelMaxPowers.size()];
for (int i = 0; i < channelMaxPowers.size(); i++) {
powerArray[i] = new ChannelMaxPower();
powerArray[i].channel = channelMaxPowers.keyAt(i);
powerArray[i].maxPower = channelMaxPowers.get(powerArray[i].channel);
}
return powerArray;
}
private static <T> void propagateError(
Executor executor,
OutcomeReceiver<T, ThreadNetworkException> receiver,
int errorCode,
String errorMsg) {
final long identity = Binder.clearCallingIdentity();
try {
executor.execute(
() -> receiver.onError(new ThreadNetworkException(errorCode, errorMsg)));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
private static final class ActiveDatasetReceiverProxy
extends IActiveOperationalDatasetReceiver.Stub {
final Executor mExecutor;
final OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> mResultReceiver;
ActiveDatasetReceiverProxy(
@CallbackExecutor Executor executor,
OutcomeReceiver<ActiveOperationalDataset, ThreadNetworkException> resultReceiver) {
this.mExecutor = executor;
this.mResultReceiver = resultReceiver;
}
@Override
public void onSuccess(ActiveOperationalDataset dataset) {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mResultReceiver.onResult(dataset));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onError(int errorCode, String errorMessage) {
propagateError(mExecutor, mResultReceiver, errorCode, errorMessage);
}
}
private static final class OperationReceiverProxy extends IOperationReceiver.Stub {
final Executor mExecutor;
final OutcomeReceiver<Void, ThreadNetworkException> mResultReceiver;
OperationReceiverProxy(
@CallbackExecutor Executor executor,
OutcomeReceiver<Void, ThreadNetworkException> resultReceiver) {
this.mExecutor = executor;
this.mResultReceiver = resultReceiver;
}
@Override
public void onSuccess() {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> mResultReceiver.onResult(null));
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Override
public void onError(int errorCode, String errorMessage) {
propagateError(mExecutor, mResultReceiver, errorCode, errorMessage);
}
}
}