Merge "changes the API setChannelMaxPowers from hide API to system API" into main

This commit is contained in:
Zhanglong Xia 2024-08-26 14:03:50 +00:00 • committed by Gerrit Code Review
commit b5ddf3a056
9 changed files with 105 additions and 207 deletions

View file

@ -15,4 +15,12 @@ flag {
namespace: "thread_network"
description: "Controls whether the Android Thread configuration is enabled"
bug: "342519412"
}
}
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"
}

View file

@ -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<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 @@ 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 {

View file

@ -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.
*
* <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 @@ public final class ThreadNetworkController {
* 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 @@ 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 {

View file

@ -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;

View file

@ -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:

View file

@ -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",

View file

@ -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<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 -> {}));
}
}

View file

@ -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);
}
};
}
}

View file

@ -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)