[thread] refactor the settings module

The current settings APIs have a few issues:
1. It doesn't support default value which is derived from resource
   overlays
2. It exposes the default values of settings entries as public APIs
   while they shouldn't be

Bug: 401654864

Change-Id: I1bbe3f14507a497320401f02fa6da512558fe385
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index cf25652..7063357 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -559,7 +559,7 @@
             // The persistent setting keeps the desired enabled state, thus it's set regardless
             // the otDaemon set enabled state operation succeeded or not, so that it can recover
             // to the desired value after reboot.
-            mPersistentSettings.put(ThreadPersistentSettings.THREAD_ENABLED.key, isEnabled);
+            mPersistentSettings.put(ThreadPersistentSettings.KEY_THREAD_ENABLED, isEnabled);
         }
 
         try {
@@ -743,7 +743,7 @@
     private boolean shouldEnableThread() {
         return !mForceStopOtDaemonEnabled
                 && !mUserRestricted
-                && mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
+                && mPersistentSettings.get(ThreadPersistentSettings.KEY_THREAD_ENABLED);
     }
 
     private void requestUpstreamNetwork() {
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
index 2cd34e8..ff0e2c1 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkCountryCode.java
@@ -16,7 +16,7 @@
 
 package com.android.server.thread;
 
-import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE;
+import static com.android.server.thread.ThreadPersistentSettings.KEY_COUNTRY_CODE;
 
 import android.annotation.Nullable;
 import android.annotation.StringDef;
@@ -496,7 +496,7 @@
             return mLocationCountryCodeInfo;
         }
 
-        String settingsCountryCode = mPersistentSettings.get(THREAD_COUNTRY_CODE);
+        String settingsCountryCode = mPersistentSettings.get(KEY_COUNTRY_CODE);
         if (settingsCountryCode != null) {
             return new CountryCodeInfo(settingsCountryCode, COUNTRY_CODE_SOURCE_SETTINGS);
         }
@@ -514,8 +514,7 @@
             public void onSuccess() {
                 synchronized ("ThreadNetworkCountryCode.this") {
                     mCurrentCountryCodeInfo = countryCodeInfo;
-                    mPersistentSettings.put(
-                            THREAD_COUNTRY_CODE.key, countryCodeInfo.getCountryCode());
+                    mPersistentSettings.put(KEY_COUNTRY_CODE, countryCodeInfo.getCountryCode());
                 }
             }
 
diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index 47cf202..fd6dec7 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -40,6 +40,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Store persistent data for Thread network settings. These are key (string) / value pairs that are
@@ -53,54 +55,55 @@
     /** File name used for storing settings. */
     private static final String FILE_NAME = "ThreadPersistentSettings.xml";
 
-    /** Current config store data version. This will be incremented for any additions. */
+    /** Current config store data version. This MUST be incremented for any incompatible changes. */
     private static final int CURRENT_SETTINGS_STORE_DATA_VERSION = 1;
 
     /**
      * Stores the version of the data. This can be used to handle migration of data if some
      * non-backward compatible change introduced.
      */
-    private static final String VERSION_KEY = "version";
+    private static final String KEY_VERSION = "version";
 
-    /******** Thread persistent setting keys ***************/
-    /** Stores the Thread feature toggle state, true for enabled and false for disabled. */
-    public static final Key<Boolean> THREAD_ENABLED = new Key<>("thread_enabled", true);
+    /**
+     * Saves the boolean flag for Thread being enabled. The value defaults to resource overlay value
+     * {@code R.bool.config_thread_default_enabled}.
+     */
+    public static final Key<Boolean> KEY_THREAD_ENABLED = new Key<>("thread_enabled");
+
+    /**
+     * Saves the boolean flag for border router being enabled. The value defaults to resource
+     * overlay value {@code R.bool.config_thread_border_router_default_enabled}.
+     */
+    private static final Key<Boolean> KEY_CONFIG_BORDER_ROUTER_ENABLED =
+            new Key<>("config_border_router_enabled");
+
+    /** Stores the Thread NAT64 feature toggle state, true for enabled and false for disabled. */
+    private static final Key<Boolean> KEY_CONFIG_NAT64_ENABLED = new Key<>("config_nat64_enabled");
+
+    /**
+     * Stores the Thread DHCPv6-PD feature toggle state, true for enabled and false for disabled.
+     */
+    private static final Key<Boolean> KEY_CONFIG_DHCP6_PD_ENABLED =
+            new Key<>("config_dhcp6_pd_enabled");
 
     /**
      * Indicates that Thread was enabled (i.e. via the setEnabled() API) when the airplane mode is
      * turned on in settings. When this value is {@code true}, the current airplane mode state will
      * be ignored when evaluating the Thread enabled state.
      */
-    public static final Key<Boolean> THREAD_ENABLED_IN_AIRPLANE_MODE =
-            new Key<>("thread_enabled_in_airplane_mode", false);
+    public static final Key<Boolean> KEY_THREAD_ENABLED_IN_AIRPLANE_MODE =
+            new Key<>("thread_enabled_in_airplane_mode");
 
     /** 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);
-
-    /**
-     * Saves the boolean flag for border router being enabled. The value defaults to {@code true} if
-     * this config is missing.
-     */
-    private static final Key<Boolean> CONFIG_BORDER_ROUTER_ENABLED =
-            new Key<>("config_border_router_enabled", false);
-
-    /** 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 ***************/
+    public static final Key<String> KEY_COUNTRY_CODE = new Key<>("thread_country_code");
 
     @GuardedBy("mLock")
     private final AtomicFile mAtomicFile;
 
     private final Object mLock = new Object();
 
+    private final Map<String, Object> mDefaultValues = new HashMap<>();
+
     @GuardedBy("mLock")
     private final PersistableBundle mSettings = new PersistableBundle();
 
@@ -116,32 +119,22 @@
     ThreadPersistentSettings(AtomicFile atomicFile, ConnectivityResources resources) {
         mAtomicFile = atomicFile;
         mResources = resources;
+
+        mDefaultValues.put(
+                KEY_THREAD_ENABLED.key,
+                mResources.get().getBoolean(R.bool.config_thread_default_enabled));
+        mDefaultValues.put(
+                KEY_CONFIG_BORDER_ROUTER_ENABLED.key,
+                mResources.get().getBoolean(R.bool.config_thread_border_router_default_enabled));
+        mDefaultValues.put(KEY_CONFIG_NAT64_ENABLED.key, false);
+        mDefaultValues.put(KEY_CONFIG_DHCP6_PD_ENABLED.key, false);
+        mDefaultValues.put(KEY_THREAD_ENABLED_IN_AIRPLANE_MODE.key, false);
+        mDefaultValues.put(KEY_COUNTRY_CODE.key, null);
     }
 
     /** Initialize the settings by reading from the settings file. */
     public void initialize() {
         readFromStoreFile();
-        synchronized (mLock) {
-            if (!mSettings.containsKey(THREAD_ENABLED.key)) {
-                boolean enabled = mResources.get().getBoolean(R.bool.config_thread_default_enabled);
-                LOG.i(
-                        "\"thread_enabled\" is missing in settings file, using default value: "
-                                + enabled);
-                put(THREAD_ENABLED.key, enabled);
-            }
-
-            if (!mSettings.containsKey(CONFIG_BORDER_ROUTER_ENABLED.key)) {
-                boolean borderRouterEnabled =
-                        mResources
-                                .get()
-                                .getBoolean(R.bool.config_thread_border_router_default_enabled);
-                LOG.i(
-                        "\"thread_border_router_enabled\" is missing in settings file, using"
-                                + " default value: "
-                                + borderRouterEnabled);
-                put(CONFIG_BORDER_ROUTER_ENABLED.key, borderRouterEnabled);
-            }
-        }
     }
 
     private void putObject(String key, @Nullable Object value) {
@@ -186,25 +179,17 @@
         return (T) value;
     }
 
-    /**
-     * Store a value to the stored settings.
-     *
-     * @param key One of the settings keys.
-     * @param value Value to be stored.
-     */
-    public <T> void put(String key, @Nullable T value) {
-        putObject(key, value);
+    /** Stores a value to the stored settings. */
+    public <T> void put(Key<T> key, @Nullable T value) {
+        putObject(key.key, value);
         writeToStoreFile();
     }
 
-    /**
-     * Retrieve a value from the stored settings.
-     *
-     * @param key One of the settings keys.
-     * @return value stored in settings, defValue if the key does not exist.
-     */
+    /** Retrieves a value from the stored settings. */
+    @Nullable
     public <T> T get(Key<T> key) {
-        return getObject(key.key, key.defaultValue);
+        T defaultValue = (T) mDefaultValues.get(key.key);
+        return getObject(key.key, defaultValue);
     }
 
     /**
@@ -217,9 +202,9 @@
         if (getConfiguration().equals(configuration)) {
             return false;
         }
-        putObject(CONFIG_BORDER_ROUTER_ENABLED.key, configuration.isBorderRouterEnabled());
-        putObject(CONFIG_NAT64_ENABLED.key, configuration.isNat64Enabled());
-        putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcpv6PdEnabled());
+        put(KEY_CONFIG_BORDER_ROUTER_ENABLED, configuration.isBorderRouterEnabled());
+        put(KEY_CONFIG_NAT64_ENABLED, configuration.isNat64Enabled());
+        put(KEY_CONFIG_DHCP6_PD_ENABLED, configuration.isDhcpv6PdEnabled());
         writeToStoreFile();
         return true;
     }
@@ -227,9 +212,9 @@
     /** Retrieve the {@link ThreadConfiguration} from the persistent settings. */
     public ThreadConfiguration getConfiguration() {
         return new ThreadConfiguration.Builder()
-                .setBorderRouterEnabled(get(CONFIG_BORDER_ROUTER_ENABLED))
-                .setNat64Enabled(get(CONFIG_NAT64_ENABLED))
-                .setDhcpv6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED))
+                .setBorderRouterEnabled(get(KEY_CONFIG_BORDER_ROUTER_ENABLED))
+                .setNat64Enabled(get(KEY_CONFIG_NAT64_ENABLED))
+                .setDhcpv6PdEnabled(get(KEY_CONFIG_DHCP6_PD_ENABLED))
                 .build();
     }
 
@@ -238,18 +223,11 @@
      *
      * @param <T> Type of the value.
      */
-    public static class Key<T> {
-        public final String key;
-        public final T defaultValue;
+    public static final class Key<T> {
+        @VisibleForTesting final String key;
 
-        private Key(String key, T defaultValue) {
+        private Key(String key) {
             this.key = key;
-            this.defaultValue = defaultValue;
-        }
-
-        @Override
-        public String toString() {
-            return "[Key: " + key + ", DefaultValue: " + defaultValue + "]";
         }
     }
 
@@ -260,7 +238,7 @@
             synchronized (mLock) {
                 bundleToWrite = new PersistableBundle(mSettings);
             }
-            bundleToWrite.putInt(VERSION_KEY, CURRENT_SETTINGS_STORE_DATA_VERSION);
+            bundleToWrite.putInt(KEY_VERSION, CURRENT_SETTINGS_STORE_DATA_VERSION);
             bundleToWrite.writeToStream(outputStream);
             synchronized (mLock) {
                 writeToAtomicFile(mAtomicFile, outputStream.toByteArray());
@@ -280,7 +258,7 @@
             final ByteArrayInputStream inputStream = new ByteArrayInputStream(readData);
             final PersistableBundle bundleRead = PersistableBundle.readFromStream(inputStream);
             // Version unused for now. May be needed in the future for handling migrations.
-            bundleRead.remove(VERSION_KEY);
+            bundleRead.remove(KEY_VERSION);
             synchronized (mLock) {
                 mSettings.putAll(bundleRead);
             }
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index 4360c7b..95ebda5 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -35,6 +35,7 @@
 import static android.net.thread.ThreadNetworkManager.PERMISSION_THREAD_NETWORK_TESTING;
 
 import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
+import static com.android.server.thread.ThreadPersistentSettings.KEY_THREAD_ENABLED;
 import static com.android.server.thread.openthread.IOtDaemon.ErrorCode.OT_ERROR_INVALID_STATE;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
 
@@ -580,7 +581,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(mFakeOtDaemon.getEnabledState()).isEqualTo(STATE_DISABLED);
-        assertThat(mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED)).isTrue();
+        assertThat(mPersistentSettings.get(KEY_THREAD_ENABLED)).isTrue();
     }
 
     @Test
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
index ca9741d..139f4c8 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkCountryCodeTest.java
@@ -19,7 +19,7 @@
 import static android.net.thread.ThreadNetworkException.ERROR_INTERNAL_ERROR;
 
 import static com.android.server.thread.ThreadNetworkCountryCode.DEFAULT_COUNTRY_CODE;
-import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE;
+import static com.android.server.thread.ThreadPersistentSettings.KEY_COUNTRY_CODE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -454,7 +454,7 @@
 
     @Test
     public void settingsCountryCode_settingsCountryCodeIsActive_settingsCountryCodeIsUsed() {
-        when(mPersistentSettings.get(THREAD_COUNTRY_CODE)).thenReturn(TEST_COUNTRY_CODE_CN);
+        when(mPersistentSettings.get(KEY_COUNTRY_CODE)).thenReturn(TEST_COUNTRY_CODE_CN);
         mThreadNetworkCountryCode.initialize();
 
         assertThat(mThreadNetworkCountryCode.getCountryCode()).isEqualTo(TEST_COUNTRY_CODE_CN);
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
index ba489d9..15f3d0b 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
@@ -16,8 +16,8 @@
 
 package com.android.server.thread;
 
-import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE;
-import static com.android.server.thread.ThreadPersistentSettings.THREAD_ENABLED;
+import static com.android.server.thread.ThreadPersistentSettings.KEY_COUNTRY_CODE;
+import static com.android.server.thread.ThreadPersistentSettings.KEY_THREAD_ENABLED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,6 +35,7 @@
 
 import com.android.connectivity.resources.R;
 import com.android.server.connectivity.ConnectivityResources;
+import com.android.server.thread.ThreadPersistentSettings.Key;
 
 import org.junit.After;
 import org.junit.Before;
@@ -83,68 +84,81 @@
 
     @Test
     public void initialize_readsFromFile() throws Exception {
-        byte[] data = createXmlForParsing(THREAD_ENABLED.key, false);
+        byte[] data = createXmlForParsing(KEY_THREAD_ENABLED, false);
         setupAtomicFileForRead(data);
 
         mThreadPersistentSettings.initialize();
 
-        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+        assertThat(mThreadPersistentSettings.get(KEY_THREAD_ENABLED)).isFalse();
     }
 
     @Test
     public void initialize_ThreadDisabledInResources_returnsThreadDisabled() throws Exception {
         when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(false);
-        setupAtomicFileForRead(new byte[0]);
+        mThreadPersistentSettings =
+                new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
 
         mThreadPersistentSettings.initialize();
 
-        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+        assertThat(mThreadPersistentSettings.get(KEY_THREAD_ENABLED)).isFalse();
+    }
+
+    @Test
+    public void initialize_ThreadEnabledInResources_returnsThreadEnabled() throws Exception {
+        when(mResources.getBoolean(eq(R.bool.config_thread_default_enabled))).thenReturn(true);
+        mThreadPersistentSettings =
+                new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
+
+        mThreadPersistentSettings.initialize();
+
+        assertThat(mThreadPersistentSettings.get(KEY_THREAD_ENABLED)).isTrue();
     }
 
     @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);
-        setupAtomicFileForRead(data);
+        setupAtomicFileForRead(createXmlForParsing(KEY_THREAD_ENABLED, true));
+        mThreadPersistentSettings =
+                new ThreadPersistentSettings(mAtomicFile, mConnectivityResources);
 
         mThreadPersistentSettings.initialize();
 
-        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
+        assertThat(mThreadPersistentSettings.get(KEY_THREAD_ENABLED)).isTrue();
     }
 
     @Test
     public void put_ThreadFeatureEnabledTrue_returnsTrue() throws Exception {
-        mThreadPersistentSettings.put(THREAD_ENABLED.key, true);
+        mThreadPersistentSettings.put(KEY_THREAD_ENABLED, true);
 
-        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isTrue();
+        assertThat(mThreadPersistentSettings.get(KEY_THREAD_ENABLED)).isTrue();
     }
 
     @Test
     public void put_ThreadFeatureEnabledFalse_returnsFalse() throws Exception {
-        mThreadPersistentSettings.put(THREAD_ENABLED.key, false);
+        mThreadPersistentSettings.put(KEY_THREAD_ENABLED, false);
 
-        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+        assertThat(mThreadPersistentSettings.get(KEY_THREAD_ENABLED)).isFalse();
         mThreadPersistentSettings.initialize();
-        assertThat(mThreadPersistentSettings.get(THREAD_ENABLED)).isFalse();
+        assertThat(mThreadPersistentSettings.get(KEY_THREAD_ENABLED)).isFalse();
     }
 
     @Test
     public void put_ThreadCountryCodeString_returnsString() throws Exception {
-        mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, TEST_COUNTRY_CODE);
+        mThreadPersistentSettings.put(KEY_COUNTRY_CODE, TEST_COUNTRY_CODE);
 
