changes the API setChannelMaxPowers from hide API to system API
This CL changes the hiden API `setChannelMaxPowers` to system API
to allow the privilege app to set the max power of each Thread channel.
Bug: b/346686506
Test: atest CtsThreadNetworkTestCases
Change-Id: I1089a4a315971c3bca8ec688a02143ddec93d039
diff --git a/common/thread_flags.aconfig b/common/thread_flags.aconfig
index 0edb7a8..a3f77a7 100644
--- a/common/thread_flags.aconfig
+++ b/common/thread_flags.aconfig
@@ -15,4 +15,12 @@
namespace: "thread_network"
description: "Controls whether the Android Thread configuration is enabled"
bug: "342519412"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "channel_max_powers_enabled"
+ is_exported: true
+ namespace: "thread_network"
+ description: "Controls whether the Android Thread setting max power of channel feature is enabled"
+ bug: "346686506"
+}
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 2354882..9f26bcf 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -516,6 +516,7 @@
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+ method @FlaggedApi("com.android.net.thread.flags.channel_max_powers_enabled") @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setChannelMaxPowers(@NonNull @Size(min=1) android.util.SparseIntArray, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void unregisterConfigurationCallback(@NonNull java.util.function.Consumer<android.net.thread.ThreadConfiguration>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
@@ -525,6 +526,7 @@
field public static final int DEVICE_ROLE_LEADER = 4; // 0x4
field public static final int DEVICE_ROLE_ROUTER = 3; // 0x3
field public static final int DEVICE_ROLE_STOPPED = 0; // 0x0
+ field public static final int MAX_POWER_CHANNEL_DISABLED = -2147483648; // 0x80000000
field public static final int STATE_DISABLED = 0; // 0x0
field public static final int STATE_DISABLING = 2; // 0x2
field public static final int STATE_ENABLED = 1; // 0x1
@@ -557,6 +559,7 @@
field public static final int ERROR_UNAVAILABLE = 4; // 0x4
field public static final int ERROR_UNKNOWN = 11; // 0xb
field public static final int ERROR_UNSUPPORTED_CHANNEL = 7; // 0x7
+ field public static final int ERROR_UNSUPPORTED_FEATURE = 13; // 0xd
}
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ThreadNetworkManager {
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index 551b98f..ecaefd0 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.Size;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Binder;
import android.os.OutcomeReceiver;
@@ -102,11 +103,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;
+ /** The value of max power to disable the Thread channel. */
+ // This constant can never change. It has "max" in the name not because it indicates
+ // maximum power, but because it's passed to an API that sets the maximum power to
+ // disabled the Thread channel.
+ @SuppressLint("MinMaxConstant")
+ public static final int MAX_POWER_CHANNEL_DISABLED = Integer.MIN_VALUE;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -704,6 +706,10 @@
/**
* Sets max power of each channel.
*
+ * <p>This method sets the max power for the given channel. The platform sets the actual
+ * output power to be less than or equal to the {@code channelMaxPowers} and as close as
+ * possible to the {@code channelMaxPowers}.
+ *
* <p>If not set, the default max power is set by the Thread HAL service or the Thread radio
* chip firmware.
*
@@ -712,22 +718,27 @@
* OutcomeReceiver#onError} will be called with a specific error:
*
* <ul>
- * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_OPERATION} the operation is no
- * supported by the platform.
+ * <li>{@link ThreadNetworkException#ERROR_UNSUPPORTED_FEATURE} the feature is not 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.
+ * ActiveOperationalDataset#CHANNEL_MAX_24_GHZ}. The unit of the max power is 0.01dBm. For
+ * example, 1000 means 0.01W and 2000 means 0.1W. If the power value of
+ * {@code channelMaxPowers} is lower than the minimum output power supported by the
+ * platform, the output power will be set to the minimum output power supported by the
+ * platform. If the power value of {@code channelMaxPowers} is higher than the maximum
+ * output power supported by the platform, the output power will be set to the maximum
+ * output power supported by the platform. If the power value of {@code channelMaxPowers}
+ * is set to {@link #MAX_POWER_CHANNEL_DISABLED}, the corresponding channel is disabled.
* @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
*/
+ @FlaggedApi(Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED)
@RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
public final void setChannelMaxPowers(
@NonNull @Size(min = 1) SparseIntArray channelMaxPowers,
@@ -756,19 +767,6 @@
+ 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 {
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkException.java b/thread/framework/java/android/net/thread/ThreadNetworkException.java
index b6973f8..1ea2459 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkException.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkException.java
@@ -141,16 +141,14 @@
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
+ * The operation failed because the feature 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.
*/
- public static final int ERROR_UNSUPPORTED_OPERATION = 13;
+ public static final int ERROR_UNSUPPORTED_FEATURE = 13;
private static final int ERROR_MIN = ERROR_INTERNAL_ERROR;
- private static final int ERROR_MAX = ERROR_UNSUPPORTED_OPERATION;
+ private static final int ERROR_MAX = ERROR_UNSUPPORTED_FEATURE;
private final int mErrorCode;
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index b621a6a..452fab9 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -40,7 +40,7 @@
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
import static android.net.thread.ThreadNetworkException.ERROR_TIMEOUT;
import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
-import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_OPERATION;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
import static android.net.thread.ThreadNetworkManager.DISALLOW_THREAD_NETWORK;
import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_PRIVILEGED;
@@ -1055,7 +1055,7 @@
case OT_ERROR_BUSY:
return ERROR_BUSY;
case OT_ERROR_NOT_IMPLEMENTED:
- return ERROR_UNSUPPORTED_OPERATION;
+ return ERROR_UNSUPPORTED_FEATURE;
case OT_ERROR_NO_BUFS:
return ERROR_RESOURCE_EXHAUSTED;
case OT_ERROR_PARSE:
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index 6db7c9c..6572755 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -40,6 +40,7 @@
static_libs: [
"androidx.test.ext.junit",
"compatibility-device-util-axt",
+ "com.android.net.thread.flags-aconfig-java",
"ctstestrunner-axt",
"guava",
"guava-android-testlib",
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 1a101b6..c048394 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -35,6 +35,7 @@
import static android.net.thread.ThreadNetworkException.ERROR_FAILED_PRECONDITION;
import static android.net.thread.ThreadNetworkException.ERROR_REJECTED_BY_PEER;
import static android.net.thread.ThreadNetworkException.ERROR_THREAD_DISABLED;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -70,12 +71,15 @@
import android.os.Build;
import android.os.HandlerThread;
import android.os.OutcomeReceiver;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.SparseIntArray;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.LargeTest;
import com.android.net.module.util.ArrayTrackRecord;
+import com.android.net.thread.flags.Flags;
import com.android.testutils.FunctionalUtils.ThrowingRunnable;
import org.junit.After;
@@ -116,11 +120,20 @@
private static final int SET_CONFIGURATION_TIMEOUT_MILLIS = 1_000;
private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000;
private static final int SERVICE_LOST_TIMEOUT_MILLIS = 20_000;
+ private static final int VALID_POWER = 32_767;
+ private static final int VALID_CHANNEL = 20;
+ private static final int INVALID_CHANNEL = 10;
private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
private static final ThreadConfiguration DEFAULT_CONFIG =
new ThreadConfiguration.Builder().build();
+ private static final SparseIntArray CHANNEL_MAX_POWERS =
+ new SparseIntArray() {
+ {
+ put(VALID_CHANNEL, VALID_POWER);
+ }
+ };
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
@@ -1460,4 +1473,52 @@
@Override
public void onServiceInfoCallbackUnregistered() {}
}
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED})
+ public void setChannelMaxPowers_withPrivilegedPermission_success() throws Exception {
+ CompletableFuture<Void> powerFuture = new CompletableFuture<>();
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.setChannelMaxPowers(
+ CHANNEL_MAX_POWERS, mExecutor, newOutcomeReceiver(powerFuture)));
+
+ try {
+ assertThat(powerFuture.get()).isNull();
+ } catch (ExecutionException exception) {
+ ThreadNetworkException thrown = (ThreadNetworkException) exception.getCause();
+ assertThat(thrown.getErrorCode()).isEqualTo(ERROR_UNSUPPORTED_FEATURE);
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED})
+ public void setChannelMaxPowers_withoutPrivilegedPermission_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.setChannelMaxPowers(CHANNEL_MAX_POWERS, mExecutor, v -> {}));
+ }
+
+ @Test
+ @RequiresFlagsEnabled({Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED})
+ public void setChannelMaxPowers_invalidChannel_throwsIllegalArgumentException() {
+ final SparseIntArray INVALID_CHANNEL_ARRAY =
+ new SparseIntArray() {
+ {
+ put(INVALID_CHANNEL, VALID_POWER);
+ }
+ };
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.setChannelMaxPowers(new SparseIntArray(), mExecutor, v -> {}));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.setChannelMaxPowers(INVALID_CHANNEL_ARRAY, mExecutor, v -> {}));
+ }
}
diff --git a/thread/tests/integration/src/android/net/thread/ThreadNetworkControllerTest.java b/thread/tests/integration/src/android/net/thread/ThreadNetworkControllerTest.java
deleted file mode 100644
index ba04348..0000000
--- a/thread/tests/integration/src/android/net/thread/ThreadNetworkControllerTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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;
-
-import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_OPERATION;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.testutils.TestPermissionUtil.runAsShell;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertThrows;
-
-import android.content.Context;
-import android.net.thread.utils.ThreadFeatureCheckerRule;
-import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
-import android.os.OutcomeReceiver;
-import android.util.SparseIntArray;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/** Tests for hide methods of {@link ThreadNetworkController}. */
-@LargeTest
-@RequiresThreadFeature
-@RunWith(AndroidJUnit4.class)
-public class ThreadNetworkControllerTest {
- private static final int VALID_POWER = 32_767;
- private static final int INVALID_POWER = 32_768;
- private static final int VALID_CHANNEL = 20;
- private static final int INVALID_CHANNEL = 10;
- private static final String THREAD_NETWORK_PRIVILEGED =
- "android.permission.THREAD_NETWORK_PRIVILEGED";
-
- private static final SparseIntArray CHANNEL_MAX_POWERS =
- new SparseIntArray() {
- {
- put(20, 32767);
- }
- };
-
- @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
-
- private final Context mContext = ApplicationProvider.getApplicationContext();
- private ExecutorService mExecutor;
- private ThreadNetworkController mController;
-
- @Before
- public void setUp() throws Exception {
- mController =
- mContext.getSystemService(ThreadNetworkManager.class)
- .getAllThreadNetworkControllers()
- .get(0);
-
- mExecutor = Executors.newSingleThreadExecutor();
- }
-
- @After
- public void tearDown() throws Exception {
- dropAllPermissions();
- }
-
- @Test
- public void setChannelMaxPowers_withPrivilegedPermission_success() throws Exception {
- CompletableFuture<Void> powerFuture = new CompletableFuture<>();
-
- runAsShell(
- THREAD_NETWORK_PRIVILEGED,
- () ->
- mController.setChannelMaxPowers(
- CHANNEL_MAX_POWERS, mExecutor, newOutcomeReceiver(powerFuture)));
-
- try {
- assertThat(powerFuture.get()).isNull();
- } catch (ExecutionException exception) {
- ThreadNetworkException thrown = (ThreadNetworkException) exception.getCause();
- assertThat(thrown.getErrorCode()).isEqualTo(ERROR_UNSUPPORTED_OPERATION);
- }
- }
-
- @Test
- public void setChannelMaxPowers_withoutPrivilegedPermission_throwsSecurityException()
- throws Exception {
- dropAllPermissions();
-
- assertThrows(
- SecurityException.class,
- () -> mController.setChannelMaxPowers(CHANNEL_MAX_POWERS, mExecutor, v -> {}));
- }
-
- @Test
- public void setChannelMaxPowers_emptyChannelMaxPower_throwsIllegalArgumentException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> mController.setChannelMaxPowers(new SparseIntArray(), mExecutor, v -> {}));
- }
-
- @Test
- public void setChannelMaxPowers_invalidChannel_throwsIllegalArgumentException() {
- final SparseIntArray INVALID_CHANNEL_ARRAY =
- new SparseIntArray() {
- {
- put(INVALID_CHANNEL, VALID_POWER);
- }
- };
-
- assertThrows(
- IllegalArgumentException.class,
- () -> mController.setChannelMaxPowers(INVALID_CHANNEL_ARRAY, mExecutor, v -> {}));
- }
-
- @Test
- public void setChannelMaxPowers_invalidPower_throwsIllegalArgumentException() {
- final SparseIntArray INVALID_POWER_ARRAY =
- new SparseIntArray() {
- {
- put(VALID_CHANNEL, INVALID_POWER);
- }
- };
-
- assertThrows(
- IllegalArgumentException.class,
- () -> mController.setChannelMaxPowers(INVALID_POWER_ARRAY, mExecutor, v -> {}));
- }
-
- private static void dropAllPermissions() {
- getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
- }
-
- private static <V> OutcomeReceiver<V, ThreadNetworkException> newOutcomeReceiver(
- CompletableFuture<V> future) {
- return new OutcomeReceiver<V, ThreadNetworkException>() {
- @Override
- public void onResult(V result) {
- future.complete(result);
- }
-
- @Override
- public void onError(ThreadNetworkException e) {
- future.completeExceptionally(e);
- }
- };
- }
-}
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
index ac74372..0423578 100644
--- a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
@@ -19,7 +19,7 @@
import static android.net.thread.ThreadNetworkController.DEVICE_ROLE_CHILD;
import static android.net.thread.ThreadNetworkException.ERROR_UNAVAILABLE;
import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_CHANNEL;
-import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_OPERATION;
+import static android.net.thread.ThreadNetworkException.ERROR_UNSUPPORTED_FEATURE;
import static android.os.Process.SYSTEM_UID;
import static com.google.common.io.BaseEncoding.base16;
@@ -394,7 +394,7 @@
doAnswer(
invoke -> {
getSetChannelMaxPowersReceiver(invoke)
- .onError(ERROR_UNSUPPORTED_OPERATION, "");
+ .onError(ERROR_UNSUPPORTED_FEATURE, "");
return null;
})
.when(mMockService)