Merge "Overcome disconnect during oem override"
diff --git a/Android.bp b/Android.bp
index e6aa7cf..34cc52c4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -596,6 +596,7 @@
         "android.hardware.vibrator-V1.0-java-constants",
         "android.hardware.vibrator-V1.1-java-constants",
         "android.hardware.wifi-V1.0-java-constants",
+        "android.hardware.radio-V1.0-java",
     ],
 
     // Loaded with System.loadLibrary by android.view.textclassifier
diff --git a/Android.mk b/Android.mk
index 9877557..2d74249 100644
--- a/Android.mk
+++ b/Android.mk
@@ -698,4 +698,4 @@
 include $(call first-makefiles-under,$(LOCAL_PATH))
 endif
 
-endif # ANDROID_BUILD_EMBEDDED
+endif # ANDROID_BUILD_EMBEDDED
\ No newline at end of file
diff --git a/api/current.txt b/api/current.txt
index 6702b94..392f4db 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -40931,6 +40931,78 @@
 
 }
 
+package android.telephony.data {
+
+  public class ApnSetting implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getApnName();
+    method public int getAuthType();
+    method public java.lang.String getEntryName();
+    method public int getId();
+    method public int getMmsPort();
+    method public java.net.InetAddress getMmsProxy();
+    method public java.net.URL getMmsc();
+    method public java.lang.String getMvnoType();
+    method public java.lang.String getOperatorNumeric();
+    method public java.lang.String getPassword();
+    method public int getPort();
+    method public java.lang.String getProtocol();
+    method public java.net.InetAddress getProxy();
+    method public java.lang.String getRoamingProtocol();
+    method public java.util.List<java.lang.String> getTypes();
+    method public java.lang.String getUser();
+    method public boolean isEnabled();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int AUTH_TYPE_CHAP = 2; // 0x2
+    field public static final int AUTH_TYPE_NONE = 0; // 0x0
+    field public static final int AUTH_TYPE_PAP = 1; // 0x1
+    field public static final int AUTH_TYPE_PAP_OR_CHAP = 3; // 0x3
+    field public static final android.os.Parcelable.Creator<android.telephony.data.ApnSetting> CREATOR;
+    field public static final java.lang.String MVNO_TYPE_GID = "gid";
+    field public static final java.lang.String MVNO_TYPE_ICCID = "iccid";
+    field public static final java.lang.String MVNO_TYPE_IMSI = "imsi";
+    field public static final java.lang.String MVNO_TYPE_SPN = "spn";
+    field public static final java.lang.String PROTOCOL_IP = "IP";
+    field public static final java.lang.String PROTOCOL_IPV4V6 = "IPV4V6";
+    field public static final java.lang.String PROTOCOL_IPV6 = "IPV6";
+    field public static final java.lang.String PROTOCOL_PPP = "PPP";
+    field public static final java.lang.String TYPE_ALL = "*";
+    field public static final java.lang.String TYPE_CBS = "cbs";
+    field public static final java.lang.String TYPE_DEFAULT = "default";
+    field public static final java.lang.String TYPE_DUN = "dun";
+    field public static final java.lang.String TYPE_EMERGENCY = "emergency";
+    field public static final java.lang.String TYPE_FOTA = "fota";
+    field public static final java.lang.String TYPE_HIPRI = "hipri";
+    field public static final java.lang.String TYPE_IA = "ia";
+    field public static final java.lang.String TYPE_IMS = "ims";
+    field public static final java.lang.String TYPE_MMS = "mms";
+    field public static final java.lang.String TYPE_SUPL = "supl";
+  }
+
+  public static class ApnSetting.Builder {
+    ctor public ApnSetting.Builder();
+    method public android.telephony.data.ApnSetting build();
+    method public android.telephony.data.ApnSetting.Builder setApnName(java.lang.String);
+    method public android.telephony.data.ApnSetting.Builder setAuthType(int);
+    method public android.telephony.data.ApnSetting.Builder setCarrierEnabled(boolean);
+    method public android.telephony.data.ApnSetting.Builder setEntryName(java.lang.String);
+    method public android.telephony.data.ApnSetting.Builder setId(int);
+    method public android.telephony.data.ApnSetting.Builder setMmsPort(int);
+    method public android.telephony.data.ApnSetting.Builder setMmsProxy(java.net.InetAddress);
+    method public android.telephony.data.ApnSetting.Builder setMmsc(java.net.URL);
+    method public android.telephony.data.ApnSetting.Builder setMvnoType(java.lang.String);
+    method public android.telephony.data.ApnSetting.Builder setOperatorNumeric(java.lang.String);
+    method public android.telephony.data.ApnSetting.Builder setPassword(java.lang.String);
+    method public android.telephony.data.ApnSetting.Builder setPort(int);
+    method public android.telephony.data.ApnSetting.Builder setProtocol(java.lang.String);
+    method public android.telephony.data.ApnSetting.Builder setProxy(java.net.InetAddress);
+    method public android.telephony.data.ApnSetting.Builder setRoamingProtocol(java.lang.String);
+    method public android.telephony.data.ApnSetting.Builder setTypes(java.util.List<java.lang.String>);
+    method public android.telephony.data.ApnSetting.Builder setUser(java.lang.String);
+  }
+
+}
+
 package android.telephony.gsm {
 
   public class GsmCellLocation extends android.telephony.CellLocation {
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index a474b47..a5e1934 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -339,7 +339,8 @@
     /**
      * Configure name servers, search paths, and resolver parameters for the given network.
      */
-    void setDnsConfigurationForNetwork(int netId, in String[] servers, String domains);
+    void setDnsConfigurationForNetwork(int netId, in String[] servers, in String[] domains,
+            in int[] params, boolean useTls, String tlsHostname);
 
     void setFirewallEnabled(boolean enabled);
     boolean isFirewallEnabled();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5228498..f34730c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -130,6 +130,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
@@ -232,8 +233,6 @@
     // 0 is full bad, 100 is full good
     private int mDefaultInetConditionPublished = 0;
 
-    private int mNumDnsEntries;
-
     private boolean mTestMode;
     private static ConnectivityService sServiceInstance;
 
@@ -407,6 +406,7 @@
     final private InternalHandler mHandler;
     /** Handler used for incoming {@link NetworkStateTracker} events. */
     final private NetworkStateTrackerHandler mTrackerHandler;
+    private final DnsManager mDnsManager;
 
     private boolean mSystemReady;
     private Intent mInitialBroadcast;
@@ -857,6 +857,8 @@
         mMultinetworkPolicyTracker = createMultinetworkPolicyTracker(
                 mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
         mMultinetworkPolicyTracker.start();
+
+        mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties);
     }
 
     private Tethering makeTethering() {
@@ -1803,24 +1805,6 @@
         }
     }
 
