Merge changes from topic "thread-configuration-api" into main
* changes: [Thread] support ThreadConfiguration API at ThreadNetworkControllerService [Thread] add support for configuration at ThreadNetworkController
This commit is contained in:
commit
bc9c0e2b03
10 changed files with 561 additions and 40 deletions
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 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 android.net.thread.ThreadConfiguration;
|
||||
|
||||
/** Receives the result of a Thread Configuration change. @hide */
|
||||
oneway interface IConfigurationReceiver {
|
||||
void onConfigurationChanged(in ThreadConfiguration configuration);
|
||||
}
|
||||
|
|
@ -19,11 +19,13 @@ 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.IConfigurationReceiver;
|
||||
import android.net.thread.IOperationReceiver;
|
||||
import android.net.thread.IOperationalDatasetCallback;
|
||||
import android.net.thread.IScheduleMigrationReceiver;
|
||||
import android.net.thread.IStateCallback;
|
||||
import android.net.thread.PendingOperationalDataset;
|
||||
import android.net.thread.ThreadConfiguration;
|
||||
|
||||
/**
|
||||
* Interface for communicating with ThreadNetworkControllerService.
|
||||
|
|
@ -46,4 +48,7 @@ interface IThreadNetworkController {
|
|||
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
|
||||
|
||||
void setEnabled(boolean enabled, in IOperationReceiver receiver);
|
||||
void setConfiguration(in ThreadConfiguration config, in IOperationReceiver receiver);
|
||||
void registerConfigurationCallback(in IConfigurationReceiver receiver);
|
||||
void unregisterConfigurationCallback(in IConfigurationReceiver receiver);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 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;
|
||||
|
||||
parcelable ThreadConfiguration;
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* Copyright 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 android.annotation.NonNull;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Data interface for Thread device configuration.
|
||||
*
|
||||
* <p>An example usage of creating a {@link ThreadConfiguration} that turns on NAT64 feature based
|
||||
* on an existing {@link ThreadConfiguration}:
|
||||
*
|
||||
* <pre>{@code
|
||||
* ThreadConfiguration config =
|
||||
* new ThreadConfiguration.Builder(existingConfig).setNat64Enabled(true).build();
|
||||
* }</pre>
|
||||
*
|
||||
* @see ThreadNetworkController#setConfiguration
|
||||
* @see ThreadNetworkController#registerConfigurationCallback
|
||||
* @see ThreadNetworkController#unregisterConfigurationCallback
|
||||
* @hide
|
||||
*/
|
||||
// @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
|
||||
// @SystemApi
|
||||
public final class ThreadConfiguration implements Parcelable {
|
||||
private final boolean mNat64Enabled;
|
||||
private final boolean mDhcp6PdEnabled;
|
||||
|
||||
private ThreadConfiguration(Builder builder) {
|
||||
this(builder.mNat64Enabled, builder.mDhcp6PdEnabled);
|
||||
}
|
||||
|
||||
private ThreadConfiguration(boolean nat64Enabled, boolean dhcp6PdEnabled) {
|
||||
this.mNat64Enabled = nat64Enabled;
|
||||
this.mDhcp6PdEnabled = dhcp6PdEnabled;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if NAT64 is enabled. */
|
||||
public boolean isNat64Enabled() {
|
||||
return mNat64Enabled;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if DHCPv6 Prefix Delegation is enabled. */
|
||||
public boolean isDhcp6PdEnabled() {
|
||||
return mDhcp6PdEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
} else if (!(other instanceof ThreadConfiguration)) {
|
||||
return false;
|
||||
} else {
|
||||
ThreadConfiguration otherConfig = (ThreadConfiguration) other;
|
||||
return mNat64Enabled == otherConfig.mNat64Enabled
|
||||
&& mDhcp6PdEnabled == otherConfig.mDhcp6PdEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mNat64Enabled, mDhcp6PdEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('{');
|
||||
sb.append("Nat64Enabled=").append(mNat64Enabled);
|
||||
sb.append(", Dhcp6PdEnabled=").append(mDhcp6PdEnabled);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeBoolean(mNat64Enabled);
|
||||
dest.writeBoolean(mDhcp6PdEnabled);
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<ThreadConfiguration> CREATOR =
|
||||
new Creator<>() {
|
||||
@Override
|
||||
public ThreadConfiguration createFromParcel(Parcel in) {
|
||||
ThreadConfiguration.Builder builder = new ThreadConfiguration.Builder();
|
||||
builder.setNat64Enabled(in.readBoolean());
|
||||
builder.setDhcp6PdEnabled(in.readBoolean());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadConfiguration[] newArray(int size) {
|
||||
return new ThreadConfiguration[size];
|
||||
}
|
||||
};
|
||||
|
||||
/** The builder for creating {@link ThreadConfiguration} objects. */
|
||||
public static final class Builder {
|
||||
private boolean mNat64Enabled = false;
|
||||
private boolean mDhcp6PdEnabled = false;
|
||||
|
||||
/** Creates a new {@link Builder} object with all features disabled. */
|
||||
public Builder() {}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Builder} object from a {@link ThreadConfiguration} object.
|
||||
*
|
||||
* @param config the Border Router configurations to be copied
|
||||
*/
|
||||
public Builder(@NonNull ThreadConfiguration config) {
|
||||
Objects.requireNonNull(config);
|
||||
|
||||
mNat64Enabled = config.mNat64Enabled;
|
||||
mDhcp6PdEnabled = config.mDhcp6PdEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables NAT64 for the device.
|
||||
*
|
||||
* <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
|
||||
* IPv4.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setNat64Enabled(boolean enabled) {
|
||||
this.mNat64Enabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables Prefix Delegation for the device.
|
||||
*
|
||||
* <p>Enabling this feature will allow Thread devices to connect to the internet/cloud over
|
||||
* IPv6.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setDhcp6PdEnabled(boolean enabled) {
|
||||
this.mDhcp6PdEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Creates a new {@link ThreadConfiguration} object. */
|
||||
@NonNull
|
||||
public ThreadConfiguration build() {
|
||||
return new ThreadConfiguration(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ import java.time.Duration;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Provides the primary APIs for controlling all aspects of a Thread network.
|
||||
|
|
@ -124,6 +125,12 @@ public final class ThreadNetworkController {
|
|||
private final Map<OperationalDatasetCallback, OperationalDatasetCallbackProxy>
|
||||
mOpDatasetCallbackMap = new HashMap<>();
|
||||
|
||||
private final Object mConfigurationCallbackMapLock = new Object();
|
||||
|
||||
@GuardedBy("mConfigurationCallbackMapLock")
|
||||
private final Map<Consumer<ThreadConfiguration>, ConfigurationCallbackProxy>
|
||||
mConfigurationCallbackMap = new HashMap<>();
|
||||
|
||||
/** @hide */
|
||||
public ThreadNetworkController(@NonNull IThreadNetworkController controllerService) {
|
||||
requireNonNull(controllerService, "controllerService cannot be null");
|
||||
|
|
@ -578,6 +585,97 @@ public final class ThreadNetworkController {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the Thread features for this device.
|
||||
*
|
||||
* <p>This method sets the {@link ThreadConfiguration} for this device. On success, the {@link
|
||||
* OutcomeReceiver#onResult} will be called, and the {@code configuration} will be applied and
|
||||
* persisted to the device; the configuration changes can be observed by {@link
|
||||
* #registerConfigurationCallback}. On failure, {@link OutcomeReceiver#onError} of {@code
|
||||
* receiver} will be invoked with a specific error.
|
||||
*
|
||||
* @param configuration the configuration to set
|
||||
* @param executor the executor to execute {@code receiver}
|
||||
* @param receiver the receiver to receive result of this operation
|
||||
* @hide
|
||||
*/
|
||||
// @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
|
||||
// @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
|
||||
public void setConfiguration(
|
||||
@NonNull ThreadConfiguration configuration,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
|
||||
requireNonNull(configuration, "Configuration cannot be null");
|
||||
requireNonNull(executor, "executor cannot be null");
|
||||
requireNonNull(receiver, "receiver cannot be null");
|
||||
try {
|
||||
mControllerService.setConfiguration(
|
||||
configuration, new OperationReceiverProxy(executor, receiver));
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to be called when the configuration is changed.
|
||||
*
|
||||
* <p>Upon return of this method, {@code callback} will be invoked immediately with the new
|
||||
* {@link ThreadConfiguration}.
|
||||
*
|
||||
* @param executor the executor to execute the {@code callback}
|
||||
* @param callback the callback to receive Thread configuration changes
|
||||
* @throws IllegalArgumentException if {@code callback} has already been registered
|
||||
* @hide
|
||||
*/
|
||||
// @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
|
||||
// @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
|
||||
public void registerConfigurationCallback(
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull Consumer<ThreadConfiguration> callback) {
|
||||
requireNonNull(executor, "executor cannot be null");
|
||||
requireNonNull(callback, "callback cannot be null");
|
||||
synchronized (mConfigurationCallbackMapLock) {
|
||||
if (mConfigurationCallbackMap.containsKey(callback)) {
|
||||
throw new IllegalArgumentException("callback has already been registered");
|
||||
}
|
||||
ConfigurationCallbackProxy callbackProxy =
|
||||
new ConfigurationCallbackProxy(executor, callback);
|
||||
mConfigurationCallbackMap.put(callback, callbackProxy);
|
||||
try {
|
||||
mControllerService.registerConfigurationCallback(callbackProxy);
|
||||
} catch (RemoteException e) {
|
||||
mConfigurationCallbackMap.remove(callback);
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the configuration callback.
|
||||
*
|
||||
* @param callback the callback which has been registered with {@link
|
||||
* #registerConfigurationCallback}
|
||||
* @throws IllegalArgumentException if {@code callback} hasn't been registered
|
||||
* @hide
|
||||
*/
|
||||
// @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
|
||||
// @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
|
||||
public void unregisterConfigurationCallback(@NonNull Consumer<ThreadConfiguration> callback) {
|
||||
requireNonNull(callback, "callback cannot be null");
|
||||
synchronized (mConfigurationCallbackMapLock) {
|
||||
ConfigurationCallbackProxy callbackProxy = mConfigurationCallbackMap.get(callback);
|
||||
if (callbackProxy == null) {
|
||||
throw new IllegalArgumentException("callback hasn't been registered");
|
||||
}
|
||||
try {
|
||||
mControllerService.unregisterConfigurationCallback(callbackProxy);
|
||||
mConfigurationCallbackMap.remove(callbackProxy.mConfigurationConsumer);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to use a specified test network as the upstream.
|
||||
*
|
||||
|
|
@ -764,4 +862,26 @@ public final class ThreadNetworkController {
|
|||
propagateError(mExecutor, mResultReceiver, errorCode, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ConfigurationCallbackProxy extends IConfigurationReceiver.Stub {
|
||||
final Executor mExecutor;
|
||||
final Consumer<ThreadConfiguration> mConfigurationConsumer;
|
||||
|
||||
ConfigurationCallbackProxy(
|
||||
@CallbackExecutor Executor executor,
|
||||
Consumer<ThreadConfiguration> ConfigurationConsumer) {
|
||||
this.mExecutor = executor;
|
||||
this.mConfigurationConsumer = ConfigurationConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(ThreadConfiguration configuration) {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mConfigurationConsumer.accept(configuration));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,5 +27,9 @@ public final class ThreadNetworkFlags {
|
|||
/** @hide */
|
||||
public static final String FLAG_THREAD_ENABLED = "com.android.net.thread.flags.thread_enabled";
|
||||
|
||||
/** @hide */
|
||||
public static final String FLAG_CONFIGURATION_ENABLED =
|
||||
"com.android.net.thread.flags.configuration_enabled";
|
||||
|
||||
private ThreadNetworkFlags() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,12 +91,14 @@ import android.net.thread.ActiveOperationalDataset;
|
|||
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
|
||||
import android.net.thread.ChannelMaxPower;
|
||||
import android.net.thread.IActiveOperationalDatasetReceiver;
|
||||
import android.net.thread.IConfigurationReceiver;
|
||||
import android.net.thread.IOperationReceiver;
|
||||
import android.net.thread.IOperationalDatasetCallback;
|
||||
import android.net.thread.IStateCallback;
|
||||
import android.net.thread.IThreadNetworkController;
|
||||
import android.net.thread.OperationalDatasetTimestamp;
|
||||
import android.net.thread.PendingOperationalDataset;
|
||||
import android.net.thread.ThreadConfiguration;
|
||||
import android.net.thread.ThreadNetworkController;
|
||||
import android.net.thread.ThreadNetworkController.DeviceRole;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
|
|
@ -189,6 +191,8 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
private final OtDaemonCallbackProxy mOtDaemonCallbackProxy = new OtDaemonCallbackProxy();
|
||||
private final ConnectivityResources mResources;
|
||||
private final Supplier<String> mCountryCodeSupplier;
|
||||
private final Map<IConfigurationReceiver, IBinder.DeathRecipient> mConfigurationReceivers =
|
||||
new HashMap<>();
|
||||
|
||||
// This should not be directly used for calling IOtDaemon APIs because ot-daemon may die and
|
||||
// {@code mOtDaemon} will be set to {@code null}. Instead, use {@code getOtDaemon()}
|
||||
|
|
@ -518,6 +522,77 @@ final class ThreadNetworkControllerService extends IThreadNetworkController.Stub
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(
|
||||
@NonNull ThreadConfiguration configuration, @NonNull IOperationReceiver receiver) {
|
||||
enforceAllPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
|
||||
mHandler.post(() -> setConfigurationInternal(configuration, receiver));
|
||||
}
|
||||
|
||||
private void setConfigurationInternal(
|
||||
@NonNull ThreadConfiguration configuration,
|
||||
@NonNull IOperationReceiver operationReceiver) {
|
||||
checkOnHandlerThread();
|
||||
|
||||
Log.i(TAG, "Set Thread configuration: " + configuration);
|
||||
|
||||
final boolean changed = mPersistentSettings.putConfiguration(configuration);
|
||||
try {
|
||||
operationReceiver.onSuccess();
|
||||
} catch (RemoteException e) {
|
||||
// do nothing if the client is dead
|
||||
}
|
||||
if (changed) {
|
||||
for (IConfigurationReceiver configReceiver : mConfigurationReceivers.keySet()) {
|
||||
try {
|
||||
configReceiver.onConfigurationChanged(configuration);
|
||||
} catch (RemoteException e) {
|
||||
// do nothing if the client is dead
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerConfigurationCallback(@NonNull IConfigurationReceiver callback) {
|
||||
enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
|
||||
mHandler.post(() -> registerConfigurationCallbackInternal(callback));
|
||||
}
|
||||
|
||||
private void registerConfigurationCallbackInternal(@NonNull IConfigurationReceiver callback) {
|
||||
checkOnHandlerThread();
|
||||
if (mConfigurationReceivers.containsKey(callback)) {
|
||||
throw new IllegalStateException("Registering the same IConfigurationReceiver twice");
|
||||
}
|
||||
IBinder.DeathRecipient deathRecipient =
|
||||
() -> mHandler.post(() -> unregisterConfigurationCallbackInternal(callback));
|
||||
try {
|
||||
callback.asBinder().linkToDeath(deathRecipient, 0);
|
||||
} catch (RemoteException e) {
|
||||
return;
|
||||
}
|
||||
mConfigurationReceivers.put(callback, deathRecipient);
|
||||
try {
|
||||
callback.onConfigurationChanged(mPersistentSettings.getConfiguration());
|
||||
} catch (RemoteException e) {
|
||||
// do nothing if the client is dead
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterConfigurationCallback(@NonNull IConfigurationReceiver callback) {
|
||||
enforceAllPermissionsGranted(permission.THREAD_NETWORK_PRIVILEGED);
|
||||
mHandler.post(() -> unregisterConfigurationCallbackInternal(callback));
|
||||
}
|
||||
|
||||
private void unregisterConfigurationCallbackInternal(@NonNull IConfigurationReceiver callback) {
|
||||
checkOnHandlerThread();
|
||||
if (!mConfigurationReceivers.containsKey(callback)) {
|
||||
return;
|
||||
}
|
||||
callback.asBinder().unlinkToDeath(mConfigurationReceivers.remove(callback), 0);
|
||||
}
|
||||
|
||||
private void registerUserRestrictionsReceiver() {
|
||||
mContext.registerReceiver(
|
||||
new BroadcastReceiver() {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ package com.android.server.thread;
|
|||
|
||||
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ApexEnvironment;
|
||||
import android.content.Context;
|
||||
import android.net.thread.ThreadConfiguration;
|
||||
import android.os.PersistableBundle;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Log;
|
||||
|
|
@ -74,6 +76,16 @@ public class ThreadPersistentSettings {
|
|||
/** Stores the Thread country code, null if no country code is stored. */
|
||||
public static final Key<String> THREAD_COUNTRY_CODE = new Key<>("thread_country_code", null);
|
||||
|
||||
/** Stores the Thread NAT64 feature toggle state, true for enabled and false for disabled. */
|
||||
private static final Key<Boolean> CONFIG_NAT64_ENABLED =
|
||||
new Key<>("config_nat64_enabled", false);
|
||||
|
||||
/**
|
||||
* Stores the Thread DHCPv6-PD feature toggle state, true for enabled and false for disabled.
|
||||
*/
|
||||
private static final Key<Boolean> CONFIG_DHCP6_PD_ENABLED =
|
||||
new Key<>("config_dhcp6_pd_enabled", false);
|
||||
|
||||
/******** Thread persistent setting keys ***************/
|
||||
|
||||
@GuardedBy("mLock")
|
||||
|
|
@ -174,6 +186,30 @@ public class ThreadPersistentSettings {
|
|||
return getObject(key.key, key.defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a {@link ThreadConfiguration} to the persistent settings.
|
||||
*
|
||||
* @param configuration {@link ThreadConfiguration} to be stored.
|
||||
* @return {@code true} if the configuration was changed, {@code false} otherwise.
|
||||
*/
|
||||
public boolean putConfiguration(@NonNull ThreadConfiguration configuration) {
|
||||
if (getConfiguration().equals(configuration)) {
|
||||
return false;
|
||||
}
|
||||
putObject(CONFIG_NAT64_ENABLED.key, configuration.isNat64Enabled());
|
||||
putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcp6PdEnabled());
|
||||
writeToStoreFile();
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Retrieve the {@link ThreadConfiguration} from the persistent settings. */
|
||||
public ThreadConfiguration getConfiguration() {
|
||||
return new ThreadConfiguration.Builder()
|
||||
.setNat64Enabled(get(CONFIG_NAT64_ENABLED))
|
||||
.setDhcp6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class to store string key and its default value.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import android.net.NetworkProvider;
|
|||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.IActiveOperationalDatasetReceiver;
|
||||
import android.net.thread.IOperationReceiver;
|
||||
import android.net.thread.ThreadConfiguration;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
|
|
@ -706,4 +707,35 @@ public final class ThreadNetworkControllerServiceTest {
|
|||
inOrder.verify(mMockTunIfController, times(1)).setInterfaceUp(false);
|
||||
inOrder.verify(mMockTunIfController, times(1)).setInterfaceUp(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setConfiguration_configurationUpdated() throws Exception {
|
||||
mService.initialize();
|
||||
final IOperationReceiver mockReceiver1 = mock(IOperationReceiver.class);
|
||||
final IOperationReceiver mockReceiver2 = mock(IOperationReceiver.class);
|
||||
final IOperationReceiver mockReceiver3 = mock(IOperationReceiver.class);
|
||||
ThreadConfiguration config1 =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setNat64Enabled(false)
|
||||
.setDhcp6PdEnabled(false)
|
||||
.build();
|
||||
ThreadConfiguration config2 =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setNat64Enabled(true)
|
||||
.setDhcp6PdEnabled(true)
|
||||
.build();
|
||||
ThreadConfiguration config3 =
|
||||
new ThreadConfiguration.Builder(config2).build(); // Same as config2
|
||||
|
||||
mService.setConfiguration(config1, mockReceiver1);
|
||||
mService.setConfiguration(config2, mockReceiver2);
|
||||
mService.setConfiguration(config3, mockReceiver3);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(mPersistentSettings.getConfiguration()).isEqualTo(config3);
|
||||
InOrder inOrder = Mockito.inOrder(mockReceiver1, mockReceiver2, mockReceiver3);
|
||||
inOrder.verify(mockReceiver1).onSuccess();
|
||||
inOrder.verify(mockReceiver2).onSuccess();
|
||||
inOrder.verify(mockReceiver3).onSuccess();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,16 +21,12 @@ import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED;
|
|||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.validateMockitoUsage;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.net.thread.ThreadConfiguration;
|
||||
import android.os.PersistableBundle;
|
||||
import android.util.AtomicFile;
|
||||
|
||||
|
|
@ -42,13 +38,14 @@ import com.android.server.connectivity.ConnectivityResources;
|
|||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
/** Unit tests for {@link ThreadPersistentSettings}. */
|
||||
|
|
@ -57,12 +54,15 @@ import java.io.FileOutputStream;
|
|||
public class ThreadPersistentSettingsTest {
|
||||
private static final String TEST_COUNTRY_CODE = "CN";
|
||||
|
||||
@Mock private AtomicFile mAtomicFile;
|
||||
@Mock Resources mResources;
|
||||
@Mock ConnectivityResources mConnectivityResources;
|
||||
|
||||
private AtomicFile mAtomicFile;
|
||||
private ThreadPersistentSettings mThreadPersistentSettings;
|
||||
|
||||
@Rule(order = 0)
|
||||
public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
|
@ -70,8 +70,7 @@ public class ThreadPersistentSettingsTest {
|
|||
when(mConnectivityResources.get()).thenReturn(mResources);
|
||||
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
|
||||
|
||||
FileOutputStream fos = mock(FileOutputStream.class);
|
||||
when(mAtomicFile.startWrite()).thenReturn(fos);
|
||||
mAtomicFile = createAtomicFile();
|
||||
mThreadPersistentSettings =
|
||||
new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
|
||||
}
|
||||
|
|
@ -85,7 +84,7 @@ public class ThreadPersistentSettingsTest {
|
|||
@Test
|
||||
public void initialize_readsFromFile() throws Exception {
|
||||
byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
|
||||
setupAtomicFileMockForRead(data);
|
||||
setupAtomicFileForRead(data);
|
||||
|
||||
mThreadPersistentSettings.initialize();
|
||||
|
||||
|
|
@ -95,7 +94,7 @@ public class ThreadPersistentSettingsTest {
|
|||
@Test
|
||||
public void initialize_ThreadDisabledInResources_returnsThreadDisabled() throws Exception {
|
||||
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
|
||||
setupAtomicFileMockForRead(new byte[0]);
|
||||
setupAtomicFileForRead(new byte[0]);
|
||||
|
||||
mThreadPersistentSettings.initialize();
|
||||
|
||||
|
|
@ -107,7 +106,7 @@ public class ThreadPersistentSettingsTest {
|
|||
throws Exception {
|
||||
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
|
||||
byte[] data = createXmlForParsing(THREAD_ENABLED.key, true);
|
||||
setupAtomicFileMockForRead(data);
|
||||
setupAtomicFileForRead(data);
|
||||
|
||||
mThreadPersistentSettings.initialize();
|
||||
|
||||
|
|
@ -119,9 +118,6 @@ public class ThreadPersistentSettingsTest {
|
|||
mThreadPersistentSettings.put(THREAD_ENABLED.key, true);
|
||||
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
|
||||
// Confirm that file writes have been triggered.
|
||||
verify(mAtomicFile).startWrite();
|
||||
verify(mAtomicFile).finishWrite(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -129,9 +125,8 @@ public class ThreadPersistentSettingsTest {
|
|||
mThreadPersistentSettings.put(THREAD_ENABLED.key, false);
|
||||
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
|
||||
// Confirm that file writes have been triggered.
|
||||
verify(mAtomicFile).startWrite();
|
||||
verify(mAtomicFile).finishWrite(any());
|
||||
mThreadPersistentSettings.initialize();
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -139,10 +134,8 @@ public class ThreadPersistentSettingsTest {
|
|||
mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, TEST_COUNTRY_CODE);
|
||||
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
|
||||
|
||||
// Confirm that file writes have been triggered.
|
||||
verify(mAtomicFile).startWrite();
|
||||
verify(mAtomicFile).finishWrite(any());
|
||||
mThreadPersistentSettings.initialize();
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -150,10 +143,63 @@ public class ThreadPersistentSettingsTest {
|
|||
mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, null);
|
||||
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
|
||||
mThreadPersistentSettings.initialize();
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
|
||||
}
|
||||
|
||||
// Confirm that file writes have been triggered.
|
||||
verify(mAtomicFile).startWrite();
|
||||
verify(mAtomicFile).finishWrite(any());
|
||||
@Test
|
||||
public void putConfiguration_sameValues_returnsFalse() {
|
||||
ThreadConfiguration configuration =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setNat64Enabled(true)
|
||||
.setDhcp6PdEnabled(true)
|
||||
.build();
|
||||
mThreadPersistentSettings.putConfiguration(configuration);
|
||||
|
||||
assertThat(mThreadPersistentSettings.putConfiguration(configuration)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putConfiguration_differentValues_returnsTrue() {
|
||||
ThreadConfiguration configuration1 =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setNat64Enabled(false)
|
||||
.setDhcp6PdEnabled(false)
|
||||
.build();
|
||||
mThreadPersistentSettings.putConfiguration(configuration1);
|
||||
ThreadConfiguration configuration2 =
|
||||
new ThreadConfiguration.Builder()
|
||||
.setNat64Enabled(true)
|
||||
.setDhcp6PdEnabled(true)
|
||||
.build();
|
||||
|
||||
assertThat(mThreadPersistentSettings.putConfiguration(configuration2)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putConfiguration_nat64Enabled_valuesUpdatedAndPersisted() throws Exception {
|
||||
ThreadConfiguration configuration =
|
||||
new ThreadConfiguration.Builder().setNat64Enabled(true).build();
|
||||
mThreadPersistentSettings.putConfiguration(configuration);
|
||||
|
||||
assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
|
||||
mThreadPersistentSettings.initialize();
|
||||
assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putConfiguration_dhcp6PdEnabled_valuesUpdatedAndPersisted() throws Exception {
|
||||
ThreadConfiguration configuration =
|
||||
new ThreadConfiguration.Builder().setDhcp6PdEnabled(true).build();
|
||||
mThreadPersistentSettings.putConfiguration(configuration);
|
||||
|
||||
assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
|
||||
mThreadPersistentSettings.initialize();
|
||||
assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);
|
||||
}
|
||||
|
||||
private AtomicFile createAtomicFile() throws Exception {
|
||||
return new AtomicFile(mTemporaryFolder.newFile());
|
||||
}
|
||||
|
||||
private byte[] createXmlForParsing(String key, Boolean value) throws Exception {
|
||||
|
|
@ -164,19 +210,9 @@ public class ThreadPersistentSettingsTest {
|
|||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
private void setupAtomicFileMockForRead(byte[] dataToRead) throws Exception {
|
||||
FileInputStream is = mock(FileInputStream.class);
|
||||
when(mAtomicFile.openRead()).thenReturn(is);
|
||||
when(is.available()).thenReturn(dataToRead.length).thenReturn(0);
|
||||
doAnswer(
|
||||
invocation -> {
|
||||
byte[] data = invocation.getArgument(0);
|
||||
int pos = invocation.getArgument(1);
|
||||
if (pos == dataToRead.length) return 0; // read complete.
|
||||
System.arraycopy(dataToRead, 0, data, 0, dataToRead.length);
|
||||
return dataToRead.length;
|
||||
})
|
||||
.when(is)
|
||||
.read(any(), anyInt(), anyInt());
|
||||
private void setupAtomicFileForRead(byte[] dataToRead) throws Exception {
|
||||
try (FileOutputStream outputStream = new FileOutputStream(mAtomicFile.getBaseFile())) {
|
||||
outputStream.write(dataToRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue