From c8359f4eb11d5583413aeedd813509e46e856739 Mon Sep 17 00:00:00 2001 From: Kangping Dong Date: Mon, 29 Jan 2024 12:10:54 +0800 Subject: [PATCH] [Thread] make Thread state customisable via resource overlay Make the default Thread and Border Router enabled state customizable for vendor via resource overlay. Bug: 322734489 Change-Id: I278d6afa82aac8d1bb06ee79f22bae6fb0199386 --- .../res/values/config_thread.xml | 7 +- .../res/values/overlayable.xml | 1 + .../server/thread/ThreadNetworkService.java | 27 +------ .../thread/ThreadPersistentSettings.java | 44 ++++++++++-- .../thread/ThreadPersistentSettingsTest.java | 70 +++++++++++++------ 5 files changed, 98 insertions(+), 51 deletions(-) rename thread/tests/unit/src/{android/net => com/android/server}/thread/ThreadPersistentSettingsTest.java (67%) diff --git a/service/ServiceConnectivityResources/res/values/config_thread.xml b/service/ServiceConnectivityResources/res/values/config_thread.xml index 14b54275c1..f7e47f552d 100644 --- a/service/ServiceConnectivityResources/res/values/config_thread.xml +++ b/service/ServiceConnectivityResources/res/values/config_thread.xml @@ -20,10 +20,15 @@ --> + + true + true - diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml index f2c4d91534..d9af5a3983 100644 --- a/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -46,6 +46,7 @@ + diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkService.java b/thread/service/java/com/android/server/thread/ThreadNetworkService.java index 5cf27f7f13..5664922f85 100644 --- a/thread/service/java/com/android/server/thread/ThreadNetworkService.java +++ b/thread/service/java/com/android/server/thread/ThreadNetworkService.java @@ -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); - } } diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java index d32f0bf028..aba4193da0 100644 --- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java +++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java @@ -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); + } } diff --git a/thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java similarity index 67% rename from thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java rename to thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java index 11aabb803e..927b5aeda9 100644 --- a/thread/tests/unit/src/android/net/thread/ThreadPersistentSettingsTest.java +++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java @@ -23,18 +23,22 @@ 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.never; 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; @@ -51,16 +55,22 @@ import java.io.FileOutputStream; @SmallTest public class ThreadPersistentSettingsTest { @Mock private AtomicFile mAtomicFile; + @Mock Resources mResources; + @Mock ConnectivityResources mConnectivityResources; - private ThreadPersistentSettings mThreadPersistentSetting; + 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); - mThreadPersistentSetting = new ThreadPersistentSettings(mAtomicFile); + mThreadPersistentSettings = + new ThreadPersistentSettings(mAtomicFile, mConnectivityResources); } /** Called after each test */ @@ -70,10 +80,42 @@ public class ThreadPersistentSettingsTest { } @Test - public void put_ThreadFeatureEnabledTrue_returnsTrue() throws Exception { - mThreadPersistentSetting.put(THREAD_ENABLED.key, true); + public void initialize_readsFromFile() throws Exception { + byte[] data = createXmlForParsing(THREAD_ENABLED.key, false); + setupAtomicFileMockForRead(data); - assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isTrue(); + 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()); @@ -81,26 +123,14 @@ public class ThreadPersistentSettingsTest { @Test public void put_ThreadFeatureEnabledFalse_returnsFalse() throws Exception { - mThreadPersistentSetting.put(THREAD_ENABLED.key, false); + mThreadPersistentSettings.put(THREAD_ENABLED.key, false); - assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isFalse(); + assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse(); // Confirm that file writes have been triggered. verify(mAtomicFile).startWrite(); verify(mAtomicFile).finishWrite(any()); } - @Test - public void initialize_readsFromFile() throws Exception { - byte[] data = createXmlForParsing(THREAD_ENABLED.key, false); - setupAtomicFileMockForRead(data); - - // Trigger file read. - mThreadPersistentSetting.initialize(); - - assertThat(mThreadPersistentSetting.get(THREAD_ENABLED)).isFalse(); - verify(mAtomicFile, never()).startWrite(); - } - private byte[] createXmlForParsing(String key, Boolean value) throws Exception { PersistableBundle bundle = new PersistableBundle(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();