-    private void flushVmDnsCache() {
-        /*
-         * Tell the VMs to toss their DNS caches
-         */
-        Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        /*
-         * Connectivity events can happen before boot has completed ...
-         */
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
     @Override
     public int getRestoreDefaultNetworkDelay(int networkType) {
         String restoreDefaultNetworkDelayStr = mSystemProperties.get(
@@ -4558,41 +4542,17 @@
             return;  // no updating necessary
         }
 
+        final NetworkAgentInfo defaultNai = getDefaultNetwork();
+        final boolean isDefaultNetwork = (defaultNai != null && defaultNai.network.netId == netId);
+
         Collection<InetAddress> dnses = newLp.getDnsServers();
         if (DBG) log("Setting DNS servers for network " + netId + " to " + dnses);
         try {
-            mNetd.setDnsConfigurationForNetwork(
-                    netId, NetworkUtils.makeStrings(dnses), newLp.getDomains());
+            mDnsManager.setDnsConfigurationForNetwork(
+                    netId, dnses, newLp.getDomains(), isDefaultNetwork);
         } catch (Exception e) {
             loge("Exception in setDnsConfigurationForNetwork: " + e);
         }
-        final NetworkAgentInfo defaultNai = getDefaultNetwork();
-        if (defaultNai != null && defaultNai.network.netId == netId) {
-            setDefaultDnsSystemProperties(dnses);
-        }
-        flushVmDnsCache();
-    }
-
-    private void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
-        int last = 0;
-        for (InetAddress dns : dnses) {
-            ++last;
-            setNetDnsProperty(last, dns.getHostAddress());
-        }
-        for (int i = last + 1; i <= mNumDnsEntries; ++i) {
-            setNetDnsProperty(i, "");
-        }
-        mNumDnsEntries = last;
-    }
-
-    private void setNetDnsProperty(int which, String value) {
-        final String key = "net.dns" + which;
-        // Log and forget errors setting unsupported properties.
-        try {
-            mSystemProperties.set(key, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Error setting unsupported net.dns property: ", e);
-        }
     }
 
     private String getNetworkPermission(NetworkCapabilities nc) {
@@ -4865,7 +4825,7 @@
         notifyLockdownVpn(newNetwork);
         handleApplyDefaultProxy(newNetwork.linkProperties.getHttpProxy());
         updateTcpBufferSizes(newNetwork);
-        setDefaultDnsSystemProperties(newNetwork.linkProperties.getDnsServers());
+        mDnsManager.setDefaultDnsSystemProperties(newNetwork.linkProperties.getDnsServers());
     }
 
     private void processListenRequests(NetworkAgentInfo nai, boolean capabilitiesChanged) {
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 8a15ded..40e6d26 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.SHUTDOWN;
 import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
@@ -209,12 +210,6 @@
         public static final int StrictCleartext           = 617;
     }
 
-    /* Defaults for resolver parameters. */
-    public static final int DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800;
-    public static final int DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25;
-    public static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
-    public static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
-
     /**
      * String indicating a softap command.
      */
@@ -1768,6 +1763,8 @@
 
     @Override
     public boolean setDataSaverModeEnabled(boolean enable) {
+        mContext.enforceCallingOrSelfPermission(NETWORK_SETTINGS, TAG);
+
         if (DBG) Log.d(TAG, "setDataSaverMode: " + enable);
         synchronized (mQuotaLock) {
             if (mDataSaverMode == enable) {
@@ -1947,66 +1944,14 @@
     }
 
     @Override
-    public void setDnsConfigurationForNetwork(int netId, String[] servers, String domains) {
+    public void setDnsConfigurationForNetwork(int netId, String[] servers, String[] domains,
+                    int[] params, boolean useTls, String tlsHostname) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
 
-        final ContentResolver cr = mContext.getContentResolver();
-
-        int sampleValidity = Settings.Global.getInt(cr,
-                Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
-                DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
-        if (sampleValidity < 0 || sampleValidity > 65535) {
-            Slog.w(TAG, "Invalid sampleValidity=" + sampleValidity + ", using default=" +
-                    DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
-            sampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
-        }
-
-        int successThreshold = Settings.Global.getInt(cr,
-                Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
-                DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
-        if (successThreshold < 0 || successThreshold > 100) {
-            Slog.w(TAG, "Invalid successThreshold=" + successThreshold + ", using default=" +
-                    DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
-            successThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
-        }
-
-        int minSamples = Settings.Global.getInt(cr,
-                Settings.Global.DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
-        int maxSamples = Settings.Global.getInt(cr,
-                Settings.Global.DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
-        if (minSamples < 0 || minSamples > maxSamples || maxSamples > 64) {
-            Slog.w(TAG, "Invalid sample count (min, max)=(" + minSamples + ", " + maxSamples +
-                    "), using default=(" + DNS_RESOLVER_DEFAULT_MIN_SAMPLES + ", " +
-                    DNS_RESOLVER_DEFAULT_MAX_SAMPLES + ")");
-            minSamples = DNS_RESOLVER_DEFAULT_MIN_SAMPLES;
-            maxSamples = DNS_RESOLVER_DEFAULT_MAX_SAMPLES;
-        }
-
-        final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
-        final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
-        final boolean useTls = shouldUseTls(cr);
-        // TODO: Populate tlsHostname once it's decided how the hostname's IP
-        // addresses will be resolved:
-        //
-        //     [1] network-provided DNS servers are included here with the
-        //         hostname and netd will use the network-provided servers to
-        //         resolve the hostname and fix up its internal structures, or
-        //
-        //     [2] network-provided DNS servers are included here without the
-        //         hostname, the ConnectivityService layer resolves the given
-        //         hostname, and then reconfigures netd with this information.
-        //
-        // In practice, there will always be a need for ConnectivityService or
-        // the captive portal app to use the network-provided services to make
-        // some queries. This argues in favor of [1], in concert with another
-        // mechanism, perhaps setting a high bit in the netid, to indicate
-        // via existing DNS APIs which set of servers (network-provided or
-        // non-network-provided private DNS) should be queried.
-        final String tlsHostname = "";
         final String[] tlsFingerprints = new String[0];
         try {
-            mNetdService.setResolverConfiguration(netId, servers, domainStrs, params,
-                    useTls, tlsHostname, tlsFingerprints);
+            mNetdService.setResolverConfiguration(
+                    netId, servers, domains, params, useTls, tlsHostname, tlsFingerprints);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
new file mode 100644
index 0000000..a4170ce
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2018 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.connectivity;
+
+import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+import static android.provider.Settings.Global.DNS_RESOLVER_MIN_SAMPLES;
+import static android.provider.Settings.Global.DNS_RESOLVER_MAX_SAMPLES;
+import static android.provider.Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS;
+import static android.provider.Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT;
+import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
+import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkUtils;
+import android.os.Binder;
+import android.os.INetworkManagementService;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.server.connectivity.MockableSystemProperties;
+
+import java.net.InetAddress;
+import java.util.Collection;
+
+
+/**
+ * Encapsulate the management of DNS settings for networks.
+ *
+ * This class it NOT designed for concurrent access. Furthermore, all non-static
+ * methods MUST be called from ConnectivityService's thread.
+ *
+ * @hide
+ */
+public class DnsManager {
+    private static final String TAG = DnsManager.class.getSimpleName();
+
+    /* Defaults for resolver parameters. */
+    private static final int DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS = 1800;
+    private static final int DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT = 25;
+    private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
+    private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+    private final INetworkManagementService mNMS;
+    private final MockableSystemProperties mSystemProperties;
+
+    private int mNumDnsEntries;
+    private int mSampleValidity;
+    private int mSuccessThreshold;
+    private int mMinSamples;
+    private int mMaxSamples;
+    private String mPrivateDnsMode;
+    private String mPrivateDnsSpecifier;
+
+    public DnsManager(Context ctx, INetworkManagementService nms, MockableSystemProperties sp) {
+        mContext = ctx;
+        mContentResolver = mContext.getContentResolver();
+        mNMS = nms;
+        mSystemProperties = sp;
+
+        // TODO: Create and register ContentObservers to track every setting
+        // used herein, posting messages to respond to changes.
+    }
+
+    public boolean isPrivateDnsInStrictMode() {
+        return !TextUtils.isEmpty(mPrivateDnsMode) &&
+               mPrivateDnsMode.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) &&
+               !TextUtils.isEmpty(mPrivateDnsSpecifier);
+    }
+
+    public void setDnsConfigurationForNetwork(
+            int netId, Collection<InetAddress> servers, String domains, boolean isDefaultNetwork) {
+        updateParametersSettings();
+        updatePrivateDnsSettings();
+
+        final String[] serverStrs = NetworkUtils.makeStrings(servers);
+        final String[] domainStrs = (domains == null) ? new String[0] : domains.split(" ");
+        final int[] params = { mSampleValidity, mSuccessThreshold, mMinSamples, mMaxSamples };
+        final boolean useTls = shouldUseTls(mPrivateDnsMode);
+        // TODO: Populate tlsHostname once it's decided how the hostname's IP
+        // addresses will be resolved:
+        //
+        //     [1] network-provided DNS servers are included here with the
+        //         hostname and netd will use the network-provided servers to
+        //         resolve the hostname and fix up its internal structures, or
+        //
+        //     [2] network-provided DNS servers are included here without the
+        //         hostname, the ConnectivityService layer resolves the given
+        //         hostname, and then reconfigures netd with this information.
+        //
+        // In practice, there will always be a need for ConnectivityService or
+        // the captive portal app to use the network-provided services to make
+        // some queries. This argues in favor of [1], in concert with another
+        // mechanism, perhaps setting a high bit in the netid, to indicate
+        // via existing DNS APIs which set of servers (network-provided or
+        // non-network-provided private DNS) should be queried.
+        final String tlsHostname = "";
+        try {
+            mNMS.setDnsConfigurationForNetwork(
+                    netId, serverStrs, domainStrs, params, useTls, tlsHostname);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error setting DNS configuration: " + e);
+            return;
+        }
+
+        // TODO: netd should listen on [::1]:53 and proxy queries to the current
+        // default network, and we should just set net.dns1 to ::1, not least
+        // because applications attempting to use net.dns resolvers will bypass
+        // the privacy protections of things like DNS-over-TLS.
+        if (isDefaultNetwork) setDefaultDnsSystemProperties(servers);
+        flushVmDnsCache();
+    }
+
+    public void setDefaultDnsSystemProperties(Collection<InetAddress> dnses) {
+        int last = 0;
+        for (InetAddress dns : dnses) {
+            ++last;
+            setNetDnsProperty(last, dns.getHostAddress());
+        }
+        for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+            setNetDnsProperty(i, "");
+        }
+        mNumDnsEntries = last;
+    }
+
+    private void flushVmDnsCache() {
+        /*
+         * Tell the VMs to toss their DNS caches
+         */
+        final Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        /*
+         * Connectivity events can happen before boot has completed ...
+         */
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void updatePrivateDnsSettings() {
+        mPrivateDnsMode = getStringSetting(PRIVATE_DNS_MODE);
+        mPrivateDnsSpecifier = getStringSetting(PRIVATE_DNS_SPECIFIER);
+    }
+
+    private void updateParametersSettings() {
+        mSampleValidity = getIntSetting(
+                DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
+                DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
+        if (mSampleValidity < 0 || mSampleValidity > 65535) {
+            Slog.w(TAG, "Invalid sampleValidity=" + mSampleValidity + ", using default=" +
+                    DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
+            mSampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
+        }
+
+        mSuccessThreshold = getIntSetting(
+                DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
+                DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
+        if (mSuccessThreshold < 0 || mSuccessThreshold > 100) {
+            Slog.w(TAG, "Invalid successThreshold=" + mSuccessThreshold + ", using default=" +
+                    DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
+            mSuccessThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
+        }
+
+        mMinSamples = getIntSetting(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
+        mMaxSamples = getIntSetting(DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
+        if (mMinSamples < 0 || mMinSamples > mMaxSamples || mMaxSamples > 64) {
+            Slog.w(TAG, "Invalid sample count (min, max)=(" + mMinSamples + ", " + mMaxSamples +
+                    "), using default=(" + DNS_RESOLVER_DEFAULT_MIN_SAMPLES + ", " +
+                    DNS_RESOLVER_DEFAULT_MAX_SAMPLES + ")");
+            mMinSamples = DNS_RESOLVER_DEFAULT_MIN_SAMPLES;
+            mMaxSamples = DNS_RESOLVER_DEFAULT_MAX_SAMPLES;
+        }
+    }
+
+    private String getStringSetting(String which) {
+        return Settings.Global.getString(mContentResolver, which);
+    }
+
+    private int getIntSetting(String which, int dflt) {
+        return Settings.Global.getInt(mContentResolver, which, dflt);
+    }
+
+    private void setNetDnsProperty(int which, String value) {
+        final String key = "net.dns" + which;
+        // Log and forget errors setting unsupported properties.
+        try {
+            mSystemProperties.set(key, value);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error setting unsupported net.dns property: ", e);
+        }
+    }
+
+    private static boolean shouldUseTls(String mode) {
+        if (TextUtils.isEmpty(mode)) {
+            mode = PRIVATE_DNS_DEFAULT_MODE;
+        }
+        return mode.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
+               mode.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
index 17adb1a..2224913 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -30,6 +30,7 @@
 import android.net.ip.InterfaceController;
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.net.util.InterfaceParams;
 import android.net.util.NetdService;
 import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
@@ -48,7 +49,6 @@
 
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
@@ -120,8 +120,7 @@
     private int mLastError;
     private int mServingMode;
     private String mMyUpstreamIfaceName;  // may change over time
-    private NetworkInterface mNetworkInterface;
-    private byte[] mHwAddr;
+    private InterfaceParams mInterfaceParams;
     // TODO: De-duplicate this with mLinkProperties above. Currently, these link
     // properties are those selected by the IPv6TetheringCoordinator and relayed
     // to us. By comparison, mLinkProperties contains the addresses and directly
@@ -247,31 +246,16 @@
     }
 
     private boolean startIPv6() {
-        // TODO: Refactor for testability (perhaps passing an android.system.Os
-        // instance and calling getifaddrs() directly).
-        try {
-            mNetworkInterface = NetworkInterface.getByName(mIfaceName);
-        } catch (SocketException e) {
-            mLog.e("Error looking up NetworkInterfaces: " + e);
-            stopIPv6();
-            return false;
-        }
-        if (mNetworkInterface == null) {
-            mLog.e("Failed to find NetworkInterface");
+        // TODO: Refactor for better testability.  This is one of the things
+        // that prohibits unittesting IPv6 tethering setup.
+        mInterfaceParams = InterfaceParams.getByName(mIfaceName);
+        if (mInterfaceParams == null) {
+            mLog.e("Failed to find InterfaceParams");
             stopIPv6();
             return false;
         }
 
-        try {
-            mHwAddr = mNetworkInterface.getHardwareAddress();
-        } catch (SocketException e) {
-            mLog.e("Failed to find hardware address: " + e);
-            stopIPv6();
-            return false;
-        }
-
-        final int ifindex = mNetworkInterface.getIndex();
-        mRaDaemon = new RouterAdvertisementDaemon(mIfaceName, ifindex, mHwAddr);
+        mRaDaemon = new RouterAdvertisementDaemon(mInterfaceParams);
         if (!mRaDaemon.start()) {
             stopIPv6();
             return false;
@@ -281,8 +265,7 @@
     }
 
     private void stopIPv6() {
-        mNetworkInterface = null;
-        mHwAddr = null;
+        mInterfaceParams = null;
         setRaParams(null);
 
         if (mRaDaemon != null) {
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 31a1abb..7d9736e 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -38,6 +38,7 @@
 import android.net.metrics.ApfStats;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.RaEvent;
+import android.net.util.InterfaceParams;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.PacketSocketAddress;
@@ -56,7 +57,6 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
@@ -247,7 +247,7 @@
 
     private final ApfCapabilities mApfCapabilities;
     private final IpClient.Callback mIpClientCallback;
-    private final NetworkInterface mNetworkInterface;
+    private final InterfaceParams mInterfaceParams;
     private final IpConnectivityLog mMetricsLog;
 
     @VisibleForTesting
@@ -269,11 +269,11 @@
     private int mIPv4PrefixLength;
 
     @VisibleForTesting
-    ApfFilter(ApfConfiguration config, NetworkInterface networkInterface,
+    ApfFilter(ApfConfiguration config, InterfaceParams ifParams,
             IpClient.Callback ipClientCallback, IpConnectivityLog log) {
         mApfCapabilities = config.apfCapabilities;
         mIpClientCallback = ipClientCallback;
-        mNetworkInterface = networkInterface;
+        mInterfaceParams = ifParams;
         mMulticastFilter = config.multicastFilter;
         mDrop802_3Frames = config.ieee802_3Filter;
 
@@ -287,7 +287,7 @@
     }
 
     private void log(String s) {
-        Log.d(TAG, "(" + mNetworkInterface.getName() + "): " + s);
+        Log.d(TAG, "(" + mInterfaceParams.name + "): " + s);
     }
 
     @GuardedBy("this")
@@ -332,14 +332,14 @@
     void maybeStartFilter() {
         FileDescriptor socket;
         try {
-            mHardwareAddress = mNetworkInterface.getHardwareAddress();
+            mHardwareAddress = mInterfaceParams.macAddr.toByteArray();
             synchronized(this) {
                 // Install basic filters
                 installNewProgramLocked();
             }
             socket = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IPV6);
-            PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IPV6,
-                    mNetworkInterface.getIndex());
+            PacketSocketAddress addr = new PacketSocketAddress(
+                    (short) ETH_P_IPV6, mInterfaceParams.index);
             Os.bind(socket, addr);
             NetworkUtils.attachRaFilter(socket, mApfCapabilities.apfPacketFormat);
         } catch(SocketException|ErrnoException e) {
@@ -1168,10 +1168,10 @@
      * filtering using APF programs.
      */
     public static ApfFilter maybeCreate(ApfConfiguration config,
-            NetworkInterface networkInterface, IpClient.Callback ipClientCallback) {
-        if (config == null) return null;
+            InterfaceParams ifParams, IpClient.Callback ipClientCallback) {
+        if (config == null || ifParams == null) return null;
         ApfCapabilities apfCapabilities =  config.apfCapabilities;
-        if (apfCapabilities == null || networkInterface == null) return null;
+        if (apfCapabilities == null) return null;
         if (apfCapabilities.apfVersionSupported == 0) return null;
         if (apfCapabilities.maximumApfProgramSize < 512) {
             Log.e(TAG, "Unacceptably small APF limit: " + apfCapabilities.maximumApfProgramSize);
@@ -1186,7 +1186,7 @@
             Log.e(TAG, "Unsupported APF version: " + apfCapabilities.apfVersionSupported);
             return null;
         }
-        return new ApfFilter(config, networkInterface, ipClientCallback, new IpConnectivityLog());
+        return new ApfFilter(config, ifParams, ipClientCallback, new IpConnectivityLog());
     }
 
     public synchronized void shutdown() {
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index ed78175..a956cef 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -34,6 +34,7 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.DhcpClientEvent;
 import android.net.metrics.DhcpErrorEvent;
+import android.net.util.InterfaceParams;
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -50,7 +51,6 @@
 import java.io.IOException;
 import java.lang.Thread;
 import java.net.Inet4Address;
-import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
@@ -187,7 +187,8 @@
     private final String mIfaceName;
 
     private boolean mRegisteredForPreDhcpNotification;
-    private NetworkInterface mIface;
+    private InterfaceParams mIface;
+    // TODO: MacAddress-ify more of this class hierarchy.
     private byte[] mHwAddr;
     private PacketSocketAddress mInterfaceBroadcastAddr;
     private int mTransactionId;
@@ -221,8 +222,9 @@
         return new WakeupMessage(mContext, getHandler(), cmdName, cmd);
     }
 
+    // TODO: Take an InterfaceParams instance instead of an interface name String.
     private DhcpClient(Context context, StateMachine controller, String iface) {
-        super(TAG);
+        super(TAG, controller.getHandler());
 
         mContext = context;
         mController = controller;
@@ -262,23 +264,23 @@
     }
 
     public static DhcpClient makeDhcpClient(
-            Context context, StateMachine controller, String intf) {
-        DhcpClient client = new DhcpClient(context, controller, intf);
+            Context context, StateMachine controller, InterfaceParams ifParams) {
+        DhcpClient client = new DhcpClient(context, controller, ifParams.name);
+        client.mIface = ifParams;
         client.start();
         return client;
     }
 
     private boolean initInterface() {
-        try {
-            mIface = NetworkInterface.getByName(mIfaceName);
-            mHwAddr = mIface.getHardwareAddress();
-            mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.getIndex(),
-                    DhcpPacket.ETHER_BROADCAST);
-            return true;
-        } catch(SocketException | NullPointerException e) {
-            Log.e(TAG, "Can't determine ifindex or MAC address for " + mIfaceName, e);
+        if (mIface == null) mIface = InterfaceParams.getByName(mIfaceName);
+        if (mIface == null) {
+            Log.e(TAG, "Can't determine InterfaceParams for " + mIfaceName);
             return false;
         }
+
+        mHwAddr = mIface.macAddr.toByteArray();
+        mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.index, DhcpPacket.ETHER_BROADCAST);
+        return true;
     }
 
     private void startNewTransaction() {
@@ -293,7 +295,7 @@
     private boolean initPacketSocket() {
         try {
             mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
-            PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.getIndex());
+            PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.index);
             Os.bind(mPacketSock, addr);
             NetworkUtils.attachDhcpFilter(mPacketSock);
         } catch(SocketException|ErrnoException e) {
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
index 6cf4fa9a..e6ddbbc 100644
--- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java
+++ b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
@@ -21,6 +21,7 @@
 import android.net.NetworkUtils;
 import android.net.util.PacketReader;
 import android.net.util.ConnectivityPacketSummary;
+import android.net.util.InterfaceParams;
 import android.os.Handler;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -35,7 +36,6 @@
 import java.io.FileDescriptor;
 import java.io.InterruptedIOException;
 import java.io.IOException;
-import java.net.NetworkInterface;
 import java.net.SocketException;
 
 
@@ -69,24 +69,12 @@
     private boolean mRunning;
     private String mDisplayName;
 
-    public ConnectivityPacketTracker(Handler h, NetworkInterface netif, LocalLog log) {
-        final String ifname;
-        final int ifindex;
-        final byte[] hwaddr;
-        final int mtu;
+    public ConnectivityPacketTracker(Handler h, InterfaceParams ifParams, LocalLog log) {
+        if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
 
-        try {
-            ifname = netif.getName();
-            ifindex = netif.getIndex();
-            hwaddr = netif.getHardwareAddress();
-            mtu = netif.getMTU();
-        } catch (NullPointerException|SocketException e) {
-            throw new IllegalArgumentException("bad network interface", e);
-        }
-
-        mTag = TAG + "." + ifname;
+        mTag = TAG + "." + ifParams.name;
         mLog = log;
-        mPacketListener = new PacketListener(h, ifindex, hwaddr, mtu);
+        mPacketListener = new PacketListener(h, ifParams);
     }
 
     public void start(String displayName) {
@@ -102,13 +90,11 @@
     }
 
     private final class PacketListener extends PacketReader {
-        private final int mIfIndex;
-        private final byte mHwAddr[];
+        private final InterfaceParams mInterface;
 
-        PacketListener(Handler h, int ifindex, byte[] hwaddr, int mtu) {
-            super(h, mtu);
-            mIfIndex = ifindex;
-            mHwAddr = hwaddr;
+        PacketListener(Handler h, InterfaceParams ifParams) {
+            super(h, ifParams.defaultMtu);
+            mInterface = ifParams;
         }
 
         @Override
@@ -117,7 +103,7 @@
             try {
                 s = Os.socket(AF_PACKET, SOCK_RAW, 0);
                 NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
-                Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex));
+                Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mInterface.index));
             } catch (ErrnoException | IOException e) {
                 logError("Failed to create packet tracking socket: ", e);
                 closeFd(s);
@@ -129,7 +115,7 @@
         @Override
         protected void handlePacket(byte[] recvbuf, int length) {
             final String summary = ConnectivityPacketSummary.summarize(
-                    mHwAddr, recvbuf, length);
+                    mInterface.macAddr, recvbuf, length);
             if (summary == null) return;
 
             if (DBG) Log.d(mTag, summary);
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index fdb366c..d3a97b3 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -35,6 +35,7 @@
 import android.net.dhcp.DhcpClient;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
+import android.net.util.InterfaceParams;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
 import android.net.util.NetworkConstants;
@@ -63,7 +64,6 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -556,7 +556,7 @@
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
     private final InterfaceController mInterfaceCtrl;
 
-    private NetworkInterface mNetworkInterface;
+    private InterfaceParams mInterfaceParams;
 
     /**
      * Non-final member variables accessed only from within our StateMachine.
@@ -722,7 +722,12 @@
             return;
         }
 
-        getNetworkInterface();
+        mInterfaceParams = InterfaceParams.getByName(mInterfaceName);
+        if (mInterfaceParams == null) {
+            logError("Failed to find InterfaceParams for " + mInterfaceName);
+            // TODO: call doImmediateProvisioningFailure() with an error code
+            // indicating something like "interface not ready".
+        }
 
         mCallback.setNeighborDiscoveryOffload(true);
         sendMessage(CMD_START, new ProvisioningConfiguration(req));
@@ -858,7 +863,7 @@
     protected String getLogRecString(Message msg) {
         final String logLine = String.format(
                 "%s/%d %d %d %s [%s]",
-                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
+                mInterfaceName, (mInterfaceParams == null) ? -1 : mInterfaceParams.index,
                 msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger);
 
         final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
@@ -889,15 +894,6 @@
         mLog.log(msg);
     }
 
-    private void getNetworkInterface() {
-        try {
-            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
-        } catch (SocketException | NullPointerException e) {
-            // TODO: throw new IllegalStateException.
-            logError("Failed to get interface object: %s", e);
-        }
-    }
-
     // This needs to be called with care to ensure that our LinkProperties
     // are in sync with the actual LinkProperties of the interface. For example,
     // we should only call this if we know for sure that there are no IP addresses
@@ -1218,7 +1214,7 @@
             }
         } else {
             // Start DHCPv4.
-            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceName);
+            mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams);
             mDhcpClient.registerForPreDhcpNotification();
             mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
         }
@@ -1245,7 +1241,7 @@
         try {
             mIpReachabilityMonitor = new IpReachabilityMonitor(
                     mContext,
-                    mInterfaceName,
+                    mInterfaceParams,
                     getHandler(),
                     mLog,
                     new IpReachabilityMonitor.Callback() {
@@ -1447,7 +1443,7 @@
                     mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames);
             apfConfig.ethTypeBlackList =
                     mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList);
-            mApfFilter = ApfFilter.maybeCreate(apfConfig, mNetworkInterface, mCallback);
+            mApfFilter = ApfFilter.maybeCreate(apfConfig, mInterfaceParams, mCallback);
             // TODO: investigate the effects of any multicast filtering racing/interfering with the
             // rest of this IP configuration startup.
             if (mApfFilter == null) {
@@ -1515,7 +1511,7 @@
         private ConnectivityPacketTracker createPacketTracker() {
             try {
                 return new ConnectivityPacketTracker(
-                        getHandler(), mNetworkInterface, mConnectivityPacketLog);
+                        getHandler(), mInterfaceParams, mConnectivityPacketLog);
             } catch (IllegalArgumentException e) {
                 return null;
             }
diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/services/net/java/android/net/ip/IpNeighborMonitor.java
index 6807334..fc07aa1 100644
--- a/services/net/java/android/net/ip/IpNeighborMonitor.java
+++ b/services/net/java/android/net/ip/IpNeighborMonitor.java
@@ -16,7 +16,11 @@
 
 package android.net.ip;
 
-import android.net.netlink.NetlinkConstants;
+import static android.net.netlink.NetlinkConstants.hexify;
+import static android.net.netlink.NetlinkConstants.RTM_DELNEIGH;
+import static android.net.netlink.NetlinkConstants.stringForNlMsgType;
+
+import android.net.MacAddress;
 import android.net.netlink.NetlinkErrorMessage;
 import android.net.netlink.NetlinkMessage;
 import android.net.netlink.NetlinkSocket;
@@ -92,37 +96,35 @@
         final int ifindex;
         final InetAddress ip;
         final short nudState;
-        final byte[] linkLayerAddr;
+        final MacAddress macAddr;
 
         public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
-                short nudState, byte[] linkLayerAddr) {
+                short nudState, MacAddress macAddr) {
             this.elapsedMs = elapsedMs;
             this.msgType = msgType;
             this.ifindex = ifindex;
             this.ip = ip;
             this.nudState = nudState;
-            this.linkLayerAddr = linkLayerAddr;
+            this.macAddr = macAddr;
         }
 
         boolean isConnected() {
-            return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
-                   StructNdMsg.isNudStateConnected(nudState);
+            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
         }
 
         boolean isValid() {
-            return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
-                   StructNdMsg.isNudStateValid(nudState);
+            return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
         }
 
         @Override
         public String toString() {
             final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
             return j.add("@" + elapsedMs)
-                    .add(NetlinkConstants.stringForNlMsgType(msgType))
+                    .add(stringForNlMsgType(msgType))
                     .add("if=" + ifindex)
                     .add(ip.getHostAddress())
                     .add(StructNdMsg.stringForNudState(nudState))
-                    .add("[" + NetlinkConstants.hexify(linkLayerAddr) + "]")
+                    .add("[" + macAddr + "]")
                     .toString();
         }
     }
@@ -183,7 +185,7 @@
             final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
             if (nlMsg == null || nlMsg.getHeader() == null) {
                 byteBuffer.position(position);
-                mLog.e("unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
+                mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
                 break;
             }
 
@@ -217,12 +219,13 @@
         final int ifindex = ndMsg.ndm_ifindex;
         final InetAddress destination = neighMsg.getDestination();
         final short nudState =
-                (msgType == NetlinkConstants.RTM_DELNEIGH)
+                (msgType == RTM_DELNEIGH)
                 ? StructNdMsg.NUD_NONE
                 : ndMsg.ndm_state;
 
         final NeighborEvent event = new NeighborEvent(
-                whenMs, msgType, ifindex, destination, nudState, neighMsg.getLinkLayerAddress());
+                whenMs, msgType, ifindex, destination, nudState,
+                getMacAddress(neighMsg.getLinkLayerAddress()));
 
         if (VDBG) {
             Log.d(TAG, neighMsg.toString());
@@ -233,4 +236,16 @@
 
         mConsumer.accept(event);
     }
+
+    private static MacAddress getMacAddress(byte[] linkLayerAddress) {
+        if (linkLayerAddress != null) {
+            try {
+                return MacAddress.fromBytes(linkLayerAddress);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress));
+            }
+        }
+
+        return null;
+    }
 }
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index b31ffbb..7e02a28 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -26,6 +26,7 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpReachabilityEvent;
 import android.net.netlink.StructNdMsg;
+import android.net.util.InterfaceParams;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.SharedLog;
 import android.os.Handler;
@@ -46,9 +47,7 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
-import java.net.NetworkInterface;
 import java.net.SocketAddress;
-import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -168,8 +167,7 @@
         }
     }
 
-    private final String mInterfaceName;
-    private final int mInterfaceIndex;
+    private final InterfaceParams mInterfaceParams;
     private final IpNeighborMonitor mIpNeighborMonitor;
     private final SharedLog mLog;
     private final Callback mCallback;
@@ -182,30 +180,25 @@
     private volatile long mLastProbeTimeMs;
 
     public IpReachabilityMonitor(
-            Context context, String ifName, Handler h, SharedLog log, Callback callback) {
-        this(context, ifName, h, log, callback, null);
-    }
-
-    public IpReachabilityMonitor(
-            Context context, String ifName, Handler h, SharedLog log, Callback callback,
+            Context context, InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
             MultinetworkPolicyTracker tracker) {
-        this(ifName, getInterfaceIndex(ifName), h, log, callback, tracker,
-                Dependencies.makeDefault(context, ifName));
+        this(ifParams, h, log, callback, tracker, Dependencies.makeDefault(context, ifParams.name));
     }
 
     @VisibleForTesting
-    IpReachabilityMonitor(String ifName, int ifIndex, Handler h, SharedLog log, Callback callback,
+    IpReachabilityMonitor(InterfaceParams ifParams, Handler h, SharedLog log, Callback callback,
             MultinetworkPolicyTracker tracker, Dependencies dependencies) {
-        mInterfaceName = ifName;
+        if (ifParams == null) throw new IllegalArgumentException("null InterfaceParams");
+
+        mInterfaceParams = ifParams;
         mLog = log.forSubComponent(TAG);
         mCallback = callback;
         mMultinetworkPolicyTracker = tracker;
-        mInterfaceIndex = ifIndex;
         mDependencies = dependencies;
 
         mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
                 (NeighborEvent event) -> {
-                    if (mInterfaceIndex != event.ifindex) return;
+                    if (mInterfaceParams.index != event.ifindex) return;
                     if (!mNeighborWatchList.containsKey(event.ip)) return;
 
                     final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
@@ -241,7 +234,7 @@
 
     private String describeWatchList(String sep) {
         final StringBuilder sb = new StringBuilder();
-        sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}," + sep);
+        sb.append("iface{" + mInterfaceParams + "}," + sep);
         sb.append("ntable=[" + sep);
         String delimiter = "";
         for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
@@ -262,10 +255,10 @@
     }
 
     public void updateLinkProperties(LinkProperties lp) {
-        if (!mInterfaceName.equals(lp.getInterfaceName())) {
+        if (!mInterfaceParams.name.equals(lp.getInterfaceName())) {
             // TODO: figure out whether / how to cope with interface changes.
             Log.wtf(TAG, "requested LinkProperties interface '" + lp.getInterfaceName() +
-                    "' does not match: " + mInterfaceName);
+                    "' does not match: " + mInterfaceParams.name);
             return;
         }
 
@@ -353,10 +346,10 @@
             mDependencies.acquireWakeLock(getProbeWakeLockDuration());
         }
 
-        for (InetAddress target : ipProbeList) {
-            final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceIndex, target);
+        for (InetAddress ip : ipProbeList) {
+            final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceParams.index, ip);
             mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
-                     target.getHostAddress(), rval));
+                     ip.getHostAddress(), rval));
             logEvent(IpReachabilityEvent.PROBE, rval);
         }
         mLastProbeTimeMs = SystemClock.elapsedRealtime();
@@ -378,22 +371,9 @@
         return (numUnicastProbes * retransTimeMs) + gracePeriodMs;
     }
 
-    private static int getInterfaceIndex(String ifname) {
-        final NetworkInterface iface;
-        try {
-            iface = NetworkInterface.getByName(ifname);
-        } catch (SocketException e) {
-            throw new IllegalArgumentException("invalid interface '" + ifname + "': ", e);
-        }
-        if (iface == null) {
-            throw new IllegalArgumentException("NetworkInterface was null for " + ifname);
-        }
-        return iface.getIndex();
-    }
-
     private void logEvent(int probeType, int errorCode) {
         int eventType = probeType | (errorCode & 0xff);
-        mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
+        mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
     }
 
     private void logNudFailed(ProvisioningChange delta) {
@@ -401,6 +381,6 @@
         boolean isFromProbe = (duration < getProbeWakeLockDuration());
         boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING);
         int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
-        mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
+        mMetricsLog.log(mInterfaceParams.name, new IpReachabilityEvent(eventType));
     }
 }
diff --git a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
index cb3123c..49a1e79 100644
--- a/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
+++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
@@ -25,6 +25,7 @@
 import android.net.LinkProperties;
 import android.net.NetworkUtils;
 import android.net.TrafficStats;
+import android.net.util.InterfaceParams;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.StructGroupReq;
@@ -96,9 +97,7 @@
             (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
     };
 
-    private final String mIfName;
-    private final int mIfIndex;
-    private final byte[] mHwAddr;
+    private final InterfaceParams mInterface;
     private final InetSocketAddress mAllNodes;
 
     // This lock is to protect the RA from being updated while being
@@ -223,11 +222,9 @@
     }
 
 
-    public RouterAdvertisementDaemon(String ifname, int ifindex, byte[] hwaddr) {
-        mIfName = ifname;
-        mIfIndex = ifindex;
-        mHwAddr = hwaddr;
-        mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 0);
+    public RouterAdvertisementDaemon(InterfaceParams ifParams) {
+        mInterface = ifParams;
+        mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0);
         mDeprecatedInfoTracker = new DeprecatedInfoTracker();
     }
 
@@ -279,7 +276,7 @@
 
         try {
             putHeader(ra, mRaParams != null && mRaParams.hasDefaultRoute);
-            putSlla(ra, mHwAddr);
+            putSlla(ra, mInterface.macAddr.toByteArray());
             mRaLength = ra.position();
 
             // https://tools.ietf.org/html/rfc5175#section-4 says:
@@ -579,9 +576,9 @@
             // Setting SNDTIMEO is purely for defensive purposes.
             Os.setsockoptTimeval(
                     mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(SEND_TIMEOUT_MS));
-            Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIfName);
+            Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mInterface.name);
             NetworkUtils.protectFromVpn(mSocket);
-            NetworkUtils.setupRaSocket(mSocket, mIfIndex);
+            NetworkUtils.setupRaSocket(mSocket, mInterface.index);
         } catch (ErrnoException | IOException e) {
             Log.e(TAG, "Failed to create RA daemon socket: " + e);
             return false;
@@ -614,7 +611,7 @@
         final InetAddress destip = dest.getAddress();
         return (destip instanceof Inet6Address) &&
                 destip.isLinkLocalAddress() &&
-               (((Inet6Address) destip).getScopeId() == mIfIndex);
+               (((Inet6Address) destip).getScopeId() == mInterface.index);
     }
 
     private void maybeSendRA(InetSocketAddress dest) {
diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/services/net/java/android/net/util/ConnectivityPacketSummary.java
index dae93af..4951400 100644
--- a/services/net/java/android/net/util/ConnectivityPacketSummary.java
+++ b/services/net/java/android/net/util/ConnectivityPacketSummary.java
@@ -17,6 +17,7 @@
 package android.net.util;
 
 import android.net.dhcp.DhcpPacket;
+import android.net.MacAddress;
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -45,21 +46,20 @@
     private final ByteBuffer mPacket;
     private final String mSummary;
 
-    public static String summarize(byte[] hwaddr, byte[] buffer) {
+    public static String summarize(MacAddress hwaddr, byte[] buffer) {
         return summarize(hwaddr, buffer, buffer.length);
     }
 
     // Methods called herein perform some but by no means all error checking.
     // They may throw runtime exceptions on malformed packets.
-    public static String summarize(byte[] hwaddr, byte[] buffer, int length) {
-        if ((hwaddr == null) || (hwaddr.length != ETHER_ADDR_LEN)) return null;
-        if (buffer == null) return null;
+    public static String summarize(MacAddress macAddr, byte[] buffer, int length) {
+        if ((macAddr == null) || (buffer == null)) return null;
         length = Math.min(length, buffer.length);
-        return (new ConnectivityPacketSummary(hwaddr, buffer, length)).toString();
+        return (new ConnectivityPacketSummary(macAddr, buffer, length)).toString();
     }
 
-    private ConnectivityPacketSummary(byte[] hwaddr, byte[] buffer, int length) {
-        mHwAddr = hwaddr;
+    private ConnectivityPacketSummary(MacAddress macAddr, byte[] buffer, int length) {
+        mHwAddr = macAddr.toByteArray();
         mBytes = buffer;
         mLength = Math.min(length, mBytes.length);
         mPacket = ByteBuffer.wrap(mBytes, 0, mLength);
diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java
new file mode 100644
index 0000000..a4b2fbb
--- /dev/null
+++ b/services/net/java/android/net/util/InterfaceParams.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import static android.net.MacAddress.ALL_ZEROS_ADDRESS;
+import static android.net.util.NetworkConstants.ETHER_MTU;
+import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
+import static com.android.internal.util.Preconditions.checkArgument;
+
+import android.net.MacAddress;
+import android.text.TextUtils;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
+
+
+/**
+ * Encapsulate the interface parameters common to IpClient/IpServer components.
+ *
+ * Basically all java.net.NetworkInterface methods throw Exceptions. IpClient
+ * and IpServer (sub)components need most or all of this information at some
+ * point during their lifecycles, so pass only this simplified object around
+ * which can be created once when IpClient/IpServer are told to start.
+ *
+ * @hide
+ */
+public class InterfaceParams {
+    public final String name;
+    public final int index;
+    public final MacAddress macAddr;
+    public final int defaultMtu;
+
+    public static InterfaceParams getByName(String name) {
+        final NetworkInterface netif = getNetworkInterfaceByName(name);
+        if (netif == null) return null;
+
+        // Not all interfaces have MAC addresses, e.g. rmnet_data0.
+        final MacAddress macAddr = getMacAddress(netif);
+
+        try {
+            return new InterfaceParams(name, netif.getIndex(), macAddr, netif.getMTU());
+        } catch (IllegalArgumentException|SocketException e) {
+            return null;
+        }
+    }
+
+    public InterfaceParams(String name, int index, MacAddress macAddr) {
+        this(name, index, macAddr, ETHER_MTU);
+    }
+
+    public InterfaceParams(String name, int index, MacAddress macAddr, int defaultMtu) {
+        checkArgument((!TextUtils.isEmpty(name)), "impossible interface name");
+        checkArgument((index > 0), "invalid interface index");
+        this.name = name;
+        this.index = index;
+        this.macAddr = (macAddr != null) ? macAddr : ALL_ZEROS_ADDRESS;
+        this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s/%d/%s/%d", name, index, macAddr, defaultMtu);
+    }
+
+    private static NetworkInterface getNetworkInterfaceByName(String name) {
+        try {
+            return NetworkInterface.getByName(name);
+        } catch (NullPointerException|SocketException e) {
+            return null;
+        }
+    }
+
+    private static MacAddress getMacAddress(NetworkInterface netif) {
+        try {
+            return MacAddress.fromBytes(netif.getHardwareAddress());
+        } catch (IllegalArgumentException|NullPointerException|SocketException e) {
+            return null;
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/data/ApnSetting.aidl b/telephony/java/android/telephony/data/ApnSetting.aidl
new file mode 100644
index 0000000..381e5e8
--- /dev/null
+++ b/telephony/java/android/telephony/data/ApnSetting.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2018 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.telephony.data;
+
+parcelable ApnSetting;
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
new file mode 100644
index 0000000..2ab8d4f
--- /dev/null
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -0,0 +1,1370 @@
+/*
+ * Copyright (C) 2018 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.telephony.data;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.hardware.radio.V1_0.ApnTypes;
+import android.net.NetworkUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony;
+import android.telephony.Rlog;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.MalformedURLException;
+import java.net.UnknownHostException;
+import java.net.URL;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A class representing an APN configuration.
+ */
+public class ApnSetting implements Parcelable {
+
+    static final String LOG_TAG = "ApnSetting";
+    private static final boolean VDBG = false;
+
+    private final String mEntryName;
+    private final String mApnName;
+    private final InetAddress mProxy;
+    private final int mPort;
+    private final URL mMmsc;
+    private final InetAddress mMmsProxy;
+    private final int mMmsPort;
+    private final String mUser;
+    private final String mPassword;
+    private final int mAuthType;
+    private final List<String> mTypes;
+    private final int mTypesBitmap;
+    private final int mId;
+    private final String mOperatorNumeric;
+    private final String mProtocol;
+    private final String mRoamingProtocol;
+    private final int mMtu;
+
+    private final boolean mCarrierEnabled;
+    private final int mBearer;
+    private final int mBearerBitmask;
+
+    private final int mProfileId;
+
+    private final boolean mModemCognitive;
+    private final int mMaxConns;
+    private final int mWaitTime;
+    private final int mMaxConnsTime;
+
+    private final String mMvnoType;
+    private final String mMvnoMatchData;
+
+    private boolean mPermanentFailed = false;
+
+    /**
+     * Returns the types bitmap of the APN.
+     *
+     * @return types bitmap of the APN
+     * @hide
+     */
+    public int getTypesBitmap() {
+        return mTypesBitmap;
+    }
+
+    /**
+     * Returns the MTU size of the mobile interface to which the APN connected.
+     *
+     * @return the MTU size of the APN
+     * @hide
+     */
+    public int getMtu() {
+        return mMtu;
+    }
+
+    /**
+     * Radio Access Technology info.
+     * To check what values can hold, refer to ServiceState.java.
+     * This should be spread to other technologies,
+     * but currently only used for LTE(14) and EHRPD(13).
+     *
+     * @return the bearer info of the APN
+     * @hide
+     */
+    public int getBearer() {
+        return mBearer;
+    }
+
+    /**
+     * Returns the radio access technology bitmask for this APN.
+     *
+     * To check what values can hold, refer to ServiceState.java. This is a bitmask of radio
+     * technologies in ServiceState.
+     * This should be spread to other technologies,
+     * but currently only used for LTE(14) and EHRPD(13).
+     *
+     * @return the radio access technology bitmask
+     * @hide
+     */
+    public int getBearerBitmask() {
+        return mBearerBitmask;
+    }
+
+    /**
+     * Returns the profile id to which the APN saved in modem.
+     *
+     * @return the profile id of the APN
+     * @hide
+     */
+    public int getProfileId() {
+        return mProfileId;
+    }
+
+    /**
+     * Returns if the APN setting is to be set in modem.
+     *
+     * @return is the APN setting to be set in modem
+     * @hide
+     */
+    public boolean getModemCognitive() {
+        return mModemCognitive;
+    }
+
+    /**
+     * Returns the max connections of this APN.
+     *
+     * @return the max connections of this APN
+     * @hide
+     */
+    public int getMaxConns() {
+        return mMaxConns;
+    }
+
+    /**
+     * Returns the wait time for retry of the APN.
+     *
+     * @return the wait time for retry of the APN
+     * @hide
+     */
+    public int getWaitTime() {
+        return mWaitTime;
+    }
+
+    /**
+     * Returns the time to limit max connection for the APN.
+     *
+     * @return the time to limit max connection for the APN
+     * @hide
+     */
+    public int getMaxConnsTime() {
+        return mMaxConnsTime;
+    }
+
+    /**
+     * Returns the MVNO data. Examples:
+     *   "spn": A MOBILE, BEN NL
+     *   "imsi": 302720x94, 2060188
+     *   "gid": 4E, 33
+     *   "iccid": 898603 etc..
+     *
+     * @return the mvno match data
+     * @hide
+     */
+    public String getMvnoMatchData() {
+        return mMvnoMatchData;
+    }
+
+    /**
+     * Indicates this APN setting is permanently failed and cannot be
+     * retried by the retry manager anymore.
+     *
+     * @return if this APN setting is permanently failed
+     * @hide
+     */
+    public boolean getPermanentFailed() {
+        return mPermanentFailed;
+    }
+
+    /**
+     * Sets if this APN setting is permanently failed.
+     *
+     * @param permanentFailed if this APN setting is permanently failed
+     * @hide
+     */
+    public void setPermanentFailed(boolean permanentFailed) {
+        mPermanentFailed = permanentFailed;
+    }
+
+    /**
+     * Returns the entry name of the APN.
+     *
+     * @return the entry name for the APN
+     */
+    public String getEntryName() {
+        return mEntryName;
+    }
+
+    /**
+     * Returns the name of the APN.
+     *
+     * @return APN name
+     */
+    public String getApnName() {
+        return mApnName;
+    }
+
+    /**
+     * Returns the proxy address of the APN.
+     *
+     * @return proxy address.
+     */
+    public InetAddress getProxy() {
+        return mProxy;
+    }
+
+    /**
+     * Returns the proxy port of the APN.
+     *
+     * @return proxy port
+     */
+    public int getPort() {
+        return mPort;
+    }
+    /**
+     * Returns the MMSC URL of the APN.
+     *
+     * @return MMSC URL.
+     */
+    public URL getMmsc() {
+        return mMmsc;
+    }
+
+    /**
+     * Returns the MMS proxy address of the APN.
+     *
+     * @return MMS proxy address.
+     */
+    public InetAddress getMmsProxy() {
+        return mMmsProxy;
+    }
+
+    /**
+     * Returns the MMS proxy port of the APN.
+     *
+     * @return MMS proxy port
+     */
+    public int getMmsPort() {
+        return mMmsPort;
+    }
+
+    /**
+     * Returns the APN username of the APN.
+     *
+     * @return APN username
+     */
+    public String getUser() {
+        return mUser;
+    }
+
+    /**
+     * Returns the APN password of the APN.
+     *
+     * @return APN password
+     */
+    public String getPassword() {
+        return mPassword;
+    }
+
+    /** @hide */
+    @IntDef({
+            AUTH_TYPE_NONE,
+            AUTH_TYPE_PAP,
+            AUTH_TYPE_CHAP,
+            AUTH_TYPE_PAP_OR_CHAP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AuthType {}
+
+    /**
+     * Returns the authentication type of the APN.
+     *
+     * Example of possible values: {@link #AUTH_TYPE_NONE}, {@link #AUTH_TYPE_PAP}.
+     *
+     * @return authentication type
+     */
+    @AuthType
+    public int getAuthType() {
+        return mAuthType;
+    }
+
+    /** @hide */
+    @StringDef({
+            TYPE_DEFAULT,
+            TYPE_MMS,
+            TYPE_SUPL,
+            TYPE_DUN,
+            TYPE_HIPRI,
+            TYPE_FOTA,
+            TYPE_IMS,
+            TYPE_CBS,
+            TYPE_IA,
+            TYPE_EMERGENCY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ApnType {}
+
+    /**
+     * Returns the list of APN types of the APN.
+     *
+     * Example of possible values: {@link #TYPE_DEFAULT}, {@link #TYPE_MMS}.
+     *
+     * @return the list of APN types
+     */
+    @ApnType
+    public List<String> getTypes() {
+        return mTypes;
+    }
+
+    /**
+     * Returns the unique database id for this entry.
+     *
+     * @return the unique database id
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Returns the numeric operator ID for the APN. Usually
+     * {@link android.provider.Telephony.Carriers#MCC} +
+     * {@link android.provider.Telephony.Carriers#MNC}.
+     *
+     * @return the numeric operator ID
+     */
+    public String getOperatorNumeric() {
+        return mOperatorNumeric;
+    }
+
+    /** @hide */
+    @StringDef({
+            PROTOCOL_IP,
+            PROTOCOL_IPV6,
+            PROTOCOL_IPV4V6,
+            PROTOCOL_PPP,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProtocolType {}
+
+    /**
+     * Returns the protocol to use to connect to this APN.
+     *
+     * One of the {@code PDP_type} values in TS 27.007 section 10.1.1.
+     * Example of possible values: {@link #PROTOCOL_IP}, {@link #PROTOCOL_IPV6}.
+     *
+     * @return the protocol
+     */
+    @ProtocolType
+    public String getProtocol() {
+        return mProtocol;
+    }
+
+    /**
+     * Returns the protocol to use to connect to this APN when roaming.
+     *
+     * The syntax is the same as {@link android.provider.Telephony.Carriers#PROTOCOL}.
+     *
+     * @return the roaming protocol
+     */
+    public String getRoamingProtocol() {
+        return mRoamingProtocol;
+    }
+
+    /**
+     * Returns the current status of APN.
+     *
+     * {@code true} : enabled APN.
+     * {@code false} : disabled APN.
+     *
+     * @return the current status
+     */
+    public boolean isEnabled() {
+        return mCarrierEnabled;
+    }
+
+    /** @hide */
+    @StringDef({
+            MVNO_TYPE_SPN,
+            MVNO_TYPE_IMSI,
+            MVNO_TYPE_GID,
+            MVNO_TYPE_ICCID,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MvnoType {}
+
+    /**
+     * Returns the MVNO match type for this APN.
+     *
+     * Example of possible values: {@link #MVNO_TYPE_SPN}, {@link #MVNO_TYPE_IMSI}.
+     *
+     * @return the MVNO match type
+     */
+    @MvnoType
+    public String getMvnoType() {
+        return mMvnoType;
+    }
+
+    private ApnSetting(Builder builder) {
+        this.mEntryName = builder.mEntryName;
+        this.mApnName = builder.mApnName;
+        this.mProxy = builder.mProxy;
+        this.mPort = builder.mPort;
+        this.mMmsc = builder.mMmsc;
+        this.mMmsProxy = builder.mMmsProxy;
+        this.mMmsPort = builder.mMmsPort;
+        this.mUser = builder.mUser;
+        this.mPassword = builder.mPassword;
+        this.mAuthType = builder.mAuthType;
+        this.mTypes = (builder.mTypes == null ? new ArrayList<String>() : builder.mTypes);
+        this.mTypesBitmap = builder.mTypesBitmap;
+        this.mId = builder.mId;
+        this.mOperatorNumeric = builder.mOperatorNumeric;
+        this.mProtocol = builder.mProtocol;
+        this.mRoamingProtocol = builder.mRoamingProtocol;
+        this.mMtu = builder.mMtu;
+        this.mCarrierEnabled = builder.mCarrierEnabled;
+        this.mBearer = builder.mBearer;
+        this.mBearerBitmask = builder.mBearerBitmask;
+        this.mProfileId = builder.mProfileId;
+        this.mModemCognitive = builder.mModemCognitive;
+        this.mMaxConns = builder.mMaxConns;
+        this.mWaitTime = builder.mWaitTime;
+        this.mMaxConnsTime = builder.mMaxConnsTime;
+        this.mMvnoType = builder.mMvnoType;
+        this.mMvnoMatchData = builder.mMvnoMatchData;
+    }
+
+    /** @hide */
+    public static ApnSetting makeApnSetting(int id, String operatorNumeric, String entryName,
+            String apnName, InetAddress proxy, int port, URL mmsc, InetAddress mmsProxy,
+            int mmsPort, String user, String password, int authType, List<String> types,
+            String protocol, String roamingProtocol, boolean carrierEnabled, int bearer,
+            int bearerBitmask, int profileId, boolean modemCognitive, int maxConns,
+            int waitTime, int maxConnsTime, int mtu, String mvnoType, String mvnoMatchData) {
+        return new Builder()
+                .setId(id)
+                .setOperatorNumeric(operatorNumeric)
+                .setEntryName(entryName)
+                .setApnName(apnName)
+                .setProxy(proxy)
+                .setPort(port)
+                .setMmsc(mmsc)
+                .setMmsProxy(mmsProxy)
+                .setMmsPort(mmsPort)
+                .setUser(user)
+                .setPassword(password)
+                .setAuthType(authType)
+                .setTypes(types)
+                .setProtocol(protocol)
+                .setRoamingProtocol(roamingProtocol)
+                .setCarrierEnabled(carrierEnabled)
+                .setBearer(bearer)
+                .setBearerBitmask(bearerBitmask)
+                .setProfileId(profileId)
+                .setModemCognitive(modemCognitive)
+                .setMaxConns(maxConns)
+                .setWaitTime(waitTime)
+                .setMaxConnsTime(maxConnsTime)
+                .setMtu(mtu)
+                .setMvnoType(mvnoType)
+                .setMvnoMatchData(mvnoMatchData)
+                .build();
+    }
+
+    /** @hide */
+    public static ApnSetting makeApnSetting(Cursor cursor) {
+        String[] types = parseTypes(
+                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));
+
+        return makeApnSetting(
+                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
+                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NUMERIC)),
+                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.NAME)),
+                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.APN)),
+                inetAddressFromString(cursor.getString(
+                        cursor.getColumnIndexOrThrow(Telephony.Carriers.PROXY))),
+                portFromString(cursor.getString(
+                        cursor.getColumnIndexOrThrow(Telephony.Carriers.PORT))),
+                URLFromString(cursor.getString(
+                        cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSC))),
+                inetAddressFromString(cursor.getString(
+                        cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPROXY))),
+                portFromString(cursor.getString(
+                        cursor.getColumnIndexOrThrow(Telephony.Carriers.MMSPORT))),
+                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.USER)),
+                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PASSWORD)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.AUTH_TYPE)),
+                Arrays.asList(types),
+                cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROTOCOL)),
+                cursor.getString(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.ROAMING_PROTOCOL)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.CARRIER_ENABLED)) == 1,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.BEARER)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.BEARER_BITMASK)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.PROFILE_ID)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.MODEM_COGNITIVE)) == 1,
+                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MAX_CONNS)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.WAIT_TIME)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.MAX_CONNS_TIME)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers.MTU)),
+                cursor.getString(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.MVNO_TYPE)),
+                cursor.getString(cursor.getColumnIndexOrThrow(
+                        Telephony.Carriers.MVNO_MATCH_DATA)));
+    }
+
+    /** @hide */
+    public static ApnSetting makeApnSetting(ApnSetting apn) {
+        return makeApnSetting(apn.mId, apn.mOperatorNumeric, apn.mEntryName, apn.mApnName,
+                apn.mProxy, apn.mPort, apn.mMmsc, apn.mMmsProxy, apn.mMmsPort, apn.mUser,
+                apn.mPassword, apn.mAuthType, apn.mTypes, apn.mProtocol, apn.mRoamingProtocol,
+                apn.mCarrierEnabled, apn.mBearer, apn.mBearerBitmask, apn.mProfileId,
+                apn.mModemCognitive, apn.mMaxConns, apn.mWaitTime, apn.mMaxConnsTime, apn.mMtu,
+                apn.mMvnoType, apn.mMvnoMatchData);
+    }
+
+    /** @hide */
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[ApnSettingV3] ")
+                .append(mEntryName)
+                .append(", ").append(mId)
+                .append(", ").append(mOperatorNumeric)
+                .append(", ").append(mApnName)
+                .append(", ").append(inetAddressToString(mProxy))
+                .append(", ").append(URLToString(mMmsc))
+                .append(", ").append(inetAddressToString(mMmsProxy))
+                .append(", ").append(portToString(mMmsPort))
+                .append(", ").append(portToString(mPort))
+                .append(", ").append(mAuthType).append(", ");
+        for (int i = 0; i < mTypes.size(); i++) {
+            sb.append(mTypes.get(i));
+            if (i < mTypes.size() - 1) {
+                sb.append(" | ");
+            }
+        }
+        sb.append(", ").append(mProtocol);
+        sb.append(", ").append(mRoamingProtocol);
+        sb.append(", ").append(mCarrierEnabled);
+        sb.append(", ").append(mBearer);
+        sb.append(", ").append(mBearerBitmask);
+        sb.append(", ").append(mProfileId);
+        sb.append(", ").append(mModemCognitive);
+        sb.append(", ").append(mMaxConns);
+        sb.append(", ").append(mWaitTime);
+        sb.append(", ").append(mMaxConnsTime);
+        sb.append(", ").append(mMtu);
+        sb.append(", ").append(mMvnoType);
+        sb.append(", ").append(mMvnoMatchData);
+        sb.append(", ").append(mPermanentFailed);
+        return sb.toString();
+    }
+
+    /**
+     * Returns true if there are MVNO params specified.
+     * @hide
+     */
+    public boolean hasMvnoParams() {
+        return !TextUtils.isEmpty(mMvnoType) && !TextUtils.isEmpty(mMvnoMatchData);
+    }
+
+    /** @hide */
+    public boolean canHandleType(String type) {
+        if (!mCarrierEnabled) return false;
+        boolean wildcardable = true;
+        if (TYPE_IA.equalsIgnoreCase(type)) wildcardable = false;
+        for (String t : mTypes) {
+            // DEFAULT handles all, and HIPRI is handled by DEFAULT
+            if (t.equalsIgnoreCase(type)
+                    || (wildcardable && t.equalsIgnoreCase(TYPE_ALL))
+                    || (t.equalsIgnoreCase(TYPE_DEFAULT)
+                    && type.equalsIgnoreCase(TYPE_HIPRI))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // check whether the types of two APN same (even only one type of each APN is same)
+    private boolean typeSameAny(ApnSetting first, ApnSetting second) {
+        if (VDBG) {
+            StringBuilder apnType1 = new StringBuilder(first.mApnName + ": ");
+            for (int index1 = 0; index1 < first.mTypes.size(); index1++) {
+                apnType1.append(first.mTypes.get(index1));
+                apnType1.append(",");
+            }
+
+            StringBuilder apnType2 = new StringBuilder(second.mApnName + ": ");
+            for (int index1 = 0; index1 < second.mTypes.size(); index1++) {
+                apnType2.append(second.mTypes.get(index1));
+                apnType2.append(",");
+            }
+            Rlog.d(LOG_TAG, "APN1: is " + apnType1);
+            Rlog.d(LOG_TAG, "APN2: is " + apnType2);
+        }
+
+        for (int index1 = 0; index1 < first.mTypes.size(); index1++) {
+            for (int index2 = 0; index2 < second.mTypes.size(); index2++) {
+                if (first.mTypes.get(index1).equals(ApnSetting.TYPE_ALL)
+                        || second.mTypes.get(index2).equals(ApnSetting.TYPE_ALL)
+                        || first.mTypes.get(index1).equals(second.mTypes.get(index2))) {
+                    if (VDBG) Rlog.d(LOG_TAG, "typeSameAny: return true");
+                    return true;
+                }
+            }
+        }
+
+        if (VDBG) Rlog.d(LOG_TAG, "typeSameAny: return false");
+        return false;
+    }
+
+    // TODO - if we have this function we should also have hashCode.
+    // Also should handle changes in type order and perhaps case-insensitivity
+    /** @hide */
+    public boolean equals(Object o) {
+        if (o instanceof ApnSetting == false) {
+            return false;
+        }
+
+        ApnSetting other = (ApnSetting) o;
+
+        return mEntryName.equals(other.mEntryName)
+                && Objects.equals(mId, other.mId)
+                && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
+                && Objects.equals(mApnName, other.mApnName)
+                && Objects.equals(mProxy, other.mProxy)
+                && Objects.equals(mMmsc, other.mMmsc)
+                && Objects.equals(mMmsProxy, other.mMmsProxy)
+                && Objects.equals(mMmsPort, other.mMmsPort)
+                && Objects.equals(mPort,other.mPort)
+                && Objects.equals(mUser, other.mUser)
+                && Objects.equals(mPassword, other.mPassword)
+                && Objects.equals(mAuthType, other.mAuthType)
+                && Objects.equals(mTypes, other.mTypes)
+                && Objects.equals(mTypesBitmap, other.mTypesBitmap)
+                && Objects.equals(mProtocol, other.mProtocol)
+                && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
+                && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
+                && Objects.equals(mBearer, other.mBearer)
+                && Objects.equals(mBearerBitmask, other.mBearerBitmask)
+                && Objects.equals(mProfileId, other.mProfileId)
+                && Objects.equals(mModemCognitive, other.mModemCognitive)
+                && Objects.equals(mMaxConns, other.mMaxConns)
+                && Objects.equals(mWaitTime, other.mWaitTime)
+                && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
+                && Objects.equals(mMtu, other.mMtu)
+                && Objects.equals(mMvnoType, other.mMvnoType)
+                && Objects.equals(mMvnoMatchData, other.mMvnoMatchData);
+    }
+
+    /**
+     * Compare two APN settings
+     *
+     * Note: This method does not compare 'id', 'bearer', 'bearerBitmask'. We only use this for
+     * determining if tearing a data call is needed when conditions change. See
+     * cleanUpConnectionsOnUpdatedApns in DcTracker.
+     *
+     * @param o the other object to compare
+     * @param isDataRoaming True if the device is on data roaming
+     * @return True if the two APN settings are same
+     * @hide
+     */
+    public boolean equals(Object o, boolean isDataRoaming) {
+        if (!(o instanceof ApnSetting)) {
+            return false;
+        }
+
+        ApnSetting other = (ApnSetting) o;
+
+        return mEntryName.equals(other.mEntryName)
+                && Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
+                && Objects.equals(mApnName, other.mApnName)
+                && Objects.equals(mProxy, other.mProxy)
+                && Objects.equals(mMmsc, other.mMmsc)
+                && Objects.equals(mMmsProxy, other.mMmsProxy)
+                && Objects.equals(mMmsPort, other.mMmsPort)
+                && Objects.equals(mPort, other.mPort)
+                && Objects.equals(mUser, other.mUser)
+                && Objects.equals(mPassword, other.mPassword)
+                && Objects.equals(mAuthType, other.mAuthType)
+                && Objects.equals(mTypes, other.mTypes)
+                && Objects.equals(mTypesBitmap, other.mTypesBitmap)
+                && (isDataRoaming || Objects.equals(mProtocol,other.mProtocol))
+                && (!isDataRoaming || Objects.equals(mRoamingProtocol, other.mRoamingProtocol))
+                && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
+                && Objects.equals(mProfileId, other.mProfileId)
+                && Objects.equals(mModemCognitive, other.mModemCognitive)
+                && Objects.equals(mMaxConns, other.mMaxConns)
+                && Objects.equals(mWaitTime, other.mWaitTime)
+                && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
+                && Objects.equals(mMtu, other.mMtu)
+                && Objects.equals(mMvnoType, other.mMvnoType)
+                && Objects.equals(mMvnoMatchData, other.mMvnoMatchData);
+    }
+
+    /**
+     * Check if neither mention DUN and are substantially similar
+     *
+     * @param other The other APN settings to compare
+     * @return True if two APN settings are similar
+     * @hide
+     */
+    public boolean similar(ApnSetting other) {
+        return (!this.canHandleType(TYPE_DUN)
+                && !other.canHandleType(TYPE_DUN)
+                && Objects.equals(this.mApnName, other.mApnName)
+                && !typeSameAny(this, other)
+                && xorEqualsInetAddress(this.mProxy, other.mProxy)
+                && xorEqualsPort(this.mPort, other.mPort)
+                && xorEquals(this.mProtocol, other.mProtocol)
+                && xorEquals(this.mRoamingProtocol, other.mRoamingProtocol)
+                && Objects.equals(this.mCarrierEnabled, other.mCarrierEnabled)
+                && Objects.equals(this.mBearerBitmask, other.mBearerBitmask)
+                && Objects.equals(this.mProfileId, other.mProfileId)
+                && Objects.equals(this.mMvnoType, other.mMvnoType)
+                && Objects.equals(this.mMvnoMatchData, other.mMvnoMatchData)
+                && xorEqualsURL(this.mMmsc, other.mMmsc)
+                && xorEqualsInetAddress(this.mMmsProxy, other.mMmsProxy)
+                && xorEqualsPort(this.mMmsPort, other.mMmsPort));
+    }
+
+    // Equal or one is not specified.
+    private boolean xorEquals(String first, String second) {
+        return (Objects.equals(first, second)
+                || TextUtils.isEmpty(first)
+                || TextUtils.isEmpty(second));
+    }
+
+    // Equal or one is not specified.
+    private boolean xorEqualsInetAddress(InetAddress first, InetAddress second) {
+        return first == null || second == null || first.equals(second);
+    }
+
+    // Equal or one is not specified.
+    private boolean xorEqualsURL(URL first, URL second) {
+        return first == null || second == null || first.equals(second);
+    }
+
+    // Equal or one is not specified.
+    private boolean xorEqualsPort(int first, int second) {
+        return first == -1 || second == -1 || Objects.equals(first, second);
+    }
+
+    // Helper function to convert APN string into a 32-bit bitmask.
+    private static int getApnBitmask(String apn) {
+        switch (apn) {
+            case TYPE_DEFAULT: return ApnTypes.DEFAULT;
+            case TYPE_MMS: return ApnTypes.MMS;
+            case TYPE_SUPL: return ApnTypes.SUPL;
+            case TYPE_DUN: return ApnTypes.DUN;
+            case TYPE_HIPRI: return ApnTypes.HIPRI;
+            case TYPE_FOTA: return ApnTypes.FOTA;
+            case TYPE_IMS: return ApnTypes.IMS;
+            case TYPE_CBS: return ApnTypes.CBS;
+            case TYPE_IA: return ApnTypes.IA;
+            case TYPE_EMERGENCY: return ApnTypes.EMERGENCY;
+            case TYPE_ALL: return ApnTypes.ALL;
+            default: return ApnTypes.NONE;
+        }
+    }
+
+    private String deParseTypes(List<String> types) {
+        if (types == null) {
+            return null;
+        }
+        return TextUtils.join(",", types);
+    }
+
+    /** @hide */
+    // Called by DPM.
+    public ContentValues toContentValues() {
+        ContentValues apnValue = new ContentValues();
+        if (mOperatorNumeric != null) {
+            apnValue.put(Telephony.Carriers.NUMERIC, mOperatorNumeric);
+        }
+        if (mEntryName != null) {
+            apnValue.put(Telephony.Carriers.NAME, mEntryName);
+        }
+        if (mApnName != null) {
+            apnValue.put(Telephony.Carriers.APN, mApnName);
+        }
+        if (mProxy != null) {
+            apnValue.put(Telephony.Carriers.PROXY, inetAddressToString(mProxy));
+        }
+        apnValue.put(Telephony.Carriers.PORT, portToString(mPort));
+        if (mMmsc != null) {
+            apnValue.put(Telephony.Carriers.MMSC, URLToString(mMmsc));
+        }
+        apnValue.put(Telephony.Carriers.MMSPORT, portToString(mMmsPort));
+        if (mMmsProxy != null) {
+            apnValue.put(Telephony.Carriers.MMSPROXY, inetAddressToString(mMmsProxy));
+        }
+        if (mUser != null) {
+            apnValue.put(Telephony.Carriers.USER, mUser);
+        }
+        if (mPassword != null) {
+            apnValue.put(Telephony.Carriers.PASSWORD, mPassword);
+        }
+        apnValue.put(Telephony.Carriers.AUTH_TYPE, mAuthType);
+        String apnType = deParseTypes(mTypes);
+        if (apnType != null) {
+            apnValue.put(Telephony.Carriers.TYPE, apnType);
+        }
+        if (mProtocol != null) {
+            apnValue.put(Telephony.Carriers.PROTOCOL, mProtocol);
+        }
+        if (mRoamingProtocol != null) {
+            apnValue.put(Telephony.Carriers.ROAMING_PROTOCOL, mRoamingProtocol);
+        }
+        apnValue.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled);
+        // networkTypeBit.
+        apnValue.put(Telephony.Carriers.BEARER_BITMASK, mBearerBitmask);
+        if (mMvnoType != null) {
+            apnValue.put(Telephony.Carriers.MVNO_TYPE, mMvnoType);
+        }
+
+        return apnValue;
+    }
+
+    /**
+     * @param types comma delimited list of APN types
+     * @return array of APN types
+     * @hide
+     */
+    public static String[] parseTypes(String types) {
+        String[] result;
+        // If unset, set to DEFAULT.
+        if (TextUtils.isEmpty(types)) {
+            result = new String[1];
+            result[0] = TYPE_ALL;
+        } else {
+            result = types.split(",");
+        }
+        return result;
+    }
+
+    private static URL URLFromString(String url) {
+        try {
+            return TextUtils.isEmpty(url) ? null : new URL(url);
+        } catch (MalformedURLException e) {
+            Log.e(LOG_TAG, "Can't parse URL from string.");
+            return null;
+        }
+    }
+
+    private static String URLToString(URL url) {
+        return url == null ? "" : url.toString();
+    }
+
+    private static InetAddress inetAddressFromString(String inetAddress) {
+        if (TextUtils.isEmpty(inetAddress)) {
+            return null;
+        }
+        try {
+            return InetAddress.getByName(inetAddress);
+        } catch (UnknownHostException e) {
+            Log.e(LOG_TAG, "Can't parse InetAddress from string: unknown host.");
+            return null;
+        }
+    }
+
+    private static String inetAddressToString(InetAddress inetAddress) {
+        if (inetAddress == null) {
+            return null;
+        }
+        return TextUtils.isEmpty(inetAddress.getHostName())
+                ? inetAddress.getHostAddress() : inetAddress.getHostName();
+    }
+
+    private static int portFromString(String strPort) {
+        int port = -1;
+        if (!TextUtils.isEmpty(strPort)) {
+            try {
+                port = Integer.parseInt(strPort);
+            } catch (NumberFormatException e) {
+                Log.e(LOG_TAG, "Can't parse port from String");
+            }
+        }
+        return port;
+    }
+
+    private static String portToString(int port) {
+        return port == -1 ? "" : Integer.toString(port);
+    }
+
+    // Implement Parcelable.
+    @Override
+    /** @hide */
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    /** @hide */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeString(mOperatorNumeric);
+        dest.writeString(mEntryName);
+        dest.writeString(mApnName);
+        dest.writeValue(mProxy);
+        dest.writeInt(mPort);
+        dest.writeValue(mMmsc);
+        dest.writeValue(mMmsProxy);
+        dest.writeInt(mMmsPort);
+        dest.writeString(mUser);
+        dest.writeString(mPassword);
+        dest.writeInt(mAuthType);
+        dest.writeStringArray(mTypes.toArray(new String[0]));
+        dest.writeString(mProtocol);
+        dest.writeString(mRoamingProtocol);
+        dest.writeInt(mCarrierEnabled ? 1: 0);
+        dest.writeString(mMvnoType);
+    }
+
+    private static ApnSetting readFromParcel(Parcel in) {
+        return makeApnSetting(in.readInt(), in.readString(), in.readString(), in.readString(),
+                (InetAddress)in.readValue(InetAddress.class.getClassLoader()),
+                in.readInt(), (URL)in.readValue(URL.class.getClassLoader()),
+                (InetAddress)in.readValue(InetAddress.class.getClassLoader()),
+                in.readInt(), in.readString(), in.readString(), in.readInt(),
+                Arrays.asList(in.readStringArray()), in.readString(), in.readString(),
+                in.readInt() > 0, 0, 0, 0, false, 0, 0, 0, 0, in.readString(), null);
+    }
+
+    public static final Parcelable.Creator<ApnSetting> CREATOR =
+            new Parcelable.Creator<ApnSetting>() {
+                @Override
+                public ApnSetting createFromParcel(Parcel in) {
+                    return readFromParcel(in);
+                }
+
+                @Override
+                public ApnSetting[] newArray(int size) {
+                    return new ApnSetting[size];
+                }
+            };
+
+    /**
+     * APN types for data connections.  These are usage categories for an APN
+     * entry.  One APN entry may support multiple APN types, eg, a single APN
+     * may service regular internet traffic ("default") as well as MMS-specific
+     * connections.<br/>
+     * ALL is a special type to indicate that this APN entry can
+     * service all data connections.
+     */
+    public static final String TYPE_ALL = "*";
+    /** APN type for default data traffic */
+    public static final String TYPE_DEFAULT = "default";
+    /** APN type for MMS traffic */
+    public static final String TYPE_MMS = "mms";
+    /** APN type for SUPL assisted GPS */
+    public static final String TYPE_SUPL = "supl";
+    /** APN type for DUN traffic */
+    public static final String TYPE_DUN = "dun";
+    /** APN type for HiPri traffic */
+    public static final String TYPE_HIPRI = "hipri";
+    /** APN type for FOTA */
+    public static final String TYPE_FOTA = "fota";
+    /** APN type for IMS */
+    public static final String TYPE_IMS = "ims";
+    /** APN type for CBS */
+    public static final String TYPE_CBS = "cbs";
+    /** APN type for IA Initial Attach APN */
+    public static final String TYPE_IA = "ia";
+    /** APN type for Emergency PDN. This is not an IA apn, but is used
+     * for access to carrier services in an emergency call situation. */
+    public static final String TYPE_EMERGENCY = "emergency";
+    /**
+     * Array of all APN types
+     *
+     * @hide
+     */
+    public static final String[] ALL_TYPES = {
+            TYPE_DEFAULT,
+            TYPE_MMS,
+            TYPE_SUPL,
+            TYPE_DUN,
+            TYPE_HIPRI,
+            TYPE_FOTA,
+            TYPE_IMS,
+            TYPE_CBS,
+            TYPE_IA,
+            TYPE_EMERGENCY
+    };
+
+    // Possible values for authentication types.
+    public static final int AUTH_TYPE_NONE = 0;
+    public static final int AUTH_TYPE_PAP = 1;
+    public static final int AUTH_TYPE_CHAP = 2;
+    public static final int AUTH_TYPE_PAP_OR_CHAP = 3;
+
+    // Possible values for protocol.
+    public static final String PROTOCOL_IP = "IP";
+    public static final String PROTOCOL_IPV6 = "IPV6";
+    public static final String PROTOCOL_IPV4V6 = "IPV4V6";
+    public static final String PROTOCOL_PPP = "PPP";
+
+    // Possible values for MVNO type.
+    public static final String MVNO_TYPE_SPN = "spn";
+    public static final String MVNO_TYPE_IMSI = "imsi";
+    public static final String MVNO_TYPE_GID = "gid";
+    public static final String MVNO_TYPE_ICCID = "iccid";
+
+    public static class Builder{
+        private String mEntryName;
+        private String mApnName;
+        private InetAddress mProxy;
+        private int mPort = -1;
+        private URL mMmsc;
+        private InetAddress mMmsProxy;
+        private int mMmsPort = -1;
+        private String mUser;
+        private String mPassword;
+        private int mAuthType;
+        private List<String> mTypes;
+        private int mTypesBitmap;
+        private int mId;
+        private String mOperatorNumeric;
+        private String mProtocol;
+        private String mRoamingProtocol;
+        private int mMtu;
+        private boolean mCarrierEnabled;
+        private int mBearer;
+        private int mBearerBitmask;
+        private int mProfileId;
+        private boolean mModemCognitive;
+        private int mMaxConns;
+        private int mWaitTime;
+        private int mMaxConnsTime;
+        private String mMvnoType;
+        private String mMvnoMatchData;
+
+        /**
+         * Default constructor for Builder.
+         */
+        public Builder() {}
+
+        /**
+         * Set the MTU size of the mobile interface to which the APN connected.
+         *
+         * @param mtu the MTU size to set for the APN
+         * @hide
+         */
+        public Builder setMtu(int mtu) {
+            this.mMtu = mtu;
+            return this;
+        }
+
+        /**
+         * Sets bearer info.
+         *
+         * @param bearer the bearer info to set for the APN
+         * @hide
+         */
+        public Builder setBearer(int bearer) {
+            this.mBearer = bearer;
+            return this;
+        }
+
+        /**
+         * Sets the radio access technology bitmask for this APN.
+         *
+         * @param bearerBitmask the radio access technology bitmask to set for this APN
+         * @hide
+         */
+        public Builder setBearerBitmask(int bearerBitmask) {
+            this.mBearerBitmask = bearerBitmask;
+            return this;
+        }
+
+        /**
+         * Sets the profile id to which the APN saved in modem.
+         *
+         * @param profileId the profile id to set for the APN
+         * @hide
+         */
+        public Builder setProfileId(int profileId) {
+            this.mProfileId = profileId;
+            return this;
+        }
+
+        /**
+         * Sets if the APN setting is to be set in modem.
+         *
+         * @param modemCognitive if the APN setting is to be set in modem
+         * @hide
+         */
+        public Builder setModemCognitive(boolean modemCognitive) {
+            this.mModemCognitive = modemCognitive;
+            return this;
+        }
+
+        /**
+         * Sets the max connections of this APN.
+         *
+         * @param maxConns the max connections of this APN
+         * @hide
+         */
+        public Builder setMaxConns(int maxConns) {
+            this.mMaxConns = maxConns;
+            return this;
+        }
+
+        /**
+         * Sets the wait time for retry of the APN.
+         *
+         * @param waitTime the wait time for retry of the APN
+         * @hide
+         */
+        public Builder setWaitTime(int waitTime) {
+            this.mWaitTime = waitTime;
+            return this;
+        }
+
+        /**
+         * Sets the time to limit max connection for the APN.
+         *
+         * @param maxConnsTime the time to limit max connection for the APN
+         * @hide
+         */
+        public Builder setMaxConnsTime(int maxConnsTime) {
+            this.mMaxConnsTime = maxConnsTime;
+            return this;
+        }
+
+        /**
+         * Sets the MVNO match data for the APN.
+         *
+         * @param mvnoMatchData the MVNO match data for the APN
+         * @hide
+         */
+        public Builder setMvnoMatchData(String mvnoMatchData) {
+            this.mMvnoMatchData = mvnoMatchData;
+            return this;
+        }
+
+        /**
+         * Sets the entry name of the APN.
+         *
+         * @param entryName the entry name to set for the APN
+         */
+        public Builder setEntryName(String entryName) {
+            this.mEntryName = entryName;
+            return this;
+        }
+
+        /**
+         * Sets the name of the APN.
+         *
+         * @param apnName the name to set for the APN
+         */
+        public Builder setApnName(String apnName) {
+            this.mApnName = apnName;
+            return this;
+        }
+
+        /**
+         * Sets the proxy address of the APN.
+         *
+         * @param proxy the proxy address to set for the APN
+         */
+        public Builder setProxy(InetAddress proxy) {
+            this.mProxy = proxy;
+            return this;
+        }
+
+        /**
+         * Sets the proxy port of the APN.
+         *
+         * @param port the proxy port to set for the APN
+         */
+        public Builder setPort(int port) {
+            this.mPort = port;
+            return this;
+        }
+
+        /**
+         * Sets the MMSC URL of the APN.
+         *
+         * @param mmsc the MMSC URL to set for the APN
+         */
+        public Builder setMmsc(URL mmsc) {
+            this.mMmsc = mmsc;
+            return this;
+        }
+
+        /**
+         * Sets the MMS proxy address of the APN.
+         *
+         * @param mmsProxy the MMS proxy address to set for the APN
+         */
+        public Builder setMmsProxy(InetAddress mmsProxy) {
+            this.mMmsProxy = mmsProxy;
+            return this;
+        }
+
+        /**
+         * Sets the MMS proxy port of the APN.
+         *
+         * @param mmsPort the MMS proxy port to set for the APN
+         */
+        public Builder setMmsPort(int mmsPort) {
+            this.mMmsPort = mmsPort;
+            return this;
+        }
+
+        /**
+         * Sets the APN username of the APN.
+         *
+         * @param user the APN username to set for the APN
+         */
+        public Builder setUser(String user) {
+            this.mUser = user;
+            return this;
+        }
+
+        /**
+         * Sets the APN password of the APN.
+         *
+         * @see android.provider.Telephony.Carriers#PASSWORD
+         * @param password the APN password to set for the APN
+         */
+        public Builder setPassword(String password) {
+            this.mPassword = password;
+            return this;
+        }
+
+        /**
+         * Sets the authentication type of the APN.
+         *
+         * Example of possible values: {@link #AUTH_TYPE_NONE}, {@link #AUTH_TYPE_PAP}.
+         *
+         * @param authType the authentication type to set for the APN
+         */
+        public Builder setAuthType(@AuthType int authType) {
+            this.mAuthType = authType;
+            return this;
+        }
+
+        /**
+         * Sets the list of APN types of the APN.
+         *
+         * Example of possible values: {@link #TYPE_DEFAULT}, {@link #TYPE_MMS}.
+         *
+         * @param types the list of APN types to set for the APN
+         */
+        public Builder setTypes(@ApnType List<String> types) {
+            this.mTypes = types;
+            int apnBitmap = 0;
+            for (int i = 0; i < mTypes.size(); i++) {
+                mTypes.set(i, mTypes.get(i).toLowerCase());
+                apnBitmap |= getApnBitmask(mTypes.get(i));
+            }
+            this.mTypesBitmap = apnBitmap;
+            return this;
+        }
+
+        /**
+         * Sets the unique database id for this entry.
+         *
+         * @param id the unique database id to set for this entry
+         */
+        public Builder setId(int id) {
+            this.mId = id;
+            return this;
+        }
+
+        /**
+         * Set the numeric operator ID for the APN.
+         *
+         * @param operatorNumeric the numeric operator ID to set for this entry
+         */
+        public Builder setOperatorNumeric(String operatorNumeric) {
+            this.mOperatorNumeric = operatorNumeric;
+            return this;
+        }
+
+        /**
+         * Sets the protocol to use to connect to this APN.
+         *
+         * One of the {@code PDP_type} values in TS 27.007 section 10.1.1.
+         * Example of possible values: {@link #PROTOCOL_IP}, {@link #PROTOCOL_IPV6}.
+         *
+         * @param protocol the protocol to set to use to connect to this APN
+         */
+        public Builder setProtocol(@ProtocolType String protocol) {
+            this.mProtocol = protocol;
+            return this;
+        }
+
+        /**
+         * Sets the protocol to use to connect to this APN when roaming.
+         *
+         * @param roamingProtocol the protocol to set to use to connect to this APN when roaming
+         */
+        public Builder setRoamingProtocol(String roamingProtocol) {
+            this.mRoamingProtocol = roamingProtocol;
+            return this;
+        }
+
+        /**
+         * Sets the current status of APN.
+         *
+         * @param carrierEnabled the current status to set for this APN
+         */
+        public Builder setCarrierEnabled(boolean carrierEnabled) {
+            this.mCarrierEnabled = carrierEnabled;
+            return this;
+        }
+
+        /**
+         * Sets the MVNO match type for this APN.
+         *
+         * Example of possible values: {@link #MVNO_TYPE_SPN}, {@link #MVNO_TYPE_IMSI}.
+         *
+         * @param mvnoType the MVNO match type to set for this APN
+         */
+        public Builder setMvnoType(@MvnoType String mvnoType) {
+            this.mMvnoType = mvnoType;
+            return this;
+        }
+
+        public ApnSetting build() {
+            return new ApnSetting(this);
+        }
+    }
+}
+
diff --git a/core/tests/coretests/src/android/net/NetworkUtilsTest.java b/tests/net/java/android/net/NetworkUtilsTest.java
similarity index 100%
rename from core/tests/coretests/src/android/net/NetworkUtilsTest.java
rename to tests/net/java/android/net/NetworkUtilsTest.java
diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/tests/net/java/android/net/RouteInfoTest.java
similarity index 100%
rename from core/tests/coretests/src/android/net/RouteInfoTest.java
rename to tests/net/java/android/net/RouteInfoTest.java
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 725ddb9..9b75a50 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -35,6 +35,7 @@
 import android.net.ip.IpManager;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.RaEvent;
+import android.net.util.InterfaceParams;
 import android.os.ConditionVariable;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -62,7 +63,6 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.InetAddress;
-import java.net.NetworkInterface;
 import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.Random;
@@ -635,7 +635,7 @@
 
         public TestApfFilter(ApfConfiguration config, IpManager.Callback ipManagerCallback,
                 IpConnectivityLog log) throws Exception {
-            super(config, NetworkInterface.getByName("lo"), ipManagerCallback, log);
+            super(config, InterfaceParams.getByName("lo"), ipManagerCallback, log);
         }
 
         // Pretend an RA packet has been received and show it to ApfFilter.
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
index 54776db..e65585f 100644
--- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
+++ b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.when;
 
+import android.net.util.InterfaceParams;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.Looper;
@@ -54,8 +55,8 @@
     }
 
     IpReachabilityMonitor makeMonitor() {
-        return new IpReachabilityMonitor(
-                "fake0", 1, mHandler, mLog, mCallback, null, mDependencies);
+        final InterfaceParams ifParams = new InterfaceParams("fake0", 1, null);
+        return new IpReachabilityMonitor(ifParams, mHandler, mLog, mCallback, null, mDependencies);
     }
 
     @Test
diff --git a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
index 38d3d74..f9b7ec8 100644
--- a/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
+++ b/tests/net/java/android/net/util/ConnectivityPacketSummaryTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.net.MacAddress;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.filters.SmallTest;
 
@@ -36,9 +37,7 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class ConnectivityPacketSummaryTest {
-    private static final byte[] MYHWADDR = {
-        asByte(0x80), asByte(0x7a), asByte(0xbf), asByte(0x6f), asByte(0x48), asByte(0xf3)
-    };
+    private static final MacAddress MYHWADDR = MacAddress.fromString("80:7a:bf:6f:48:f3");
 
     private String getSummary(String hexBytes) {
         hexBytes = hexBytes.replaceAll("\\s+", "");
diff --git a/tests/net/java/android/net/util/InterfaceParamsTest.java b/tests/net/java/android/net/util/InterfaceParamsTest.java
new file mode 100644
index 0000000..21728af
--- /dev/null
+++ b/tests/net/java/android/net/util/InterfaceParamsTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InterfaceParamsTest {
+    @Test
+    public void testNullInterfaceReturnsNull() {
+        assertNull(InterfaceParams.getByName(null));
+    }
+
+    @Test
+    public void testNonExistentInterfaceReturnsNull() {
+        assertNull(InterfaceParams.getByName("doesnotexist0"));
+    }
+
+    @Test
+    public void testLoopback() {
+        final InterfaceParams ifParams = InterfaceParams.getByName("lo");
+        assertNotNull(ifParams);
+        assertEquals("lo", ifParams.name);
+        assertTrue(ifParams.index > 0);
+        assertNotNull(ifParams.macAddr);
+        assertTrue(ifParams.defaultMtu >= NetworkConstants.ETHER_MTU);
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 113cd37..2b0349c 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -55,14 +55,20 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -116,6 +122,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.WakeupMessage;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
@@ -132,6 +139,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
@@ -174,8 +182,11 @@
 
     @Mock IpConnectivityMetrics.Logger mMetricsService;
     @Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
+    @Mock INetworkManagementService mNetworkManagementService;
     @Mock INetworkStatsService mStatsService;
 
+    private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class);
+
     // This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods
     // do not go through ConnectivityService but talk to netd directly, so they don't automatically
     // reflect the state of our test ConnectivityService.
@@ -872,7 +883,7 @@
         LocalServices.addService(
                 NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class));
         mService = new WrappedConnectivityService(mServiceContext,
-                mock(INetworkManagementService.class),
+                mNetworkManagementService,
                 mStatsService,
                 mock(INetworkPolicyManager.class),
                 mock(IpConnectivityLog.class));
@@ -3489,6 +3500,44 @@
         reset(mStatsService);
     }
 
+    @Test
+    public void testBasicDnsConfigurationPushed() throws Exception {
+        mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+        waitForIdle();
+        verify(mNetworkManagementService, never()).setDnsConfigurationForNetwork(
+                anyInt(), any(), any(), any(), anyBoolean(), anyString());
+
+        final LinkProperties cellLp = new LinkProperties();
+        cellLp.setInterfaceName("test_rmnet_data0");
+        mCellNetworkAgent.sendLinkProperties(cellLp);
+        mCellNetworkAgent.connect(false);
+        waitForIdle();
+        verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
+                anyInt(), mStringArrayCaptor.capture(), any(), any(), anyBoolean(), anyString());
+        // CS tells netd about the empty DNS config for this network.
+        assertEmpty(mStringArrayCaptor.getValue());
+        reset(mNetworkManagementService);
+
+        cellLp.addDnsServer(InetAddress.getByName("2001:db8::1"));
+        mCellNetworkAgent.sendLinkProperties(cellLp);
+        waitForIdle();
+        verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
+                anyInt(), mStringArrayCaptor.capture(), any(), any(), anyBoolean(), anyString());
+        assertEquals(1, mStringArrayCaptor.getValue().length);
+        assertTrue(ArrayUtils.contains(mStringArrayCaptor.getValue(), "2001:db8::1"));
+        reset(mNetworkManagementService);
+
+        cellLp.addDnsServer(InetAddress.getByName("192.0.2.1"));
+        mCellNetworkAgent.sendLinkProperties(cellLp);
+        waitForIdle();
+        verify(mNetworkManagementService, times(1)).setDnsConfigurationForNetwork(
+                anyInt(), mStringArrayCaptor.capture(), any(), any(), anyBoolean(), anyString());
+        assertEquals(2, mStringArrayCaptor.getValue().length);
+        assertTrue(ArrayUtils.containsAll(mStringArrayCaptor.getValue(),
+                new String[]{"2001:db8::1", "192.0.2.1"}));
+        reset(mNetworkManagementService);
+    }
+
     private void checkDirectlyConnectedRoutes(Object callbackObj,
             Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) {
         assertTrue(callbackObj instanceof LinkProperties);