Merge "[Thread] make Thread state customisable via resource overlay" into main
This commit is contained in:
commit
5ce2519a3e
5 changed files with 97 additions and 50 deletions
|
|
@ -20,10 +20,15 @@
|
|||
-->
|
||||
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<!-- Sets to {@code true} to enable Thread on the device by default. Note this is the default
|
||||
value, the actual Thread enabled state can be changed by the {@link
|
||||
ThreadNetworkController#setEnabled} API.
|
||||
-->
|
||||
<bool name="config_thread_default_enabled">true</bool>
|
||||
|
||||
<!-- Whether to use location APIs in the algorithm to determine country code or not.
|
||||
If disabled, will use other sources (telephony, wifi, etc) to determine device location for
|
||||
Thread Network regulatory purposes.
|
||||
-->
|
||||
<bool name="config_thread_location_use_for_country_code_enabled">true</bool>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
<item type="integer" name="config_netstats_validate_import" />
|
||||
|
||||
<!-- Configuration values for ThreadNetworkService -->
|
||||
<item type="bool" name="config_thread_default_enabled" />
|
||||
<item type="bool" name="config_thread_location_use_for_country_code_enabled" />
|
||||
</policy>
|
||||
</overlayable>
|
||||
|
|
|
|||
|
|
@ -18,21 +18,16 @@ package com.android.server.thread;
|
|||
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
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.IThreadNetworkController;
|
||||
import android.net.thread.IThreadNetworkManager;
|
||||
import android.os.Binder;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.AtomicFile;
|
||||
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
|
|
@ -51,12 +46,7 @@ public class ThreadNetworkService extends IThreadNetworkManager.Stub {
|
|||
/** Creates a new {@link ThreadNetworkService} object. */
|
||||
public ThreadNetworkService(Context context) {
|
||||
mContext = context;
|
||||
mPersistentSettings =
|
||||
new ThreadPersistentSettings(
|
||||
new AtomicFile(
|
||||
new File(
|
||||
getOrCreateThreadnetworkDir(),
|
||||
ThreadPersistentSettings.FILE_NAME)));
|
||||
mPersistentSettings = ThreadPersistentSettings.newInstance(context);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -123,19 +113,4 @@ public class ThreadNetworkService extends IThreadNetworkManager.Stub {
|
|||
|
||||
pw.println();
|
||||
}
|
||||
|
||||
/** Get device protected storage dir for the tethering apex. */
|
||||
private static File getOrCreateThreadnetworkDir() {
|
||||
final File threadnetworkDir;
|
||||
final File apexDataDir =
|
||||
ApexEnvironment.getApexEnvironment(TETHERING_MODULE_NAME)
|
||||
.getDeviceProtectedDataDir();
|
||||
threadnetworkDir = new File(apexDataDir, "thread");
|
||||
|
||||
if (threadnetworkDir.exists() || threadnetworkDir.mkdirs()) {
|
||||
return threadnetworkDir;
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Cannot write into thread network data directory: " + threadnetworkDir);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,23 @@
|
|||
|
||||
package com.android.server.thread;
|
||||
|
||||
import static com.android.net.module.util.DeviceConfigUtils.TETHERING_MODULE_NAME;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ApexEnvironment;
|
||||
import android.content.Context;
|
||||
import android.os.PersistableBundle;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.connectivity.resources.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.connectivity.ConnectivityResources;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
|
|
@ -39,7 +47,7 @@ import java.io.InputStream;
|
|||
public class ThreadPersistentSettings {
|
||||
private static final String TAG = "ThreadPersistentSettings";
|
||||
/** File name used for storing settings. */
|
||||
public static final String FILE_NAME = "ThreadPersistentSettings.xml";
|
||||
private static final String FILE_NAME = "ThreadPersistentSettings.xml";
|
||||
/** Current config store data version. This will be incremented for any additions. */
|
||||
private static final int CURRENT_SETTINGS_STORE_DATA_VERSION = 1;
|
||||
/**
|
||||
|
|
@ -62,16 +70,29 @@ public class ThreadPersistentSettings {
|
|||
@GuardedBy("mLock")
|
||||
private final PersistableBundle mSettings = new PersistableBundle();
|
||||
|
||||
public ThreadPersistentSettings(AtomicFile atomicFile) {
|
||||
private final ConnectivityResources mResources;
|
||||
|
||||
public static ThreadPersistentSettings newInstance(Context context) {
|
||||
return new ThreadPersistentSettings(
|
||||
new AtomicFile(new File(getOrCreateThreadNetworkDir(), FILE_NAME)),
|
||||
new ConnectivityResources(context));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ThreadPersistentSettings(AtomicFile atomicFile, ConnectivityResources resources) {
|
||||
mAtomicFile = atomicFile;
|
||||
mResources = resources;
|
||||
}
|
||||
|
||||
/** Initialize the settings by reading from the settings file. */
|
||||
public void initialize() {
|
||||
readFromStoreFile();
|
||||
synchronized (mLock) {
|
||||
if (mSettings.isEmpty()) {
|
||||
put(THREAD_ENABLED.key, THREAD_ENABLED.defaultValue);
|
||||
if (!mSettings.containsKey(THREAD_ENABLED.key)) {
|
||||
Log.i(TAG, "\"thread_enabled\" is missing in settings file, using default value");
|
||||
put(
|
||||
THREAD_ENABLED.key,
|
||||
mResources.get().getBoolean(R.bool.config_thread_default_enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -240,4 +261,19 @@ public class ThreadPersistentSettings {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/** Get device protected storage dir for the tethering apex. */
|
||||
private static File getOrCreateThreadNetworkDir() {
|
||||
final File threadnetworkDir;
|
||||
final File apexDataDir =
|
||||
ApexEnvironment.getApexEnvironment(TETHERING_MODULE_NAME)
|
||||
.getDeviceProtectedDataDir();
|
||||
threadnetworkDir = new File(apexDataDir, "thread");
|
||||
|
||||
if (threadnetworkDir.exists() || threadnetworkDir.mkdirs()) {
|
||||
return threadnetworkDir;
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Cannot write into thread network data directory: " + threadnetworkDir);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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 com.android.server.thread;
|
||||
|
||||
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.os.PersistableBundle;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.AtomicFile;
|
||||
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.connectivity.resources.R;
|
||||
import com.android.server.connectivity.ConnectivityResources;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ThreadPersistentSettingsTest {
|
||||
@Mock private AtomicFile mAtomicFile;
|
||||
@Mock Resources mResources;
|
||||
@Mock ConnectivityResources mConnectivityResources;
|
||||
|
||||
private ThreadPersistentSettings mThreadPersistentSettings;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
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);
|
||||
mThreadPersistentSettings =
|
||||
new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
|
||||
}
|
||||
|
||||
/** Called after each test */
|
||||
@After
|
||||
public void tearDown() {
|
||||
validateMockitoUsage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_readsFromFile() throws Exception {
|
||||
byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
|
||||
setupAtomicFileMockForRead(data);
|
||||
|
||||
mThreadPersistentSettings.initialize();
|
||||
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_ThreadDisabledInResources_returnsThreadDisabled() throws Exception {
|
||||
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
|
||||
setupAtomicFileMockForRead(new byte[0]);
|
||||
|
||||
mThreadPersistentSettings.initialize();
|
||||
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initialize_ThreadDisabledInResourcesButEnabledInXml_returnsThreadEnabled()
|
||||
throws Exception {
|
||||
when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
|
||||
byte[] data = createXmlForParsing(THREAD_ENABLED.key, true);
|
||||
setupAtomicFileMockForRead(data);
|
||||
|
||||
mThreadPersistentSettings.initialize();
|
||||
|
||||
assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void put_ThreadFeatureEnabledTrue_returnsTrue() throws Exception {
|
||||
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
|
||||
public void put_ThreadFeatureEnabledFalse_returnsFalse() throws Exception {
|
||||
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());
|
||||
}
|
||||
|
||||
private byte[] createXmlForParsing(String key, Boolean value) throws Exception {
|
||||
PersistableBundle bundle = new PersistableBundle();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
bundle.putBoolean(key, value);
|
||||
bundle.writeToStream(outputStream);
|
||||
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());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue