Move Ethernet related files to f/b/packages/ConnectivityT.

ethernet-service is going to be moved into Connectivity mainline module.
Move all ethernet related files in f/b/ to f/b/packages/ConnectivityT so
that it's easier to migrate these files to Connectivity module finally
after clearing the hidden API usages. Below files to be moved:

Ethernet framework related files:
    - EthernetManager.java
    - EthernetNetworkSpecifier.java
    - IEthernetManager.aidl
    - IEthernetServiceListener.aidl
    - ITetheredInterfaceCallback.aidl

Ethernet service related files:
    - IpConfigStore.java(EthernetConfigStore has dependency on the class)

For the ethernet-service related files, keep it as-is temproraliy and
fix the hiden API dependencies in f/opt/net/ethernet/. After this work
is done, then migrating the whole of ethernet folder to Connectivity
module completely.

This CL also fixes some minor errors of code style format to pass the
code style check.

Bug: 210586283
Test: build pass
      atest FrameworksNetTests
      atest EthernetServiceTests
Change-Id: Ib359d29d5221105f648bc4194c6d6dbe4cc6e3e5
diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp
index 931a55b..0bda923 100644
--- a/packages/ConnectivityT/framework-t/Android.bp
+++ b/packages/ConnectivityT/framework-t/Android.bp
@@ -114,6 +114,23 @@
     ],
 }
 
