From 5bbfd91b047b74ecd9487496162da7663df5c731 Mon Sep 17 00:00:00 2001 From: Zhanglong Xia Date: Thu, 20 Jun 2024 13:04:29 +0800 Subject: [PATCH] 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 --- common/thread_flags.aconfig | 10 +- framework-t/api/system-current.txt | 3 + .../net/thread/ThreadNetworkController.java | 46 +++-- .../net/thread/ThreadNetworkException.java | 12 +- .../ThreadNetworkControllerService.java | 4 +- thread/tests/cts/Android.bp | 1 + .../cts/ThreadNetworkControllerTest.java | 61 +++++++ .../thread/ThreadNetworkControllerTest.java | 171 ------------------ .../thread/ThreadNetworkControllerTest.java | 4 +- 9 files changed, 105 insertions(+), 207 deletions(-) delete mode 100644 thread/tests/integration/src/android/net/thread/ThreadNetworkControllerTest.java diff --git a/common/thread_flags.aconfig b/common/thread_flags.aconfig index 0edb7a836f..a3f77a7feb 100644 --- a/common/thread_flags.aconfig +++ b/common/thread_flags.aconfig @@ -15,4 +15,12 @@ flag { 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 2354882b62..9f26bcff76 100644 --- a/framework-t/api/system-current.txt +++ b/framework-t/api/system-current.txt @@ -516,6 +516,7 @@ package android.net.thread { 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); + 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); method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver); method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void unregisterConfigurationCallback(@NonNull java.util.function.Consumer); 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 @@ package android.net.thread { 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 @@ package android.net.thread { 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 551b98f46d..ecaefd00fb 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.NonNull; 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 @@ public final class ThreadNetworkController { /** 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 @@ public final class ThreadNetworkController { /** * Sets max power of each channel. * + *

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}. + * *

If not set, the default max power is set by the Thread HAL service or the Thread radio * chip firmware. * @@ -712,22 +718,27 @@ public final class ThreadNetworkController { * OutcomeReceiver#onError} will be called with a specific error: * *

    - *
  • {@link ThreadNetworkException#ERROR_UNSUPPORTED_OPERATION} the operation is no - * supported by the platform. + *
  • {@link ThreadNetworkException#ERROR_UNSUPPORTED_FEATURE} the feature is not supported + * by the platform. *
* * @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 @@ public final class ThreadNetworkController { + 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 b6973f8e13..1ea2459bed 100644 --- a/thread/framework/java/android/net/thread/ThreadNetworkException.java +++ b/thread/framework/java/android/net/thread/ThreadNetworkException.java @@ -141,16 +141,14 @@ public class ThreadNetworkException extends Exception { 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 b621a6a51d..452fab9f77 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_RESPONSE_BAD_FORMA 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 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub 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 6db7c9c452..65727556a6 100644 --- a/thread/tests/cts/Android.bp +++ b/thread/tests/cts/Android.bp @@ -40,6 +40,7 @@ android_test { 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 1a101b6daa..c04839463f 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_ABORTED; 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.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature; 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 @@ public class ThreadNetworkControllerTest { 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 @@ public class ThreadNetworkControllerTest { @Override public void onServiceInfoCallbackUnregistered() {} } + + @Test + @RequiresFlagsEnabled({Flags.FLAG_CHANNEL_MAX_POWERS_ENABLED}) + public void setChannelMaxPowers_withPrivilegedPermission_success() throws Exception { + CompletableFuture 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 ba04348de3..0000000000 --- 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 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 OutcomeReceiver newOutcomeReceiver( - CompletableFuture future) { - return new OutcomeReceiver() { - @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 ac74372c01..04235782cf 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 @@ package android.net.thread; 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 @@ public final class ThreadNetworkControllerTest { doAnswer( invoke -> { getSetChannelMaxPowersReceiver(invoke) - .onError(ERROR_UNSUPPORTED_OPERATION, ""); + .onError(ERROR_UNSUPPORTED_FEATURE, ""); return null; }) .when(mMockService)