[Thread] add system API setChannelMaxPowers
The `setChannelMaxPowers` allows privilege app to set the max
power of each channel. It is going to be used for controlling the
output power of the Thread radio chip.
Bug: b/320594263
Test: atest ThreadNetworkUnitTests && atest CtsThreadNetworkTestCases
Change-Id: Ic47eccd6de2da0e6cdb8f21d5b4df156b1f11747
diff --git a/thread/framework/Android.bp b/thread/framework/Android.bp
index 846253c..f8fe422 100644
--- a/thread/framework/Android.bp
+++ b/thread/framework/Android.bp
@@ -30,3 +30,14 @@
"//packages/modules/Connectivity:__subpackages__",
],
}
+
+filegroup {
+ name: "framework-thread-ot-daemon-shared-aidl-sources",
+ srcs: [
+ "java/android/net/thread/ChannelMaxPower.aidl",
+ ],
+ path: "java",
+ visibility: [
+ "//external/ot-br-posix:__subpackages__",
+ ],
+}
diff --git a/thread/framework/java/android/net/thread/ChannelMaxPower.aidl b/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
new file mode 100644
index 0000000..bcda8a8
--- /dev/null
+++ b/thread/framework/java/android/net/thread/ChannelMaxPower.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 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;
+
+ /**
+ * Mapping from a channel to its max power.
+ *
+ * {@hide}
+ */
+parcelable ChannelMaxPower {
+ int channel; // The Thread radio channel.
+ int maxPower; // The max power in the unit of 0.01dBm. Passing INT16_MAX(32767) will
+ // disable the channel.
+}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index 485e25d..c5ca557 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -17,6 +17,7 @@
package android.net.thread;
import android.net.thread.ActiveOperationalDataset;
+import android.net.thread.ChannelMaxPower;
import android.net.thread.IActiveOperationalDatasetReceiver;
import android.net.thread.IOperationalDatasetCallback;
import android.net.thread.IOperationReceiver;
@@ -39,6 +40,7 @@
void leave(in IOperationReceiver receiver);
void setTestNetworkAsUpstream(in String testNetworkInterfaceName, in IOperationReceiver receiver);
+ void setChannelMaxPowers(in ChannelMaxPower[] channelMaxPowers, in IOperationReceiver receiver);
int getThreadVersion();
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index db761a3..8d6b40a 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -25,10 +25,12 @@
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;
@@ -98,6 +100,12 @@
/** 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})
@@ -596,6 +604,98 @@
}
}
+ /**
+ * 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,
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkException.java b/thread/framework/java/android/net/thread/ThreadNetworkException.java
index 4def0fb..f699c30 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkException.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkException.java
@@ -138,8 +138,17 @@
*/
public static final int ERROR_THREAD_DISABLED = 12;
+ /**
+ * The operation failed because it is not supported by the platform. For example, some platforms
+ * may not support setting the target power of each channel. The caller should not retry and may
+ * return an error to the user.
+ *
+ * @hide
+ */
+ public static final int ERROR_UNSUPPORTED_OPERATION = 13;
+
private static final int ERROR_MIN = ERROR_INTERNAL_ERROR;
- private static final int ERROR_MAX = ERROR_THREAD_DISABLED;
+ private static final int ERROR_MAX = ERROR_UNSUPPORTED_OPERATION;
private final int mErrorCode;