+// Ethernet related libraries.
+
+filegroup {
+    name: "framework-connectivity-ethernet-sources",
+    srcs: [
+        "src/android/net/EthernetManager.java",
+        "src/android/net/EthernetNetworkSpecifier.java",
+        "src/android/net/IEthernetManager.aidl",
+        "src/android/net/IEthernetServiceListener.aidl",
+        "src/android/net/ITetheredInterfaceCallback.aidl",
+    ],
+    path: "src",
+    visibility: [
+        "//visibility:private",
+    ],
+}
+
 // Connectivity-T common libraries.
 
 filegroup {
@@ -130,6 +147,7 @@
 filegroup {
     name: "framework-connectivity-tiramisu-sources",
     srcs: [
+        ":framework-connectivity-ethernet-sources",
         ":framework-connectivity-ipsec-sources",
         ":framework-connectivity-netstats-sources",
         ":framework-connectivity-nsd-sources",
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
new file mode 100644
index 0000000..7cd63ef
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetManager.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Build;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * A class representing the IP configuration of the Ethernet network.
+ *
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.ETHERNET_SERVICE)
+public class EthernetManager {
+    private static final String TAG = "EthernetManager";
+
+    private final IEthernetManager mService;
+    @GuardedBy("mListeners")
+    private final ArrayList<ListenerInfo> mListeners = new ArrayList<>();
+    private final IEthernetServiceListener.Stub mServiceListener =
+            new IEthernetServiceListener.Stub() {
+                @Override
+                public void onAvailabilityChanged(String iface, boolean isAvailable) {
+                    synchronized (mListeners) {
+                        for (ListenerInfo li : mListeners) {
+                            li.executor.execute(() ->
+                                    li.listener.onAvailabilityChanged(iface, isAvailable));
+                        }
+                    }
+                }
+            };
+
+    private static class ListenerInfo {
+        @NonNull
+        public final Executor executor;
+        @NonNull
+        public final Listener listener;
+
+        private ListenerInfo(@NonNull Executor executor, @NonNull Listener listener) {
+            this.executor = executor;
+            this.listener = listener;
+        }
+    }
+
+    /**
+     * A listener interface to receive notification on changes in Ethernet.
+     * @hide
+     */
+    public interface Listener {
+        /**
+         * Called when Ethernet port's availability is changed.
+         * @param iface Ethernet interface name
+         * @param isAvailable {@code true} if Ethernet port exists.
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+        void onAvailabilityChanged(String iface, boolean isAvailable);
+    }
+
+    /**
+     * Create a new EthernetManager instance.
+     * Applications will almost always want to use
+     * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
+     * the standard {@link android.content.Context#ETHERNET_SERVICE Context.ETHERNET_SERVICE}.
+     * @hide
+     */
+    public EthernetManager(Context context, IEthernetManager service) {
+        mService = service;
+    }
+
+    /**
+     * Get Ethernet configuration.
+     * @return the Ethernet Configuration, contained in {@link IpConfiguration}.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public IpConfiguration getConfiguration(String iface) {
+        try {
+            return mService.getConfiguration(iface);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set Ethernet configuration.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void setConfiguration(String iface, IpConfiguration config) {
+        try {
+            mService.setConfiguration(iface, config);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates whether the system currently has one or more Ethernet interfaces.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean isAvailable() {
+        return getAvailableInterfaces().length > 0;
+    }
+
+    /**
+     * Indicates whether the system has given interface.
+     *
+     * @param iface Ethernet interface name
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public boolean isAvailable(String iface) {
+        try {
+            return mService.isAvailable(iface);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Adds a listener.
+     *
+     * Consider using {@link #addListener(Listener, Executor)} instead: this method uses a default
+     * executor that may have higher latency than a provided executor.
+     * @param listener A {@link Listener} to add.
+     * @throws IllegalArgumentException If the listener is null.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void addListener(@NonNull Listener listener) {
+        addListener(listener, BackgroundThread.getExecutor());
+    }
+
+    /**
+     * Adds a listener.
+     * @param listener A {@link Listener} to add.
+     * @param executor Executor to run callbacks on.
+     * @throws IllegalArgumentException If the listener or executor is null.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void addListener(@NonNull Listener listener, @NonNull Executor executor) {
+        if (listener == null || executor == null) {
+            throw new NullPointerException("listener and executor must not be null");
+        }
+        synchronized (mListeners) {
+            mListeners.add(new ListenerInfo(executor, listener));
+            if (mListeners.size() == 1) {
+                try {
+                    mService.addListener(mServiceListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns an array of available Ethernet interface names.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public String[] getAvailableInterfaces() {
+        try {
+            return mService.getAvailableInterfaces();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * Removes a listener.
+     * @param listener A {@link Listener} to remove.
+     * @throws IllegalArgumentException If the listener is null.
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    public void removeListener(@NonNull Listener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        synchronized (mListeners) {
+            mListeners.removeIf(l -> l.listener == listener);
+            if (mListeners.isEmpty()) {
+                try {
+                    mService.removeListener(mServiceListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Whether to treat interfaces created by {@link TestNetworkManager#createTapInterface}
+     * as Ethernet interfaces. The effects of this method apply to any test interfaces that are
+     * already present on the system.
+     * @hide
+     */
+    @TestApi
+    public void setIncludeTestInterfaces(boolean include) {
+        try {
+            mService.setIncludeTestInterfaces(include);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * A request for a tethered interface.
+     */
+    public static class TetheredInterfaceRequest {
+        private final IEthernetManager mService;
+        private final ITetheredInterfaceCallback mCb;
+
+        private TetheredInterfaceRequest(@NonNull IEthernetManager service,
+                @NonNull ITetheredInterfaceCallback cb) {
+            this.mService = service;
+            this.mCb = cb;
+        }
+
+        /**
+         * Release the request, causing the interface to revert back from tethering mode if there
+         * is no other requestor.
+         */
+        public void release() {
+            try {
+                mService.releaseTetheredInterface(mCb);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Callback for {@link #requestTetheredInterface(TetheredInterfaceCallback)}.
+     */
+    public interface TetheredInterfaceCallback {
+        /**
+         * Called when the tethered interface is available.
+         * @param iface The name of the interface.
+         */
+        void onAvailable(@NonNull String iface);
+
+        /**
+         * Called when the tethered interface is now unavailable.
+         */
+        void onUnavailable();
+    }
+
+    /**
+     * Request a tethered interface in tethering mode.
+     *
+     * <p>When this method is called and there is at least one ethernet interface available, the
+     * system will designate one to act as a tethered interface. If there is already a tethered
+     * interface, the existing interface will be used.
+     * @param callback A callback to be called once the request has been fulfilled.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_STACK,
+            android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
+    })
+    @NonNull
+    public TetheredInterfaceRequest requestTetheredInterface(@NonNull final Executor executor,
+            @NonNull final TetheredInterfaceCallback callback) {
+        Objects.requireNonNull(callback, "Callback must be non-null");
+        Objects.requireNonNull(executor, "Executor must be non-null");
+        final ITetheredInterfaceCallback cbInternal = new ITetheredInterfaceCallback.Stub() {
+            @Override
+            public void onAvailable(String iface) {
+                executor.execute(() -> callback.onAvailable(iface));
+            }
+
+            @Override
+            public void onUnavailable() {
+                executor.execute(() -> callback.onUnavailable());
+            }
+        };
+
+        try {
+            mService.requestTetheredInterface(cbInternal);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return new TetheredInterfaceRequest(mService, cbInternal);
+    }
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
new file mode 100644
index 0000000..62c5761
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/EthernetNetworkSpecifier.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * A {@link NetworkSpecifier} used to identify ethernet interfaces.
+ *
+ * @see EthernetManager
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class EthernetNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+    /**
+     * Name of the network interface.
+     */
+    @NonNull
+    private final String mInterfaceName;
+
+    /**
+     * Create a new EthernetNetworkSpecifier.
+     * @param interfaceName Name of the ethernet interface the specifier refers to.
+     */
+    public EthernetNetworkSpecifier(@NonNull String interfaceName) {
+        Preconditions.checkStringNotEmpty(interfaceName);
+        mInterfaceName = interfaceName;
+    }
+
+    /**
+     * Get the name of the ethernet interface the specifier refers to.
+     */
+    @Nullable
+    public String getInterfaceName() {
+        // This may be null in the future to support specifiers based on data other than the
+        // interface name.
+        return mInterfaceName;
+    }
+
+    @Override
+    public boolean canBeSatisfiedBy(@Nullable NetworkSpecifier other) {
+        return equals(other);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!(o instanceof EthernetNetworkSpecifier)) return false;
+        return TextUtils.equals(mInterfaceName, ((EthernetNetworkSpecifier) o).mInterfaceName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mInterfaceName);
+    }
+
+    @Override
+    public String toString() {
+        return "EthernetNetworkSpecifier (" + mInterfaceName + ")";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mInterfaceName);
+    }
+
+    public static final @NonNull Parcelable.Creator<EthernetNetworkSpecifier> CREATOR =
+            new Parcelable.Creator<EthernetNetworkSpecifier>() {
+        public EthernetNetworkSpecifier createFromParcel(Parcel in) {
+            return new EthernetNetworkSpecifier(in.readString());
+        }
+        public EthernetNetworkSpecifier[] newArray(int size) {
+            return new EthernetNetworkSpecifier[size];
+        }
+    };
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
new file mode 100644
index 0000000..e058e5a
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetManager.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.net.IpConfiguration;
+import android.net.IEthernetServiceListener;
+import android.net.ITetheredInterfaceCallback;
+
+/**
+ * Interface that answers queries about, and allows changing
+ * ethernet configuration.
+ */
+/** {@hide} */
+interface IEthernetManager
+{
+    String[] getAvailableInterfaces();
+    IpConfiguration getConfiguration(String iface);
+    void setConfiguration(String iface, in IpConfiguration config);
+    boolean isAvailable(String iface);
+    void addListener(in IEthernetServiceListener listener);
+    void removeListener(in IEthernetServiceListener listener);
+    void setIncludeTestInterfaces(boolean include);
+    void requestTetheredInterface(in ITetheredInterfaceCallback callback);
+    void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
new file mode 100644
index 0000000..782fa19
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/IEthernetServiceListener.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 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;
+
+/** @hide */
+oneway interface IEthernetServiceListener
+{
+    void onAvailabilityChanged(String iface, boolean isAvailable);
+}
diff --git a/packages/ConnectivityT/framework-t/src/android/net/ITetheredInterfaceCallback.aidl b/packages/ConnectivityT/framework-t/src/android/net/ITetheredInterfaceCallback.aidl
new file mode 100644
index 0000000..14aa023
--- /dev/null
+++ b/packages/ConnectivityT/framework-t/src/android/net/ITetheredInterfaceCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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;
+
+/** @hide */
+oneway interface ITetheredInterfaceCallback {
+    void onAvailable(in String iface);
+    void onUnavailable();
+}
\ No newline at end of file
diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp
index 7b88176..97dfb64 100644
--- a/packages/ConnectivityT/service/Android.bp
+++ b/packages/ConnectivityT/service/Android.bp
@@ -61,11 +61,25 @@
     ],
 }
 
+// Ethernet related libraries.
+
+filegroup {
+    name: "services.connectivity-ethernet-sources",
+    srcs: [
+        "src/com/android/server/net/IpConfigStore.java",
+    ],
+    path: "src",
+    visibility: [
+        "//frameworks/opt/net/ethernet",
+    ],
+}
+
 // Connectivity-T common libraries.
 
 filegroup {
     name: "services.connectivity-tiramisu-sources",
     srcs: [
+        ":services.connectivity-ethernet-sources",
         ":services.connectivity-ipsec-sources",
         ":services.connectivity-netstats-sources",
         ":services.connectivity-nsd-sources",
diff --git a/packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java b/packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java
new file mode 100644
index 0000000..3a9a544
--- /dev/null
+++ b/packages/ConnectivityT/service/src/com/android/server/net/IpConfigStore.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2014 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.net;
+
+import android.net.InetAddresses;
+import android.net.IpConfiguration;
+import android.net.IpConfiguration.IpAssignment;
+import android.net.IpConfiguration.ProxySettings;
+import android.net.LinkAddress;
+import android.net.ProxyInfo;
+import android.net.StaticIpConfiguration;
+import android.net.Uri;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.ProxyUtils;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides an API to store and manage L3 network IP configuration.
+ */
+public class IpConfigStore {
+    private static final String TAG = "IpConfigStore";
+    private static final boolean DBG = false;
+
+    protected final DelayedDiskWrite mWriter;
+
+    /* IP and proxy configuration keys */
+    protected static final String ID_KEY = "id";
+    protected static final String IP_ASSIGNMENT_KEY = "ipAssignment";
+    protected static final String LINK_ADDRESS_KEY = "linkAddress";
+    protected static final String GATEWAY_KEY = "gateway";
+    protected static final String DNS_KEY = "dns";
+    protected static final String PROXY_SETTINGS_KEY = "proxySettings";
+    protected static final String PROXY_HOST_KEY = "proxyHost";
+    protected static final String PROXY_PORT_KEY = "proxyPort";
+    protected static final String PROXY_PAC_FILE = "proxyPac";
+    protected static final String EXCLUSION_LIST_KEY = "exclusionList";
+    protected static final String EOS = "eos";
+
+    protected static final int IPCONFIG_FILE_VERSION = 3;
+
+    public IpConfigStore(DelayedDiskWrite writer) {
+        mWriter = writer;
+    }
+
+    public IpConfigStore() {
+        this(new DelayedDiskWrite());
+    }
+
+    private static boolean writeConfig(DataOutputStream out, String configKey,
+            IpConfiguration config) throws IOException {
+        return writeConfig(out, configKey, config, IPCONFIG_FILE_VERSION);
+    }
+
+    /**
+     *  Write the IP configuration with the given parameters to {@link DataOutputStream}.
+     */
+    @VisibleForTesting
+    public static boolean writeConfig(DataOutputStream out, String configKey,
+                                IpConfiguration config, int version) throws IOException {
+        boolean written = false;
+
+        try {
+            switch (config.getIpAssignment()) {
+                case STATIC:
+                    out.writeUTF(IP_ASSIGNMENT_KEY);
+                    out.writeUTF(config.getIpAssignment().toString());
+                    StaticIpConfiguration staticIpConfiguration = config.getStaticIpConfiguration();
+                    if (staticIpConfiguration != null) {
+                        if (staticIpConfiguration.getIpAddress() != null) {
+                            LinkAddress ipAddress = staticIpConfiguration.getIpAddress();
+                            out.writeUTF(LINK_ADDRESS_KEY);
+                            out.writeUTF(ipAddress.getAddress().getHostAddress());
+                            out.writeInt(ipAddress.getPrefixLength());
+                        }
+                        if (staticIpConfiguration.getGateway() != null) {
+                            out.writeUTF(GATEWAY_KEY);
+                            out.writeInt(0);  // Default route.
+                            out.writeInt(1);  // Have a gateway.
+                            out.writeUTF(staticIpConfiguration.getGateway().getHostAddress());
+                        }
+                        for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) {
+                            out.writeUTF(DNS_KEY);
+                            out.writeUTF(inetAddr.getHostAddress());
+                        }
+                    }
+                    written = true;
+                    break;
+                case DHCP:
+                    out.writeUTF(IP_ASSIGNMENT_KEY);
+                    out.writeUTF(config.getIpAssignment().toString());
+                    written = true;
+                    break;
+                case UNASSIGNED:
+                /* Ignore */
+                    break;
+                default:
+                    loge("Ignore invalid ip assignment while writing");
+                    break;
+            }
+
+            switch (config.getProxySettings()) {
+                case STATIC:
+                    ProxyInfo proxyProperties = config.getHttpProxy();
+                    String exclusionList = ProxyUtils.exclusionListAsString(
+                            proxyProperties.getExclusionList());
+                    out.writeUTF(PROXY_SETTINGS_KEY);
+                    out.writeUTF(config.getProxySettings().toString());
+                    out.writeUTF(PROXY_HOST_KEY);
+                    out.writeUTF(proxyProperties.getHost());
+                    out.writeUTF(PROXY_PORT_KEY);
+                    out.writeInt(proxyProperties.getPort());
+                    if (exclusionList != null) {
+                        out.writeUTF(EXCLUSION_LIST_KEY);
+                        out.writeUTF(exclusionList);
+                    }
+                    written = true;
+                    break;
+                case PAC:
+                    ProxyInfo proxyPacProperties = config.getHttpProxy();
+                    out.writeUTF(PROXY_SETTINGS_KEY);
+                    out.writeUTF(config.getProxySettings().toString());
+                    out.writeUTF(PROXY_PAC_FILE);
+                    out.writeUTF(proxyPacProperties.getPacFileUrl().toString());
+                    written = true;
+                    break;
+                case NONE:
+                    out.writeUTF(PROXY_SETTINGS_KEY);
+                    out.writeUTF(config.getProxySettings().toString());
+                    written = true;
+                    break;
+                case UNASSIGNED:
+                    /* Ignore */
+                    break;
+                default:
+                    loge("Ignore invalid proxy settings while writing");
+                    break;
+            }
+
+            if (written) {
+                out.writeUTF(ID_KEY);
+                if (version < 3) {
+                    out.writeInt(Integer.valueOf(configKey));
+                } else {
+                    out.writeUTF(configKey);
+                }
+            }
+        } catch (NullPointerException e) {
+            loge("Failure in writing " + config + e);
+        }
+        out.writeUTF(EOS);
+
+        return written;
+    }
+
+    /**
+     * @deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead.
+     * New method uses string as network identifier which could be interface name or MAC address or
+     * other token.
+     */
+    @Deprecated
+    public void writeIpAndProxyConfigurationsToFile(String filePath,
+                                              final SparseArray<IpConfiguration> networks) {
+        mWriter.write(filePath, out -> {
+            out.writeInt(IPCONFIG_FILE_VERSION);
+            for (int i = 0; i < networks.size(); i++) {
+                writeConfig(out, String.valueOf(networks.keyAt(i)), networks.valueAt(i));
+            }
+        });
+    }
+
+    /**
+     *  Write the IP configuration associated to the target networks to the destination path.
+     */
+    public void writeIpConfigurations(String filePath,
+                                      ArrayMap<String, IpConfiguration> networks) {
+        mWriter.write(filePath, out -> {
+            out.writeInt(IPCONFIG_FILE_VERSION);
+            for (int i = 0; i < networks.size(); i++) {
+                writeConfig(out, networks.keyAt(i), networks.valueAt(i));
+            }
+        });
+    }
+
+    /**
+     * Read the IP configuration from the destination path to {@link BufferedInputStream}.
+     */
+    public static ArrayMap<String, IpConfiguration> readIpConfigurations(String filePath) {
+        BufferedInputStream bufferedInputStream;
+        try {
+            bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
+        } catch (FileNotFoundException e) {
+            // Return an empty array here because callers expect an empty array when the file is
+            // not present.
+            loge("Error opening configuration file: " + e);
+            return new ArrayMap<>(0);
+        }
+        return readIpConfigurations(bufferedInputStream);
+    }
+
+    /** @deprecated use {@link #readIpConfigurations(String)} */
+    @Deprecated
+    public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
+        BufferedInputStream bufferedInputStream;
+        try {
+            bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
+        } catch (FileNotFoundException e) {
+            // Return an empty array here because callers expect an empty array when the file is
+            // not present.
+            loge("Error opening configuration file: " + e);
+            return new SparseArray<>();
+        }
+        return readIpAndProxyConfigurations(bufferedInputStream);
+    }
+
+    /** @deprecated use {@link #readIpConfigurations(InputStream)} */
+    @Deprecated
+    public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(
+            InputStream inputStream) {
+        ArrayMap<String, IpConfiguration> networks = readIpConfigurations(inputStream);
+        if (networks == null) {
+            return null;
+        }
+
+        SparseArray<IpConfiguration> networksById = new SparseArray<>();
+        for (int i = 0; i < networks.size(); i++) {
+            int id = Integer.valueOf(networks.keyAt(i));
+            networksById.put(id, networks.valueAt(i));
+        }
+
+        return networksById;
+    }
+
+    /** Returns a map of network identity token and {@link IpConfiguration}. */
+    public static ArrayMap<String, IpConfiguration> readIpConfigurations(
+            InputStream inputStream) {
+        ArrayMap<String, IpConfiguration> networks = new ArrayMap<>();
+        DataInputStream in = null;
+        try {
+            in = new DataInputStream(inputStream);
+
+            int version = in.readInt();
+            if (version != 3 && version != 2 && version != 1) {
+                loge("Bad version on IP configuration file, ignore read");
+                return null;
+            }
+
+            while (true) {
+                String uniqueToken = null;
+                // Default is DHCP with no proxy
+                IpAssignment ipAssignment = IpAssignment.DHCP;
+                ProxySettings proxySettings = ProxySettings.NONE;
+                StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
+                LinkAddress linkAddress = null;
+                InetAddress gatewayAddress = null;
+                String proxyHost = null;
+                String pacFileUrl = null;
+                int proxyPort = -1;
+                String exclusionList = null;
+                String key;
+                final List<InetAddress> dnsServers = new ArrayList<>();
+
+                do {
+                    key = in.readUTF();
+                    try {
+                        if (key.equals(ID_KEY)) {
+                            if (version < 3) {
+                                int id = in.readInt();
+                                uniqueToken = String.valueOf(id);
+                            } else {
+                                uniqueToken = in.readUTF();
+                            }
+                        } else if (key.equals(IP_ASSIGNMENT_KEY)) {
+                            ipAssignment = IpAssignment.valueOf(in.readUTF());
+                        } else if (key.equals(LINK_ADDRESS_KEY)) {
+                            LinkAddress parsedLinkAddress =
+                                    new LinkAddress(
+                                            InetAddresses.parseNumericAddress(in.readUTF()),
+                                            in.readInt());
+                            if (parsedLinkAddress.getAddress() instanceof Inet4Address
+                                    && linkAddress == null) {
+                                linkAddress = parsedLinkAddress;
+                            } else {
+                                loge("Non-IPv4 or duplicate address: " + parsedLinkAddress);
+                            }
+                        } else if (key.equals(GATEWAY_KEY)) {
+                            LinkAddress dest = null;
+                            InetAddress gateway = null;
+                            if (version == 1) {
+                                // only supported default gateways - leave the dest/prefix empty
+                                gateway = InetAddresses.parseNumericAddress(in.readUTF());
+                                if (gatewayAddress == null) {
+                                    gatewayAddress = gateway;
+                                } else {
+                                    loge("Duplicate gateway: " + gateway.getHostAddress());
+                                }
+                            } else {
+                                if (in.readInt() == 1) {
+                                    dest =
+                                            new LinkAddress(
+                                                    InetAddresses.parseNumericAddress(in.readUTF()),
+                                                    in.readInt());
+                                }
+                                if (in.readInt() == 1) {
+                                    gateway = InetAddresses.parseNumericAddress(in.readUTF());
+                                }
+                                // If the destination is a default IPv4 route, use the gateway
+                                // address unless already set. If there is no destination, assume
+                                // it is default route and use the gateway address in all cases.
+                                if (dest == null) {
+                                    gatewayAddress = gateway;
+                                } else if (dest.getAddress() instanceof Inet4Address
+                                        && dest.getPrefixLength() == 0 && gatewayAddress == null) {
+                                    gatewayAddress = gateway;
+                                } else {
+                                    loge("Non-IPv4 default or duplicate route: "
+                                            + dest.getAddress());
+                                }
+                            }
+                        } else if (key.equals(DNS_KEY)) {
+                            dnsServers.add(InetAddresses.parseNumericAddress(in.readUTF()));
+                        } else if (key.equals(PROXY_SETTINGS_KEY)) {
+                            proxySettings = ProxySettings.valueOf(in.readUTF());
+                        } else if (key.equals(PROXY_HOST_KEY)) {
+                            proxyHost = in.readUTF();
+                        } else if (key.equals(PROXY_PORT_KEY)) {
+                            proxyPort = in.readInt();
+                        } else if (key.equals(PROXY_PAC_FILE)) {
+                            pacFileUrl = in.readUTF();
+                        } else if (key.equals(EXCLUSION_LIST_KEY)) {
+                            exclusionList = in.readUTF();
+                        } else if (key.equals(EOS)) {
+                            break;
+                        } else {
+                            loge("Ignore unknown key " + key + "while reading");
+                        }
+                    } catch (IllegalArgumentException e) {
+                        loge("Ignore invalid address while reading" + e);
+                    }
+                } while (true);
+
+                staticIpConfiguration = new StaticIpConfiguration.Builder()
+                    .setIpAddress(linkAddress)
+                    .setGateway(gatewayAddress)
+                    .setDnsServers(dnsServers)
+                    .build();
+
+                if (uniqueToken != null) {
+                    IpConfiguration config = new IpConfiguration();
+                    networks.put(uniqueToken, config);
+
+                    switch (ipAssignment) {
+                        case STATIC:
+                            config.setStaticIpConfiguration(staticIpConfiguration);
+                            config.setIpAssignment(ipAssignment);
+                            break;
+                        case DHCP:
+                            config.setIpAssignment(ipAssignment);
+                            break;
+                        case UNASSIGNED:
+                            loge("BUG: Found UNASSIGNED IP on file, use DHCP");
+                            config.setIpAssignment(IpAssignment.DHCP);
+                            break;
+                        default:
+                            loge("Ignore invalid ip assignment while reading.");
+                            config.setIpAssignment(IpAssignment.UNASSIGNED);
+                            break;
+                    }
+
+                    switch (proxySettings) {
+                        case STATIC:
+                            ProxyInfo proxyInfo = ProxyInfo.buildDirectProxy(proxyHost, proxyPort,
+                                    ProxyUtils.exclusionStringAsList(exclusionList));
+                            config.setProxySettings(proxySettings);
+                            config.setHttpProxy(proxyInfo);
+                            break;
+                        case PAC:
+                            ProxyInfo proxyPacProperties =
+                                    ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
+                            config.setProxySettings(proxySettings);
+                            config.setHttpProxy(proxyPacProperties);
+                            break;
+                        case NONE:
+                            config.setProxySettings(proxySettings);
+                            break;
+                        case UNASSIGNED:
+                            loge("BUG: Found UNASSIGNED proxy on file, use NONE");
+                            config.setProxySettings(ProxySettings.NONE);
+                            break;
+                        default:
+                            loge("Ignore invalid proxy settings while reading");
+                            config.setProxySettings(ProxySettings.UNASSIGNED);
+                            break;
+                    }
+                } else {
+                    if (DBG) log("Missing id while parsing configuration");
+                }
+            }
+        } catch (EOFException ignore) {
+        } catch (IOException e) {
+            loge("Error parsing configuration: " + e);
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (Exception e) { }
+            }
+        }
+
+        return networks;
+    }
+
+    protected static void loge(String s) {
+        Log.e(TAG, s);
+    }
+
+    protected static void log(String s) {
+        Log.d(TAG, s);
+    }
+}