-        assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
+        assertThat(mThreadPersistentSettings.get(KEY_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
         mThreadPersistentSettings.initialize();
-        assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
+        assertThat(mThreadPersistentSettings.get(KEY_COUNTRY_CODE)).isEqualTo(TEST_COUNTRY_CODE);
     }
 
     @Test
     public void put_ThreadCountryCodeNull_returnsNull() throws Exception {
-        mThreadPersistentSettings.put(THREAD_COUNTRY_CODE.key, null);
+        mThreadPersistentSettings.put(KEY_COUNTRY_CODE, null);
 
-        assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
+        assertThat(mThreadPersistentSettings.get(KEY_COUNTRY_CODE)).isNull();
         mThreadPersistentSettings.initialize();
-        assertThat(mThreadPersistentSettings.get(THREAD_COUNTRY_CODE)).isNull();
+        assertThat(mThreadPersistentSettings.get(KEY_COUNTRY_CODE)).isNull();
     }
 
     @Test
@@ -202,10 +216,10 @@
         return new AtomicFile(mTemporaryFolder.newFile());
     }
 
-    private byte[] createXmlForParsing(String key, Boolean value) throws Exception {
+    private byte[] createXmlForParsing(Key<Boolean> key, Boolean value) throws Exception {
         PersistableBundle bundle = new PersistableBundle();
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        bundle.putBoolean(key, value);
+        bundle.putBoolean(key.key, value);
         bundle.writeToStream(outputStream);
         return outputStream.toByteArray();
     }