Merge "Propagate some SIP INVITE header fields on a MT call"
diff --git a/Android.bp b/Android.bp
index 482b19d..8d0e1d3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -697,6 +697,7 @@
         "android.hardware.radio-V1.3-java",
         "android.hardware.radio-V1.4-java",
         "android.hardware.usb.gadget-V1.0-java",
+        "networkstack-aidl-interfaces-java",
         "netd_aidl_interface-java",
     ],
 
@@ -824,8 +825,16 @@
     name: "networkstack-aidl-interfaces",
     local_include_dir: "core/java",
     srcs: [
+        "core/java/android/net/INetworkMonitor.aidl",
+        "core/java/android/net/INetworkMonitorCallbacks.aidl",
+        "core/java/android/net/IIpMemoryStore.aidl",
         "core/java/android/net/INetworkStackConnector.aidl",
+        "core/java/android/net/INetworkStackStatusCallback.aidl",
+        "core/java/android/net/PrivateDnsConfigParcel.aidl",
         "core/java/android/net/dhcp/DhcpServingParamsParcel.aidl",
+        "core/java/android/net/dhcp/IDhcpServer.aidl",
+        "core/java/android/net/dhcp/IDhcpServerCallbacks.aidl",
+        "core/java/android/net/ipmemorystore/**/*.aidl",
     ],
     api_dir: "aidl/networkstack",
 }
diff --git a/api/current.txt b/api/current.txt
index 5992a6e..a3b3722 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -8339,42 +8339,44 @@
     field public static final java.lang.String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = "android.bluetooth.headset.intent.category.companyid";
   }
 
-  public final class BluetoothHealth implements android.bluetooth.BluetoothProfile {
-    method public boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
-    method public boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
+  public final deprecated class BluetoothHealth implements android.bluetooth.BluetoothProfile {
+    ctor public BluetoothHealth();
+    method public deprecated boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
+    method public deprecated boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
     method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method public int getConnectionState(android.bluetooth.BluetoothDevice);
     method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
-    method public android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
-    method public boolean registerSinkAppConfiguration(java.lang.String, int, android.bluetooth.BluetoothHealthCallback);
-    method public boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
-    field public static final int APP_CONFIG_REGISTRATION_FAILURE = 1; // 0x1
-    field public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0; // 0x0
-    field public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; // 0x3
-    field public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2; // 0x2
-    field public static final int CHANNEL_TYPE_RELIABLE = 10; // 0xa
-    field public static final int CHANNEL_TYPE_STREAMING = 11; // 0xb
-    field public static final int SINK_ROLE = 2; // 0x2
-    field public static final int SOURCE_ROLE = 1; // 0x1
-    field public static final int STATE_CHANNEL_CONNECTED = 2; // 0x2
-    field public static final int STATE_CHANNEL_CONNECTING = 1; // 0x1
-    field public static final int STATE_CHANNEL_DISCONNECTED = 0; // 0x0
-    field public static final int STATE_CHANNEL_DISCONNECTING = 3; // 0x3
+    method public deprecated android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
+    method public deprecated boolean registerSinkAppConfiguration(java.lang.String, int, android.bluetooth.BluetoothHealthCallback);
+    method public deprecated boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
+    field public static final deprecated int APP_CONFIG_REGISTRATION_FAILURE = 1; // 0x1
+    field public static final deprecated int APP_CONFIG_REGISTRATION_SUCCESS = 0; // 0x0
+    field public static final deprecated int APP_CONFIG_UNREGISTRATION_FAILURE = 3; // 0x3
+    field public static final deprecated int APP_CONFIG_UNREGISTRATION_SUCCESS = 2; // 0x2
+    field public static final deprecated int CHANNEL_TYPE_RELIABLE = 10; // 0xa
+    field public static final deprecated int CHANNEL_TYPE_STREAMING = 11; // 0xb
+    field public static final deprecated int SINK_ROLE = 2; // 0x2
+    field public static final deprecated int SOURCE_ROLE = 1; // 0x1
+    field public static final deprecated int STATE_CHANNEL_CONNECTED = 2; // 0x2
+    field public static final deprecated int STATE_CHANNEL_CONNECTING = 1; // 0x1
+    field public static final deprecated int STATE_CHANNEL_DISCONNECTED = 0; // 0x0
+    field public static final deprecated int STATE_CHANNEL_DISCONNECTING = 3; // 0x3
   }
 
-  public final class BluetoothHealthAppConfiguration implements android.os.Parcelable {
+  public final deprecated class BluetoothHealthAppConfiguration implements android.os.Parcelable {
+    ctor public BluetoothHealthAppConfiguration();
     method public int describeContents();
-    method public int getDataType();
-    method public java.lang.String getName();
-    method public int getRole();
+    method public deprecated int getDataType();
+    method public deprecated java.lang.String getName();
+    method public deprecated int getRole();
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothHealthAppConfiguration> CREATOR;
+    field public static final deprecated android.os.Parcelable.Creator<android.bluetooth.BluetoothHealthAppConfiguration> CREATOR;
   }
 
-  public abstract class BluetoothHealthCallback {
+  public abstract deprecated class BluetoothHealthCallback {
     ctor public BluetoothHealthCallback();
-    method public void onHealthAppConfigurationStatusChange(android.bluetooth.BluetoothHealthAppConfiguration, int);
-    method public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
+    method public deprecated void onHealthAppConfigurationStatusChange(android.bluetooth.BluetoothHealthAppConfiguration, int);
+    method public deprecated void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
   }
 
   public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
@@ -8471,7 +8473,7 @@
     field public static final int GATT = 7; // 0x7
     field public static final int GATT_SERVER = 8; // 0x8
     field public static final int HEADSET = 1; // 0x1
-    field public static final int HEALTH = 3; // 0x3
+    field public static final deprecated int HEALTH = 3; // 0x3
     field public static final int HID_DEVICE = 19; // 0x13
     field public static final int SAP = 10; // 0xa
     field public static final int STATE_CONNECTED = 2; // 0x2
@@ -42214,6 +42216,10 @@
     field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
     field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
     field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
+    field public static final java.lang.String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT = "opportunistic_network_entry_threshold_rsrp_int";
+    field public static final java.lang.String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT = "opportunistic_network_entry_threshold_rssnr_int";
+    field public static final java.lang.String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT = "opportunistic_network_exit_threshold_rsrp_int";
+    field public static final java.lang.String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int";
     field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
     field public static final java.lang.String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array";
     field public static final java.lang.String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
@@ -43102,6 +43108,7 @@
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
+    field public static final int DATA_UNKNOWN = -1; // 0xffffffff
     field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
     field public static final java.lang.String EXTRA_CARRIER_ID = "android.telephony.extra.CARRIER_ID";
     field public static final java.lang.String EXTRA_CARRIER_NAME = "android.telephony.extra.CARRIER_NAME";
diff --git a/api/system-current.txt b/api/system-current.txt
index f7dce3d..43ddfc9 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5061,6 +5061,87 @@
     field public static final java.lang.String KEY_CARRIER_SETUP_APP_STRING = "carrier_setup_app_string";
   }
 
+  public final class DataFailCause {
+    field public static final int ACTIVATION_REJECT_GGSN = 30; // 0x1e
+    field public static final int ACTIVATION_REJECT_UNSPECIFIED = 31; // 0x1f
+    field public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 65; // 0x41
+    field public static final int APN_TYPE_CONFLICT = 112; // 0x70
+    field public static final int AUTH_FAILURE_ON_EMERGENCY_CALL = 122; // 0x7a
+    field public static final int COMPANION_IFACE_IN_USE = 118; // 0x76
+    field public static final int CONDITIONAL_IE_ERROR = 100; // 0x64
+    field public static final int EMERGENCY_IFACE_ONLY = 116; // 0x74
+    field public static final int EMM_ACCESS_BARRED = 115; // 0x73
+    field public static final int EMM_ACCESS_BARRED_INFINITE_RETRY = 121; // 0x79
+    field public static final int ERROR_UNSPECIFIED = 65535; // 0xffff
+    field public static final int ESM_INFO_NOT_RECEIVED = 53; // 0x35
+    field public static final int FEATURE_NOT_SUPP = 40; // 0x28
+    field public static final int FILTER_SEMANTIC_ERROR = 44; // 0x2c
+    field public static final int FILTER_SYTAX_ERROR = 45; // 0x2d
+    field public static final int GPRS_REGISTRATION_FAIL = -2; // 0xfffffffe
+    field public static final int IFACE_AND_POL_FAMILY_MISMATCH = 120; // 0x78
+    field public static final int IFACE_MISMATCH = 117; // 0x75
+    field public static final int INSUFFICIENT_RESOURCES = 26; // 0x1a
+    field public static final int INTERNAL_CALL_PREEMPT_BY_HIGH_PRIO_APN = 114; // 0x72
+    field public static final int INVALID_MANDATORY_INFO = 96; // 0x60
+    field public static final int INVALID_PCSCF_ADDR = 113; // 0x71
+    field public static final int INVALID_TRANSACTION_ID = 81; // 0x51
+    field public static final int IP_ADDRESS_MISMATCH = 119; // 0x77
+    field public static final int LLC_SNDCP = 25; // 0x19
+    field public static final int LOST_CONNECTION = 65540; // 0x10004
+    field public static final int MESSAGE_INCORRECT_SEMANTIC = 95; // 0x5f
+    field public static final int MESSAGE_TYPE_UNSUPPORTED = 97; // 0x61
+    field public static final int MISSING_UNKNOWN_APN = 27; // 0x1b
+    field public static final int MSG_AND_PROTOCOL_STATE_UNCOMPATIBLE = 101; // 0x65
+    field public static final int MSG_TYPE_NONCOMPATIBLE_STATE = 98; // 0x62
+    field public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 55; // 0x37
+    field public static final int NAS_SIGNALLING = 14; // 0xe
+    field public static final int NETWORK_FAILURE = 38; // 0x26
+    field public static final int NONE = 0; // 0x0
+    field public static final int NSAPI_IN_USE = 35; // 0x23
+    field public static final int OEM_DCFAILCAUSE_1 = 4097; // 0x1001
+    field public static final int OEM_DCFAILCAUSE_10 = 4106; // 0x100a
+    field public static final int OEM_DCFAILCAUSE_11 = 4107; // 0x100b
+    field public static final int OEM_DCFAILCAUSE_12 = 4108; // 0x100c
+    field public static final int OEM_DCFAILCAUSE_13 = 4109; // 0x100d
+    field public static final int OEM_DCFAILCAUSE_14 = 4110; // 0x100e
+    field public static final int OEM_DCFAILCAUSE_15 = 4111; // 0x100f
+    field public static final int OEM_DCFAILCAUSE_2 = 4098; // 0x1002
+    field public static final int OEM_DCFAILCAUSE_3 = 4099; // 0x1003
+    field public static final int OEM_DCFAILCAUSE_4 = 4100; // 0x1004
+    field public static final int OEM_DCFAILCAUSE_5 = 4101; // 0x1005
+    field public static final int OEM_DCFAILCAUSE_6 = 4102; // 0x1006
+    field public static final int OEM_DCFAILCAUSE_7 = 4103; // 0x1007
+    field public static final int OEM_DCFAILCAUSE_8 = 4104; // 0x1008
+    field public static final int OEM_DCFAILCAUSE_9 = 4105; // 0x1009
+    field public static final int ONLY_IPV4_ALLOWED = 50; // 0x32
+    field public static final int ONLY_IPV6_ALLOWED = 51; // 0x33
+    field public static final int ONLY_SINGLE_BEARER_ALLOWED = 52; // 0x34
+    field public static final int OPERATOR_BARRED = 8; // 0x8
+    field public static final int PDN_CONN_DOES_NOT_EXIST = 54; // 0x36
+    field public static final int PDP_WITHOUT_ACTIVE_TFT = 46; // 0x2e
+    field public static final int PREF_RADIO_TECH_CHANGED = -4; // 0xfffffffc
+    field public static final int PROTOCOL_ERRORS = 111; // 0x6f
+    field public static final int QOS_NOT_ACCEPTED = 37; // 0x25
+    field public static final int RADIO_NOT_AVAILABLE = 65537; // 0x10001
+    field public static final int RADIO_POWER_OFF = -5; // 0xfffffffb
+    field public static final int REGISTRATION_FAIL = -1; // 0xffffffff
+    field public static final int REGULAR_DEACTIVATION = 36; // 0x24
+    field public static final int SERVICE_OPTION_NOT_SUBSCRIBED = 33; // 0x21
+    field public static final int SERVICE_OPTION_NOT_SUPPORTED = 32; // 0x20
+    field public static final int SERVICE_OPTION_OUT_OF_ORDER = 34; // 0x22
+    field public static final int SIGNAL_LOST = -3; // 0xfffffffd
+    field public static final int TETHERED_CALL_ACTIVE = -6; // 0xfffffffa
+    field public static final int TFT_SEMANTIC_ERROR = 41; // 0x29
+    field public static final int TFT_SYTAX_ERROR = 42; // 0x2a
+    field public static final int UMTS_REACTIVATION_REQ = 39; // 0x27
+    field public static final int UNKNOWN = 65536; // 0x10000
+    field public static final int UNKNOWN_INFO_ELEMENT = 99; // 0x63
+    field public static final int UNKNOWN_PDP_ADDRESS_TYPE = 28; // 0x1c
+    field public static final int UNKNOWN_PDP_CONTEXT = 43; // 0x2b
+    field public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 66; // 0x42
+    field public static final int USER_AUTHENTICATION = 29; // 0x1d
+  }
+
   public class DisconnectCause {
     field public static final int ALREADY_DIALING = 72; // 0x48
     field public static final int ANSWERED_ELSEWHERE = 52; // 0x34
@@ -5228,11 +5309,13 @@
   public class PhoneStateListener {
     method public void onCallDisconnectCauseChanged(int, int);
     method public void onPreciseCallStateChanged(android.telephony.PreciseCallState);
+    method public void onPreciseDataConnectionStateChanged(android.telephony.PreciseDataConnectionState);
     method public void onRadioPowerStateChanged(int);
     method public void onSrvccStateChanged(int);
     method public void onVoiceActivationStateChanged(int);
     field public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
     field public static final int LISTEN_PRECISE_CALL_STATE = 2048; // 0x800
+    field public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
     field public static final int LISTEN_RADIO_POWER_STATE_CHANGED = 8388608; // 0x800000
     field public static final int LISTEN_SRVCC_STATE_CHANGED = 16384; // 0x4000
     field public static final int LISTEN_VOICE_ACTIVATION_STATE = 131072; // 0x20000
@@ -5257,6 +5340,16 @@
     field public static final int PRECISE_CALL_STATE_WAITING = 6; // 0x6
   }
 
+  public final class PreciseDataConnectionState implements android.os.Parcelable {
+    method public int describeContents();
+    method public java.lang.String getDataConnectionApn();
+    method public int getDataConnectionApnTypeBitMask();
+    method public int getDataConnectionFailCause();
+    method public int getDataConnectionState();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.telephony.PreciseDataConnectionState> CREATOR;
+  }
+
   public class PreciseDisconnectCause {
     field public static final int ACCESS_CLASS_BLOCKED = 260; // 0x104
     field public static final int ACCESS_INFORMATION_DISCARDED = 43; // 0x2b
@@ -5392,6 +5485,7 @@
   public class SubscriptionInfo implements android.os.Parcelable {
     method public java.util.List<android.telephony.UiccAccessRule> getAccessRules();
     method public int getCardId();
+    method public int getProfileClass();
   }
 
   public class SubscriptionManager {
@@ -5400,6 +5494,11 @@
     method public void setDefaultDataSubId(int);
     method public void setDefaultSmsSubId(int);
     field public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
+    field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
+    field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
+    field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
+    field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
+    field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
     field public static final android.net.Uri VT_ENABLED_CONTENT_URI;
     field public static final android.net.Uri WFC_ENABLED_CONTENT_URI;
     field public static final android.net.Uri WFC_MODE_CONTENT_URI;
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index e305b54..80e6b9b 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -82,6 +82,7 @@
                         Status::EX_SECURITY,
                         "Calling process does not have permission to get local data.");
             }
+            break;
         case DEST_EXPLICIT:
             if (callingUid != AID_SHELL && callingUid != AID_ROOT && callingUid != AID_STATSD &&
                 callingUid != AID_SYSTEM) {
@@ -91,6 +92,7 @@
                         Status::EX_SECURITY,
                         "Calling process does not have permission to get explicit data.");
             }
+            break;
     }
     return Status::ok();
 }
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index d9fa0f1..68b6522 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -1640,6 +1640,9 @@
 
     // SWAP
     optional int64 swap_in_bytes = 8;
+
+    // The elapsed real time of start of the process.
+    optional int64 process_start_time_nanos = 9;
 }
 
 /*
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 15005d0..d2f2468 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -82,8 +82,10 @@
 import android.net.EthernetManager;
 import android.net.IConnectivityManager;
 import android.net.IEthernetManager;
+import android.net.IIpMemoryStore;
 import android.net.IIpSecService;
 import android.net.INetworkPolicyManager;
+import android.net.IpMemoryStore;
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
@@ -107,6 +109,7 @@
 import android.nfc.NfcManager;
 import android.os.BatteryManager;
 import android.os.BatteryStats;
+import android.os.BugreportManager;
 import android.os.Build;
 import android.os.DeviceIdleManager;
 import android.os.DropBoxManager;
@@ -114,6 +117,7 @@
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
 import android.os.IDeviceIdleController;
+import android.os.IDumpstate;
 import android.os.IHardwarePropertiesManager;
 import android.os.IPowerManager;
 import android.os.IRecoverySystem;
@@ -286,10 +290,21 @@
 
         registerService(Context.NETWORK_STACK_SERVICE, NetworkStack.class,
                 new StaticServiceFetcher<NetworkStack>() {
-                @Override
-                public NetworkStack createService() {
-                    return new NetworkStack();
-                }});
+                    @Override
+                    public NetworkStack createService() {
+                        return new NetworkStack();
+                    }});
+
+        registerService(Context.IP_MEMORY_STORE_SERVICE, IpMemoryStore.class,
+                new CachedServiceFetcher<IpMemoryStore>() {
+                    @Override
+                    public IpMemoryStore createService(final ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(
+                                Context.IP_MEMORY_STORE_SERVICE);
+                        IIpMemoryStore service = IIpMemoryStore.Stub.asInterface(b);
+                        return new IpMemoryStore(ctx, service);
+                    }});
 
         registerService(Context.IPSEC_SERVICE, IpSecManager.class,
                 new CachedServiceFetcher<IpSecManager>() {
@@ -959,6 +974,16 @@
                 return new IncidentManager(ctx);
             }});
 
+        registerService(Context.BUGREPORT_SERVICE, BugreportManager.class,
+                new CachedServiceFetcher<BugreportManager>() {
+                    @Override
+                    public BugreportManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder b = ServiceManager.getServiceOrThrow(Context.BUGREPORT_SERVICE);
+                        return new BugreportManager(ctx.getOuterContext(),
+                                IDumpstate.Stub.asInterface(b));
+                    }});
+
         registerService(Context.AUTOFILL_MANAGER_SERVICE, AutofillManager.class,
                 new CachedServiceFetcher<AutofillManager>() {
             @Override
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 10c8b15..38245fb 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2066,8 +2066,7 @@
      * Get the current connection state of a profile.
      * This function can be used to check whether the local Bluetooth adapter
      * is connected to any remote device for a specific profile.
-     * Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET},
-     * {@link BluetoothProfile#A2DP}.
+     * Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}.
      *
      * <p> Return value can be one of
      * {@link BluetoothProfile#STATE_DISCONNECTED},
@@ -2441,16 +2440,15 @@
     /**
      * Get the profile proxy object associated with the profile.
      *
-     * <p>Profile can be one of {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET},
-     * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, or
-     * {@link BluetoothProfile#GATT_SERVER}. Clients must implement
-     * {@link BluetoothProfile.ServiceListener} to get notified of
-     * the connection status and to get the proxy object.
+     * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
+     * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must
+     * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status
+     * and to get the proxy object.
      *
      * @param context Context of the application
      * @param listener The service Listener for connection callbacks.
-     * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEALTH}, {@link
-     * BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
+     * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET},
+     * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
      * {@link BluetoothProfile#GATT_SERVER}.
      * @return true on success, false on error
      */
@@ -2479,8 +2477,8 @@
             BluetoothPan pan = new BluetoothPan(context, listener);
             return true;
         } else if (profile == BluetoothProfile.HEALTH) {
-            BluetoothHealth health = new BluetoothHealth(context, listener);
-            return true;
+            Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
+            return false;
         } else if (profile == BluetoothProfile.MAP) {
             BluetoothMap map = new BluetoothMap(context, listener);
             return true;
@@ -2512,8 +2510,7 @@
      *
      * <p> Clients should call this when they are no longer using
      * the proxy obtained from {@link #getProfileProxy}.
-     * Profile can be one of  {@link BluetoothProfile#HEALTH}, {@link BluetoothProfile#HEADSET} or
-     * {@link BluetoothProfile#A2DP}
+     * Profile can be one of  {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#A2DP}
      *
      * @param profile
      * @param proxy Profile proxy object
@@ -2548,10 +2545,6 @@
                 BluetoothPan pan = (BluetoothPan) proxy;
                 pan.close();
                 break;
-            case BluetoothProfile.HEALTH:
-                BluetoothHealth health = (BluetoothHealth) proxy;
-                health.close();
-                break;
             case BluetoothProfile.GATT:
                 BluetoothGatt gatt = (BluetoothGatt) proxy;
                 gatt.close();
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index b967fb2..e2e56fd 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -16,14 +16,7 @@
 
 package android.bluetooth;
 
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Binder;
-import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -53,79 +46,59 @@
  * <li> When done, close the health channel by calling {@link #disconnectChannel}
  * and unregister the application configuration calling
  * {@link #unregisterAppConfiguration}
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New apps
+ * should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
  */
+@Deprecated
 public final class BluetoothHealth implements BluetoothProfile {
     private static final String TAG = "BluetoothHealth";
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
-
     /**
      * Health Profile Source Role - the health device.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public static final int SOURCE_ROLE = 1 << 0;
 
     /**
      * Health Profile Sink Role the device talking to the health device.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public static final int SINK_ROLE = 1 << 1;
 
     /**
      * Health Profile - Channel Type used - Reliable
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public static final int CHANNEL_TYPE_RELIABLE = 10;
 
     /**
      * Health Profile - Channel Type used - Streaming
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public static final int CHANNEL_TYPE_STREAMING = 11;
 
-    /**
-     * @hide
-     */
-    public static final int CHANNEL_TYPE_ANY = 12;
-
-    /** @hide */
-    public static final int HEALTH_OPERATION_SUCCESS = 6000;
-    /** @hide */
-    public static final int HEALTH_OPERATION_ERROR = 6001;
-    /** @hide */
-    public static final int HEALTH_OPERATION_INVALID_ARGS = 6002;
-    /** @hide */
-    public static final int HEALTH_OPERATION_GENERIC_FAILURE = 6003;
-    /** @hide */
-    public static final int HEALTH_OPERATION_NOT_FOUND = 6004;
-    /** @hide */
-    public static final int HEALTH_OPERATION_NOT_ALLOWED = 6005;
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
-                }
-            };
-
 
     /**
      * Register an application configuration that acts as a Health SINK.
@@ -141,53 +114,17 @@
      * @param callback A callback to indicate success or failure of the registration and all
      * operations done on this application configuration.
      * @return If true, callback will be called.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public boolean registerSinkAppConfiguration(String name, int dataType,
             BluetoothHealthCallback callback) {
-        if (!isEnabled() || name == null) return false;
-
-        if (VDBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
-        return registerAppConfiguration(name, dataType, SINK_ROLE,
-                CHANNEL_TYPE_ANY, callback);
-    }
-
-    /**
-     * Register an application configuration that acts as a Health SINK or in a Health
-     * SOURCE role.This is an asynchronous call and so
-     * the callback is used to notify success or failure if the function returns true.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
-     * @param name The friendly name associated with the application or configuration.
-     * @param dataType The dataType of the Source role of Health Profile.
-     * @param channelType The channel type. Will be one of {@link #CHANNEL_TYPE_RELIABLE}  or {@link
-     * #CHANNEL_TYPE_STREAMING}
-     * @param callback - A callback to indicate success or failure.
-     * @return If true, callback will be called.
-     * @hide
-     */
-    public boolean registerAppConfiguration(String name, int dataType, int role,
-            int channelType, BluetoothHealthCallback callback) {
-        boolean result = false;
-        if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
-
-        if (VDBG) log("registerApplication(" + name + ":" + dataType + ")");
-        BluetoothHealthCallbackWrapper wrapper = new BluetoothHealthCallbackWrapper(callback);
-        BluetoothHealthAppConfiguration config =
-                new BluetoothHealthAppConfiguration(name, dataType, role, channelType);
-
-        final IBluetoothHealth service = mService;
-        if (service != null) {
-            try {
-                result = service.registerAppConfiguration(config, wrapper);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return result;
+        Log.e(TAG, "registerSinkAppConfiguration(): BluetoothHealth is deprecated");
+        return false;
     }
 
     /**
@@ -198,22 +135,16 @@
      *
      * @param config The health app configuration
      * @return Success or failure.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
-        boolean result = false;
-        final IBluetoothHealth service = mService;
-        if (service != null && isEnabled() && config != null) {
-            try {
-                result = service.unregisterAppConfiguration(config);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-
-        return result;
+        Log.e(TAG, "unregisterAppConfiguration(): BluetoothHealth is deprecated");
+        return false;
     }
 
     /**
@@ -227,49 +158,16 @@
      * @param config The application configuration which has been registered using {@link
      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
      * @return If true, the callback associated with the application config will be called.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public boolean connectChannelToSource(BluetoothDevice device,
             BluetoothHealthAppConfiguration config) {
-        final IBluetoothHealth service = mService;
-        if (service != null && isEnabled() && isValidDevice(device) && config != null) {
-            try {
-                return service.connectChannelToSource(device, config);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
-     * Connect to a health device which has the {@link #SINK_ROLE}.
-     * This is an asynchronous call. If this function returns true, the callback
-     * associated with the application configuration will be called.
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
-     *
-     * @param device The remote Bluetooth device.
-     * @param config The application configuration which has been registered using {@link
-     * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
-     * @return If true, the callback associated with the application config will be called.
-     * @hide
-     */
-    public boolean connectChannelToSink(BluetoothDevice device,
-            BluetoothHealthAppConfiguration config, int channelType) {
-        final IBluetoothHealth service = mService;
-        if (service != null && isEnabled() && isValidDevice(device) && config != null) {
-            try {
-                return service.connectChannelToSink(device, config, channelType);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
+        Log.e(TAG, "connectChannelToSource(): BluetoothHealth is deprecated");
         return false;
     }
 
@@ -285,20 +183,16 @@
      * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
      * @param channelId The channel id associated with the channel
      * @return If true, the callback associated with the application config will be called.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public boolean disconnectChannel(BluetoothDevice device,
             BluetoothHealthAppConfiguration config, int channelId) {
-        final IBluetoothHealth service = mService;
-        if (service != null && isEnabled() && isValidDevice(device) && config != null) {
-            try {
-                return service.disconnectChannel(device, config, channelId);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
+        Log.e(TAG, "disconnectChannel(): BluetoothHealth is deprecated");
         return false;
     }
 
@@ -314,20 +208,16 @@
      * @param device The remote Bluetooth health device
      * @param config The application configuration
      * @return null on failure, ParcelFileDescriptor on success.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
             BluetoothHealthAppConfiguration config) {
-        final IBluetoothHealth service = mService;
-        if (service != null && isEnabled() && isValidDevice(device) && config != null) {
-            try {
-                return service.getMainChannelFd(device, config);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
+        Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated");
         return null;
     }
 
@@ -347,17 +237,7 @@
      */
     @Override
     public int getConnectionState(BluetoothDevice device) {
-        final IBluetoothHealth service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getHealthDeviceConnectionState(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
+        Log.e(TAG, "getConnectionState(): BluetoothHealth is deprecated");
         return STATE_DISCONNECTED;
     }
 
@@ -377,17 +257,8 @@
      */
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
-        final IBluetoothHealth service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.getConnectedHealthDevices();
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        Log.e(TAG, "getConnectedDevices(): BluetoothHealth is deprecated");
+        return new ArrayList<>();
     }
 
     /**
@@ -409,163 +280,81 @@
      */
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        final IBluetoothHealth service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.getHealthDevicesMatchingConnectionStates(states);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-                return new ArrayList<BluetoothDevice>();
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return new ArrayList<BluetoothDevice>();
+        Log.e(TAG, "getDevicesMatchingConnectionStates(): BluetoothHealth is deprecated");
+        return new ArrayList<>();
     }
 
-    private static class BluetoothHealthCallbackWrapper extends IBluetoothHealthCallback.Stub {
-        private BluetoothHealthCallback mCallback;
-
-        public BluetoothHealthCallbackWrapper(BluetoothHealthCallback callback) {
-            mCallback = callback;
-        }
-
-        @Override
-        public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
-                int status) {
-            mCallback.onHealthAppConfigurationStatusChange(config, status);
-        }
-
-        @Override
-        public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
-                BluetoothDevice device, int prevState, int newState,
-                ParcelFileDescriptor fd, int channelId) {
-            mCallback.onHealthChannelStateChange(config, device, prevState, newState, fd,
-                    channelId);
-        }
-    }
-
-    /** Health Channel Connection State - Disconnected */
+    /** Health Channel Connection State - Disconnected
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
     public static final int STATE_CHANNEL_DISCONNECTED = 0;
-    /** Health Channel Connection State - Connecting */
+    /** Health Channel Connection State - Connecting
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
     public static final int STATE_CHANNEL_CONNECTING = 1;
-    /** Health Channel Connection State - Connected */
+    /** Health Channel Connection State - Connected
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
     public static final int STATE_CHANNEL_CONNECTED = 2;
-    /** Health Channel Connection State - Disconnecting */
+    /** Health Channel Connection State - Disconnecting
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
     public static final int STATE_CHANNEL_DISCONNECTING = 3;
 
-    /** Health App Configuration registration success */
-    public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
-    /** Health App Configuration registration failure */
-    public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
-    /** Health App Configuration un-registration success */
-    public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
-    /** Health App Configuration un-registration failure */
-    public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
-
-    private Context mContext;
-    private ServiceListener mServiceListener;
-    private volatile IBluetoothHealth mService;
-    BluetoothAdapter mAdapter;
-
-    /**
-     * Create a BluetoothHealth proxy object.
+    /** Health App Configuration registration success
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
-    /*package*/ BluetoothHealth(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothHealth.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent);
-            return false;
-        }
-        return true;
-    }
-
-    /*package*/ void close() {
-        if (VDBG) log("close()");
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
-    }
-
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothHealth.Stub.asInterface(Binder.allowBlocking(service));
-
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, BluetoothHealth.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.HEALTH);
-            }
-        }
-    };
-
-    private boolean isEnabled() {
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-
-        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
-        log("Bluetooth is Not enabled");
-        return false;
-    }
-
-    private static boolean isValidDevice(BluetoothDevice device) {
-        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
-    }
-
-    private boolean checkAppParam(String name, int role, int channelType,
-            BluetoothHealthCallback callback) {
-        if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE)
-                || (channelType != CHANNEL_TYPE_RELIABLE && channelType != CHANNEL_TYPE_STREAMING
-                    && channelType != CHANNEL_TYPE_ANY)
-                || callback == null) {
-            return false;
-        }
-        if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
-        return true;
-    }
-
-    private static void log(String msg) {
-        Log.d(TAG, msg);
-    }
+    @Deprecated
+    public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
+    /** Health App Configuration registration failure
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
+    /** Health App Configuration un-registration success
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
+    /** Health App Configuration un-registration failure
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
 }
diff --git a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
index 7c9db6f..9788bbf 100644
--- a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
+++ b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
@@ -25,72 +25,14 @@
  * the {@link BluetoothHealth} class. This class represents an application configuration
  * that the Bluetooth Health third party application will register to communicate with the
  * remote Bluetooth health device.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
  */
+@Deprecated
 public final class BluetoothHealthAppConfiguration implements Parcelable {
-    private final String mName;
-    private final int mDataType;
-    private final int mRole;
-    private final int mChannelType;
-
-    /**
-     * Constructor to register the SINK role
-     *
-     * @param name Friendly name associated with the application configuration
-     * @param dataType Data Type of the remote Bluetooth Health device
-     * @hide
-     */
-    BluetoothHealthAppConfiguration(String name, int dataType) {
-        mName = name;
-        mDataType = dataType;
-        mRole = BluetoothHealth.SINK_ROLE;
-        mChannelType = BluetoothHealth.CHANNEL_TYPE_ANY;
-    }
-
-    /**
-     * Constructor to register the application configuration.
-     *
-     * @param name Friendly name associated with the application configuration
-     * @param dataType Data Type of the remote Bluetooth Health device
-     * @param role {@link BluetoothHealth#SOURCE_ROLE} or {@link BluetoothHealth#SINK_ROLE}
-     * @hide
-     */
-    BluetoothHealthAppConfiguration(String name, int dataType, int role, int
-            channelType) {
-        mName = name;
-        mDataType = dataType;
-        mRole = role;
-        mChannelType = channelType;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof BluetoothHealthAppConfiguration) {
-            BluetoothHealthAppConfiguration config = (BluetoothHealthAppConfiguration) o;
-
-            if (mName == null) return false;
-
-            return mName.equals(config.getName()) && mDataType == config.getDataType()
-                    && mRole == config.getRole() && mChannelType == config.getChannelType();
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-        result = 31 * result + (mName != null ? mName.hashCode() : 0);
-        result = 31 * result + mDataType;
-        result = 31 * result + mRole;
-        result = 31 * result + mChannelType;
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "BluetoothHealthAppConfiguration [mName = " + mName + ",mDataType = " + mDataType
-                + ", mRole = " + mRole + ",mChannelType = " + mChannelType + "]";
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -100,50 +42,59 @@
      * Return the data type associated with this application configuration.
      *
      * @return dataType
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public int getDataType() {
-        return mDataType;
+        return 0;
     }
 
     /**
      * Return the name of the application configuration.
      *
      * @return String name
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public String getName() {
-        return mName;
+        return null;
     }
 
     /**
      * Return the role associated with this application configuration.
      *
      * @return One of {@link BluetoothHealth#SOURCE_ROLE} or {@link BluetoothHealth#SINK_ROLE}
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     public int getRole() {
-        return mRole;
+        return 0;
     }
 
     /**
-     * Return the channel type associated with this application configuration.
-     *
-     * @return One of {@link BluetoothHealth#CHANNEL_TYPE_RELIABLE} or {@link
-     * BluetoothHealth#CHANNEL_TYPE_STREAMING} or {@link BluetoothHealth#CHANNEL_TYPE_ANY}.
-     * @hide
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
-    public int getChannelType() {
-        return mChannelType;
-    }
-
+    @Deprecated
     public static final Parcelable.Creator<BluetoothHealthAppConfiguration> CREATOR =
             new Parcelable.Creator<BluetoothHealthAppConfiguration>() {
                 @Override
                 public BluetoothHealthAppConfiguration createFromParcel(Parcel in) {
-                    String name = in.readString();
-                    int type = in.readInt();
-                    int role = in.readInt();
-                    int channelType = in.readInt();
-                    return new BluetoothHealthAppConfiguration(name, type, role,
-                            channelType);
+                    return new BluetoothHealthAppConfiguration();
                 }
 
                 @Override
@@ -153,10 +104,5 @@
             };
 
     @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeString(mName);
-        out.writeInt(mDataType);
-        out.writeInt(mRole);
-        out.writeInt(mChannelType);
-    }
+    public void writeToParcel(Parcel out, int flags) {}
 }
diff --git a/core/java/android/bluetooth/BluetoothHealthCallback.java b/core/java/android/bluetooth/BluetoothHealthCallback.java
index 4023485..4769212 100644
--- a/core/java/android/bluetooth/BluetoothHealthCallback.java
+++ b/core/java/android/bluetooth/BluetoothHealthCallback.java
@@ -23,7 +23,13 @@
 
 /**
  * This abstract class is used to implement {@link BluetoothHealth} callbacks.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
  */
+@Deprecated
 public abstract class BluetoothHealthCallback {
     private static final String TAG = "BluetoothHealthCallback";
 
@@ -38,8 +44,14 @@
      * BluetoothHealth#APP_CONFIG_REGISTRATION_FAILURE} or
      * {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_SUCCESS}
      * or {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_FAILURE}
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
     @BinderThread
+    @Deprecated
     public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
             int status) {
         Log.d(TAG, "onHealthAppConfigurationStatusChange: " + config + "Status: " + status);
@@ -58,8 +70,14 @@
      * @param fd The Parcel File Descriptor when the channel state is connected.
      * @param channelId The id associated with the channel. This id will be used in future calls
      * like when disconnecting the channel.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
     @BinderThread
+    @Deprecated
     public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
             BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd,
             int channelId) {
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 3c3a01b..3c87c73 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -72,7 +72,13 @@
 
     /**
      * Health Profile
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
      */
+    @Deprecated
     int HEALTH = 3;
 
     /**
@@ -269,9 +275,8 @@
          * Called to notify the client when the proxy object has been
          * connected to the service.
          *
-         * @param profile - One of {@link #HEALTH}, {@link #HEADSET} or {@link #A2DP}
-         * @param proxy - One of {@link BluetoothHealth}, {@link BluetoothHeadset} or {@link
-         * BluetoothA2dp}
+         * @param profile - One of {@link #HEADSET} or {@link #A2DP}
+         * @param proxy - One of {@link BluetoothHeadset} or {@link BluetoothA2dp}
          */
         public void onServiceConnected(int profile, BluetoothProfile proxy);
 
@@ -279,7 +284,7 @@
          * Called to notify the client that this proxy object has been
          * disconnected from the service.
          *
-         * @param profile - One of {@link #HEALTH}, {@link #HEADSET} or {@link #A2DP}
+         * @param profile - One of {@link #HEADSET} or {@link #A2DP}
          */
         public void onServiceDisconnected(int profile);
     }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 81e72cc..89cd064 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3015,6 +3015,7 @@
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
             CONNECTIVITY_SERVICE,
+            //@hide: IP_MEMORY_STORE_SERVICE,
             IPSEC_SERVICE,
             //@hide: UPDATE_LOCK_SERVICE,
             //@hide: NETWORKMANAGEMENT_SERVICE,
@@ -3514,6 +3515,14 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.net.IpMemoryStore} to store and read information about
+     * known networks.
+     * @hide
+     */
+    public static final String IP_MEMORY_STORE_SERVICE = "ipmemorystore";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
      * IPSec.
      *
@@ -4204,6 +4213,16 @@
     public static final String STATS_MANAGER = "stats";
 
     /**
+     * Service to capture a bugreport.
+     * @see #getSystemService(String)
+     * @see android.os.BugreportManager
+     * @hide
+     */
+    // TODO: Expose API when the implementation is more complete.
+    // @SystemApi
+    public static final String BUGREPORT_SERVICE = "bugreport";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link
      * android.content.om.OverlayManager} for managing overlay packages.
      *
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 23e4ec0..abc00fe 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2051,6 +2051,16 @@
         return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
+    /** @hide */
+    public NetworkRequest getDefaultRequest() {
+        try {
+            // This is not racy as the default request is final in ConnectivityService.
+            return mService.getDefaultRequest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /* TODO: These permissions checks don't belong in client-side code. Move them to
      * services.jar, possibly in com.android.server.net. */
 
@@ -2485,6 +2495,8 @@
     public static final int TETHER_ERROR_IFACE_CFG_ERROR      = 10;
     /** {@hide} */
     public static final int TETHER_ERROR_PROVISION_FAILED     = 11;
+    /** {@hide} */
+    public static final int TETHER_ERROR_DHCPSERVER_ERROR     = 12;
 
     /**
      * Get a more detailed error code after a Tethering or Untethering
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e7d441d..da5d96e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -167,6 +167,8 @@
 
     int getMultipathPreference(in Network Network);
 
+    NetworkRequest getDefaultRequest();
+
     int getRestoreDefaultNetworkDelay(int networkType);
 
     boolean addVpnAddress(String address, int prefixLength);
diff --git a/core/java/android/net/IIpMemoryStore.aidl b/core/java/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..6f88dec
--- /dev/null
+++ b/core/java/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,113 @@
+/*
+ * 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.net;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+
+/** {@hide} */
+oneway interface IIpMemoryStore {
+    /**
+     * Store network attributes for a given L2 key.
+     * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+     * calling findL2Key with the attributes and storing in the returned value.
+     *
+     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+     *              key and only care about grouping can pass a unique ID here like the ones
+     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+     *              relevance of such a network will lead to it being evicted soon if it's not
+     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+     * @param attributes The attributes for this network.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * @return (through the listener) The L2 key. This is useful if the L2 key was not specified.
+     *         If the call failed, the L2 key will be null.
+     */
+    void storeNetworkAttributes(String l2Key, in NetworkAttributesParcelable attributes,
+            IOnStatusListener listener);
+
+    /**
+     * Store a binary blob associated with an L2 key and a name.
+     *
+     * @param l2Key The L2 key for this network.
+     * @param clientId The ID of the client.
+     * @param name The name of this data.
+     * @param data The data to store.
+     * @param listener A listener to inform of the completion of this call, or null if the client
+     *        is not interested in learning about success/failure.
+     * @return (through the listener) A status to indicate success or failure.
+     */
+    void storeBlob(String l2Key, String clientId, String name, in Blob data,
+            IOnStatusListener listener);
+
+    /**
+     * Returns the best L2 key associated with the attributes.
+     *
+     * This will find a record that would be in the same group as the passed attributes. This is
+     * useful to choose the key for storing a sample or private data when the L2 key is not known.
+     * If multiple records are group-close to these attributes, the closest match is returned.
+     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+     * order) L2 key is returned.
+     * If no record matches these attributes, null is returned.
+     *
+     * @param attributes The attributes of the network to find.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The L2 key if one matched, or null.
+     */
+    void findL2Key(in NetworkAttributesParcelable attributes, IOnL2KeyResponseListener listener);
+
+    /**
+     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+     * to the same L3 network. Group-closeness is used to determine this.
+     *
+     * @param l2Key1 The key for the first network.
+     * @param l2Key2 The key for the second network.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence.
+     */
+    void isSameNetwork(String l2Key1, String l2Key2, IOnSameNetworkResponseListener listener);
+
+    /**
+     * Retrieve the network attributes for a key.
+     * If no record is present for this key, this will return null attributes.
+     *
+     * @param l2Key The key of the network to query.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The network attributes and the L2 key associated with
+     *         the query.
+     */
+    void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrieved listener);
+
+    /**
+     * Retrieve previously stored private data.
+     * If no data was stored for this L2 key and name this will return null.
+     *
+     * @param l2Key The L2 key.
+     * @param clientId The id of the client that stored this data.
+     * @param name The name of the data.
+     * @param listener The listener that will be invoked to return the answer.
+     * @return (through the listener) The private data (or null), with the L2 key
+     *         and the name of the data associated with the query.
+     */
+    void retrieveBlob(String l2Key, String clientId, String name,
+            IOnBlobRetrievedListener listener);
+}
diff --git a/core/java/android/net/INetworkMonitor.aidl b/core/java/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..41f969a
--- /dev/null
+++ b/core/java/android/net/INetworkMonitor.aidl
@@ -0,0 +1,45 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitor {
+    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+    // The network should be used as a default internet connection.  It was found to be:
+    // 1. a functioning network providing internet access, or
+    // 2. a captive portal and the user decided to use it as is.
+    const int NETWORK_TEST_RESULT_VALID = 0;
+
+    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
+    // The network should not be used as a default internet connection.  It was found to be:
+    // 1. a captive portal and the user is prompted to sign-in, or
+    // 2. a captive portal and the user did not want to use it, or
+    // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
+    const int NETWORK_TEST_RESULT_INVALID = 1;
+
+    void start();
+    void launchCaptivePortalApp();
+    void forceReevaluation(int uid);
+    void notifyPrivateDnsChanged(in PrivateDnsConfigParcel config);
+    void notifyDnsResponse(int returnCode);
+    void notifySystemReady();
+    void notifyNetworkConnected();
+    void notifyNetworkDisconnected();
+    void notifyLinkPropertiesChanged();
+    void notifyNetworkCapabilitiesChanged();
+}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkMonitorCallbacks.aidl b/core/java/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..0bc2575
--- /dev/null
+++ b/core/java/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.net;
+
+import android.net.INetworkMonitor;
+import android.net.PrivateDnsConfigParcel;
+
+/** @hide */
+oneway interface INetworkMonitorCallbacks {
+    void onNetworkMonitorCreated(in INetworkMonitor networkMonitor);
+    void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+    void notifyPrivateDnsConfigResolved(in PrivateDnsConfigParcel config);
+    void showProvisioningNotification(String action);
+    void hideProvisioningNotification();
+}
\ No newline at end of file
diff --git a/core/java/android/net/INetworkStackConnector.aidl b/core/java/android/net/INetworkStackConnector.aidl
index 29f8828..2df8ab7 100644
--- a/core/java/android/net/INetworkStackConnector.aidl
+++ b/core/java/android/net/INetworkStackConnector.aidl
@@ -15,7 +15,13 @@
  */
 package android.net;
 
+import android.net.INetworkMonitorCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServerCallbacks;
+
 /** @hide */
 oneway interface INetworkStackConnector {
-    // TODO: requestDhcpServer(), etc. will go here
+    void makeDhcpServer(in String ifName, in DhcpServingParamsParcel params,
+        in IDhcpServerCallbacks cb);
+    void makeNetworkMonitor(int netId, String name, in INetworkMonitorCallbacks cb);
 }
\ No newline at end of file
diff --git a/core/java/android/net/INetworkStackStatusCallback.aidl b/core/java/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..51032d8
--- /dev/null
+++ b/core/java/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.net;
+
+/** @hide */
+oneway interface INetworkStackStatusCallback {
+    void onStatusAvailable(int statusCode);
+}
\ No newline at end of file
diff --git a/core/java/android/net/IpMemoryStore.java b/core/java/android/net/IpMemoryStore.java
new file mode 100644
index 0000000..b35f097
--- /dev/null
+++ b/core/java/android/net/IpMemoryStore.java
@@ -0,0 +1,174 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * The interface for system components to access the IP memory store.
+ * @see com.android.server.net.ipmemorystore.IpMemoryStoreService
+ * @hide
+ */
+@SystemService(Context.IP_MEMORY_STORE_SERVICE)
+public class IpMemoryStore {
+    @NonNull final Context mContext;
+    @NonNull final IIpMemoryStore mService;
+
+    public IpMemoryStore(@NonNull final Context context, @NonNull final IIpMemoryStore service) {
+        mContext = Preconditions.checkNotNull(context, "missing context");
+        mService = Preconditions.checkNotNull(service, "missing IIpMemoryStore");
+    }
+
+    /**
+     * Store network attributes for a given L2 key.
+     * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
+     * calling findL2Key with the attributes and storing in the returned value.
+     *
+     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+     *              key and only care about grouping can pass a unique ID here like the ones
+     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+     *              relevance of such a network will lead to it being evicted soon if it's not
+     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+     * @param attributes The attributes for this network.
+     * @param listener A listener that will be invoked to inform of the completion of this call,
+     *                 or null if the client is not interested in learning about success/failure.
+     * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+     * If the call failed, the L2 key will be null.
+     */
+    public void storeNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final NetworkAttributes attributes,
+            @Nullable final IOnStatusListener listener) {
+        try {
+            mService.storeNetworkAttributes(l2Key, attributes.toParcelable(), listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Store a binary blob associated with an L2 key and a name.
+     *
+     * @param l2Key The L2 key for this network.
+     * @param clientId The ID of the client.
+     * @param name The name of this data.
+     * @param data The data to store.
+     * @param listener A listener to inform of the completion of this call, or null if the client
+     *        is not interested in learning about success/failure.
+     * Through the listener, returns a status to indicate success or failure.
+     */
+    public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final Blob data,
+            @Nullable final IOnStatusListener listener) {
+        try {
+            mService.storeBlob(l2Key, clientId, name, data, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the best L2 key associated with the attributes.
+     *
+     * This will find a record that would be in the same group as the passed attributes. This is
+     * useful to choose the key for storing a sample or private data when the L2 key is not known.
+     * If multiple records are group-close to these attributes, the closest match is returned.
+     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+     * order) L2 key is returned.
+     * If no record matches these attributes, null is returned.
+     *
+     * @param attributes The attributes of the network to find.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the L2 key if one matched, or null.
+     */
+    public void findL2Key(@NonNull final NetworkAttributes attributes,
+            @NonNull final IOnL2KeyResponseListener listener) {
+        try {
+            mService.findL2Key(attributes.toParcelable(), listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+     * to the same L3 network. Group-closeness is used to determine this.
+     *
+     * @param l2Key1 The key for the first network.
+     * @param l2Key2 The key for the second network.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+     */
+    public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+            @NonNull final IOnSameNetworkResponseListener listener) {
+        try {
+            mService.isSameNetwork(l2Key1, l2Key2, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve the network attributes for a key.
+     * If no record is present for this key, this will return null attributes.
+     *
+     * @param l2Key The key of the network to query.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the network attributes and the L2 key associated with
+     *         the query.
+     */
+    public void retrieveNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final IOnNetworkAttributesRetrieved listener) {
+        try {
+            mService.retrieveNetworkAttributes(l2Key, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve previously stored private data.
+     * If no data was stored for this L2 key and name this will return null.
+     *
+     * @param l2Key The L2 key.
+     * @param clientId The id of the client that stored this data.
+     * @param name The name of the data.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the private data (or null), with the L2 key
+     *         and the name of the data associated with the query.
+     */
+    public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+        try {
+            mService.retrieveBlob(l2Key, clientId, name, listener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 3a79206..4a466f3 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -55,6 +55,8 @@
     private String mIfaceName;
     private ArrayList<LinkAddress> mLinkAddresses = new ArrayList<>();
     private ArrayList<InetAddress> mDnses = new ArrayList<>();
+    // PCSCF addresses are addresses of SIP proxies that only exist for the IMS core service.
+    private ArrayList<InetAddress> mPcscfs = new ArrayList<InetAddress>();
     private ArrayList<InetAddress> mValidatedPrivateDnses = new ArrayList<>();
     private boolean mUsePrivateDns;
     private String mPrivateDnsServerName;
@@ -179,6 +181,7 @@
             mValidatedPrivateDnses.addAll(source.mValidatedPrivateDnses);
             mUsePrivateDns = source.mUsePrivateDns;
             mPrivateDnsServerName = source.mPrivateDnsServerName;
+            mPcscfs.addAll(source.mPcscfs);
             mDomains = source.mDomains;
             mRoutes.addAll(source.mRoutes);
             mHttpProxy = (source.mHttpProxy == null) ? null : new ProxyInfo(source.mHttpProxy);
@@ -525,6 +528,60 @@
     }
 
     /**
+     * Adds the given {@link InetAddress} to the list of PCSCF servers, if not present.
+     *
+     * @param pcscfServer The {@link InetAddress} to add to the list of PCSCF servers.
+     * @return true if the PCSCF server was added, false otherwise.
+     * @hide
+     */
+    public boolean addPcscfServer(InetAddress pcscfServer) {
+        if (pcscfServer != null && !mPcscfs.contains(pcscfServer)) {
+            mPcscfs.add(pcscfServer);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Removes the given {@link InetAddress} from the list of PCSCF servers.
+     *
+     * @param pcscf Server The {@link InetAddress} to remove from the list of PCSCF servers.
+     * @return true if the PCSCF server was removed, false otherwise.
+     * @hide
+     */
+    public boolean removePcscfServer(InetAddress pcscfServer) {
+        if (pcscfServer != null) {
+            return mPcscfs.remove(pcscfServer);
+        }
+        return false;
+    }
+
+    /**
+     * Replaces the PCSCF servers in this {@code LinkProperties} with
+     * the given {@link Collection} of {@link InetAddress} objects.
+     *
+     * @param addresses The {@link Collection} of PCSCF servers to set in this object.
+     * @hide
+     */
+    public void setPcscfServers(Collection<InetAddress> pcscfServers) {
+        mPcscfs.clear();
+        for (InetAddress pcscfServer: pcscfServers) {
+            addPcscfServer(pcscfServer);
+        }
+    }
+
+    /**
+     * Returns all the {@link InetAddress} for PCSCF servers on this link.
+     *
+     * @return An unmodifiable {@link List} of {@link InetAddress} for PCSCF servers on
+     *         this link.
+     * @hide
+     */
+    public List<InetAddress> getPcscfServers() {
+        return Collections.unmodifiableList(mPcscfs);
+    }
+
+    /**
      * Sets the DNS domain search path used on this link.
      *
      * @param domains A {@link String} listing in priority order the comma separated
@@ -767,6 +824,7 @@
         mDnses.clear();
         mUsePrivateDns = false;
         mPrivateDnsServerName = null;
+        mPcscfs.clear();
         mDomains = null;
         mRoutes.clear();
         mHttpProxy = null;
@@ -813,6 +871,12 @@
             resultJoiner.add(mPrivateDnsServerName);
         }
 
+        if (!mPcscfs.isEmpty()) {
+            resultJoiner.add("PcscfAddresses: [");
+            resultJoiner.add(TextUtils.join(",", mPcscfs));
+            resultJoiner.add("]");
+        }
+
         if (!mValidatedPrivateDnses.isEmpty()) {
             final StringJoiner validatedPrivateDnsesJoiner =
                     new StringJoiner(",", "ValidatedPrivateDnsAddresses: [", "]");
@@ -965,6 +1029,36 @@
     }
 
     /**
+     * Returns true if this link has an IPv4 PCSCF server.
+     *
+     * @return {@code true} if there is an IPv4 PCSCF server, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv4PcscfServer() {
+        for (InetAddress ia : mPcscfs) {
+          if (ia instanceof Inet4Address) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this link has an IPv6 PCSCF server.
+     *
+     * @return {@code true} if there is an IPv6 PCSCF server, {@code false} otherwise.
+     * @hide
+     */
+    public boolean hasIPv6PcscfServer() {
+        for (InetAddress ia : mPcscfs) {
+          if (ia instanceof Inet6Address) {
+            return true;
+          }
+        }
+        return false;
+    }
+
+    /**
      * Returns true if this link is provisioned for global IPv4 connectivity.
      * This requires an IP address, default route, and DNS server.
      *
@@ -1117,6 +1211,19 @@
     }
 
     /**
+     * Compares this {@code LinkProperties} PCSCF addresses against the target
+     *
+     * @param target LinkProperties to compare.
+     * @return {@code true} if both are identical, {@code false} otherwise.
+     * @hide
+     */
+    public boolean isIdenticalPcscfs(LinkProperties target) {
+        Collection<InetAddress> targetPcscfs = target.getPcscfServers();
+        return (mPcscfs.size() == targetPcscfs.size()) ?
+                    mPcscfs.containsAll(targetPcscfs) : false;
+    }
+
+    /**
      * Compares this {@code LinkProperties} Routes against the target
      *
      * @param target LinkProperties to compare.
@@ -1218,6 +1325,7 @@
                 && isIdenticalDnses(target)
                 && isIdenticalPrivateDns(target)
                 && isIdenticalValidatedPrivateDnses(target)
+                && isIdenticalPcscfs(target)
                 && isIdenticalRoutes(target)
                 && isIdenticalHttpProxy(target)
                 && isIdenticalStackedLinks(target)
@@ -1334,6 +1442,7 @@
                 + mMtu * 51
                 + ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode())
                 + (mUsePrivateDns ? 57 : 0)
+                + mPcscfs.size() * 67
                 + ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode());
     }
 
@@ -1357,6 +1466,10 @@
         }
         dest.writeBoolean(mUsePrivateDns);
         dest.writeString(mPrivateDnsServerName);
+        dest.writeInt(mPcscfs.size());
+        for (InetAddress d : mPcscfs) {
+            dest.writeByteArray(d.getAddress());
+        }
         dest.writeString(mDomains);
         dest.writeInt(mMtu);
         dest.writeString(mTcpBufferSizes);
@@ -1406,6 +1519,12 @@
                 }
                 netProp.setUsePrivateDns(in.readBoolean());
                 netProp.setPrivateDnsServerName(in.readString());
+                addressCount = in.readInt();
+                for (int i = 0; i < addressCount; i++) {
+                    try {
+                        netProp.addPcscfServer(InetAddress.getByAddress(in.createByteArray()));
+                    } catch (UnknownHostException e) { }
+                }
                 netProp.setDomains(in.readString());
                 netProp.setMtu(in.readInt());
                 netProp.setTcpBufferSizes(in.readString());
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index 82a4e31..2eac6de 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -25,9 +25,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServerCallbacks;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.Slog;
@@ -45,19 +48,52 @@
 public class NetworkStack {
     private static final String TAG = NetworkStack.class.getSimpleName();
 
+    public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack";
+
     @NonNull
     @GuardedBy("mPendingNetStackRequests")
-    private final ArrayList<NetworkStackRequest> mPendingNetStackRequests = new ArrayList<>();
+    private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
     @Nullable
     @GuardedBy("mPendingNetStackRequests")
     private INetworkStackConnector mConnector;
 
-    private interface NetworkStackRequest {
+    private interface NetworkStackCallback {
         void onNetworkStackConnected(INetworkStackConnector connector);
     }
 
     public NetworkStack() { }
 
+    /**
+     * Create a DHCP server according to the specified parameters.
+     *
+     * <p>The server will be returned asynchronously through the provided callbacks.
+     */
+    public void makeDhcpServer(final String ifName, final DhcpServingParamsParcel params,
+            final IDhcpServerCallbacks cb) {
+        requestConnector(connector -> {
+            try {
+                connector.makeDhcpServer(ifName, params, cb);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        });
+    }
+
+    /**
+     * Create a NetworkMonitor.
+     *
+     * <p>The INetworkMonitor will be returned asynchronously through the provided callbacks.
+     */
+    public void makeNetworkMonitor(Network network, String name, INetworkMonitorCallbacks cb) {
+        requestConnector(connector -> {
+            try {
+                connector.makeNetworkMonitor(network.netId, name, cb);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        });
+    }
+
     private class NetworkStackConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -77,14 +113,14 @@
         ServiceManager.addService(Context.NETWORK_STACK_SERVICE, service, false /* allowIsolated */,
                 DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
 
-        final ArrayList<NetworkStackRequest> requests;
+        final ArrayList<NetworkStackCallback> requests;
         synchronized (mPendingNetStackRequests) {
             requests = new ArrayList<>(mPendingNetStackRequests);
             mPendingNetStackRequests.clear();
             mConnector = connector;
         }
 
-        for (NetworkStackRequest r : requests) {
+        for (NetworkStackCallback r : requests) {
             r.onNetworkStackConnected(connector);
         }
     }
@@ -105,7 +141,8 @@
                     "com.android.server.NetworkStackService",
                     true /* initialize */,
                     context.getClassLoader());
-            connector = (IBinder) service.getMethod("makeConnector").invoke(null);
+            connector = (IBinder) service.getMethod("makeConnector", Context.class)
+                    .invoke(null, context);
         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
             Slog.wtf(TAG, "Could not create network stack connector from NetworkStackService");
             // TODO: crash/reboot system here ?
@@ -134,7 +171,7 @@
     }
 
     // TODO: use this method to obtain the connector when implementing network stack operations
-    private void requestConnector(@NonNull NetworkStackRequest request) {
+    private void requestConnector(@NonNull NetworkStackCallback request) {
         // TODO: PID check.
         if (Binder.getCallingUid() != Process.SYSTEM_UID) {
             // Don't even attempt to obtain the connector and give a nice error message
diff --git a/core/java/android/net/PrivateDnsConfigParcel.aidl b/core/java/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..b52fce6
--- /dev/null
+++ b/core/java/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.net;
+
+parcelable PrivateDnsConfigParcel {
+    String hostname;
+    String[] ips;
+}
diff --git a/core/java/android/net/dhcp/DhcpServerCallbacks.java b/core/java/android/net/dhcp/DhcpServerCallbacks.java
new file mode 100644
index 0000000..bb56876
--- /dev/null
+++ b/core/java/android/net/dhcp/DhcpServerCallbacks.java
@@ -0,0 +1,33 @@
+/*
+ * 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.net.dhcp;
+
+/**
+ * Convenience wrapper around IDhcpServerCallbacks.Stub that implements getInterfaceVersion().
+ * @hide
+ */
+public abstract class DhcpServerCallbacks extends IDhcpServerCallbacks.Stub {
+    // TODO: add @Override here once the API is versioned
+
+    /**
+     * Get the version of the aidl interface implemented by the callbacks.
+     */
+    public int getInterfaceVersion() {
+        // TODO: return IDhcpServerCallbacks.VERSION;
+        return 0;
+    }
+}
diff --git a/core/java/android/net/dhcp/IDhcpServer.aidl b/core/java/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..559433b
--- /dev/null
+++ b/core/java/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,32 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import android.net.INetworkStackStatusCallback;
+import android.net.dhcp.DhcpServingParamsParcel;
+
+/** @hide */
+oneway interface IDhcpServer {
+    const int STATUS_UNKNOWN = 0;
+    const int STATUS_SUCCESS = 1;
+    const int STATUS_INVALID_ARGUMENT = 2;
+    const int STATUS_UNKNOWN_ERROR = 3;
+
+    void start(in INetworkStackStatusCallback cb);
+    void updateParams(in DhcpServingParamsParcel params, in INetworkStackStatusCallback cb);
+    void stop(in INetworkStackStatusCallback cb);
+}
diff --git a/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl b/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..7ab4dcd
--- /dev/null
+++ b/core/java/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,24 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import android.net.dhcp.IDhcpServer;
+
+/** @hide */
+oneway interface IDhcpServerCallbacks {
+    void onDhcpServerCreated(int statusCode, in IDhcpServer server);
+}
diff --git a/core/java/android/net/ipmemorystore/Blob.aidl b/core/java/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..9dbef11
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.net.ipmemorystore;
+
+/**
+ * A blob of data opaque to the memory store. The client mutates this at its own risk,
+ * and it is strongly suggested to never do it at all and treat this as immutable.
+ * {@hide}
+ */
+parcelable Blob {
+    byte[] data;
+}
diff --git a/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..4926feb
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnBlobRetrievedListener {
+    /**
+     * Private data was retrieved for the L2 key and name specified.
+     * Note this does not return the client ID, as clients are expected to only ever use one ID.
+     */
+     void onBlobRetrieved(in StatusParcelable status, in String l2Key, in String name,
+             in Blob data);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..dea0cc4
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnL2KeyResponseListener {
+    /**
+     * The operation completed with the specified L2 key.
+     */
+     void onL2KeyResponse(in StatusParcelable status, in String l2Key);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
new file mode 100644
index 0000000..57f59a1
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnNetworkAttributesRetrieved {
+    /**
+     * Network attributes were fetched for the specified L2 key. While the L2 key will never
+     * be null, the attributes may be if no data is stored about this L2 key.
+     */
+     void onL2KeyResponse(in StatusParcelable status, in String l2Key,
+             in NetworkAttributesParcelable attributes);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
new file mode 100644
index 0000000..294bd3b
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.net.ipmemorystore.SameL3NetworkResponseParcelable;
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnSameNetworkResponseListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+     void onSameNetworkResponse(in StatusParcelable status,
+             in SameL3NetworkResponseParcelable response);
+}
diff --git a/core/java/android/net/ipmemorystore/IOnStatusListener.aidl b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..5d07504
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.net.ipmemorystore.StatusParcelable;
+
+/** {@hide} */
+oneway interface IOnStatusListener {
+    /**
+     * The operation has completed with the specified status.
+     */
+     void onComplete(in StatusParcelable status);
+}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributes.java b/core/java/android/net/ipmemorystore/NetworkAttributes.java
new file mode 100644
index 0000000..d7e5b27
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/NetworkAttributes.java
@@ -0,0 +1,210 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A POD object to represent attributes of a single L2 network entry.
+ * @hide
+ */
+public class NetworkAttributes {
+    private static final boolean DBG = true;
+
+    // The v4 address that was assigned to this device the last time it joined this network.
+    // This typically comes from DHCP but could be something else like static configuration.
+    // This does not apply to IPv6.
+    // TODO : add a list of v6 prefixes for the v6 case.
+    @Nullable
+    public final Inet4Address assignedV4Address;
+
+    // Optionally supplied by the client if it has an opinion on L3 network. For example, this
+    // could be a hash of the SSID + security type on WiFi.
+    @Nullable
+    public final String groupHint;
+
+    // The list of DNS server addresses.
+    @Nullable
+    public final List<InetAddress> dnsAddresses;
+
+    // The mtu on this network.
+    @Nullable
+    public final Integer mtu;
+
+    NetworkAttributes(
+            @Nullable final Inet4Address assignedV4Address,
+            @Nullable final String groupHint,
+            @Nullable final List<InetAddress> dnsAddresses,
+            @Nullable final Integer mtu) {
+        if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+        this.assignedV4Address = assignedV4Address;
+        this.groupHint = groupHint;
+        this.dnsAddresses = null == dnsAddresses ? null :
+                Collections.unmodifiableList(new ArrayList<>(dnsAddresses));
+        this.mtu = mtu;
+    }
+
+    @VisibleForTesting
+    public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) {
+        // The call to the other constructor must be the first statement of this constructor,
+        // so everything has to be inline
+        this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address),
+                parcelable.groupHint,
+                blobArrayToInetAddressList(parcelable.dnsAddresses),
+                parcelable.mtu >= 0 ? parcelable.mtu : null);
+    }
+
+    @Nullable
+    private static InetAddress getByAddressOrNull(@Nullable final byte[] address) {
+        try {
+            return InetAddress.getByAddress(address);
+        } catch (UnknownHostException e) {
+            return null;
+        }
+    }
+
+    @Nullable
+    private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) {
+        if (null == blobs) return null;
+        final ArrayList<InetAddress> list = new ArrayList<>(blobs.length);
+        for (final Blob b : blobs) {
+            final InetAddress addr = getByAddressOrNull(b.data);
+            if (null != addr) list.add(addr);
+        }
+        return list;
+    }
+
+    @Nullable
+    private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) {
+        if (null == addresses) return null;
+        final ArrayList<Blob> blobs = new ArrayList<>();
+        for (int i = 0; i < addresses.size(); ++i) {
+            final InetAddress addr = addresses.get(i);
+            if (null == addr) continue;
+            final Blob b = new Blob();
+            b.data = addr.getAddress();
+            blobs.add(b);
+        }
+        return blobs.toArray(new Blob[0]);
+    }
+
+    /** Converts this NetworkAttributes to a parcelable object */
+    @NonNull
+    public NetworkAttributesParcelable toParcelable() {
+        final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable();
+        parcelable.assignedV4Address =
+                (null == assignedV4Address) ? null : assignedV4Address.getAddress();
+        parcelable.groupHint = groupHint;
+        parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses);
+        parcelable.mtu = (null == mtu) ? -1 : mtu;
+        return parcelable;
+    }
+
+    /** @hide */
+    public static class Builder {
+        @Nullable
+        private Inet4Address mAssignedAddress;
+        @Nullable
+        private String mGroupHint;
+        @Nullable
+        private List<InetAddress> mDnsAddresses;
+        @Nullable
+        private Integer mMtu;
+
+        /**
+         * Set the assigned address.
+         * @param assignedV4Address The assigned address.
+         * @return This builder.
+         */
+        public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) {
+            mAssignedAddress = assignedV4Address;
+            return this;
+        }
+
+        /**
+         * Set the group hint.
+         * @param groupHint The group hint.
+         * @return This builder.
+         */
+        public Builder setGroupHint(@Nullable final String groupHint) {
+            mGroupHint = groupHint;
+            return this;
+        }
+
+        /**
+         * Set the DNS addresses.
+         * @param dnsAddresses The DNS addresses.
+         * @return This builder.
+         */
+        public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) {
+            if (DBG && null != dnsAddresses) {
+                // Parceling code crashes if one of the addresses is null, therefore validate
+                // them when running in debug.
+                for (final InetAddress address : dnsAddresses) {
+                    if (null == address) throw new IllegalArgumentException("Null DNS address");
+                }
+            }
+            this.mDnsAddresses = dnsAddresses;
+            return this;
+        }
+
+        /**
+         * Set the MTU.
+         * @param mtu The MTU.
+         * @return This builder.
+         */
+        public Builder setMtu(@Nullable final Integer mtu) {
+            if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative");
+            mMtu = mtu;
+            return this;
+        }
+
+        /**
+         * Return the built NetworkAttributes object.
+         * @return The built NetworkAttributes object.
+         */
+        public NetworkAttributes build() {
+            return new NetworkAttributes(mAssignedAddress, mGroupHint, mDnsAddresses, mMtu);
+        }
+    }
+
+    @Override
+    public boolean equals(@Nullable final Object o) {
+        if (!(o instanceof NetworkAttributes)) return false;
+        final NetworkAttributes other = (NetworkAttributes) o;
+        return Objects.equals(assignedV4Address, other.assignedV4Address)
+                && Objects.equals(groupHint, other.groupHint)
+                && Objects.equals(dnsAddresses, other.dnsAddresses)
+                && Objects.equals(mtu, other.mtu);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(assignedV4Address, groupHint, dnsAddresses, mtu);
+    }
+}
diff --git a/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..0894d72
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,36 @@
+/*
+ * 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.net.ipmemorystore;
+
+// Blob[] is used to represent an array of byte[], as structured AIDL does not support arrays
+// of arrays.
+import android.net.ipmemorystore.Blob;
+
+/**
+ * An object to represent attributes of a single L2 network entry.
+ * See NetworkAttributes.java for a description of each field. The types used in this class
+ * are structured parcelable types instead of the richer types of the NetworkAttributes object,
+ * but they have the same purpose. The NetworkAttributes.java file also contains the code
+ * to convert the richer types to the parcelable types and back.
+ * @hide
+ */
+parcelable NetworkAttributesParcelable {
+    byte[] assignedV4Address;
+    String groupHint;
+    Blob[] dnsAddresses;
+    int mtu;
+}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
new file mode 100644
index 0000000..0cb37e9
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponse.java
@@ -0,0 +1,131 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An object representing the answer to a query whether two given L2 networks represent the
+ * same L3 network. Parcels as a SameL3NetworkResponseParceled object.
+ * @hide
+ */
+public class SameL3NetworkResponse {
+    @IntDef(prefix = "NETWORK_",
+            value = {NETWORK_SAME, NETWORK_DIFFERENT, NETWORK_NEVER_CONNECTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NetworkSameness {}
+
+    /**
+     * Both L2 networks represent the same L3 network.
+     */
+    public static final int NETWORK_SAME = 1;
+
+    /**
+     * The two L2 networks represent a different L3 network.
+     */
+    public static final int NETWORK_DIFFERENT = 2;
+
+    /**
+     * The device has never connected to at least one of these two L2 networks, or data
+     * has been wiped. Therefore the device has never seen the L3 network behind at least
+     * one of these two L2 networks, and can't evaluate whether it's the same as the other.
+     */
+    public static final int NETWORK_NEVER_CONNECTED = 3;
+
+    /**
+     * The first L2 key specified in the query.
+     */
+    @NonNull
+    public final String l2Key1;
+
+    /**
+     * The second L2 key specified in the query.
+     */
+    @NonNull
+    public final String l2Key2;
+
+    /**
+     * A confidence value indicating whether the two L2 networks represent the same L3 network.
+     *
+     * If both L2 networks were known, this value will be between 0.0 and 1.0, with 0.0
+     * representing complete confidence that the given L2 networks represent a different
+     * L3 network, and 1.0 representing complete confidence that the given L2 networks
+     * represent the same L3 network.
+     * If at least one of the L2 networks was not known, this value will be outside of the
+     * 0.0~1.0 range.
+     *
+     * Most apps should not be interested in this, and are encouraged to use the collapsing
+     * {@link #getNetworkSameness()} function below.
+     */
+    public final float confidence;
+
+    /**
+     * @return whether the two L2 networks represent the same L3 network. Either
+     *     {@code NETWORK_SAME}, {@code NETWORK_DIFFERENT} or {@code NETWORK_NEVER_CONNECTED}.
+     */
+    @NetworkSameness
+    public final int getNetworkSameness() {
+        if (confidence > 1.0 || confidence < 0.0) return NETWORK_NEVER_CONNECTED;
+        return confidence > 0.5 ? NETWORK_SAME : NETWORK_DIFFERENT;
+    }
+
+    SameL3NetworkResponse(@NonNull final String l2Key1, @NonNull final String l2Key2,
+            final float confidence) {
+        this.l2Key1 = l2Key1;
+        this.l2Key2 = l2Key2;
+        this.confidence = confidence;
+    }
+
+    /** Builds a SameL3NetworkResponse from a parcelable object */
+    @VisibleForTesting
+    public SameL3NetworkResponse(@NonNull final SameL3NetworkResponseParcelable parceled) {
+        this(parceled.l2Key1, parceled.l2Key2, parceled.confidence);
+    }
+
+    /** Converts this SameL3NetworkResponse to a parcelable object */
+    @NonNull
+    public SameL3NetworkResponseParcelable toParcelable() {
+        final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+        parcelable.l2Key1 = l2Key1;
+        parcelable.l2Key2 = l2Key2;
+        parcelable.confidence = confidence;
+        return parcelable;
+    }
+
+    // Note key1 and key2 have to match each other for this to return true. If
+    // key1 matches o.key2 and the other way around this returns false.
+    @Override
+    public boolean equals(@Nullable final Object o) {
+        if (!(o instanceof SameL3NetworkResponse)) return false;
+        final SameL3NetworkResponse other = (SameL3NetworkResponse) o;
+        return l2Key1.equals(other.l2Key1) && l2Key2.equals(other.l2Key2)
+                && confidence == other.confidence;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(l2Key1, l2Key2, confidence);
+    }
+}
diff --git a/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..7196699
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.net.ipmemorystore;
+
+/** {@hide} */
+parcelable SameL3NetworkResponseParcelable {
+    String l2Key1;
+    String l2Key2;
+    float confidence;
+}
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
new file mode 100644
index 0000000..5b016ec
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -0,0 +1,50 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A parcelable status representing the result of an operation.
+ * Parcels as StatusParceled.
+ * @hide
+ */
+public class Status {
+    public static final int SUCCESS = 0;
+
+    public final int resultCode;
+
+    public Status(final int resultCode) {
+        this.resultCode = resultCode;
+    }
+
+    Status(@NonNull final StatusParcelable parcelable) {
+        this(parcelable.resultCode);
+    }
+
+    /** Converts this Status to a parcelable object */
+    @NonNull
+    public StatusParcelable toParcelable() {
+        final StatusParcelable parcelable = new StatusParcelable();
+        parcelable.resultCode = resultCode;
+        return parcelable;
+    }
+
+    public boolean isSuccess() {
+        return SUCCESS == resultCode;
+    }
+}
diff --git a/core/java/android/net/ipmemorystore/StatusParcelable.aidl b/core/java/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..fb36ef4
--- /dev/null
+++ b/core/java/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.net.ipmemorystore;
+
+/** {@hide} */
+parcelable StatusParcelable {
+  int resultCode;
+}
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
new file mode 100644
index 0000000..1343d24
--- /dev/null
+++ b/core/java/android/os/BugreportManager.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 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.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.IBinder.DeathRecipient;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that provides a privileged API to capture and consume bugreports.
+ *
+ * @hide
+ */
+// TODO: Expose API when the implementation is more complete.
+// @SystemApi
+@SystemService(Context.BUGREPORT_SERVICE)
+public class BugreportManager {
+    private final Context mContext;
+    private final IDumpstate mBinder;
+
+    /** @hide */
+    public BugreportManager(@NonNull Context context, IDumpstate binder) {
+        mContext = context;
+        mBinder = binder;
+    }
+
+    /**
+     * An interface describing the listener for bugreport progress and status.
+     */
+    public interface BugreportListener {
+        /**
+         * Called when there is a progress update.
+         * @param progress the progress in [0.0, 100.0]
+         */
+        void onProgress(float progress);
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
+                BUGREPORT_ERROR_INVALID_INPUT,
+                BUGREPORT_ERROR_RUNTIME
+        })
+
+        /** Possible error codes taking a bugreport can encounter */
+        @interface BugreportErrorCode {}
+
+        /** The input options were invalid */
+        int BUGREPORT_ERROR_INVALID_INPUT = 1;
+
+        /** A runtime error occured */
+        int BUGREPORT_ERROR_RUNTIME = 2;
+
+        /**
+         * Called when taking bugreport resulted in an error.
+         *
+         * @param errorCode the error that occurred. Possible values are
+         *     {@code BUGREPORT_ERROR_INVALID_INPUT}, {@code BUGREPORT_ERROR_RUNTIME}.
+         */
+        void onError(@BugreportErrorCode int errorCode);
+
+        /**
+         * Called when taking bugreport finishes successfully
+         *
+         * @param durationMs time capturing bugreport took in milliseconds
+         * @param title title for the bugreport; helpful in reminding the user why they took it
+         * @param description detailed description for the bugreport
+         */
+        void onFinished(long durationMs, @NonNull String title,
+                @NonNull String description);
+    }
+
+    /**
+     * Starts a bugreport asynchronously.
+     *
+     * @param bugreportFd file to write the bugreport. This should be opened in write-only,
+     *     append mode.
+     * @param screenshotFd file to write the screenshot, if necessary. This should be opened
+     *     in write-only, append mode.
+     * @param params options that specify what kind of a bugreport should be taken
+     * @param listener callback for progress and status updates
+     */
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void startBugreport(@NonNull FileDescriptor bugreportFd,
+            @Nullable FileDescriptor screenshotFd,
+            @NonNull BugreportParams params, @Nullable BugreportListener listener) {
+        // TODO(b/111441001): Enforce android.Manifest.permission.DUMP if necessary.
+        DumpstateListener dsListener = new DumpstateListener(listener);
+
+        try {
+            mBinder.startBugreport(bugreportFd, screenshotFd, params.getMode(), dsListener);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    // TODO(b/111441001) Connect up with BugreportListener methods.
+    private final class DumpstateListener extends IDumpstateListener.Stub
+            implements DeathRecipient {
+        private final BugreportListener mListener;
+
+        DumpstateListener(@Nullable BugreportListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            // TODO(b/111441001): implement
+        }
+
+        @Override
+        public void onProgressUpdated(int progress) throws RemoteException {
+            // TODO(b/111441001): implement
+        }
+
+        @Override
+        public void onMaxProgressUpdated(int maxProgress) throws RemoteException {
+            // TODO(b/111441001): implement
+        }
+
+        @Override
+        public void onSectionComplete(String title, int status, int size, int durationMs)
+                throws RemoteException {
+            // TODO(b/111441001): implement
+        }
+    }
+}
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
new file mode 100644
index 0000000..4e696ae
--- /dev/null
+++ b/core/java/android/os/BugreportParams.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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.os;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Parameters that specify what kind of bugreport should be taken.
+ *
+ * @hide
+ */
+// TODO: Expose API when the implementation is more complete.
+// @SystemApi
+public final class BugreportParams {
+    private final int mMode;
+
+    public BugreportParams(@BugreportMode int mode) {
+        mMode = mode;
+    }
+
+    public int getMode() {
+        return mMode;
+    }
+
+    /**
+     * Defines acceptable types of bugreports.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "BUGREPORT_MODE_" }, value = {
+            BUGREPORT_MODE_FULL,
+            BUGREPORT_MODE_INTERACTIVE,
+            BUGREPORT_MODE_REMOTE,
+            BUGREPORT_MODE_WEAR,
+            BUGREPORT_MODE_TELEPHONY,
+            BUGREPORT_MODE_WIFI
+    })
+    public @interface BugreportMode {}
+
+    /**
+     * Options for a bugreport without user interference (and hence causing less
+     * interference to the system), but includes all sections.
+     */
+    public static final int BUGREPORT_MODE_FULL = IDumpstate.BUGREPORT_MODE_FULL;
+
+    /**
+     * Options that allow user to monitor progress and enter additional data; might not
+     * include all sections.
+     */
+    public static final int BUGREPORT_MODE_INTERACTIVE = IDumpstate.BUGREPORT_MODE_INTERACTIVE;
+
+    /**
+     * Options for a bugreport requested remotely by administrator of the Device Owner app,
+     * not the device's user.
+     */
+    public static final int BUGREPORT_MODE_REMOTE = IDumpstate.BUGREPORT_MODE_REMOTE;
+
+    /**
+     * Options for a bugreport on a wearable device.
+     */
+    public static final int BUGREPORT_MODE_WEAR = IDumpstate.BUGREPORT_MODE_WEAR;
+
+    /**
+     * Options for a lightweight version of bugreport that only includes a few, urgent
+     * sections used to report telephony bugs.
+     */
+    public static final int BUGREPORT_MODE_TELEPHONY = IDumpstate.BUGREPORT_MODE_TELEPHONY;
+
+    /**
+     * Options for a lightweight bugreport that only includes a few sections related to
+     * Wifi.
+     */
+    public static final int BUGREPORT_MODE_WIFI = IDumpstate.BUGREPORT_MODE_WIFI;
+}
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 77670b3..6c039d8 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -454,6 +454,7 @@
                 (RelativeLayout.LayoutParams) mAmPmLayout.getLayoutParams();
         if (params.getRule(RelativeLayout.RIGHT_OF) != 0
                 || params.getRule(RelativeLayout.LEFT_OF) != 0) {
+            final int margin = (int) (mContext.getResources().getDisplayMetrics().density * 8);
             // Horizontal mode, with AM/PM appearing to left/right of hours and minutes.
             final boolean isAmPmAtLeft;
             if (TextUtils.getLayoutDirectionFromLocale(mLocale) == View.LAYOUT_DIRECTION_LTR) {
@@ -461,10 +462,6 @@
             } else {
                 isAmPmAtLeft = !isAmPmAtStart;
             }
-            if (mIsAmPmAtLeft == isAmPmAtLeft) {
-                // AM/PM is already at the correct location. No change needed.
-                return;
-            }
 
             if (isAmPmAtLeft) {
                 params.removeRule(RelativeLayout.RIGHT_OF);
@@ -473,6 +470,14 @@
                 params.removeRule(RelativeLayout.LEFT_OF);
                 params.addRule(RelativeLayout.RIGHT_OF, mMinuteView.getId());
             }
+
+            if (isAmPmAtStart) {
+                params.setMarginStart(0);
+                params.setMarginEnd(margin);
+            } else {
+                params.setMarginStart(margin);
+                params.setMarginEnd(0);
+            }
             mIsAmPmAtLeft = isAmPmAtLeft;
         } else if (params.getRule(RelativeLayout.BELOW) != 0
                 || params.getRule(RelativeLayout.ABOVE) != 0) {
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 0a7cff6..9bacf9b 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -20,6 +20,7 @@
 import static android.net.NetworkStats.TAG_ALL;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+
 import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
 
 import android.annotation.Nullable;
@@ -33,10 +34,8 @@
 
 import libcore.io.IoUtils;
 
-import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileReader;
 import java.io.IOException;
 import java.net.ProtocolException;
 import java.util.Arrays;
@@ -127,7 +126,7 @@
     }
 
     public NetworkStatsFactory() {
-        this(new File("/proc/"), new File("/sys/fs/bpf/traffic_uid_stats_map").exists());
+        this(new File("/proc/"), new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists());
     }
 
     @VisibleForTesting
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 685fcaf..03a463e 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -231,7 +231,7 @@
     }
 
     if (isMutable && isHardware) {
-        doThrowIAE(env, "Bitmaps with Config.HARWARE are always immutable");
+        doThrowIAE(env, "Bitmaps with Config.HARDWARE are always immutable");
         return nullObjectReturn("Cannot create mutable hardware bitmap");
     }
 
diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml
index ced1722..580c913 100644
--- a/core/res/res/layout/time_picker_header_material.xml
+++ b/core/res/res/layout/time_picker_header_material.xml
@@ -76,16 +76,14 @@
         android:layout_height="wrap_content"
         android:layout_toRightOf="@+id/minutes"
         android:layout_alignBaseline="@+id/minutes"
-        android:paddingStart="4dp"
-        android:paddingEnd="4dp"
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="0dp"
         android:orientation="vertical"
         android:baselineAlignedChildIndex="1">
         <RadioButton
             android:id="@+id/am_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:paddingLeft="4dp"
-            android:paddingRight="4dp"
             android:paddingTop="8dp"
             android:paddingBottom="8dp"
             android:layout_marginBottom="-8dp"
@@ -101,8 +99,6 @@
             android:id="@+id/pm_label"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:paddingLeft="4dp"
-            android:paddingRight="4dp"
             android:paddingTop="8dp"
             android:paddingBottom="8dp"
             android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel"
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c8bd847..5f047cb 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -229,6 +229,32 @@
         <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.mainline.networkstack">
+        <permission name="android.permission.ACCESS_NETWORK_CONDITIONS"/>
+        <permission name="android.permission.CHANGE_BACKGROUND_DATA_SETTING"/>
+        <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+        <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+        <permission name="android.permission.CONTROL_VPN"/>
+        <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
+        <permission name="android.permission.MANAGE_IPSEC_TUNNELS"/>
+        <permission name="android.permission.MANAGE_NETWORK_POLICY"/>
+        <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/>
+        <permission name="android.permission.MANAGE_USB"/>
+        <permission name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"/>
+        <permission name="android.permission.NETWORK_SETTINGS"/>
+        <permission name="android.permission.NETWORK_STACK" />
+        <permission name="android.permission.NET_TUNNELING"/>
+        <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD"/>
+        <permission name="android.permission.PEERS_MAC_ADDRESS"/>
+        <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+        <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
+        <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+        <permission name="android.permission.READ_WIFI_CREDENTIAL"/>
+        <permission name="android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"/>
+        <permission name="android.permission.TETHER_PRIVILEGED"/>
+        <permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.server.telecom">
         <permission name="android.permission.BIND_CONNECTION_SERVICE"/>
         <permission name="android.permission.BIND_INCALL_SERVICE"/>
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index adab1a9c..022fbdc 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -440,7 +440,8 @@
             if (opts == null) return;
 
             if (opts.inBitmap != null && opts.inBitmap.getConfig() == Bitmap.Config.HARDWARE) {
-                throw new IllegalArgumentException("Bitmaps with Config.HARWARE are always immutable");
+                throw new IllegalArgumentException(
+                        "Bitmaps with Config.HARDWARE are always immutable");
             }
 
             if (opts.inMutable && opts.inPreferredConfig == Bitmap.Config.HARDWARE) {
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index f0a3a95..9f3c021 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -80,7 +80,7 @@
 
     explicit Matrix4(const float* v) { load(v); }
 
-    Matrix4(const SkMatrix& v) {  // NOLINT, implicit
+    Matrix4(const SkMatrix& v) {  // NOLINT(google-explicit-constructor)
         load(v);
     }
 
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index 0715187..320190f 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -57,15 +57,15 @@
 
     inline Rect(float width, float height) : left(0.0f), top(0.0f), right(width), bottom(height) {}
 
-    inline Rect(const SkIRect& rect)
-            :  // NOLINT, implicit
+    inline Rect(const SkIRect& rect) // NOLINT(google-explicit-constructor)
+            :
             left(rect.fLeft)
             , top(rect.fTop)
             , right(rect.fRight)
             , bottom(rect.fBottom) {}
 
-    inline Rect(const SkRect& rect)
-            :  // NOLINT, implicit
+    inline Rect(const SkRect& rect) // NOLINT(google-explicit-constructor)
+            :
             left(rect.fLeft)
             , top(rect.fTop)
             , right(rect.fRight)
diff --git a/libs/hwui/debug/GlesErrorCheckWrapper.h b/libs/hwui/debug/GlesErrorCheckWrapper.h
index ee5cc1f..791400b 100644
--- a/libs/hwui/debug/GlesErrorCheckWrapper.h
+++ b/libs/hwui/debug/GlesErrorCheckWrapper.h
@@ -24,7 +24,7 @@
 
 class GlesErrorCheckWrapper : public GlesDriver {
 public:
-    GlesErrorCheckWrapper(GlesDriver& base) : mBase(base) {}
+    explicit GlesErrorCheckWrapper(GlesDriver& base) : mBase(base) {}
 
 #define GL_ENTRY(ret, api, ...) virtual ret api##_(__VA_ARGS__) override;
 #include "gles_decls.in"
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 002f759..e4d81c1 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -45,7 +45,7 @@
 
     Paint();
     Paint(const Paint& paint);
-    Paint(const SkPaint& paint);  // NOLINT(implicit)
+    Paint(const SkPaint& paint);  // NOLINT(google-explicit-constructor)
     ~Paint();
 
     Paint& operator=(const Paint& other);
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 29e4256..8a16b20 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -48,7 +48,7 @@
 
 class FileDescriptor {
 public:
-    FileDescriptor(int fd) : mFd(fd) {}
+    explicit FileDescriptor(int fd) : mFd(fd) {}
     ~FileDescriptor() {
         if (mFd != -1) {
             close(mFd);
@@ -56,7 +56,7 @@
         }
     }
     bool valid() { return mFd != -1; }
-    operator int() { return mFd; }
+    operator int() { return mFd; } // NOLINT(google-explicit-constructor)
 
 private:
     int mFd;
@@ -64,7 +64,7 @@
 
 class FileOutputStreamLite : public io::ZeroCopyOutputStream {
 public:
-    FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
+    explicit FileOutputStreamLite(int fd) : mCopyAdapter(fd), mImpl(&mCopyAdapter) {}
     virtual ~FileOutputStreamLite() {}
 
     int GetErrno() { return mCopyAdapter.mErrno; }
@@ -82,7 +82,7 @@
         int mFd;
         int mErrno = 0;
 
-        FDAdapter(int fd) : mFd(fd) {}
+        explicit FDAdapter(int fd) : mFd(fd) {}
         virtual ~FDAdapter() {}
 
         virtual bool Write(const void* buffer, int size) override {
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
index 03f685e..16cf52f 100644
--- a/libs/hwui/utils/LinearAllocator.h
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -168,7 +168,7 @@
     };
     // enable allocators to be constructed from other templated types
     template <class U>
-    LinearStdAllocator(const LinearStdAllocator<U>& other)  // NOLINT(implicit)
+    LinearStdAllocator(const LinearStdAllocator<U>& other)  // NOLINT(google-explicit-constructor)
             : linearAllocator(other.linearAllocator) {}
 
     T* allocate(size_t num, const void* = 0) {
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 0a90f85..6c5f20c 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -39,7 +39,7 @@
     virtual ~WeakLooperCallback() { }
 
 public:
-    WeakLooperCallback(const wp<LooperCallback>& callback) :
+    explicit WeakLooperCallback(const wp<LooperCallback>& callback) :
         mCallback(callback) {
     }
 
diff --git a/libs/protoutil/include/android/util/EncodedBuffer.h b/libs/protoutil/include/android/util/EncodedBuffer.h
index c84de4c..0b7f6e46 100644
--- a/libs/protoutil/include/android/util/EncodedBuffer.h
+++ b/libs/protoutil/include/android/util/EncodedBuffer.h
@@ -38,13 +38,13 @@
 {
 public:
     EncodedBuffer();
-    EncodedBuffer(size_t chunkSize);
+    explicit EncodedBuffer(size_t chunkSize);
     ~EncodedBuffer();
 
     class Pointer {
     public:
         Pointer();
-        Pointer(size_t chunkSize);
+        explicit Pointer(size_t chunkSize);
 
         size_t pos() const;
         size_t index() const;
@@ -161,7 +161,7 @@
     friend class iterator;
     class iterator {
     public:
-        iterator(const EncodedBuffer& buffer);
+        explicit iterator(const EncodedBuffer& buffer);
 
         /**
          * Returns the number of bytes written in the buffer
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index b09335c..35f2877 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -18,5 +18,7 @@
     name: "com.android.location.provider",
     srcs: ["java/**/*.java"],
     api_packages: ["com.android.location.provider"],
-    metalava_enabled: false,
+    srcs_lib: "framework",
+    srcs_lib_whitelist_dirs: ["location/java"],
+    srcs_lib_whitelist_pkgs: ["com.android.internal.location"],
 }
diff --git a/media/lib/signer/Android.bp b/media/lib/signer/Android.bp
index 8c43683..44f8725 100644
--- a/media/lib/signer/Android.bp
+++ b/media/lib/signer/Android.bp
@@ -18,5 +18,7 @@
     name: "com.android.mediadrm.signer",
     srcs: ["java/**/*.java"],
     api_packages: ["com.android.mediadrm.signer"],
-    metalava_enabled: false,
+    srcs_lib: "framework",
+    srcs_lib_whitelist_dirs: ["media/java"],
+    srcs_lib_whitelist_pkgs: ["android.media"],
 }
diff --git a/native/android/Android.bp b/native/android/Android.bp
index c26d980..40290ab 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -78,6 +78,10 @@
     include_dirs: ["bionic/libc/dns/include"],
 
     version_script: "libandroid.map.txt",
+    stubs: {
+        symbol_file: "libandroid.map.txt",
+        versions: ["29"],
+    },
 }
 
 // Network library.
diff --git a/native/android/net.c b/native/android/net.c
index 4cac371..a8104fc 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -84,26 +84,28 @@
     return android_getaddrinfofornet(node, service, hints, netid, 0, res);
 }
 
-int android_res_nquery(net_handle_t network, const char *dname, int ns_class, int ns_type) {
+int android_res_nquery(net_handle_t network, const char *dname,
+        int ns_class, int ns_type, enum ResNsendFlags flags) {
     unsigned netid;
     if (!getnetidfromhandle(network, &netid)) {
         return -ENONET;
     }
 
-    return resNetworkQuery(netid, dname, ns_class, ns_type);
+    return resNetworkQuery(netid, dname, ns_class, ns_type, flags);
 }
 
 int android_res_nresult(int fd, int *rcode, uint8_t *answer, size_t anslen) {
     return resNetworkResult(fd, rcode, answer, anslen);
 }
 
-int android_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen) {
+int android_res_nsend(net_handle_t network, const uint8_t *msg, size_t msglen,
+        enum ResNsendFlags flags) {
     unsigned netid;
     if (!getnetidfromhandle(network, &netid)) {
         return -ENONET;
     }
 
-    return resNetworkSend(netid, msg, msglen);
+    return resNetworkSend(netid, msg, msglen, flags);
 }
 
 void android_res_cancel(int nsend_fd) {
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 55bb517..2f7d599 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -21,7 +21,11 @@
     installable: true,
     srcs: [
         "src/**/*.java",
+        ":services-networkstack-shared-srcs",
     ],
+    static_libs: [
+        "dhcp-packet-lib",
+    ]
 }
 
 // Updatable network stack packaged as an application
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index d1c5cb6..0b0f1ec 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -17,13 +17,16 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.google.android.networkstack"
+          package="com.android.mainline.networkstack"
           android:sharedUserId="android.uid.networkstack">
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.NETWORK_SETTINGS" />
     <!-- Launch captive portal app as specific user -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.NETWORK_STACK" />
     <application
         android:label="NetworkStack"
         android:defaultToDeviceProtectedStorage="true"
diff --git a/services/net/java/android/net/dhcp/DhcpLease.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLease.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpLease.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpLease.java
diff --git a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
similarity index 99%
rename from services/net/java/android/net/dhcp/DhcpLeaseRepository.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
index b3d0512..0d298de 100644
--- a/services/net/java/android/net/dhcp/DhcpLeaseRepository.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpLeaseRepository.java
@@ -21,7 +21,8 @@
 import static android.net.NetworkUtils.prefixLengthToV4NetmaskIntHTH;
 import static android.net.dhcp.DhcpLease.EXPIRATION_NEVER;
 import static android.net.dhcp.DhcpLease.inet4AddrToString;
-import static android.net.util.NetworkConstants.IPV4_ADDR_BITS;
+
+import static com.android.server.util.NetworkStackConstants.IPV4_ADDR_BITS;
 
 import static java.lang.Math.min;
 
diff --git a/services/net/java/android/net/dhcp/DhcpPacketListener.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
similarity index 100%
rename from services/net/java/android/net/dhcp/DhcpPacketListener.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpPacketListener.java
diff --git a/services/net/java/android/net/dhcp/DhcpServer.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
similarity index 85%
rename from services/net/java/android/net/dhcp/DhcpServer.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
index 641bba2..14e2936 100644
--- a/services/net/java/android/net/dhcp/DhcpServer.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServer.java
@@ -23,7 +23,8 @@
 import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
 import static android.net.dhcp.DhcpPacket.DHCP_SERVER;
 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
-import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
+import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.system.OsConstants.AF_INET;
 import static android.system.OsConstants.IPPROTO_UDP;
 import static android.system.OsConstants.SOCK_DGRAM;
@@ -32,21 +33,28 @@
 import static android.system.OsConstants.SO_BROADCAST;
 import static android.system.OsConstants.SO_REUSEADDR;
 
+import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
+import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
+
 import static java.lang.Integer.toUnsignedLong;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.INetworkStackStatusCallback;
 import android.net.MacAddress;
 import android.net.NetworkUtils;
 import android.net.TrafficStats;
 import android.net.util.SharedLog;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
@@ -70,7 +78,7 @@
  * on the looper asynchronously.
  * @hide
  */
-public class DhcpServer {
+public class DhcpServer extends IDhcpServer.Stub {
     private static final String REPO_TAG = "Repository";
 
     // Lease time to transmit to client instead of a negative time in case a lease expired before
@@ -82,7 +90,7 @@
     private static final int CMD_UPDATE_PARAMS = 3;
 
     @NonNull
-    private final ServerHandler mHandler;
+    private final HandlerThread mHandlerThread;
     @NonNull
     private final String mIfName;
     @NonNull
@@ -93,10 +101,14 @@
     private final Dependencies mDeps;
     @NonNull
     private final Clock mClock;
-    @NonNull
-    private final DhcpPacketListener mPacketListener;
 
     @Nullable
+    private volatile ServerHandler mHandler;
+
+    // Accessed only on the handler thread
+    @Nullable
+    private DhcpPacketListener mPacketListener;
+    @Nullable
     private FileDescriptor mSocket;
     @NonNull
     private DhcpServingParams mServingParams;
@@ -156,6 +168,12 @@
          */
         void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr,
                 @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException;
+
+        /**
+         * Verify that the caller is allowed to call public methods on DhcpServer.
+         * @throws SecurityException The caller is not allowed to call public methods on DhcpServer.
+         */
+        void checkCaller() throws SecurityException;
     }
 
     private class DependenciesImpl implements Dependencies {
@@ -189,6 +207,11 @@
                 @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException {
             NetworkUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd);
         }
+
+        @Override
+        public void checkCaller() {
+            checkNetworkStackCallingPermission();
+        }
     }
 
     private static class MalformedPacketException extends Exception {
@@ -197,41 +220,62 @@
         }
     }
 
-    public DhcpServer(@NonNull Looper looper, @NonNull String ifName,
+    public DhcpServer(@NonNull String ifName,
             @NonNull DhcpServingParams params, @NonNull SharedLog log) {
-        this(looper, ifName, params, log, null);
+        this(new HandlerThread(DhcpServer.class.getSimpleName() + "." + ifName),
+                ifName, params, log, null);
     }
 
     @VisibleForTesting
-    DhcpServer(@NonNull Looper looper, @NonNull String ifName,
+    DhcpServer(@NonNull HandlerThread handlerThread, @NonNull String ifName,
             @NonNull DhcpServingParams params, @NonNull SharedLog log,
             @Nullable Dependencies deps) {
         if (deps == null) {
             deps = new DependenciesImpl();
         }
-        mHandler = new ServerHandler(looper);
+        mHandlerThread = handlerThread;
         mIfName = ifName;
         mServingParams = params;
         mLog = log;
         mDeps = deps;
         mClock = deps.makeClock();
-        mPacketListener = deps.makePacketListener();
         mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock);
     }
 
     /**
      * Start listening for and responding to packets.
+     *
+     * <p>It is not legal to call this method more than once; in particular the server cannot be
+     * restarted after being stopped.
      */
-    public void start() {
-        mHandler.sendEmptyMessage(CMD_START_DHCP_SERVER);
+    @Override
+    public void start(@Nullable INetworkStackStatusCallback cb) {
+        mDeps.checkCaller();
+        mHandlerThread.start();
+        mHandler = new ServerHandler(mHandlerThread.getLooper());
+        sendMessage(CMD_START_DHCP_SERVER, cb);
     }
 
     /**
      * Update serving parameters. All subsequently received requests will be handled with the new
      * parameters, and current leases that are incompatible with the new parameters are dropped.
      */
-    public void updateParams(@NonNull DhcpServingParams params) {
-        sendMessage(CMD_UPDATE_PARAMS, params);
+    @Override
+    public void updateParams(@Nullable DhcpServingParamsParcel params,
+            @Nullable INetworkStackStatusCallback cb) throws RemoteException {
+        mDeps.checkCaller();
+        final DhcpServingParams parsedParams;
+        try {
+            // throws InvalidParameterException with null params
+            parsedParams = DhcpServingParams.fromParcelableObject(params);
+        } catch (DhcpServingParams.InvalidParameterException e) {
+            mLog.e("Invalid parameters sent to DhcpServer", e);
+            if (cb != null) {
+                cb.onStatusAvailable(STATUS_INVALID_ARGUMENT);
+            }
+            return;
+        }
+        sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb));
     }
 
     /**
@@ -240,11 +284,17 @@
      * <p>As the server is stopped asynchronously, some packets may still be processed shortly after
      * calling this method.
      */
-    public void stop() {
-        mHandler.sendEmptyMessage(CMD_STOP_DHCP_SERVER);
+    @Override
+    public void stop(@Nullable INetworkStackStatusCallback cb) {
+        mDeps.checkCaller();
+        sendMessage(CMD_STOP_DHCP_SERVER, cb);
     }
 
     private void sendMessage(int what, @Nullable Object obj) {
+        if (mHandler == null) {
+            mLog.e("Attempting to send a command to stopped DhcpServer: " + what);
+            return;
+        }
         mHandler.sendMessage(mHandler.obtainMessage(what, obj));
     }
 
@@ -255,23 +305,42 @@
 
         @Override
         public void handleMessage(@NonNull Message msg) {
+            final INetworkStackStatusCallback cb;
             switch (msg.what) {
                 case CMD_UPDATE_PARAMS:
-                    final DhcpServingParams params = (DhcpServingParams) msg.obj;
+                    final Pair<DhcpServingParams, INetworkStackStatusCallback> pair =
+                            (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj;
+                    final DhcpServingParams params = pair.first;
                     mServingParams = params;
                     mLeaseRepo.updateParams(
                             DhcpServingParams.makeIpPrefix(mServingParams.serverAddr),
                             params.excludedAddrs,
                             params.dhcpLeaseTimeSecs);
+
+                    cb = pair.second;
                     break;
                 case CMD_START_DHCP_SERVER:
-                    // This is a no-op if the listener is already started
+                    mPacketListener = mDeps.makePacketListener();
                     mPacketListener.start();
+                    cb = (INetworkStackStatusCallback) msg.obj;
                     break;
                 case CMD_STOP_DHCP_SERVER:
-                    // This is a no-op if the listener was not started
-                    mPacketListener.stop();
+                    if (mPacketListener != null) {
+                        mPacketListener.stop();
+                        mPacketListener = null;
+                    }
+                    mHandlerThread.quitSafely();
+                    cb = (INetworkStackStatusCallback) msg.obj;
                     break;
+                default:
+                    return;
+            }
+            if (cb != null) {
+                try {
+                    cb.onStatusAvailable(STATUS_SUCCESS);
+                } catch (RemoteException e) {
+                    mLog.e("Could not send status back to caller", e);
+                }
             }
         }
     }
@@ -572,7 +641,7 @@
                 return mSocket;
             } catch (IOException | ErrnoException e) {
                 mLog.e("Error creating UDP socket", e);
-                DhcpServer.this.stop();
+                DhcpServer.this.stop(null);
                 return null;
             } finally {
                 TrafficStats.setThreadStatsTag(oldTag);
diff --git a/services/net/java/android/net/dhcp/DhcpServingParams.java b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
similarity index 95%
rename from services/net/java/android/net/dhcp/DhcpServingParams.java
rename to packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
index 2780814a..f38888a 100644
--- a/services/net/java/android/net/dhcp/DhcpServingParams.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpServingParams.java
@@ -18,9 +18,10 @@
 
 import static android.net.NetworkUtils.getPrefixMaskAsInet4Address;
 import static android.net.NetworkUtils.intToInet4AddressHTH;
-import static android.net.dhcp.DhcpPacket.INFINITE_LEASE;
-import static android.net.util.NetworkConstants.IPV4_MAX_MTU;
-import static android.net.util.NetworkConstants.IPV4_MIN_MTU;
+
+import static com.android.server.util.NetworkStackConstants.INFINITE_LEASE;
+import static com.android.server.util.NetworkStackConstants.IPV4_MAX_MTU;
+import static com.android.server.util.NetworkStackConstants.IPV4_MIN_MTU;
 
 import static java.lang.Integer.toUnsignedLong;
 
@@ -107,9 +108,13 @@
 
     /**
      * Create parameters from a stable AIDL-compatible parcel.
+     * @throws InvalidParameterException The parameters parcelable is null or invalid.
      */
-    public static DhcpServingParams fromParcelableObject(@NonNull DhcpServingParamsParcel parcel)
+    public static DhcpServingParams fromParcelableObject(@Nullable DhcpServingParamsParcel parcel)
             throws InvalidParameterException {
+        if (parcel == null) {
+            throw new InvalidParameterException("Null serving parameters");
+        }
         final LinkAddress serverAddr = new LinkAddress(
                 intToInet4AddressHTH(parcel.serverAddr),
                 parcel.serverAddrPrefixLength);
diff --git a/packages/NetworkStack/src/android/net/util/SharedLog.java b/packages/NetworkStack/src/android/net/util/SharedLog.java
new file mode 100644
index 0000000..4fabf10
--- /dev/null
+++ b/packages/NetworkStack/src/android/net/util/SharedLog.java
@@ -0,0 +1,201 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.StringJoiner;
+
+
+/**
+ * Class to centralize logging functionality for tethering.
+ *
+ * All access to class methods other than dump() must be on the same thread.
+ *
+ * @hide
+ */
+public class SharedLog {
+    private static final int DEFAULT_MAX_RECORDS = 500;
+    private static final String COMPONENT_DELIMITER = ".";
+
+    private enum Category {
+        NONE,
+        ERROR,
+        MARK,
+        WARN,
+    };
+
+    private final LocalLog mLocalLog;
+    // The tag to use for output to the system log. This is not output to the
+    // LocalLog because that would be redundant.
+    private final String mTag;
+    // The component (or subcomponent) of a system that is sharing this log.
+    // This can grow in depth if components call forSubComponent() to obtain
+    // their SharedLog instance. The tag is not included in the component for
+    // brevity.
+    private final String mComponent;
+
+    public SharedLog(String tag) {
+        this(DEFAULT_MAX_RECORDS, tag);
+    }
+
+    public SharedLog(int maxRecords, String tag) {
+        this(new LocalLog(maxRecords), tag, tag);
+    }
+
+    private SharedLog(LocalLog localLog, String tag, String component) {
+        mLocalLog = localLog;
+        mTag = tag;
+        mComponent = component;
+    }
+
+    public String getTag() {
+        return mTag;
+    }
+
+    /**
+     * Create a SharedLog based on this log with an additional component prefix on each logged line.
+     */
+    public SharedLog forSubComponent(String component) {
+        if (!isRootLogInstance()) {
+            component = mComponent + COMPONENT_DELIMITER + component;
+        }
+        return new SharedLog(mLocalLog, mTag, component);
+    }
+
+    /**
+     * Dump the contents of this log.
+     *
+     * <p>This method may be called on any thread.
+     */
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
+    }
+
+    //////
+    // Methods that both log an entry and emit it to the system log.
+    //////
+
+    /**
+     * Log an error due to an exception. This does not include the exception stacktrace.
+     *
+     * <p>The log entry will be also added to the system log.
+     * @see #e(String, Throwable)
+     */
+    public void e(Exception e) {
+        Log.e(mTag, record(Category.ERROR, e.toString()));
+    }
+
+    /**
+     * Log an error message.
+     *
+     * <p>The log entry will be also added to the system log.
+     */
+    public void e(String msg) {
+        Log.e(mTag, record(Category.ERROR, msg));
+    }
+
+    /**
+     * Log an error due to an exception, with the exception stacktrace if provided.
+     *
+     * <p>The error and exception message appear in the shared log, but the stacktrace is only
+     * logged in general log output (logcat). The log entry will be also added to the system log.
+     */
+    public void e(@NonNull String msg, @Nullable Throwable exception) {
+        if (exception == null) {
+            e(msg);
+            return;
+        }
+        Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception);
+    }
+
+    /**
+     * Log an informational message.
+     *
+     * <p>The log entry will be also added to the system log.
+     */
+    public void i(String msg) {
+        Log.i(mTag, record(Category.NONE, msg));
+    }
+
+    /**
+     * Log a warning message.
+     *
+     * <p>The log entry will be also added to the system log.
+     */
+    public void w(String msg) {
+        Log.w(mTag, record(Category.WARN, msg));
+    }
+
+    //////
+    // Methods that only log an entry (and do NOT emit to the system log).
+    //////
+
+    /**
+     * Log a general message to be only included in the in-memory log.
+     *
+     * <p>The log entry will *not* be added to the system log.
+     */
+    public void log(String msg) {
+        record(Category.NONE, msg);
+    }
+
+    /**
+     * Log a general, formatted message to be only included in the in-memory log.
+     *
+     * <p>The log entry will *not* be added to the system log.
+     * @see String#format(String, Object...)
+     */
+    public void logf(String fmt, Object... args) {
+        log(String.format(fmt, args));
+    }
+
+    /**
+     * Log a message with MARK level.
+     *
+     * <p>The log entry will *not* be added to the system log.
+     */
+    public void mark(String msg) {
+        record(Category.MARK, msg);
+    }
+
+    private String record(Category category, String msg) {
+        final String entry = logLine(category, msg);
+        mLocalLog.log(entry);
+        return entry;
+    }
+
+    private String logLine(Category category, String msg) {
+        final StringJoiner sj = new StringJoiner(" ");
+        if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
+        if (category != Category.NONE) sj.add(category.toString());
+        return sj.add(msg).toString();
+    }
+
+    // Check whether this SharedLog instance is nominally the top level in
+    // a potential hierarchy of shared logs (the root of a tree),
+    // or is a subcomponent within the hierarchy.
+    private boolean isRootLogInstance() {
+        return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
+    }
+}
diff --git a/services/net/java/android/net/util/Stopwatch.java b/packages/NetworkStack/src/android/net/util/Stopwatch.java
similarity index 78%
rename from services/net/java/android/net/util/Stopwatch.java
rename to packages/NetworkStack/src/android/net/util/Stopwatch.java
index cb15ee5..c316699 100644
--- a/services/net/java/android/net/util/Stopwatch.java
+++ b/packages/NetworkStack/src/android/net/util/Stopwatch.java
@@ -38,9 +38,9 @@
         return (isStarted() && !isStopped());
     }
 
-    // Returning |this| makes possible the following usage pattern:
-    //
-    //     Stopwatch s = new Stopwatch().start();
+    /**
+     * Start the Stopwatch.
+     */
     public Stopwatch start() {
         if (!isStarted()) {
             mStartTimeMs = SystemClock.elapsedRealtime();
@@ -48,7 +48,10 @@
         return this;
     }
 
-    // Returns the total time recorded, in milliseconds, or 0 if not started.
+    /**
+     * Stop the Stopwatch.
+     * @return the total time recorded, in milliseconds, or 0 if not started.
+     */
     public long stop() {
         if (isRunning()) {
             mStopTimeMs = SystemClock.elapsedRealtime();
@@ -57,9 +60,11 @@
         return (mStopTimeMs - mStartTimeMs);
     }
 
-    // Returns the total time recorded to date, in milliseconds.
-    // If the Stopwatch is not running, returns the same value as stop(),
-    // i.e. either the total time recorded before stopping or 0.
+    /**
+     * Return the total time recorded to date, in milliseconds.
+     * If the Stopwatch is not running, returns the same value as stop(),
+     * i.e. either the total time recorded before stopping or 0.
+     */
     public long lap() {
         if (isRunning()) {
             return (SystemClock.elapsedRealtime() - mStartTimeMs);
@@ -68,6 +73,9 @@
         }
     }
 
+    /**
+     * Reset the Stopwatch. It will be stopped when this method returns.
+     */
     public void reset() {
         mStartTimeMs = 0;
         mStopTimeMs = 0;
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index 5afaf58..057012d 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -16,18 +16,40 @@
 
 package com.android.server;
 
-import static android.os.Binder.getCallingUid;
+import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
+
+import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkStackConnector;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.PrivateDnsConfigParcel;
+import android.net.dhcp.DhcpServer;
+import android.net.dhcp.DhcpServingParams;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.shared.PrivateDnsConfig;
+import android.net.util.SharedLog;
 import android.os.IBinder;
-import android.os.Process;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.connectivity.NetworkMonitor;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
 
 /**
  * Android service used to start the network stack when bound to via an intent.
@@ -43,32 +65,163 @@
      * <p>On platforms where the network stack runs in the system server process, this method may
      * be called directly instead of obtaining the connector by binding to the service.
      */
-    public static IBinder makeConnector() {
-        return new NetworkStackConnector();
+    public static IBinder makeConnector(Context context) {
+        return new NetworkStackConnector(context);
     }
 
     @NonNull
     @Override
     public IBinder onBind(Intent intent) {
-        return makeConnector();
+        return makeConnector(this);
     }
 
     private static class NetworkStackConnector extends INetworkStackConnector.Stub {
-        // TODO: makeDhcpServer(), etc. will go here.
+        private static final int NUM_VALIDATION_LOG_LINES = 20;
+        private final Context mContext;
+        private final ConnectivityManager mCm;
+
+        private static final int MAX_VALIDATION_LOGS = 10;
+        @GuardedBy("mValidationLogs")
+        private final ArrayDeque<SharedLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
+
+        private SharedLog addValidationLogs(Network network, String name) {
+            final SharedLog log = new SharedLog(NUM_VALIDATION_LOG_LINES, network + " - " + name);
+            synchronized (mValidationLogs) {
+                while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
+                    mValidationLogs.removeLast();
+                }
+                mValidationLogs.addFirst(log);
+            }
+            return log;
+        }
+
+        NetworkStackConnector(Context context) {
+            mContext = context;
+            mCm = context.getSystemService(ConnectivityManager.class);
+        }
+
+        @NonNull
+        private final SharedLog mLog = new SharedLog(TAG);
+
+        @Override
+        public void makeDhcpServer(@NonNull String ifName, @NonNull DhcpServingParamsParcel params,
+                @NonNull IDhcpServerCallbacks cb) throws RemoteException {
+            checkNetworkStackCallingPermission();
+            final DhcpServer server;
+            try {
+                server = new DhcpServer(
+                        ifName,
+                        DhcpServingParams.fromParcelableObject(params),
+                        mLog.forSubComponent(ifName + ".DHCP"));
+            } catch (DhcpServingParams.InvalidParameterException e) {
+                mLog.e("Invalid DhcpServingParams", e);
+                cb.onDhcpServerCreated(STATUS_INVALID_ARGUMENT, null);
+                return;
+            } catch (Exception e) {
+                mLog.e("Unknown error starting DhcpServer", e);
+                cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
+                return;
+            }
+            cb.onDhcpServerCreated(STATUS_SUCCESS, server);
+        }
+
+        @Override
+        public void makeNetworkMonitor(int netId, String name, INetworkMonitorCallbacks cb)
+                throws RemoteException {
+            final Network network = new Network(netId, false /* privateDnsBypass */);
+            final NetworkRequest defaultRequest = mCm.getDefaultRequest();
+            final SharedLog log = addValidationLogs(network, name);
+            final NetworkMonitor nm = new NetworkMonitor(
+                    mContext, cb, network, defaultRequest, log);
+            cb.onNetworkMonitorCreated(new NetworkMonitorImpl(nm));
+        }
 
         @Override
         protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
                 @Nullable String[] args) {
-            checkCaller();
-            fout.println("NetworkStack logs:");
-            // TODO: dump logs here
+            checkNetworkStackCallingPermission();
+            final IndentingPrintWriter pw = new IndentingPrintWriter(fout, "  ");
+            pw.println("NetworkStack logs:");
+            mLog.dump(fd, pw, args);
+
+            pw.println();
+            pw.println("Validation logs (most recent first):");
+            synchronized (mValidationLogs) {
+                for (SharedLog p : mValidationLogs) {
+                    pw.println(p.getTag());
+                    pw.increaseIndent();
+                    p.dump(fd, pw, args);
+                    pw.decreaseIndent();
+                }
+            }
         }
     }
 
-    private static void checkCaller() {
-        // TODO: check that the calling PID is the system server.
-        if (getCallingUid() != Process.SYSTEM_UID && getCallingUid() != Process.ROOT_UID) {
-            throw new SecurityException("Invalid caller: " + getCallingUid());
+    private static class NetworkMonitorImpl extends INetworkMonitor.Stub {
+        private final NetworkMonitor mNm;
+
+        NetworkMonitorImpl(NetworkMonitor nm) {
+            mNm = nm;
+        }
+
+        @Override
+        public void start() {
+            checkNetworkStackCallingPermission();
+            mNm.start();
+        }
+
+        @Override
+        public void launchCaptivePortalApp() {
+            checkNetworkStackCallingPermission();
+            mNm.launchCaptivePortalApp();
+        }
+
+        @Override
+        public void forceReevaluation(int uid) {
+            checkNetworkStackCallingPermission();
+            mNm.forceReevaluation(uid);
+        }
+
+        @Override
+        public void notifyPrivateDnsChanged(PrivateDnsConfigParcel config) {
+            checkNetworkStackCallingPermission();
+            mNm.notifyPrivateDnsSettingsChanged(PrivateDnsConfig.fromParcel(config));
+        }
+
+        @Override
+        public void notifyDnsResponse(int returnCode) {
+            checkNetworkStackCallingPermission();
+            mNm.notifyDnsResponse(returnCode);
+        }
+
+        @Override
+        public void notifySystemReady() {
+            checkNetworkStackCallingPermission();
+            mNm.notifySystemReady();
+        }
+
+        @Override
+        public void notifyNetworkConnected() {
+            checkNetworkStackCallingPermission();
+            mNm.notifyNetworkConnected();
+        }
+
+        @Override
+        public void notifyNetworkDisconnected() {
+            checkNetworkStackCallingPermission();
+            mNm.notifyNetworkDisconnected();
+        }
+
+        @Override
+        public void notifyLinkPropertiesChanged() {
+            checkNetworkStackCallingPermission();
+            mNm.notifyLinkPropertiesChanged();
+        }
+
+        @Override
+        public void notifyNetworkCapabilitiesChanged() {
+            checkNetworkStackCallingPermission();
+            mNm.notifyNetworkCapabilitiesChanged();
         }
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
similarity index 84%
rename from services/core/java/com/android/server/connectivity/NetworkMonitor.java
rename to packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 2a00025..94ea1b9 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -21,6 +21,11 @@
 import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_PROBE_SPEC;
 import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.metrics.ValidationProbeEvent.DNS_FAILURE;
 import static android.net.metrics.ValidationProbeEvent.DNS_SUCCESS;
 import static android.net.metrics.ValidationProbeEvent.PROBE_FALLBACK;
@@ -35,6 +40,9 @@
 import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ICaptivePortal;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -46,11 +54,14 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.metrics.ValidationProbeEvent;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
+import android.net.util.SharedLog;
 import android.net.util.Stopwatch;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.os.Handler;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -65,8 +76,6 @@
 import android.telephony.CellInfoWcdma;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -75,7 +84,6 @@
 import com.android.internal.util.RingBufferIndices;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -104,9 +112,7 @@
     // Default configuration values for captive portal detection probes.
     // TODO: append a random length parameter to the default HTTPS url.
     // TODO: randomize browser version ids in the default User-Agent String.
-    private static final String DEFAULT_HTTPS_URL     = "https://www.google.com/generate_204";
-    private static final String DEFAULT_HTTP_URL      =
-            "http://connectivitycheck.gstatic.com/generate_204";
+    private static final String DEFAULT_HTTPS_URL = "https://www.google.com/generate_204";
     private static final String DEFAULT_FALLBACK_URL  = "http://www.google.com/gen_204";
     private static final String DEFAULT_OTHER_FALLBACK_URLS =
             "http://play.googleapis.com/generate_204";
@@ -144,33 +150,12 @@
         }
     }
 
-    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
-    // The network should be used as a default internet connection.  It was found to be:
-    // 1. a functioning network providing internet access, or
-    // 2. a captive portal and the user decided to use it as is.
-    public static final int NETWORK_TEST_RESULT_VALID = 0;
-    // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED.
-    // The network should not be used as a default internet connection.  It was found to be:
-    // 1. a captive portal and the user is prompted to sign-in, or
-    // 2. a captive portal and the user did not want to use it, or
-    // 3. a broken network (e.g. DNS failed, connect failed, HTTP request failed).
-    public static final int NETWORK_TEST_RESULT_INVALID = 1;
-
     private static final int BASE = Protocol.BASE_NETWORK_MONITOR;
-
     /**
-     * Inform NetworkMonitor that their network is connected.
+     * ConnectivityService has sent a notification to indicate that network has connected.
      * Initiates Network Validation.
      */
-    public static final int CMD_NETWORK_CONNECTED = BASE + 1;
-
-    /**
-     * Inform ConnectivityService that the network has been tested.
-     * obj = String representing URL that Internet probe was redirect to, if it was redirected.
-     * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
-     * arg2 = NetID.
-     */
-    public static final int EVENT_NETWORK_TESTED = BASE + 2;
+    private static final int CMD_NETWORK_CONNECTED = BASE + 1;
 
     /**
      * Message to self indicating it's time to evaluate a network's connectivity.
@@ -179,9 +164,9 @@
     private static final int CMD_REEVALUATE = BASE + 6;
 
     /**
-     * Inform NetworkMonitor that the network has disconnected.
+     * ConnectivityService has sent a notification to indicate that network has disconnected.
      */
-    public static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
+    private static final int CMD_NETWORK_DISCONNECTED = BASE + 7;
 
     /**
      * Force evaluation even if it has succeeded in the past.
@@ -199,21 +184,13 @@
     private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
 
     /**
-     * Request ConnectivityService display provisioning notification.
-     * arg1    = Whether to make the notification visible.
-     * arg2    = NetID.
-     * obj     = Intent to be launched when notification selected by user, null if !arg1.
-     */
-    public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10;
-
-    /**
      * Message indicating sign-in app should be launched.
      * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the
      * user touches the sign in notification, or sent by
      * ConnectivityService when the user touches the "sign into
      * network" button in the wifi access point detail page.
      */
-    public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
+    private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11;
 
     /**
      * Retest network to see if captive portal is still in place.
@@ -234,7 +211,6 @@
      * validation phase is completed.
      */
     private static final int CMD_PRIVATE_DNS_SETTINGS_CHANGED = BASE + 13;
-    public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = BASE + 14;
     private static final int CMD_EVALUATE_PRIVATE_DNS = BASE + 15;
 
     /**
@@ -263,23 +239,16 @@
     // Delay between reevaluations once a captive portal has been found.
     private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
 
-    private static final int NUM_VALIDATION_LOG_LINES = 20;
-
     private String mPrivateDnsProviderHostname = "";
 
-    public static boolean isValidationRequired(
-            NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
-        // TODO: Consider requiring validation for DUN networks.
-        return dfltNetCap.satisfiedByNetworkCapabilities(nc);
-    }
-
     private final Context mContext;
-    private final Handler mConnectivityServiceHandler;
-    private final NetworkAgentInfo mNetworkAgentInfo;
+    private final INetworkMonitorCallbacks mCallback;
     private final Network mNetwork;
+    private final Network mNonPrivateDnsBypassNetwork;
     private final int mNetId;
     private final TelephonyManager mTelephonyManager;
     private final WifiManager mWifiManager;
+    private final ConnectivityManager mCm;
     private final NetworkRequest mDefaultRequest;
     private final IpConnectivityLog mMetricsLog;
     private final Dependencies mDependencies;
@@ -292,6 +261,9 @@
     @Nullable
     private final CaptivePortalProbeSpec[] mCaptivePortalFallbackSpecs;
 
+    private NetworkCapabilities mNetworkCapabilities;
+    private LinkProperties mLinkProperties;
+
     @VisibleForTesting
     protected boolean mIsCaptivePortalCheckEnabled;
 
@@ -304,7 +276,7 @@
     // Avoids surfacing "Sign in to network" notification.
     private boolean mDontDisplaySigninNotification = false;
 
-    public boolean systemReady = false;
+    private volatile boolean mSystemReady = false;
 
     private final State mDefaultState = new DefaultState();
     private final State mValidatedState = new ValidatedState();
@@ -317,7 +289,7 @@
 
     private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
 
-    private final LocalLog validationLogs = new LocalLog(NUM_VALIDATION_LOG_LINES);
+    private final SharedLog mValidationLogs;
 
     private final Stopwatch mEvaluationTimer = new Stopwatch();
 
@@ -328,6 +300,7 @@
     private final Random mRandom;
     private int mNextFallbackUrlIndex = 0;
 
+
     private int mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
     private int mEvaluateAttempts = 0;
     private volatile int mProbeToken = 0;
@@ -338,17 +311,18 @@
     private final DnsStallDetector mDnsStallDetector;
     private long mLastProbeTime;
 
-    public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
-            NetworkRequest defaultRequest) {
-        this(context, handler, networkAgentInfo, defaultRequest, new IpConnectivityLog(),
+    public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+            NetworkRequest defaultRequest, SharedLog validationLog) {
+        this(context, cb, network, defaultRequest, new IpConnectivityLog(), validationLog,
                 Dependencies.DEFAULT);
     }
 
     @VisibleForTesting
-    protected NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo,
-            NetworkRequest defaultRequest, IpConnectivityLog logger, Dependencies deps) {
+    protected NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
+            NetworkRequest defaultRequest, IpConnectivityLog logger, SharedLog validationLogs,
+            Dependencies deps) {
         // Add suffix indicating which NetworkMonitor we're talking about.
-        super(TAG + networkAgentInfo.name());
+        super(TAG + "/" + network.netId);
 
         // Logs with a tag of the form given just above, e.g.
         //     <timestamp>   862  2402 D NetworkMonitor/NetworkAgentInfo [WIFI () - 100]: ...
@@ -356,15 +330,18 @@
 
         mContext = context;
         mMetricsLog = logger;
-        mConnectivityServiceHandler = handler;
+        mValidationLogs = validationLogs;
+        mCallback = cb;
         mDependencies = deps;
-        mNetworkAgentInfo = networkAgentInfo;
-        mNetwork = deps.getNetwork(networkAgentInfo).getPrivateDnsBypassingCopy();
+        mNonPrivateDnsBypassNetwork = network;
+        mNetwork = deps.getPrivateDnsBypassNetwork(network);
         mNetId = mNetwork.netId;
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
         mDefaultRequest = defaultRequest;
 
+        // CHECKSTYLE:OFF IndentationCheck
         addState(mDefaultState);
         addState(mMaybeNotifyState, mDefaultState);
             addState(mEvaluatingState, mMaybeNotifyState);
@@ -374,12 +351,13 @@
         addState(mEvaluatingPrivateDnsState, mDefaultState);
         addState(mValidatedState, mDefaultState);
         setInitialState(mDefaultState);
+        // CHECKSTYLE:ON IndentationCheck
 
         mIsCaptivePortalCheckEnabled = getIsCaptivePortalCheckEnabled();
         mUseHttps = getUseHttpsValidation();
         mCaptivePortalUserAgent = getCaptivePortalUserAgent();
         mCaptivePortalHttpsUrl = makeURL(getCaptivePortalServerHttpsUrl());
-        mCaptivePortalHttpUrl = makeURL(getCaptivePortalServerHttpUrl(deps, context));
+        mCaptivePortalHttpUrl = makeURL(deps.getCaptivePortalServerHttpUrl(context));
         mCaptivePortalFallbackUrls = makeCaptivePortalFallbackUrls();
         mCaptivePortalFallbackSpecs = makeCaptivePortalFallbackProbeSpecs();
         mRandom = deps.getRandom();
@@ -390,7 +368,13 @@
         mDataStallValidDnsTimeThreshold = getDataStallValidDnsTimeThreshold();
         mDataStallEvaluationType = getDataStallEvalutionType();
 
-        start();
+        // mLinkProperties and mNetworkCapbilities must never be null or we will NPE.
+        // Provide empty objects in case we are started and the network disconnects before
+        // we can ever fetch them.
+        // TODO: Delete ASAP.
+        mLinkProperties = new LinkProperties();
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.clearAll();
     }
 
     /**
@@ -401,6 +385,14 @@
     }
 
     /**
+     * Send a notification to NetworkMonitor indicating that there was a DNS query response event.
+     * @param returnCode the DNS return code of the response.
+     */
+    public void notifyDnsResponse(int returnCode) {
+        sendMessage(EVENT_DNS_NOTIFICATION, returnCode);
+    }
+
+    /**
      * Send a notification to NetworkMonitor indicating that private DNS settings have changed.
      * @param newCfg The new private DNS configuration.
      */
@@ -411,9 +403,75 @@
         sendMessage(CMD_PRIVATE_DNS_SETTINGS_CHANGED, newCfg);
     }
 
+    /**
+     * Send a notification to NetworkMonitor indicating that the system is ready.
+     */
+    public void notifySystemReady() {
+        // No need to run on the handler thread: mSystemReady is volatile and read only once on the
+        // isCaptivePortal() thread.
+        mSystemReady = true;
+    }
+
+    /**
+     * Send a notification to NetworkMonitor indicating that the network is now connected.
+     */
+    public void notifyNetworkConnected() {
+        sendMessage(CMD_NETWORK_CONNECTED);
+    }
+
+    /**
+     * Send a notification to NetworkMonitor indicating that the network is now disconnected.
+     */
+    public void notifyNetworkDisconnected() {
+        sendMessage(CMD_NETWORK_DISCONNECTED);
+    }
+
+    /**
+     * Send a notification to NetworkMonitor indicating that link properties have changed.
+     */
+    public void notifyLinkPropertiesChanged() {
+        getHandler().post(() -> {
+            updateLinkProperties();
+        });
+    }
+
+    private void updateLinkProperties() {
+        final LinkProperties lp = mCm.getLinkProperties(mNetwork);
+        // If null, we should soon get a message that the network was disconnected, and will stop.
+        if (lp != null) {
+            // TODO: send LinkProperties parceled in notifyLinkPropertiesChanged() and start().
+            mLinkProperties = lp;
+        }
+    }
+
+    /**
+     * Send a notification to NetworkMonitor indicating that network capabilities have changed.
+     */
+    public void notifyNetworkCapabilitiesChanged() {
+        getHandler().post(() -> {
+            updateNetworkCapabilities();
+        });
+    }
+
+    private void updateNetworkCapabilities() {
+        final NetworkCapabilities nc = mCm.getNetworkCapabilities(mNetwork);
+        // If null, we should soon get a message that the network was disconnected, and will stop.
+        if (nc != null) {
+            // TODO: send NetworkCapabilities parceled in notifyNetworkCapsChanged() and start().
+            mNetworkCapabilities = nc;
+        }
+    }
+
+    /**
+     * Request the captive portal application to be launched.
+     */
+    public void launchCaptivePortalApp() {
+        sendMessage(CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+    }
+
     @Override
     protected void log(String s) {
-        if (DBG) Log.d(TAG + "/" + mNetworkAgentInfo.name(), s);
+        if (DBG) Log.d(TAG + "/" + mNetwork.netId, s);
     }
 
     private void validationLog(int probeType, Object url, String msg) {
@@ -423,11 +481,7 @@
 
     private void validationLog(String s) {
         if (DBG) log(s);
-        validationLogs.log(s);
-    }
-
-    public ReadOnlyLocalLog getValidationLogs() {
-        return validationLogs.readOnlyLocalLog();
+        mValidationLogs.log(s);
     }
 
     private ValidationStage validationStage() {
@@ -435,20 +489,46 @@
     }
 
     private boolean isValidationRequired() {
-        return isValidationRequired(
-                mDefaultRequest.networkCapabilities, mNetworkAgentInfo.networkCapabilities);
+        return NetworkMonitorUtils.isValidationRequired(
+                mDefaultRequest.networkCapabilities, mNetworkCapabilities);
     }
 
 
-    private void notifyNetworkTestResultInvalid(Object obj) {
-        mConnectivityServiceHandler.sendMessage(obtainMessage(
-                EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, mNetId, obj));
+    private void notifyNetworkTested(int result, @Nullable String redirectUrl) {
+        try {
+            mCallback.notifyNetworkTested(result, redirectUrl);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error sending network test result", e);
+        }
+    }
+
+    private void showProvisioningNotification(String action) {
+        try {
+            mCallback.showProvisioningNotification(action);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error showing provisioning notification", e);
+        }
+    }
+
+    private void hideProvisioningNotification() {
+        try {
+            mCallback.hideProvisioningNotification();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error hiding provisioning notification", e);
+        }
     }
 
     // DefaultState is the parent of all States.  It exists only to handle CMD_* messages but
     // does not entail any real state (hence no enter() or exit() routines).
     private class DefaultState extends State {
         @Override
+        public void enter() {
+            // TODO: have those passed parceled in start() and remove this
+            updateLinkProperties();
+            updateNetworkCapabilities();
+        }
+
+        @Override
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case CMD_NETWORK_CONNECTED:
@@ -499,7 +579,7 @@
                         case APP_RETURN_UNWANTED:
                             mDontDisplaySigninNotification = true;
                             mUserDoesNotWant = true;
-                            notifyNetworkTestResultInvalid(null);
+                            notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
                             // TODO: Should teardown network.
                             mUidResponsibleForReeval = 0;
                             transitionTo(mEvaluatingState);
@@ -563,8 +643,7 @@
         public void enter() {
             maybeLogEvaluationResult(
                     networkEventType(validationStage(), EvaluationResult.VALIDATED));
-            mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
-                    NETWORK_TEST_RESULT_VALID, mNetId, null));
+            notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
             mValidations++;
         }
 
@@ -633,8 +712,7 @@
 
         @Override
         public void exit() {
-            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetId, null);
-            mConnectivityServiceHandler.sendMessage(message);
+            hideProvisioningNotification();
         }
     }
 
@@ -751,9 +829,7 @@
                         CMD_LAUNCH_CAPTIVE_PORTAL_APP);
             }
             // Display the sign in notification.
-            Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetId,
-                    mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent());
-            mConnectivityServiceHandler.sendMessage(message);
+            showProvisioningNotification(mLaunchCaptivePortalAppBroadcastReceiver.mAction);
             // Retest for captive portal occasionally.
             sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */,
                     CAPTIVE_PORTAL_REEVALUATE_DELAY_MS);
@@ -839,12 +915,15 @@
         }
 
         private void notifyPrivateDnsConfigResolved() {
-            mConnectivityServiceHandler.sendMessage(obtainMessage(
-                    EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0, mNetId, mPrivateDnsConfig));
+            try {
+                mCallback.notifyPrivateDnsConfigResolved(mPrivateDnsConfig.toParcel());
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error sending private DNS config resolved notification", e);
+            }
         }
 
         private void handlePrivateDnsEvaluationFailure() {
-            notifyNetworkTestResultInvalid(null);
+            notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
 
             // Queue up a re-evaluation with backoff.
             //
@@ -865,7 +944,7 @@
                     + oneTimeHostnameSuffix;
             final Stopwatch watch = new Stopwatch().start();
             try {
-                final InetAddress[] ips = mNetworkAgentInfo.network().getAllByName(host);
+                final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host);
                 final long time = watch.stop();
                 final String strIps = Arrays.toString(ips);
                 final boolean success = (ips != null && ips.length > 0);
@@ -915,12 +994,12 @@
                         // state (even if no Private DNS validation required).
                         transitionTo(mEvaluatingPrivateDnsState);
                     } else if (probeResult.isPortal()) {
-                        notifyNetworkTestResultInvalid(probeResult.redirectUrl);
+                        notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
                         mLastPortalProbeResult = probeResult;
                         transitionTo(mCaptivePortalState);
                     } else {
                         logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
-                        notifyNetworkTestResultInvalid(probeResult.redirectUrl);
+                        notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
                         transitionTo(mWaitingForNextProbeState);
                     }
                     return HANDLED;
@@ -996,18 +1075,18 @@
         }
     }
 
-    public boolean getIsCaptivePortalCheckEnabled() {
+    private boolean getIsCaptivePortalCheckEnabled() {
         String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
         int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
         int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
         return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
     }
 
-    public boolean getUseHttpsValidation() {
+    private boolean getUseHttpsValidation() {
         return mDependencies.getSetting(mContext, Settings.Global.CAPTIVE_PORTAL_USE_HTTPS, 1) == 1;
     }
 
-    public boolean getWifiScansAlwaysAvailableDisabled() {
+    private boolean getWifiScansAlwaysAvailableDisabled() {
         return mDependencies.getSetting(
                 mContext, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 0;
     }
@@ -1040,15 +1119,6 @@
                 DEFAULT_DATA_STALL_EVALUATION_TYPES);
     }
 
-    // Static for direct access by ConnectivityService
-    public static String getCaptivePortalServerHttpUrl(Context context) {
-        return getCaptivePortalServerHttpUrl(Dependencies.DEFAULT, context);
-    }
-
-    public static String getCaptivePortalServerHttpUrl(Dependencies deps, Context context) {
-        return deps.getSetting(context, Settings.Global.CAPTIVE_PORTAL_HTTP_URL, DEFAULT_HTTP_URL);
-    }
-
     private URL[] makeCaptivePortalFallbackUrls() {
         try {
             String separator = ",";
@@ -1144,7 +1214,7 @@
         // 3. PAC scripts are sometimes used to block or restrict Internet access and may in
         //    fact block fetching of the generate_204 URL which would lead to false negative
         //    results for network validation.
-        final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy();
+        final ProxyInfo proxyInfo = mLinkProperties.getHttpProxy();
         if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) {
             pacUrl = makeURL(proxyInfo.getPacFileUrl().toString());
             if (pacUrl == null) {
@@ -1416,89 +1486,86 @@
             return;
         }
 
-        if (!systemReady) {
+        if (!mSystemReady) {
             return;
         }
 
         Intent latencyBroadcast =
-                new Intent(ConnectivityConstants.ACTION_NETWORK_CONDITIONS_MEASURED);
-        switch (mNetworkAgentInfo.networkInfo.getType()) {
-            case ConnectivityManager.TYPE_WIFI:
-                WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
-                if (currentWifiInfo != null) {
-                    // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
-                    // surrounded by double quotation marks (thus violating the Javadoc), but this
-                    // was changed to match the Javadoc in API 17. Since clients may have started
-                    // sanitizing the output of this method since API 17 was released, we should
-                    // not change it here as it would become impossible to tell whether the SSID is
-                    // simply being surrounded by quotes due to the API, or whether those quotes
-                    // are actually part of the SSID.
-                    latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_SSID,
-                            currentWifiInfo.getSSID());
-                    latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_BSSID,
-                            currentWifiInfo.getBSSID());
-                } else {
-                    if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
-                    return;
-                }
-                break;
-            case ConnectivityManager.TYPE_MOBILE:
-                latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_NETWORK_TYPE,
-                        mTelephonyManager.getNetworkType());
-                List<CellInfo> info = mTelephonyManager.getAllCellInfo();
-                if (info == null) return;
-                int numRegisteredCellInfo = 0;
-                for (CellInfo cellInfo : info) {
-                    if (cellInfo.isRegistered()) {
-                        numRegisteredCellInfo++;
-                        if (numRegisteredCellInfo > 1) {
-                            if (VDBG) {
-                                logw("more than one registered CellInfo."
-                                        + " Can't tell which is active.  Bailing.");
-                            }
-                            return;
+                new Intent(NetworkMonitorUtils.ACTION_NETWORK_CONDITIONS_MEASURED);
+        if (mNetworkCapabilities.hasTransport(TRANSPORT_WIFI)) {
+            WifiInfo currentWifiInfo = mWifiManager.getConnectionInfo();
+            if (currentWifiInfo != null) {
+                // NOTE: getSSID()'s behavior changed in API 17; before that, SSIDs were not
+                // surrounded by double quotation marks (thus violating the Javadoc), but this
+                // was changed to match the Javadoc in API 17. Since clients may have started
+                // sanitizing the output of this method since API 17 was released, we should
+                // not change it here as it would become impossible to tell whether the SSID is
+                // simply being surrounded by quotes due to the API, or whether those quotes
+                // are actually part of the SSID.
+                latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_SSID,
+                        currentWifiInfo.getSSID());
+                latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_BSSID,
+                        currentWifiInfo.getBSSID());
+            } else {
+                if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
+                return;
+            }
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_WIFI);
+        } else if (mNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_NETWORK_TYPE,
+                    mTelephonyManager.getNetworkType());
+            List<CellInfo> info = mTelephonyManager.getAllCellInfo();
+            if (info == null) return;
+            int numRegisteredCellInfo = 0;
+            for (CellInfo cellInfo : info) {
+                if (cellInfo.isRegistered()) {
+                    numRegisteredCellInfo++;
+                    if (numRegisteredCellInfo > 1) {
+                        if (VDBG) {
+                            logw("more than one registered CellInfo."
+                                    + " Can't tell which is active.  Bailing.");
                         }
-                        if (cellInfo instanceof CellInfoCdma) {
-                            CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
-                            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
-                        } else if (cellInfo instanceof CellInfoGsm) {
-                            CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
-                            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
-                        } else if (cellInfo instanceof CellInfoLte) {
-                            CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
-                            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
-                        } else if (cellInfo instanceof CellInfoWcdma) {
-                            CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
-                            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CELL_ID, cellId);
-                        } else {
-                            if (VDBG) logw("Registered cellinfo is unrecognized");
-                            return;
-                        }
+                        return;
+                    }
+                    if (cellInfo instanceof CellInfoCdma) {
+                        CellIdentityCdma cellId = ((CellInfoCdma) cellInfo).getCellIdentity();
+                        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+                    } else if (cellInfo instanceof CellInfoGsm) {
+                        CellIdentityGsm cellId = ((CellInfoGsm) cellInfo).getCellIdentity();
+                        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+                    } else if (cellInfo instanceof CellInfoLte) {
+                        CellIdentityLte cellId = ((CellInfoLte) cellInfo).getCellIdentity();
+                        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+                    } else if (cellInfo instanceof CellInfoWcdma) {
+                        CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
+                        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CELL_ID, cellId);
+                    } else {
+                        if (VDBG) logw("Registered cellinfo is unrecognized");
+                        return;
                     }
                 }
-                break;
-            default:
-                return;
+            }
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_CONNECTIVITY_TYPE, TYPE_MOBILE);
+        } else {
+            return;
         }
-        latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_CONNECTIVITY_TYPE,
-                mNetworkAgentInfo.networkInfo.getType());
-        latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_RECEIVED,
+        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_RECEIVED,
                 responseReceived);
-        latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_REQUEST_TIMESTAMP_MS,
+        latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_REQUEST_TIMESTAMP_MS,
                 requestTimestampMs);
 
         if (responseReceived) {
-            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_IS_CAPTIVE_PORTAL,
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_IS_CAPTIVE_PORTAL,
                     isCaptivePortal);
-            latencyBroadcast.putExtra(ConnectivityConstants.EXTRA_RESPONSE_TIMESTAMP_MS,
+            latencyBroadcast.putExtra(NetworkMonitorUtils.EXTRA_RESPONSE_TIMESTAMP_MS,
                     responseTimestampMs);
         }
         mContext.sendBroadcastAsUser(latencyBroadcast, UserHandle.CURRENT,
-                ConnectivityConstants.PERMISSION_ACCESS_NETWORK_CONDITIONS);
+                NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS);
     }
 
     private void logNetworkEvent(int evtype) {
-        int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+        int[] transports = mNetworkCapabilities.getTransportTypes();
         mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype));
     }
 
@@ -1520,14 +1587,14 @@
 
     private void maybeLogEvaluationResult(int evtype) {
         if (mEvaluationTimer.isRunning()) {
-            int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+            int[] transports = mNetworkCapabilities.getTransportTypes();
             mMetricsLog.log(mNetId, transports, new NetworkEvent(evtype, mEvaluationTimer.stop()));
             mEvaluationTimer.reset();
         }
     }
 
     private void logValidationProbe(long durationMs, int probeType, int probeResult) {
-        int[] transports = mNetworkAgentInfo.networkCapabilities.getTransportTypes();
+        int[] transports = mNetworkCapabilities.getTransportTypes();
         boolean isFirstValidation = validationStage().mIsFirstValidation;
         ValidationProbeEvent ev = new ValidationProbeEvent();
         ev.probeType = ValidationProbeEvent.makeProbeType(probeType, isFirstValidation);
@@ -1537,9 +1604,9 @@
     }
 
     @VisibleForTesting
-    public static class Dependencies {
-        public Network getNetwork(NetworkAgentInfo networkAgentInfo) {
-            return new OneAddressPerFamilyNetwork(networkAgentInfo.network());
+    static class Dependencies {
+        public Network getPrivateDnsBypassNetwork(Network network) {
+            return new OneAddressPerFamilyNetwork(network);
         }
 
         public Random getRandom() {
@@ -1547,6 +1614,13 @@
         }
 
         /**
+         * Get the captive portal server HTTP URL that is configured on the device.
+         */
+        public String getCaptivePortalServerHttpUrl(Context context) {
+            return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(context);
+        }
+
+        /**
          * Get the value of a global integer setting.
          * @param symbol Name of the setting
          * @param defaultValue Value to return if the setting is not defined.
@@ -1666,7 +1740,7 @@
         boolean result = false;
         // Reevaluation will generate traffic. Thus, set a minimal reevaluation timer to limit the
         // possible traffic cost in metered network.
-        if (mNetworkAgentInfo.networkCapabilities.isMetered()
+        if (mNetworkCapabilities.isMetered()
                 && (SystemClock.elapsedRealtime() - getLastProbeTime()
                 < mDataStallMinEvaluateTime)) {
             return false;
diff --git a/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
new file mode 100644
index 0000000..bb5900c
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/util/NetworkStackConstants.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util;
+
+/**
+ * Network constants used by the network stack.
+ */
+public final class NetworkStackConstants {
+
+    /**
+     * IPv4 constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc791
+     */
+    public static final int IPV4_ADDR_BITS = 32;
+    public static final int IPV4_MIN_MTU = 68;
+    public static final int IPV4_MAX_MTU = 65_535;
+
+    /**
+     * DHCP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc2131
+     */
+    public static final int INFINITE_LEASE = 0xffffffff;
+
+    private NetworkStackConstants() {
+        throw new UnsupportedOperationException("This class is not to be instantiated");
+    }
+}
diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
new file mode 100644
index 0000000..733f873
--- /dev/null
+++ b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
@@ -0,0 +1,42 @@
+/*
+ * 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.util;
+
+import static android.os.Binder.getCallingUid;
+
+import android.os.Process;
+
+/**
+ * Utility class to check calling permissions on the network stack.
+ */
+public final class PermissionUtil {
+
+    /**
+     * Check that the caller is allowed to communicate with the network stack.
+     * @throws SecurityException The caller is not allowed to communicate with the network stack.
+     */
+    public static void checkNetworkStackCallingPermission() {
+        // TODO: check that the calling PID is the system server.
+        if (getCallingUid() != Process.SYSTEM_UID && getCallingUid() != Process.ROOT_UID) {
+            throw new SecurityException("Invalid caller: " + getCallingUid());
+        }
+    }
+
+    private PermissionUtil() {
+        throw new UnsupportedOperationException("This class is not to be instantiated");
+    }
+}
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
new file mode 100644
index 0000000..bd7ff2a
--- /dev/null
+++ b/packages/NetworkStack/tests/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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.
+//
+
+android_test {
+    name: "NetworkStackTests",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "android-support-test",
+        "mockito-target-extended-minus-junit4",
+        "NetworkStackLib",
+        "testables",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ]
+}
\ No newline at end of file
diff --git a/packages/NetworkStack/tests/AndroidManifest.xml b/packages/NetworkStack/tests/AndroidManifest.xml
new file mode 100644
index 0000000..8b8474f
--- /dev/null
+++ b/packages/NetworkStack/tests/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.server.networkstack.tests">
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.networkstack.tests"
+        android:label="Networking service tests">
+    </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/packages/NetworkStack/tests/AndroidTest.xml b/packages/NetworkStack/tests/AndroidTest.xml
new file mode 100644
index 0000000..6b08b57
--- /dev/null
+++ b/packages/NetworkStack/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs Tests for NetworkStack">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="NetworkStackTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="NetworkStackTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.server.networkstack.tests" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
similarity index 99%
rename from tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
index ba0448c..51d50d9 100644
--- a/tests/net/java/android/net/dhcp/DhcpLeaseRepositoryTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpLeaseRepositoryTest.java
@@ -16,6 +16,7 @@
 
 package android.net.dhcp;
 
+import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.dhcp.DhcpLease.HOSTNAME_NONE;
 import static android.net.dhcp.DhcpLeaseRepository.CLIENTID_UNSPEC;
 import static android.net.dhcp.DhcpLeaseRepository.INETADDR_UNSPEC;
@@ -29,7 +30,6 @@
 import static org.mockito.Mockito.when;
 
 import static java.lang.String.format;
-import static java.net.InetAddress.parseNumericAddress;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/tests/net/java/android/net/dhcp/DhcpServerTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java
similarity index 89%
rename from tests/net/java/android/net/dhcp/DhcpServerTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java
index ab9bd84..d4c1e2e 100644
--- a/tests/net/java/android/net/dhcp/DhcpServerTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServerTest.java
@@ -16,11 +16,13 @@
 
 package android.net.dhcp;
 
+import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.dhcp.DhcpPacket.DHCP_CLIENT;
 import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
 import static android.net.dhcp.DhcpPacket.INADDR_ANY;
 import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -33,14 +35,14 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.never;
+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 static java.net.InetAddress.parseNumericAddress;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.INetworkStackStatusCallback;
 import android.net.LinkAddress;
 import android.net.MacAddress;
 import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException;
@@ -48,9 +50,11 @@
 import android.net.dhcp.DhcpServer.Clock;
 import android.net.dhcp.DhcpServer.Dependencies;
 import android.net.util.SharedLog;
-import android.os.test.TestLooper;
+import android.os.HandlerThread;
 import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
 
 import org.junit.After;
 import org.junit.Before;
@@ -67,10 +71,10 @@
 import java.util.HashSet;
 import java.util.Set;
 
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
 @SmallTest
+@RunWithLooper
 public class DhcpServerTest {
-    private static final String PROP_DEXMAKER_SHARE_CLASSLOADER = "dexmaker.share_classloader";
     private static final String TEST_IFACE = "testiface";
 
     private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
@@ -113,18 +117,25 @@
     private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
 
     @NonNull
-    private TestLooper mLooper;
+    private HandlerThread mHandlerThread;
+    @NonNull
+    private TestableLooper mLooper;
     @NonNull
     private DhcpServer mServer;
 
     @Nullable
     private String mPrevShareClassloaderProp;
 
+    private final INetworkStackStatusCallback mAssertSuccessCallback =
+            new INetworkStackStatusCallback.Stub() {
+        @Override
+        public void onStatusAvailable(int statusCode) {
+            assertEquals(STATUS_SUCCESS, statusCode);
+        }
+    };
+
     @Before
     public void setUp() throws Exception {
-        // Allow mocking package-private classes
-        mPrevShareClassloaderProp = System.getProperty(PROP_DEXMAKER_SHARE_CLASSLOADER);
-        System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER, "true");
         MockitoAnnotations.initMocks(this);
 
         when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
@@ -143,20 +154,22 @@
                 .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
                 .build();
 
-        mLooper = new TestLooper();
-        mServer = new DhcpServer(mLooper.getLooper(), TEST_IFACE, servingParams,
+        mLooper = TestableLooper.get(this);
+        mHandlerThread = spy(new HandlerThread("TestDhcpServer"));
+        when(mHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
+        mServer = new DhcpServer(mHandlerThread, TEST_IFACE, servingParams,
                 new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
 
-        mServer.start();
-        mLooper.dispatchAll();
+        mServer.start(mAssertSuccessCallback);
+        mLooper.processAllMessages();
     }
 
     @After
-    public void tearDown() {
-        // Calling stop() several times is not an issue
-        mServer.stop();
-        System.setProperty(PROP_DEXMAKER_SHARE_CLASSLOADER,
-                (mPrevShareClassloaderProp == null ? "" : mPrevShareClassloaderProp));
+    public void tearDown() throws Exception {
+        mServer.stop(mAssertSuccessCallback);
+        mLooper.processMessages(1);
+        verify(mPacketListener, times(1)).stop();
+        verify(mHandlerThread, times(1)).quitSafely();
     }
 
     @Test
@@ -165,13 +178,6 @@
     }
 
     @Test
-    public void testStop() throws Exception {
-        mServer.stop();
-        mLooper.dispatchAll();
-        verify(mPacketListener, times(1)).stop();
-    }
-
-    @Test
     public void testDiscover() throws Exception {
         // TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields
         when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
diff --git a/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
similarity index 96%
rename from tests/net/java/android/net/dhcp/DhcpServingParamsTest.java
rename to packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
index 2ab2246..3ca0564 100644
--- a/tests/net/java/android/net/dhcp/DhcpServingParamsTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpServingParamsTest.java
@@ -16,6 +16,7 @@
 
 package android.net.dhcp;
 
+import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.NetworkUtils.inet4AddressToIntHTH;
 import static android.net.dhcp.DhcpServingParams.MTU_UNSET;
 
@@ -23,8 +24,6 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static java.net.InetAddress.parseNumericAddress;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.LinkAddress;
@@ -195,6 +194,11 @@
         assertEquals(7, numFields);
     }
 
+    @Test(expected = InvalidParameterException.class)
+    public void testFromParcelableObject_NullArgument() throws InvalidParameterException {
+        DhcpServingParams.fromParcelableObject(null);
+    }
+
     private static int[] toIntArray(Collection<Inet4Address> addrs) {
         return addrs.stream().mapToInt(NetworkUtils::inet4AddressToIntHTH).toArray();
     }
diff --git a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
similarity index 68%
rename from tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
rename to packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
index 6e07b26..d31fa77 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -16,6 +16,14 @@
 
 package com.android.server.connectivity;
 
+import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
+import static android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 
 import static org.junit.Assert.assertTrue;
@@ -24,13 +32,21 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.Intent;
+import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
+import android.net.INetworkMonitorCallbacks;
+import android.net.InetAddresses;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -38,9 +54,12 @@
 import android.net.NetworkRequest;
 import android.net.captiveportal.CaptivePortalProbeResult;
 import android.net.metrics.IpConnectivityLog;
+import android.net.util.SharedLog;
 import android.net.wifi.WifiManager;
+import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
@@ -50,8 +69,10 @@
 import org.junit.Before;
 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;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -68,21 +89,23 @@
     private static final String LOCATION_HEADER = "location";
 
     private @Mock Context mContext;
-    private @Mock Handler mHandler;
     private @Mock IpConnectivityLog mLogger;
-    private @Mock NetworkAgentInfo mAgent;
-    private @Mock NetworkAgentInfo mNotMeteredAgent;
+    private @Mock SharedLog mValidationLogger;
     private @Mock NetworkInfo mNetworkInfo;
-    private @Mock NetworkRequest mRequest;
+    private @Mock ConnectivityManager mCm;
     private @Mock TelephonyManager mTelephony;
     private @Mock WifiManager mWifi;
-    private @Mock Network mNetwork;
     private @Mock HttpURLConnection mHttpConnection;
     private @Mock HttpURLConnection mHttpsConnection;
     private @Mock HttpURLConnection mFallbackConnection;
     private @Mock HttpURLConnection mOtherFallbackConnection;
     private @Mock Random mRandom;
     private @Mock NetworkMonitor.Dependencies mDependencies;
+    private @Mock INetworkMonitorCallbacks mCallbacks;
+    private @Spy Network mNetwork = new Network(TEST_NETID);
+    private NetworkRequest mRequest;
+
+    private static final int TEST_NETID = 4242;
 
     private static final String TEST_HTTP_URL = "http://www.google.com/gen_204";
     private static final String TEST_HTTPS_URL = "https://www.google.com/gen_204";
@@ -93,33 +116,37 @@
     private static final int RETURN_CODE_DNS_SUCCESS = 0;
     private static final int RETURN_CODE_DNS_TIMEOUT = 255;
 
+    private static final int HANDLER_TIMEOUT_MS = 1000;
+
+    private static final LinkProperties TEST_LINKPROPERTIES = new LinkProperties();
+
+    private static final NetworkCapabilities METERED_CAPABILITIES = new NetworkCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .addCapability(NET_CAPABILITY_INTERNET);
+
+    private static final NetworkCapabilities NOT_METERED_CAPABILITIES = new NetworkCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .addCapability(NET_CAPABILITY_INTERNET)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+
+    private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+
     @Before
     public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
-        mAgent.linkProperties = new LinkProperties();
-        mAgent.networkCapabilities = new NetworkCapabilities()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-        mAgent.networkInfo = mNetworkInfo;
-
-        mNotMeteredAgent.linkProperties = new LinkProperties();
-        mNotMeteredAgent.networkCapabilities = new NetworkCapabilities()
-            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-        mNotMeteredAgent.networkInfo = mNetworkInfo;
-
-        when(mAgent.network()).thenReturn(mNetwork);
-        when(mDependencies.getNetwork(any())).thenReturn(mNetwork);
+        when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork);
         when(mDependencies.getRandom()).thenReturn(mRandom);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
                 .thenReturn(Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_USE_HTTPS),
                 anyInt())).thenReturn(1);
-        when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTP_URL),
-                anyString())).thenReturn(TEST_HTTP_URL);
+        when(mDependencies.getCaptivePortalServerHttpUrl(any())).thenReturn(TEST_HTTP_URL);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL),
                 anyString())).thenReturn(TEST_HTTPS_URL);
-        when(mNetwork.getPrivateDnsBypassingCopy()).thenReturn(mNetwork);
+        doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
 
+        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
         when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
         when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifi);
 
@@ -129,7 +156,7 @@
         setFallbackSpecs(null); // Test with no fallback spec by default
         when(mRandom.nextInt()).thenReturn(0);
 
-        when(mNetwork.openConnection(any())).then((invocation) -> {
+        doAnswer((invocation) -> {
             URL url = invocation.getArgument(0);
             switch(url.toString()) {
                 case TEST_HTTP_URL:
@@ -144,12 +171,20 @@
                     fail("URL not mocked: " + url.toString());
                     return null;
             }
-        });
+        }).when(mNetwork).openConnection(any());
         when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
         when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
-        when(mNetwork.getAllByName(any())).thenReturn(new InetAddress[] {
-            InetAddress.parseNumericAddress("192.168.0.0")
-        });
+        doReturn(new InetAddress[] {
+                InetAddresses.parseNumericAddress("192.168.0.0")
+        }).when(mNetwork).getAllByName(any());
+
+        mRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
+                .build();
+        // Default values. Individual tests can override these.
+        when(mCm.getLinkProperties(any())).thenReturn(TEST_LINKPROPERTIES);
+        when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
 
         setMinDataStallEvaluateInterval(500);
         setDataStallEvaluationType(1 << DATA_STALL_EVALUATION_TYPE_DNS);
@@ -160,10 +195,10 @@
     private class WrappedNetworkMonitor extends NetworkMonitor {
         private long mProbeTime = 0;
 
-        WrappedNetworkMonitor(Context context, Handler handler,
-                NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
+        WrappedNetworkMonitor(Context context, Network network, NetworkRequest defaultRequest,
                 IpConnectivityLog logger, Dependencies deps) {
-                super(context, handler, networkAgentInfo, defaultRequest, logger, deps);
+                super(context, mCallbacks, network, defaultRequest, logger,
+                        new SharedLog("test_nm"), deps);
         }
 
         @Override
@@ -176,19 +211,39 @@
         }
     }
 
-    WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
-        return new WrappedNetworkMonitor(
-                mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
+    private WrappedNetworkMonitor makeMeteredWrappedNetworkMonitor() {
+        final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
+                mContext, mNetwork, mRequest, mLogger, mDependencies);
+        when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
+        nm.start();
+        waitForIdle(nm.getHandler());
+        return nm;
     }
 
-    WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
-        return new WrappedNetworkMonitor(
-                mContext, mHandler, mNotMeteredAgent, mRequest, mLogger, mDependencies);
+    private WrappedNetworkMonitor makeNotMeteredWrappedNetworkMonitor() {
+        final WrappedNetworkMonitor nm = new WrappedNetworkMonitor(
+                mContext, mNetwork, mRequest, mLogger, mDependencies);
+        when(mCm.getNetworkCapabilities(any())).thenReturn(NOT_METERED_CAPABILITIES);
+        nm.start();
+        waitForIdle(nm.getHandler());
+        return nm;
     }
 
-    NetworkMonitor makeMonitor() {
-        return new NetworkMonitor(
-                mContext, mHandler, mAgent, mRequest, mLogger, mDependencies);
+    private NetworkMonitor makeMonitor() {
+        final NetworkMonitor nm = new NetworkMonitor(
+                mContext, mCallbacks, mNetwork, mRequest, mLogger, mValidationLogger,
+                mDependencies);
+        nm.start();
+        waitForIdle(nm.getHandler());
+        return nm;
+    }
+
+    private void waitForIdle(Handler handler) {
+        final ConditionVariable cv = new ConditionVariable(false);
+        handler.post(cv::open);
+        if (!cv.block(HANDLER_TIMEOUT_MS)) {
+            fail("Timed out waiting for handler");
+        }
     }
 
     @Test
@@ -319,6 +374,15 @@
     }
 
     @Test
+    public void testIsCaptivePortal_IgnorePortals() throws IOException {
+        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
+        setSslException(mHttpsConnection);
+        setPortal302(mHttpConnection);
+
+        assertNotPortal(makeMonitor().isCaptivePortal());
+    }
+
+    @Test
     public void testIsDataStall_EvaluationDisabled() {
         setDataStallEvaluationType(0);
         WrappedNetworkMonitor wrappedMonitor = makeMeteredWrappedNetworkMonitor();
@@ -390,6 +454,63 @@
         assertFalse(wrappedMonitor.isDataStall());
     }
 
+    @Test
+    public void testBrokenNetworkNotValidated() throws Exception {
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 404);
+        when(mCm.getNetworkCapabilities(any())).thenReturn(METERED_CAPABILITIES);
+
+        final NetworkMonitor nm = makeMonitor();
+        nm.notifyNetworkConnected();
+
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
+    }
+
+    @Test
+    public void testNoInternetCapabilityValidated() throws Exception {
+        when(mCm.getNetworkCapabilities(any())).thenReturn(NO_INTERNET_CAPABILITIES);
+
+        final NetworkMonitor nm = makeMonitor();
+        nm.notifyNetworkConnected();
+
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
+        verify(mNetwork, never()).openConnection(any());
+    }
+
+    @Test
+    public void testLaunchCaptivePortalApp() throws Exception {
+        setSslException(mHttpsConnection);
+        setPortal302(mHttpConnection);
+
+        final NetworkMonitor nm = makeMonitor();
+        nm.notifyNetworkConnected();
+
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .showProvisioningNotification(any());
+
+        // Check that startCaptivePortalApp sends the expected intent.
+        nm.launchCaptivePortalApp();
+
+        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .startActivityAsUser(intentCaptor.capture(), eq(UserHandle.CURRENT));
+        final Intent intent = intentCaptor.getValue();
+        assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
+        final Network network = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
+        assertEquals(TEST_NETID, network.netId);
+
+        // Have the app report that the captive portal is dismissed, and check that we revalidate.
+        setStatus(mHttpsConnection, 204);
+        setStatus(mHttpConnection, 204);
+        final CaptivePortal captivePortal = intent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL);
+        captivePortal.reportCaptivePortalDismissed();
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
+    }
+
     private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
         for (int i = 0; i < count; i++) {
             wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
@@ -440,6 +561,11 @@
                 eq(Settings.Global.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS), any())).thenReturn(specs);
     }
 
+    private void setCaptivePortalMode(int mode) {
+        when(mDependencies.getSetting(any(),
+                eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
+    }
+
     private void assertPortal(CaptivePortalProbeResult result) {
         assertTrue(result.isPortal());
         assertFalse(result.isFailed());
@@ -459,12 +585,12 @@
     }
 
     private void setSslException(HttpURLConnection connection) throws IOException {
-        when(connection.getResponseCode()).thenThrow(new SSLHandshakeException("Invalid cert"));
+        doThrow(new SSLHandshakeException("Invalid cert")).when(connection).getResponseCode();
     }
 
     private void set302(HttpURLConnection connection, String location) throws IOException {
         setStatus(connection, 302);
-        when(connection.getHeaderField(LOCATION_HEADER)).thenReturn(location);
+        doReturn(location).when(connection).getHeaderField(LOCATION_HEADER);
     }
 
     private void setPortal302(HttpURLConnection connection) throws IOException {
@@ -472,7 +598,7 @@
     }
 
     private void setStatus(HttpURLConnection connection, int status) throws IOException {
-        when(connection.getResponseCode()).thenReturn(status);
+        doReturn(status).when(connection).getResponseCode();
     }
 }
 
diff --git a/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java b/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java
new file mode 100644
index 0000000..07ad3123
--- /dev/null
+++ b/packages/NetworkStack/tests/src/com/android/server/util/SharedLogTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 com.android.server.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.util.SharedLog;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SharedLogTest {
+    private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}";
+    private static final String TIMESTAMP = "HH:MM:SS";
+
+    @Test
+    public void testBasicOperation() {
+        final SharedLog logTop = new SharedLog("top");
+        logTop.mark("first post!");
+
+        final SharedLog logLevel2a = logTop.forSubComponent("twoA");
+        final SharedLog logLevel2b = logTop.forSubComponent("twoB");
+        logLevel2b.e("2b or not 2b");
+        logLevel2b.e("No exception", null);
+        logLevel2b.e("Wait, here's one", new Exception("Test"));
+        logLevel2a.w("second post?");
+
+        final SharedLog logLevel3 = logLevel2a.forSubComponent("three");
+        logTop.log("still logging");
+        logLevel3.log("3 >> 2");
+        logLevel2a.mark("ok: last post");
+
+        final String[] expected = {
+            " - MARK first post!",
+            " - [twoB] ERROR 2b or not 2b",
+            " - [twoB] ERROR No exception",
+            // No stacktrace in shared log, only in logcat
+            " - [twoB] ERROR Wait, here's one: Test",
+            " - [twoA] WARN second post?",
+            " - still logging",
+            " - [twoA.three] 3 >> 2",
+            " - [twoA] MARK ok: last post",
+        };
+        // Verify the logs are all there and in the correct order.
+        verifyLogLines(expected, logTop);
+
+        // In fact, because they all share the same underlying LocalLog,
+        // every subcomponent SharedLog's dump() is identical.
+        verifyLogLines(expected, logLevel2a);
+        verifyLogLines(expected, logLevel2b);
+        verifyLogLines(expected, logLevel3);
+    }
+
+    private static void verifyLogLines(String[] expected, SharedLog log) {
+        final ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+        final PrintWriter pw = new PrintWriter(ostream, true);
+        log.dump(null, pw, null);
+
+        final String dumpOutput = ostream.toString();
+        assertTrue(dumpOutput != null);
+        assertTrue(!"".equals(dumpOutput));
+
+        final String[] lines = dumpOutput.split("\n");
+        assertEquals(expected.length, lines.length);
+
+        for (int i = 0; i < expected.length; i++) {
+            String got = lines[i];
+            String want = expected[i];
+            assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want));
+            assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP),
+                    got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP));
+        }
+    }
+}
diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
index a40cf1c..d969c69 100644
--- a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
+++ b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
@@ -16,6 +16,9 @@
 
 #define LOG_TAG "PacProcessor"
 
+#include <stdlib.h>
+#include <string>
+
 #include <utils/Log.h>
 #include <utils/Mutex.h>
 #include "android_runtime/AndroidRuntime.h"
@@ -23,24 +26,24 @@
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 
-#include "proxy_resolver_v8.h"
+#include "proxy_resolver_v8_wrapper.h"
 
 namespace android {
 
-net::ProxyResolverV8* proxyResolver = NULL;
+ProxyResolverV8Handle* proxyResolver = NULL;
 bool pacSet = false;
 
-String16 jstringToString16(JNIEnv* env, jstring jstr) {
+std::u16string jstringToString16(JNIEnv* env, jstring jstr) {
     const jchar* str = env->GetStringCritical(jstr, 0);
-    String16 str16(reinterpret_cast<const char16_t*>(str),
+    std::u16string str16(reinterpret_cast<const char16_t*>(str),
                    env->GetStringLength(jstr));
     env->ReleaseStringCritical(jstr, str);
     return str16;
 }
 
-jstring string16ToJstring(JNIEnv* env, String16 string) {
-    const char16_t* str = string.string();
-    size_t len = string.size();
+jstring string16ToJstring(JNIEnv* env, std::u16string string) {
+    const char16_t* str = string.data();
+    size_t len = string.length();
 
     return env->NewString(reinterpret_cast<const jchar*>(str), len);
 }
@@ -48,7 +51,7 @@
 static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JNIEnv* /* env */,
         jobject) {
     if (proxyResolver == NULL) {
-        proxyResolver = new net::ProxyResolverV8(net::ProxyResolverJSBindings::CreateDefault());
+        proxyResolver = ProxyResolverV8Handle_new();
         pacSet = false;
         return JNI_FALSE;
     }
@@ -58,7 +61,7 @@
 static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(JNIEnv* /* env */,
         jobject) {
     if (proxyResolver != NULL) {
-        delete proxyResolver;
+        ProxyResolverV8Handle_delete(proxyResolver);
         proxyResolver = NULL;
         return JNI_FALSE;
     }
@@ -67,14 +70,14 @@
 
 static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JNIEnv* env, jobject,
         jstring script) {
-    String16 script16 = jstringToString16(env, script);
+    std::u16string script16 = jstringToString16(env, script);
 
     if (proxyResolver == NULL) {
         ALOGE("V8 Parser not started when setting PAC script");
         return JNI_TRUE;
     }
 
-    if (proxyResolver->SetPacScript(script16) != OK) {
+    if (ProxyResolverV8Handle_SetPacScript(proxyResolver, script16.data()) != OK) {
         ALOGE("Unable to set PAC script");
         return JNI_TRUE;
     }
@@ -85,9 +88,8 @@
 
 static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(JNIEnv* env, jobject,
         jstring url, jstring host) {
-    String16 url16 = jstringToString16(env, url);
-    String16 host16 = jstringToString16(env, host);
-    String16 ret;
+    std::u16string url16 = jstringToString16(env, url);
+    std::u16string host16 = jstringToString16(env, host);
 
     if (proxyResolver == NULL) {
         ALOGE("V8 Parser not initialized when running PAC script");
@@ -99,12 +101,14 @@
         return NULL;
     }
 
-    if (proxyResolver->GetProxyForURL(url16, host16, &ret) != OK) {
-        String8 ret8(ret);
-        ALOGE("Error Running PAC: %s", ret8.string());
+    std::unique_ptr<char16_t, decltype(&free)> result = std::unique_ptr<char16_t, decltype(&free)>(
+        ProxyResolverV8Handle_GetProxyForURL(proxyResolver, url16.data(), host16.data()), &free);
+    if (result.get() == NULL) {
+        ALOGE("Error Running PAC");
         return NULL;
     }
 
+    std::u16string ret(result.get());
     jstring jret = string16ToJstring(env, ret);
 
     return jret;
diff --git a/services/Android.bp b/services/Android.bp
index bea51be..a416ca0 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -23,6 +23,7 @@
         "services.companion",
         "services.coverage",
         "services.devicepolicy",
+        "services.ipmemorystore",
         "services.midi",
         "services.net",
         "services.print",
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 66ceae4..d0666b9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -25,6 +25,7 @@
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -37,6 +38,8 @@
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.uidRulesToString;
+import static android.net.NetworkStack.NETWORKSTACK_PACKAGE_NAME;
+import static android.net.shared.NetworkMonitorUtils.isValidationRequired;
 import static android.os.Process.INVALID_UID;
 import static android.system.OsConstants.IPPROTO_TCP;
 import static android.system.OsConstants.IPPROTO_UDP;
@@ -62,6 +65,8 @@
 import android.net.INetd;
 import android.net.INetdEventCallback;
 import android.net.INetworkManagementEventObserver;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
@@ -79,9 +84,11 @@
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.NetworkWatchlistManager;
+import android.net.PrivateDnsConfigParcel;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.net.UidRange;
@@ -90,12 +97,13 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -123,8 +131,8 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.LocalLog;
-import android.util.LocalLog.ReadOnlyLocalLog;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -149,7 +157,6 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.connectivity.DataConnectionStats;
 import com.android.server.connectivity.DnsManager;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.KeepaliveTracker;
@@ -158,7 +165,6 @@
 import com.android.server.connectivity.MultipathPolicyTracker;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkDiagnostics;
-import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.NetworkNotificationManager;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.server.connectivity.PermissionMonitor;
@@ -186,7 +192,6 @@
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -442,6 +447,43 @@
      */
     private static final int EVENT_DATA_SAVER_CHANGED = 40;
 
+     /**
+      * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has
+      * been tested.
+      * obj = String representing URL that Internet probe was redirect to, if it was redirected.
+      * arg1 = One of the NETWORK_TESTED_RESULT_* constants.
+      * arg2 = NetID.
+      */
+    public static final int EVENT_NETWORK_TESTED = 41;
+
+    /**
+     * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the private DNS
+     * config was resolved.
+     * obj = PrivateDnsConfig
+     * arg2 = netid
+     */
+    public static final int EVENT_PRIVATE_DNS_CONFIG_RESOLVED = 42;
+
+    /**
+     * Request ConnectivityService display provisioning notification.
+     * arg1    = Whether to make the notification visible.
+     * arg2    = NetID.
+     * obj     = Intent to be launched when notification selected by user, null if !arg1.
+     */
+    public static final int EVENT_PROVISIONING_NOTIFICATION = 43;
+
+    /**
+     * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+     * should be shown.
+     */
+    public static final int PROVISIONING_NOTIFICATION_SHOW = 1;
+
+    /**
+     * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
+     * should be hidden.
+     */
+    public static final int PROVISIONING_NOTIFICATION_HIDE = 0;
+
     private static String eventName(int what) {
         return sMagicDecoderRing.get(what, Integer.toString(what));
     }
@@ -506,30 +548,6 @@
     private long mMaxWakelockDurationMs = 0;
     private long mLastWakeLockAcquireTimestamp = 0;
 
-    // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
-    private static final int MAX_VALIDATION_LOGS = 10;
-    private static class ValidationLog {
-        final Network mNetwork;
-        final String mName;
-        final ReadOnlyLocalLog mLog;
-
-        ValidationLog(Network network, String name, ReadOnlyLocalLog log) {
-            mNetwork = network;
-            mName = name;
-            mLog = log;
-        }
-    }
-    private final ArrayDeque<ValidationLog> mValidationLogs = new ArrayDeque<>(MAX_VALIDATION_LOGS);
-
-    private void addValidationLogs(ReadOnlyLocalLog log, Network network, String name) {
-        synchronized (mValidationLogs) {
-            while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
-                mValidationLogs.removeLast();
-            }
-            mValidationLogs.addFirst(new ValidationLog(network, name, log));
-        }
-    }
-
     private final IpConnectivityLog mMetricsLog;
 
     @GuardedBy("mBandwidthRequests")
@@ -1684,7 +1702,11 @@
             // caller type. Need to re-factor NetdEventListenerService to allow multiple
             // NetworkMonitor registrants.
             if (nai != null && nai.satisfies(mDefaultRequest)) {
-                nai.networkMonitor.sendMessage(NetworkMonitor.EVENT_DNS_NOTIFICATION, returnCode);
+                try {
+                    nai.networkMonitor().notifyDnsResponse(returnCode);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
             }
         }
     };
@@ -2266,17 +2288,6 @@
 
         if (ArrayUtils.contains(args, SHORT_ARG) == false) {
             pw.println();
-            synchronized (mValidationLogs) {
-                pw.println("mValidationLogs (most recent first):");
-                for (ValidationLog p : mValidationLogs) {
-                    pw.println(p.mNetwork + " - " + p.mName);
-                    pw.increaseIndent();
-                    p.mLog.dump(fd, pw, args);
-                    pw.decreaseIndent();
-                }
-            }
-
-            pw.println();
             pw.println("mNetworkRequestInfoLogs (most recent first):");
             pw.increaseIndent();
             mNetworkRequestInfoLogs.reverseDump(fd, pw, args);
@@ -2455,11 +2466,11 @@
             switch (msg.what) {
                 default:
                     return false;
-                case NetworkMonitor.EVENT_NETWORK_TESTED: {
+                case EVENT_NETWORK_TESTED: {
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                     if (nai == null) break;
 
-                    final boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+                    final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
                     final boolean wasValidated = nai.lastValidated;
                     final boolean wasDefault = isDefaultNetwork(nai);
 
@@ -2497,7 +2508,7 @@
                     }
                     break;
                 }
-                case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
+                case EVENT_PROVISIONING_NOTIFICATION: {
                     final int netId = msg.arg2;
                     final boolean visible = toBool(msg.arg1);
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
@@ -2530,7 +2541,7 @@
                     }
                     break;
                 }
-                case NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
+                case EVENT_PRIVATE_DNS_CONFIG_RESOLVED: {
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                     if (nai == null) break;
 
@@ -2572,8 +2583,61 @@
         }
     }
 
+    private class NetworkMonitorCallbacks extends INetworkMonitorCallbacks.Stub {
+        private final NetworkAgentInfo mNai;
+
+        private NetworkMonitorCallbacks(NetworkAgentInfo nai) {
+            mNai = nai;
+        }
+
+        @Override
+        public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+            mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT,
+                    new Pair<>(mNai, networkMonitor)));
+        }
+
+        @Override
+        public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) {
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED,
+                    testResult, mNai.network.netId, redirectUrl));
+        }
+
+        @Override
+        public void notifyPrivateDnsConfigResolved(PrivateDnsConfigParcel config) {
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+                    EVENT_PRIVATE_DNS_CONFIG_RESOLVED,
+                    0, mNai.network.netId, PrivateDnsConfig.fromParcel(config)));
+        }
+
+        @Override
+        public void showProvisioningNotification(String action) {
+            final Intent intent = new Intent(action);
+            intent.setPackage(NETWORKSTACK_PACKAGE_NAME);
+
+            final PendingIntent pendingIntent;
+            // Only the system server can register notifications with package "android"
+            final long token = Binder.clearCallingIdentity();
+            try {
+                pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+                    EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_SHOW,
+                    mNai.network.netId,
+                    pendingIntent));
+        }
+
+        @Override
+        public void hideProvisioningNotification() {
+            mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+                    EVENT_PROVISIONING_NOTIFICATION, PROVISIONING_NOTIFICATION_HIDE,
+                    mNai.network.netId));
+        }
+    }
+
     private boolean networkRequiresValidation(NetworkAgentInfo nai) {
-        return NetworkMonitor.isValidationRequired(
+        return isValidationRequired(
                 mDefaultRequest.networkCapabilities, nai.networkCapabilities);
     }
 
@@ -2603,10 +2667,14 @@
         // Internet access and therefore also require validation.
         if (!networkRequiresValidation(nai)) return;
 
-        // Notify the NetworkMonitor thread in case it needs to cancel or
+        // Notify the NetworkAgentInfo/NetworkMonitor in case NetworkMonitor needs to cancel or
         // schedule DNS resolutions. If a DNS resolution is required the
         // result will be sent back to us.
-        nai.networkMonitor.notifyPrivateDnsSettingsChanged(cfg);
+        try {
+            nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
 
         // With Private DNS bypass support, we can proceed to update the
         // Private DNS config immediately, even if we're in strict mode
@@ -2736,7 +2804,11 @@
             // Disable wakeup packet monitoring for each interface.
             wakeupModifyInterface(iface, nai.networkCapabilities, false);
         }
-        nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
+        try {
+            nai.networkMonitor().notifyNetworkDisconnected();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
         mNetworkAgentInfos.remove(nai.messenger);
         nai.maybeStopClat();
         synchronized (mNetworkForNetId) {
@@ -3096,7 +3168,11 @@
             NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
             if (nai == null) return;
             if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return;
-            nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP);
+            try {
+                nai.networkMonitor().launchCaptivePortalApp();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
         });
     }
 
@@ -3217,6 +3293,11 @@
         return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
     }
 
+    @Override
+    public NetworkRequest getDefaultRequest() {
+        return mDefaultRequest;
+    }
+
     private class InternalHandler extends Handler {
         public InternalHandler(Looper looper) {
             super(looper);
@@ -3247,7 +3328,9 @@
                     break;
                 }
                 case EVENT_REGISTER_NETWORK_AGENT: {
-                    handleRegisterNetworkAgent((NetworkAgentInfo)msg.obj);
+                    final Pair<NetworkAgentInfo, INetworkMonitor> arg =
+                            (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj;
+                    handleRegisterNetworkAgent(arg.first, arg.second);
                     break;
                 }
                 case EVENT_REGISTER_NETWORK_REQUEST:
@@ -3305,7 +3388,14 @@
                 }
                 case EVENT_SYSTEM_READY: {
                     for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
-                        nai.networkMonitor.systemReady = true;
+                        // Might have been called already in handleRegisterNetworkAgent since
+                        // mSystemReady is set before sending EVENT_SYSTEM_READY, but calling
+                        // this several times is fine.
+                        try {
+                            nai.networkMonitor().notifySystemReady();
+                        } catch (RemoteException e) {
+                            e.rethrowFromSystemServer();
+                        }
                     }
                     mMultipathPolicyTracker.start();
                     break;
@@ -3577,7 +3667,11 @@
         if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
             return;
         }
-        nai.networkMonitor.forceReevaluation(uid);
+        try {
+            nai.networkMonitor().forceReevaluation(uid);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
     }
 
     @Override
@@ -4785,27 +4879,49 @@
         final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
         final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
                 new Network(reserveNetId()), new NetworkInfo(networkInfo), lp, nc, currentScore,
-                mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
+                mContext, mTrackerHandler, new NetworkMisc(networkMisc), this);
         // Make sure the network capabilities reflect what the agent info says.
         nai.networkCapabilities = mixInCapabilities(nai, nc);
-        synchronized (this) {
-            nai.networkMonitor.systemReady = mSystemReady;
-        }
         final String extraInfo = networkInfo.getExtraInfo();
         final String name = TextUtils.isEmpty(extraInfo)
                 ? nai.networkCapabilities.getSSID() : extraInfo;
-        addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network, name);
         if (DBG) log("registerNetworkAgent " + nai);
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mContext.getSystemService(NetworkStack.class)
+                    .makeNetworkMonitor(nai.network, name, new NetworkMonitorCallbacks(nai));
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        // NetworkAgentInfo registration will finish when the NetworkMonitor is created.
+        // If the network disconnects or sends any other event before that, messages are deferred by
+        // NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the
+        // registration.
         return nai.network.netId;
     }
 
-    private void handleRegisterNetworkAgent(NetworkAgentInfo nai) {
+    private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
+        nai.onNetworkMonitorCreated(networkMonitor);
         if (VDBG) log("Got NetworkAgent Messenger");
         mNetworkAgentInfos.put(nai.messenger, nai);
         synchronized (mNetworkForNetId) {
             mNetworkForNetId.put(nai.network.netId, nai);
         }
+        synchronized (this) {
+            if (mSystemReady) {
+                try {
+                    networkMonitor.notifySystemReady();
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        try {
+            networkMonitor.start();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
         nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
         NetworkInfo networkInfo = nai.networkInfo;
         nai.networkInfo = null;
@@ -4855,6 +4971,11 @@
             networkAgent.updateClat(mNMS);
             notifyIfacesChangedForNetworkStats();
             if (networkAgent.everConnected) {
+                try {
+                    networkAgent.networkMonitor().notifyLinkPropertiesChanged();
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
                 notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
             }
         }
@@ -5092,6 +5213,11 @@
             // If the requestable capabilities have changed or the score changed, we can't have been
             // called by rematchNetworkAndRequests, so it's safe to start a rematch.
             rematchAllNetworksAndRequests(nai, oldScore);
+            try {
+                nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
 
@@ -5339,6 +5465,11 @@
         }
 
         if (capabilitiesChanged) {
+            try {
+                nai.networkMonitor().notifyNetworkCapabilitiesChanged();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
             notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
         }
 
@@ -5739,7 +5870,15 @@
             updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
                     null);
 
-            networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+            // Until parceled LinkProperties are sent directly to NetworkMonitor, the connect
+            // command must be sent after updating LinkProperties to maximize chances of
+            // NetworkMonitor seeing the correct LinkProperties when starting.
+            // TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
+            try {
+                networkAgent.networkMonitor().notifyNetworkConnected();
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
             scheduleUnvalidatedPrompt(networkAgent);
 
             if (networkAgent.isVPN()) {
@@ -6020,7 +6159,7 @@
     @Override
     public String getCaptivePortalServerUrl() {
         enforceConnectivityInternalPermission();
-        return NetworkMonitor.getCaptivePortalServerHttpUrl(mContext);
+        return NetworkMonitorUtils.getCaptivePortalServerHttpUrl(mContext);
     }
 
     @Override
@@ -6113,12 +6252,6 @@
     }
 
     @VisibleForTesting
-    public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
-            NetworkAgentInfo nai, NetworkRequest defaultRequest) {
-        return new NetworkMonitor(context, handler, nai, defaultRequest);
-    }
-
-    @VisibleForTesting
     MultinetworkPolicyTracker createMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
         return new MultinetworkPolicyTracker(c, h, r);
     }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 6d10632..566d837 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -34,6 +34,7 @@
 import android.os.UserHandle;
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
+import android.telephony.DataFailCause;
 import android.telephony.DisconnectCause;
 import android.telephony.LocationAccessPolicy;
 import android.telephony.PhoneCapability;
@@ -47,6 +48,7 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
 import android.telephony.emergency.EmergencyNumber;
 import android.util.LocalLog;
 import android.util.StatsLog;
@@ -1366,7 +1368,8 @@
                     mDataConnectionNetworkType[phoneId] = networkType;
                 }
                 mPreciseDataConnectionState = new PreciseDataConnectionState(state, networkType,
-                        apnType, apn, linkProperties, "");
+                        ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
+                        linkProperties, DataFailCause.NONE);
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
                             PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1384,7 +1387,7 @@
         broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties,
                 networkCapabilities, roaming, subId);
         broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn,
-                linkProperties, "");
+                linkProperties, DataFailCause.NONE);
     }
 
     public void notifyDataConnectionFailed(String apnType) {
@@ -1403,7 +1406,8 @@
         synchronized (mRecords) {
             mPreciseDataConnectionState = new PreciseDataConnectionState(
                     TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                    apnType, "", null, "");
+                    ApnSetting.getApnTypesBitmaskFromString(apnType), "", null,
+                    DataFailCause.NONE);
             for (Record r : mRecords) {
                 if (r.matchPhoneStateListenerEvent(
                         PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1418,7 +1422,8 @@
         }
         broadcastDataConnectionFailed(apnType, subId);
         broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN,
-                TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, "", null, "");
+                TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, "", null,
+                DataFailCause.NONE);
     }
 
     public void notifyCellLocation(Bundle cellLocation) {
@@ -1528,14 +1533,15 @@
         }
     }
 
-    public void notifyPreciseDataConnectionFailed(String apnType, String apn, String failCause) {
+    public void notifyPreciseDataConnectionFailed(String apnType,
+            String apn, @DataFailCause.FailCause int failCause) {
         if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
             return;
         }
         synchronized (mRecords) {
             mPreciseDataConnectionState = new PreciseDataConnectionState(
                     TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                    apnType, apn, null, failCause);
+                    ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
             for (Record r : mRecords) {
                 if (r.matchPhoneStateListenerEvent(
                         PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
@@ -1929,9 +1935,8 @@
     }
 
     private void broadcastPreciseDataConnectionStateChanged(int state, int networkType,
-                                                            String apnType, String apn,
-                                                            LinkProperties linkProperties,
-                                                            String failCause) {
+            String apnType, String apn, LinkProperties linkProperties,
+            @DataFailCause.FailCause int failCause) {
         Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED);
         intent.putExtra(PhoneConstants.STATE_KEY, state);
         intent.putExtra(PhoneConstants.DATA_NETWORK_TYPE_KEY, networkType);
@@ -1940,7 +1945,7 @@
         if (linkProperties != null) {
             intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
         }
-        if (failCause != null) intent.putExtra(PhoneConstants.DATA_FAILURE_CAUSE_KEY, failCause);
+        intent.putExtra(PhoneConstants.DATA_FAILURE_CAUSE_KEY, failCause);
 
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                 android.Manifest.permission.READ_PRECISE_PHONE_STATE);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 514c9f6..ec7947e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -75,6 +75,7 @@
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.FIRST_ISOLATED_UID;
 import static android.os.Process.LAST_ISOLATED_UID;
+import static android.os.Process.NETWORK_STACK_UID;
 import static android.os.Process.NFC_UID;
 import static android.os.Process.PHONE_UID;
 import static android.os.Process.PROC_CHAR;
@@ -21202,6 +21203,7 @@
             case BLUETOOTH_UID:
             case NFC_UID:
             case SE_UID:
+            case NETWORK_STACK_UID:
                 isCallerSystem = true;
                 break;
             default:
diff --git a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
index 24865bc..6fa98b8 100644
--- a/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
+++ b/services/core/java/com/android/server/connectivity/ConnectivityConstants.java
@@ -21,22 +21,6 @@
  * @hide
  */
 public class ConnectivityConstants {
-    // IPC constants
-    public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
-            "android.net.conn.NETWORK_CONDITIONS_MEASURED";
-    public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
-    public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
-    public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
-    public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
-    public static final String EXTRA_CELL_ID = "extra_cellid";
-    public static final String EXTRA_SSID = "extra_ssid";
-    public static final String EXTRA_BSSID = "extra_bssid";
-    /** real time since boot */
-    public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
-    public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
-
-    public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
-            "android.permission.ACCESS_NETWORK_CONDITIONS";
 
     // Penalty applied to scores of Networks that have not been validated.
     public static final int UNVALIDATED_SCORE_PENALTY = 40;
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index b8f057d..d8bb635 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -18,10 +18,9 @@
 
 import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-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_MIN_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_DEFAULT_MODE;
@@ -35,6 +34,7 @@
 import android.net.Network;
 import android.net.NetworkUtils;
 import android.net.Uri;
+import android.net.shared.PrivateDnsConfig;
 import android.os.Binder;
 import android.os.INetworkManagementService;
 import android.os.UserHandle;
@@ -43,10 +43,7 @@
 import android.util.Pair;
 import android.util.Slog;
 
-import com.android.server.connectivity.MockableSystemProperties;
-
 import java.net.InetAddress;
-import java.net.UnknownHostException;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -54,10 +51,8 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
 import java.util.Set;
-import java.util.StringJoiner;
+import java.util.stream.Collectors;
 
 
 /**
@@ -123,43 +118,6 @@
     private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
     private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
 
-    public static class PrivateDnsConfig {
-        public final boolean useTls;
-        public final String hostname;
-        public final InetAddress[] ips;
-
-        public PrivateDnsConfig() {
-            this(false);
-        }
-
-        public PrivateDnsConfig(boolean useTls) {
-            this.useTls = useTls;
-            this.hostname = "";
-            this.ips = new InetAddress[0];
-        }
-
-        public PrivateDnsConfig(String hostname, InetAddress[] ips) {
-            this.useTls = !TextUtils.isEmpty(hostname);
-            this.hostname = useTls ? hostname : "";
-            this.ips = (ips != null) ? ips : new InetAddress[0];
-        }
-
-        public PrivateDnsConfig(PrivateDnsConfig cfg) {
-            useTls = cfg.useTls;
-            hostname = cfg.hostname;
-            ips = cfg.ips;
-        }
-
-        public boolean inStrictMode() {
-            return useTls && !TextUtils.isEmpty(hostname);
-        }
-
-        public String toString() {
-            return PrivateDnsConfig.class.getSimpleName() +
-                    "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
-        }
-    }
-
     public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
         final String mode = getPrivateDnsMode(cr);
 
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 262184b..54c89aa 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,9 +16,8 @@
 
 package com.android.server.connectivity;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
-
 import android.content.Context;
+import android.net.INetworkMonitor;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -29,7 +28,6 @@
 import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.Messenger;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
@@ -37,11 +35,8 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.WakeupMessage;
 import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkMonitor;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.Objects;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -126,7 +121,6 @@
     public LinkProperties linkProperties;
     // This should only be modified via ConnectivityService.updateCapabilities().
     public NetworkCapabilities networkCapabilities;
-    public final NetworkMonitor networkMonitor;
     public final NetworkMisc networkMisc;
     // Indicates if netd has been told to create this Network. From this point on the appropriate
     // routing rules are setup and routes are added so packets can begin flowing over the Network.
@@ -239,6 +233,9 @@
     // Used by ConnectivityService to keep track of 464xlat.
     public Nat464Xlat clatd;
 
+    // Set after asynchronous creation of the NetworkMonitor.
+    private volatile INetworkMonitor mNetworkMonitor;
+
     private static final String TAG = ConnectivityService.class.getSimpleName();
     private static final boolean VDBG = false;
     private final ConnectivityService mConnService;
@@ -247,7 +244,7 @@
 
     public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
             LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
-            NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
+            NetworkMisc misc, ConnectivityService connService) {
         this.messenger = messenger;
         asyncChannel = ac;
         network = net;
@@ -258,10 +255,16 @@
         mConnService = connService;
         mContext = context;
         mHandler = handler;
-        networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest);
         networkMisc = misc;
     }
 
+    /**
+     * Inform NetworkAgentInfo that a new NetworkMonitor was created.
+     */
+    public void onNetworkMonitorCreated(INetworkMonitor networkMonitor) {
+        mNetworkMonitor = networkMonitor;
+    }
+
     public ConnectivityService connService() {
         return mConnService;
     }
@@ -278,6 +281,15 @@
         return network;
     }
 
+    /**
+     * Get the INetworkMonitor in this NetworkAgentInfo.
+     *
+     * <p>This will be null before {@link #onNetworkMonitorCreated(INetworkMonitor)} is called.
+     */
+    public INetworkMonitor networkMonitor() {
+        return mNetworkMonitor;
+    }
+
     // Functions for manipulating the requests satisfied by this network.
     //
     // These functions must only called on ConnectivityService's main thread.
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 9dfdddb..eb5be77 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -1837,7 +1837,7 @@
         final TetherState tetherState = new TetherState(
                 new IpServer(iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
                              makeControlCallback(), mConfig.enableLegacyDhcpServer,
-                             mDeps.getIpServerDependencies()));
+                             mDeps.getIpServerDependencies(mContext)));
         mTetherStates.put(iface, tetherState);
         tetherState.ipServer.start();
     }
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
index 7daf71d..a42efe9 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -60,8 +60,8 @@
     /**
      * Get dependencies to be used by IpServer.
      */
-    public IpServer.Dependencies getIpServerDependencies() {
-        return new IpServer.Dependencies();
+    public IpServer.Dependencies getIpServerDependencies(Context context) {
+        return new IpServer.Dependencies(context);
     }
 
     /**
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 8237185..b0adf95 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -361,7 +361,7 @@
         mStatsObservers = checkNotNull(statsObservers, "missing NetworkStatsObservers");
         mSystemDir = checkNotNull(systemDir, "missing systemDir");
         mBaseDir = checkNotNull(baseDir, "missing baseDir");
-        mUseBpfTrafficStats = new File("/sys/fs/bpf/traffic_uid_stats_map").exists();
+        mUseBpfTrafficStats = new File("/sys/fs/bpf/map_netd_app_uid_stats_map").exists();
     }
 
     private void registerLocalService() {
@@ -955,13 +955,7 @@
 
     @Override
     public long getIfaceStats(String iface, int type) {
-        // eBPF code doesn't provide per-interface TCP counters. Use xt_qtaguid for now.
-        // TODO: delete getMobileTcp(Rx|Tx)Packets entirely. See b/110443385 .
-        if (type == TYPE_TCP_TX_PACKETS || type == TYPE_TCP_RX_PACKETS) {
-            return nativeGetIfaceStat(iface, type, false);
-        } else {
-            return nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
-        }
+        return nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/os/BugreportManagerService.java b/services/core/java/com/android/server/os/BugreportManagerService.java
new file mode 100644
index 0000000..e241591
--- /dev/null
+++ b/services/core/java/com/android/server/os/BugreportManagerService.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2019 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.os;
+
+import android.content.Context;
+
+import com.android.server.SystemService;
+
+/**
+ * Service that provides a privileged API to capture and consume bugreports.
+ *
+ * @hide
+ */
+public class BugreportManagerService extends SystemService {
+    private static final String TAG = "BugreportManagerService";
+
+    private BugreportManagerServiceImpl mService;
+
+    public BugreportManagerService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        mService = new BugreportManagerServiceImpl(getContext());
+        // TODO(b/111441001): Needs sepolicy to be submitted first.
+        // publishBinderService(Context.BUGREPORT_SERVICE, mService);
+    }
+}
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
new file mode 100644
index 0000000..faa4714
--- /dev/null
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.os;
+
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.os.BugreportParams;
+import android.os.IDumpstate;
+import android.os.IDumpstateListener;
+import android.os.IDumpstateToken;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+
+// TODO(b/111441001):
+// 1. Handle the case where another bugreport is in progress
+// 2. Make everything threadsafe
+// 3. Pass validation & other errors on listener
+
+/**
+ * Implementation of the service that provides a privileged API to capture and consume bugreports.
+ *
+ * <p>Delegates the actualy generation to a native implementation of {@code Dumpstate}.
+ */
+class BugreportManagerServiceImpl extends IDumpstate.Stub {
+    private static final String TAG = "BugreportManagerService";
+    private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
+
+    private IDumpstate mDs = null;
+    private final Context mContext;
+
+    BugreportManagerServiceImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public IDumpstateToken setListener(String name, IDumpstateListener listener,
+            boolean getSectionDetails) throws RemoteException {
+        // TODO(b/111441001): Figure out if lazy setting of listener should be allowed
+        // and if so how to handle it.
+        throw new UnsupportedOperationException("setListener is not allowed on this service");
+    }
+
+
+    @Override
+    @RequiresPermission(android.Manifest.permission.DUMP)
+    public void startBugreport(FileDescriptor bugreportFd, FileDescriptor screenshotFd,
+            int bugreportMode, IDumpstateListener listener) throws RemoteException {
+
+        validate(bugreportMode);
+
+        mDs = getDumpstateService();
+        if (mDs == null) {
+            Slog.w(TAG, "Unable to get bugreport service");
+            // TODO(b/111441001): pass error on listener
+            return;
+        }
+        mDs.startBugreport(bugreportFd, screenshotFd, bugreportMode, listener);
+    }
+
+    private boolean validate(@BugreportParams.BugreportMode int mode) {
+        if (mode != BugreportParams.BUGREPORT_MODE_FULL
+                && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
+                && mode != BugreportParams.BUGREPORT_MODE_REMOTE
+                && mode != BugreportParams.BUGREPORT_MODE_WEAR
+                && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY
+                && mode != BugreportParams.BUGREPORT_MODE_WIFI) {
+            Slog.w(TAG, "Unknown bugreport mode: " + mode);
+            return false;
+        }
+        return true;
+    }
+
+    /*
+     * Start and get a handle to the native implementation of {@code IDumpstate} which does the
+     * actual bugreport generation.
+     *
+     * <p>Generating bugreports requires root privileges. To limit the footprint
+     * of the root access, the actual generation in Dumpstate binary is accessed as a
+     * oneshot service 'bugreport'.
+     */
+    private IDumpstate getDumpstateService() {
+        // Start bugreport service.
+        SystemProperties.set("ctl.start", "bugreport");
+
+        IDumpstate ds = null;
+        boolean timedOut = false;
+        int totalTimeWaitedMillis = 0;
+        int seedWaitTimeMillis = 500;
+        while (!timedOut) {
+            // Note that the binder service on the native side is "dumpstate".
+            ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
+            if (ds != null) {
+                Slog.i(TAG, "Got bugreport service handle.");
+                break;
+            }
+            SystemClock.sleep(seedWaitTimeMillis);
+            Slog.i(TAG,
+                    "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)");
+            totalTimeWaitedMillis += seedWaitTimeMillis;
+            seedWaitTimeMillis *= 2;
+            timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS;
+        }
+        if (timedOut) {
+            Slog.w(TAG,
+                    "Timed out waiting to get dumpstate service handle ("
+                    + totalTimeWaitedMillis + "ms)");
+        }
+        return ds;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 5e1ed03..51575a4 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -87,9 +87,6 @@
     // One minute over PM WATCHDOG_TIMEOUT
     private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
 
-    /** Special library name that skips shared libraries check during compilation. */
-    public static final String SKIP_SHARED_LIBRARY_CHECK = "&";
-
     @GuardedBy("mInstallLock")
     private final Installer mInstaller;
     private final Object mInstallLock;
@@ -399,23 +396,23 @@
             Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
             return DEX_OPT_FAILED;
         }
-        Log.d(TAG, "Running dexopt on: " + path
-                + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas()
-                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
-                + " target-filter=" + compilerFilter);
-
-        String classLoaderContext;
+        String classLoaderContext = null;
         if (dexUseInfo.isUnknownClassLoaderContext() || dexUseInfo.isVariableClassLoaderContext()) {
-            // If we have an unknown (not yet set), or a variable class loader chain, compile
-            // without a context and mark the oat file with SKIP_SHARED_LIBRARY_CHECK. Note that
-            // this might lead to a incorrect compilation.
-            // TODO(calin): We should just extract in this case.
-            classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
+            // If we have an unknown (not yet set), or a variable class loader chain. Just extract
+            // the dex file.
+            compilerFilter = "extract";
         } else {
             classLoaderContext = dexUseInfo.getClassLoaderContext();
         }
 
         int reason = options.getCompilationReason();
+        Log.d(TAG, "Running dexopt on: " + path
+                + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas()
+                + " reason=" + getReasonName(reason)
+                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
+                + " target-filter=" + compilerFilter
+                + " class-loader-context=" + classLoaderContext);
+
         try {
             for (String isa : dexUseInfo.getLoaderIsas()) {
                 // Reuse the same dexopt path as for the primary apks. We don't need all the
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index b47d966..b4154c7 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -17,8 +17,8 @@
 package com.android.server.pm;
 
 import android.content.pm.PackageParser;
-import android.content.pm.Signature;
 import android.content.pm.PackageParser.SigningDetails;
+import android.content.pm.Signature;
 import android.os.Environment;
 import android.util.Slog;
 import android.util.Xml;
@@ -81,6 +81,13 @@
         sMacPermissions.add(new File(
             Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml"));
 
+        // Product mac permissions (optional).
+        final File productMacPermission = new File(
+                Environment.getProductDirectory(), "/etc/selinux/product_mac_permissions.xml");
+        if (productMacPermission.exists()) {
+            sMacPermissions.add(productMacPermission);
+        }
+
         // Vendor mac permissions.
         // The filename has been renamed from nonplat_mac_permissions to
         // vendor_mac_permissions. Either of them should exist.
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index 93ee44c..91ad11e 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -22,7 +22,6 @@
 import android.util.SparseArray;
 
 import com.android.internal.os.ClassLoaderFactory;
-import com.android.server.pm.PackageDexOptimizer;
 
 import java.io.File;
 import java.util.List;
@@ -275,15 +274,11 @@
     /**
      * Encodes a single class loader dependency starting from {@param path} and
      * {@param classLoaderName}.
-     * When classpath is {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns
-     * the same. This special property is used only during OTA.
      * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
      * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
      */
     /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) {
-        if (classpath.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) {
-            return classpath;
-        }
+        classpath.getClass();  // Throw NPE if classpath is null
         String classLoaderDexoptEncoding = classLoaderName;
         if (ClassLoaderFactory.isPathClassLoaderName(classLoaderName)) {
             classLoaderDexoptEncoding = "PCL";
@@ -306,16 +301,10 @@
     /**
      * Links to dependencies together in a format accepted by dexopt.
      * For the special case when either of cl1 or cl2 equals
-     * {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns the same. This
-     * property is used only during OTA.
      * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split
      * dependencies {@see encodeClassLoader} separated by ';'.
      */
     /*package*/ static String encodeClassLoaderChain(String cl1, String cl2) {
-        if (cl1.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK) ||
-                cl2.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) {
-            return PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK;
-        }
         if (cl1.isEmpty()) return cl2;
         if (cl2.isEmpty()) return cl1;
         return cl1 + ";" + cl2;
diff --git a/services/ipmemorystore/Android.bp b/services/ipmemorystore/Android.bp
new file mode 100644
index 0000000..013cf56
--- /dev/null
+++ b/services/ipmemorystore/Android.bp
@@ -0,0 +1,4 @@
+java_library_static {
+    name: "services.ipmemorystore",
+    srcs: ["java/**/*.java"],
+}
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
new file mode 100644
index 0000000..c9759bf
--- /dev/null
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -0,0 +1,151 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.IIpMemoryStore;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnL2KeyResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+
+/**
+ * Implementation for the IP memory store.
+ * This component offers specialized services for network components to store and retrieve
+ * knowledge about networks, and provides intelligence that groups level 2 networks together
+ * into level 3 networks.
+ *
+ * @hide
+ */
+public class IpMemoryStoreService extends IIpMemoryStore.Stub {
+    final Context mContext;
+
+    public IpMemoryStoreService(@NonNull final Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Store network attributes for a given L2 key.
+     *
+     * @param l2Key The L2 key for the L2 network. Clients that don't know or care about the L2
+     *              key and only care about grouping can pass a unique ID here like the ones
+     *              generated by {@code java.util.UUID.randomUUID()}, but keep in mind the low
+     *              relevance of such a network will lead to it being evicted soon if it's not
+     *              refreshed. Use findL2Key to try and find a similar L2Key to these attributes.
+     * @param attributes The attributes for this network.
+     * @param listener A listener to inform of the completion of this call, or null if the client
+     *        is not interested in learning about success/failure.
+     * Through the listener, returns the L2 key. This is useful if the L2 key was not specified.
+     * If the call failed, the L2 key will be null.
+     */
+    @Override
+    public void storeNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final NetworkAttributesParcelable attributes,
+            @Nullable final IOnStatusListener listener) {
+        // TODO : implement this
+    }
+
+    /**
+     * Store a binary blob associated with an L2 key and a name.
+     *
+     * @param l2Key The L2 key for this network.
+     * @param clientId The ID of the client.
+     * @param name The name of this data.
+     * @param data The data to store.
+     * @param listener The listener that will be invoked to return the answer, or null if the
+     *        is not interested in learning about success/failure.
+     * Through the listener, returns a status to indicate success or failure.
+     */
+    @Override
+    public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final Blob data,
+            @Nullable final IOnStatusListener listener) {
+        // TODO : implement this
+    }
+
+    /**
+     * Returns the best L2 key associated with the attributes.
+     *
+     * This will find a record that would be in the same group as the passed attributes. This is
+     * useful to choose the key for storing a sample or private data when the L2 key is not known.
+     * If multiple records are group-close to these attributes, the closest match is returned.
+     * If multiple records have the same closeness, the one with the smaller (unicode codepoint
+     * order) L2 key is returned.
+     * If no record matches these attributes, null is returned.
+     *
+     * @param attributes The attributes of the network to find.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the L2 key if one matched, or null.
+     */
+    @Override
+    public void findL2Key(@NonNull final NetworkAttributesParcelable attributes,
+            @NonNull final IOnL2KeyResponseListener listener) {
+        // TODO : implement this
+    }
+
+    /**
+     * Returns whether, to the best of the store's ability to tell, the two specified L2 keys point
+     * to the same L3 network. Group-closeness is used to determine this.
+     *
+     * @param l2Key1 The key for the first network.
+     * @param l2Key2 The key for the second network.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
+     */
+    @Override
+    public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
+            @NonNull final IOnSameNetworkResponseListener listener) {
+        // TODO : implement this
+    }
+
+    /**
+     * Retrieve the network attributes for a key.
+     * If no record is present for this key, this will return null attributes.
+     *
+     * @param l2Key The key of the network to query.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the network attributes and the L2 key associated with
+     *         the query.
+     */
+    @Override
+    public void retrieveNetworkAttributes(@NonNull final String l2Key,
+            @NonNull final IOnNetworkAttributesRetrieved listener) {
+        // TODO : implement this.
+    }
+
+    /**
+     * Retrieve previously stored private data.
+     * If no data was stored for this L2 key and name this will return null.
+     *
+     * @param l2Key The L2 key.
+     * @param clientId The id of the client that stored this data.
+     * @param name The name of the data.
+     * @param listener The listener that will be invoked to return the answer.
+     * Through the listener, returns the private data if any or null if none, with the L2 key
+     *         and the name of the data associated with the query.
+     */
+    @Override
+    public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
+            @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+        // TODO : implement this.
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 2d07fd6..10d9798 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -16,6 +16,13 @@
 
 package com.android.server;
 
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
+import static android.os.IServiceManager.DUMP_FLAG_PROTO;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
 import android.app.ActivityThread;
 import android.app.INotificationManager;
 import android.app.usage.UsageStatsManagerInternal;
@@ -84,15 +91,17 @@
 import com.android.server.lights.LightsService;
 import com.android.server.media.MediaResourceMonitorService;
 import com.android.server.media.MediaRouterService;
-import com.android.server.media.MediaUpdateService;
 import com.android.server.media.MediaSessionService;
+import com.android.server.media.MediaUpdateService;
 import com.android.server.media.projection.MediaProjectionManagerService;
 import com.android.server.net.NetworkPolicyManagerService;
 import com.android.server.net.NetworkStatsService;
+import com.android.server.net.ipmemorystore.IpMemoryStoreService;
 import com.android.server.net.watchlist.NetworkWatchlistService;
 import com.android.server.notification.NotificationManagerService;
 import com.android.server.oemlock.OemLockService;
 import com.android.server.om.OverlayManagerService;
+import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.SchedulingPolicyService;
 import com.android.server.pm.BackgroundDexOptService;
@@ -133,12 +142,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
 
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_HIGH;
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
-import static android.os.IServiceManager.DUMP_FLAG_PROTO;
-import static android.view.Display.DEFAULT_DISPLAY;
-
 public final class SystemServer {
     private static final String TAG = "SystemServer";
 
@@ -740,6 +743,11 @@
         traceBeginAndSlog("StartBinderCallsStatsService");
         BinderCallsStatsService.start();
         traceEnd();
+
+        // Service to capture bugreports.
+        traceBeginAndSlog("StartBugreportManagerService");
+        mSystemServiceManager.startService(BugreportManagerService.class);
+        traceEnd();
     }
 
     /**
@@ -1098,6 +1106,15 @@
             }
             traceEnd();
 
+            traceBeginAndSlog("StartIpMemoryStoreService");
+            try {
+                ServiceManager.addService(Context.IP_MEMORY_STORE_SERVICE,
+                        new IpMemoryStoreService(context));
+            } catch (Throwable e) {
+                reportWtf("starting IP Memory Store Service", e);
+            }
+            traceEnd();
+
             traceBeginAndSlog("StartIpSecService");
             try {
                 ipSecService = IpSecService.create(context);
@@ -1981,7 +1998,7 @@
         windowManager.onSystemUiStarted();
     }
 
-    private static void traceBeginAndSlog(String name) {
+    private static void traceBeginAndSlog(@NonNull String name) {
         Slog.i(TAG, name);
         BOOT_TIMINGS_TRACE_LOG.traceBegin(name);
     }
diff --git a/services/net/Android.bp b/services/net/Android.bp
index e0ae68f..3b4d6a7 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -2,3 +2,19 @@
     name: "services.net",
     srcs: ["java/**/*.java"],
 }
+
+// TODO: move to networking module with DhcpClient and remove lib
+java_library {
+    name: "dhcp-packet-lib",
+    srcs: [
+        "java/android/net/dhcp/*Packet.java",
+    ]
+}
+
+filegroup {
+    name: "services-networkstack-shared-srcs",
+    srcs: [
+        "java/android/net/util/FdEventsReader.java", // TODO: move to NetworkStack with IpClient
+        "java/android/net/shared/*.java",
+    ]
+}
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
index 6ba7d94..ce8b7e7 100644
--- a/services/net/java/android/net/dhcp/DhcpPacket.java
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -1,8 +1,5 @@
 package android.net.dhcp;
 
-import static android.net.util.NetworkConstants.IPV4_MAX_MTU;
-import static android.net.util.NetworkConstants.IPV4_MIN_MTU;
-
 import android.annotation.Nullable;
 import android.net.DhcpResults;
 import android.net.LinkAddress;
@@ -37,6 +34,9 @@
 public abstract class DhcpPacket {
     protected static final String TAG = "DhcpPacket";
 
+    // TODO: use NetworkStackConstants.IPV4_MIN_MTU once this class is moved to the network stack.
+    private static final int IPV4_MIN_MTU = 68;
+
     // dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the
     // CPU for anything shorter than 5 minutes. For sanity's sake, this must be higher than the
     // DHCP client timeout.
diff --git a/services/net/java/android/net/ip/IpServer.java b/services/net/java/android/net/ip/IpServer.java
index 493350d..8b22f68 100644
--- a/services/net/java/android/net/ip/IpServer.java
+++ b/services/net/java/android/net/ip/IpServer.java
@@ -17,20 +17,26 @@
 package android.net.ip;
 
 import static android.net.NetworkUtils.numericToInetAddress;
-import static android.net.util.NetworkConstants.asByte;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.util.NetworkConstants.FF;
 import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+import static android.net.util.NetworkConstants.asByte;
 
+import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetd;
+import android.net.INetworkStackStatusCallback;
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkStack;
 import android.net.RouteInfo;
-import android.net.dhcp.DhcpServer;
-import android.net.dhcp.DhcpServingParams;
+import android.net.dhcp.DhcpServerCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.DhcpServingParamsParcelExt;
+import android.net.dhcp.IDhcpServer;
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
@@ -126,6 +132,10 @@
     }
 
     public static class Dependencies {
+        private final Context mContext;
+        public Dependencies(Context context) {
+            mContext = context;
+        }
         public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
             return new RouterAdvertisementDaemon(ifParams);
         }
@@ -138,9 +148,12 @@
             return NetdService.getInstance();
         }
 
-        public DhcpServer makeDhcpServer(Looper looper, String ifName,
-                DhcpServingParams params, SharedLog log) {
-            return new DhcpServer(looper, ifName, params, log);
+        /**
+         * Create a DhcpServer instance to be used by IpServer.
+         */
+        public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
+                DhcpServerCallbacks cb) {
+            mContext.getSystemService(NetworkStack.class).makeDhcpServer(ifName, params, cb);
         }
     }
 
@@ -197,7 +210,10 @@
     // Advertisements (otherwise, we do not add them to mLinkProperties at all).
     private LinkProperties mLastIPv6LinkProperties;
     private RouterAdvertisementDaemon mRaDaemon;
-    private DhcpServer mDhcpServer;
+
+    // To be accessed only on the handler thread
+    private int mDhcpServerStartIndex = 0;
+    private IDhcpServer mDhcpServer;
     private RaParams mLastRaParams;
 
     public IpServer(
@@ -252,35 +268,109 @@
 
     private boolean startIPv4() { return configureIPv4(true); }
 
+    /**
+     * Convenience wrapper around INetworkStackStatusCallback to run callbacks on the IpServer
+     * handler.
+     *
+     * <p>Different instances of this class can be created for each call to IDhcpServer methods,
+     * with different implementations of the callback, to differentiate handling of success/error in
+     * each call.
+     */
+    private abstract class OnHandlerStatusCallback extends INetworkStackStatusCallback.Stub {
+        @Override
+        public void onStatusAvailable(int statusCode) {
+            getHandler().post(() -> callback(statusCode));
+        }
+
+        public abstract void callback(int statusCode);
+    }
+
+    private class DhcpServerCallbacksImpl extends DhcpServerCallbacks {
+        private final int mStartIndex;
+
+        private DhcpServerCallbacksImpl(int startIndex) {
+            mStartIndex = startIndex;
+        }
+
+        @Override
+        public void onDhcpServerCreated(int statusCode, IDhcpServer server) throws RemoteException {
+            getHandler().post(() -> {
+                // We are on the handler thread: mDhcpServerStartIndex can be read safely.
+                if (mStartIndex != mDhcpServerStartIndex) {
+                    // This start request is obsolete. When the |server| binder token goes out of
+                    // scope, the garbage collector will finalize it, which causes the network stack
+                    // process garbage collector to collect the server itself.
+                    return;
+                }
+
+                if (statusCode != STATUS_SUCCESS) {
+                    mLog.e("Error obtaining DHCP server: " + statusCode);
+                    handleError();
+                    return;
+                }
+
+                mDhcpServer = server;
+                try {
+                    mDhcpServer.start(new OnHandlerStatusCallback() {
+                        @Override
+                        public void callback(int startStatusCode) {
+                            if (startStatusCode != STATUS_SUCCESS) {
+                                mLog.e("Error starting DHCP server: " + startStatusCode);
+                                handleError();
+                            }
+                        }
+                    });
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            });
+        }
+
+        private void handleError() {
+            mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+            transitionTo(mInitialState);
+        }
+    }
+
     private boolean startDhcp(Inet4Address addr, int prefixLen) {
         if (mUsingLegacyDhcp) {
             return true;
         }
-        final DhcpServingParams params;
-        try {
-            params = new DhcpServingParams.Builder()
-                    .setDefaultRouters(addr)
-                    .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
-                    .setDnsServers(addr)
-                    .setServerAddr(new LinkAddress(addr, prefixLen))
-                    .setMetered(true)
-                    .build();
-            // TODO: also advertise link MTU
-        } catch (DhcpServingParams.InvalidParameterException e) {
-            Log.e(TAG, "Invalid DHCP parameters", e);
-            return false;
-        }
+        final DhcpServingParamsParcel params;
+        params = new DhcpServingParamsParcelExt()
+                .setDefaultRouters(addr)
+                .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
+                .setDnsServers(addr)
+                .setServerAddr(new LinkAddress(addr, prefixLen))
+                .setMetered(true);
+        // TODO: also advertise link MTU
 
-        mDhcpServer = mDeps.makeDhcpServer(getHandler().getLooper(), mIfaceName, params,
-                mLog.forSubComponent("DHCP"));
-        mDhcpServer.start();
+        mDhcpServerStartIndex++;
+        mDeps.makeDhcpServer(
+                mIfaceName, params, new DhcpServerCallbacksImpl(mDhcpServerStartIndex));
         return true;
     }
 
     private void stopDhcp() {
+        // Make all previous start requests obsolete so servers are not started later
+        mDhcpServerStartIndex++;
+
         if (mDhcpServer != null) {
-            mDhcpServer.stop();
-            mDhcpServer = null;
+            try {
+                mDhcpServer.stop(new OnHandlerStatusCallback() {
+                    @Override
+                    public void callback(int statusCode) {
+                        if (statusCode != STATUS_SUCCESS) {
+                            mLog.e("Error stopping DHCP server: " + statusCode);
+                            mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+                            // Not much more we can do here
+                        }
+                    }
+                });
+                mDhcpServer = null;
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
         }
     }
 
diff --git a/services/net/java/android/net/shared/NetworkMonitorUtils.java b/services/net/java/android/net/shared/NetworkMonitorUtils.java
new file mode 100644
index 0000000..463cf2a
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkMonitorUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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.net.shared;
+
+import android.content.Context;
+import android.net.NetworkCapabilities;
+import android.provider.Settings;
+
+/** @hide */
+public class NetworkMonitorUtils {
+
+    // Network conditions broadcast constants
+    public static final String ACTION_NETWORK_CONDITIONS_MEASURED =
+            "android.net.conn.NETWORK_CONDITIONS_MEASURED";
+    public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type";
+    public static final String EXTRA_NETWORK_TYPE = "extra_network_type";
+    public static final String EXTRA_RESPONSE_RECEIVED = "extra_response_received";
+    public static final String EXTRA_IS_CAPTIVE_PORTAL = "extra_is_captive_portal";
+    public static final String EXTRA_CELL_ID = "extra_cellid";
+    public static final String EXTRA_SSID = "extra_ssid";
+    public static final String EXTRA_BSSID = "extra_bssid";
+    /** real time since boot */
+    public static final String EXTRA_REQUEST_TIMESTAMP_MS = "extra_request_timestamp_ms";
+    public static final String EXTRA_RESPONSE_TIMESTAMP_MS = "extra_response_timestamp_ms";
+    public static final String PERMISSION_ACCESS_NETWORK_CONDITIONS =
+            "android.permission.ACCESS_NETWORK_CONDITIONS";
+
+    // TODO: once the URL is a resource overlay, remove and have the resource define the default
+    private static final String DEFAULT_HTTP_URL =
+            "http://connectivitycheck.gstatic.com/generate_204";
+
+    /**
+     * Get the captive portal server HTTP URL that is configured on the device.
+     */
+    public static String getCaptivePortalServerHttpUrl(Context context) {
+        final String settingUrl = Settings.Global.getString(
+                context.getContentResolver(),
+                Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+        return settingUrl != null ? settingUrl : DEFAULT_HTTP_URL;
+    }
+
+    /**
+     * Return whether validation is required for a network.
+     * @param dfltNetCap Default requested network capabilities.
+     * @param nc Network capabilities of the network to test.
+     */
+    public static boolean isValidationRequired(
+            NetworkCapabilities dfltNetCap, NetworkCapabilities nc) {
+        // TODO: Consider requiring validation for DUN networks.
+        return dfltNetCap.satisfiedByNetworkCapabilities(nc);
+    }
+}
diff --git a/services/net/java/android/net/shared/PrivateDnsConfig.java b/services/net/java/android/net/shared/PrivateDnsConfig.java
new file mode 100644
index 0000000..41e0bad
--- /dev/null
+++ b/services/net/java/android/net/shared/PrivateDnsConfig.java
@@ -0,0 +1,94 @@
+/*
+ * 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.net.shared;
+
+import android.net.InetAddresses;
+import android.net.PrivateDnsConfigParcel;
+import android.text.TextUtils;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+
+/** @hide */
+public class PrivateDnsConfig {
+    public final boolean useTls;
+    public final String hostname;
+    public final InetAddress[] ips;
+
+    public PrivateDnsConfig() {
+        this(false);
+    }
+
+    public PrivateDnsConfig(boolean useTls) {
+        this.useTls = useTls;
+        this.hostname = "";
+        this.ips = new InetAddress[0];
+    }
+
+    public PrivateDnsConfig(String hostname, InetAddress[] ips) {
+        this.useTls = !TextUtils.isEmpty(hostname);
+        this.hostname = useTls ? hostname : "";
+        this.ips = (ips != null) ? ips : new InetAddress[0];
+    }
+
+    public PrivateDnsConfig(PrivateDnsConfig cfg) {
+        useTls = cfg.useTls;
+        hostname = cfg.hostname;
+        ips = cfg.ips;
+    }
+
+    /**
+     * Indicates whether this is a strict mode private DNS configuration.
+     */
+    public boolean inStrictMode() {
+        return useTls && !TextUtils.isEmpty(hostname);
+    }
+
+    @Override
+    public String toString() {
+        return PrivateDnsConfig.class.getSimpleName()
+                + "{" + useTls + ":" + hostname + "/" + Arrays.toString(ips) + "}";
+    }
+
+    /**
+     * Create a stable AIDL-compatible parcel from the current instance.
+     */
+    public PrivateDnsConfigParcel toParcel() {
+        final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel();
+        parcel.hostname = hostname;
+
+        final String[] parceledIps = new String[ips.length];
+        for (int i = 0; i < ips.length; i++) {
+            parceledIps[i] = ips[i].getHostAddress();
+        }
+        parcel.ips = parceledIps;
+
+        return parcel;
+    }
+
+    /**
+     * Build a configuration from a stable AIDL-compatible parcel.
+     */
+    public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) {
+        final InetAddress[] ips = new InetAddress[parcel.ips.length];
+        for (int i = 0; i < ips.length; i++) {
+            ips[i] = InetAddresses.parseNumericAddress(parcel.ips[i]);
+        }
+
+        return new PrivateDnsConfig(parcel.hostname, ips);
+    }
+}
diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java
index 3defe56..c183b81 100644
--- a/services/net/java/android/net/util/NetworkConstants.java
+++ b/services/net/java/android/net/util/NetworkConstants.java
@@ -16,9 +16,6 @@
 
 package android.net.util;
 
-import java.nio.ByteBuffer;
-
-
 /**
  * Networking protocol constants.
  *
@@ -81,8 +78,6 @@
      *     - https://tools.ietf.org/html/rfc791
      */
     public static final int IPV4_HEADER_MIN_LEN = 20;
-    public static final int IPV4_MIN_MTU = 68;
-    public static final int IPV4_MAX_MTU = 65_535;
     public static final int IPV4_IHL_MASK = 0xf;
     public static final int IPV4_FLAGS_OFFSET = 6;
     public static final int IPV4_FRAGMENT_MASK = 0x1fff;
diff --git a/services/net/java/android/net/util/SharedLog.java b/services/net/java/android/net/util/SharedLog.java
index 74bc147..2cdb2b0 100644
--- a/services/net/java/android/net/util/SharedLog.java
+++ b/services/net/java/android/net/util/SharedLog.java
@@ -32,6 +32,7 @@
  *
  * All access to class methods other than dump() must be on the same thread.
  *
+ * TODO: this is a copy of SharedLog in the NetworkStack. Remove after Tethering is migrated.
  * @hide
  */
 public class SharedLog {
@@ -69,6 +70,10 @@
         mComponent = component;
     }
 
+    public String getTag() {
+        return mTag;
+    }
+
     /**
      * Create a SharedLog based on this log with an additional component prefix on each logged line.
      */
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
index 1542120..aa51ecd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -16,9 +16,6 @@
 
 package com.android.server.pm.dex;
 
-import com.android.server.pm.PackageDexOptimizer;
-
-import static com.android.server.pm.PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -412,12 +409,6 @@
 
     @Test
     public void testEncodeClassLoader() {
-        assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader(
-                SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.PathClassLoader"));
-        assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader(
-                SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DexClassLoader"));
-        assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoader(
-                SKIP_SHARED_LIBRARY_CHECK, "dalvik.system.DelegateLastClassLoader"));
         assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz",
                 "dalvik.system.PathClassLoader"));
         assertEquals("PCL[xyz]", DexoptUtils.encodeClassLoader("xyz",
@@ -435,15 +426,8 @@
 
     @Test
     public void testEncodeClassLoaderChain() {
-        assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain(
-                SKIP_SHARED_LIBRARY_CHECK, "PCL[a]"));
-        assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]",
-                SKIP_SHARED_LIBRARY_CHECK));
         assertEquals("PCL[a];DLC[b]", DexoptUtils.encodeClassLoaderChain("PCL[a]",
                 "DLC[b]"));
-        assertEquals(SKIP_SHARED_LIBRARY_CHECK, DexoptUtils.encodeClassLoaderChain("PCL[a]",
-                SKIP_SHARED_LIBRARY_CHECK));
-
         try {
             DexoptUtils.encodeClassLoaderChain("a", null);
             fail(); // exception is expected
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 89b646f..eb010bc 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2340,6 +2340,34 @@
     public static final String KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL =
             "support_emergency_dialer_shortcut_bool";
 
+    /**
+     * Controls RSRP threshold at which AlternativeNetworkService will decide whether
+     * the opportunistic network is good enough for internet data.
+     */
+    public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT =
+            "opportunistic_network_entry_threshold_rsrp_int";
+
+    /**
+     * Controls RSSNR threshold at which AlternativeNetworkService will decide whether
+     * the opportunistic network is good enough for internet data.
+     */
+    public static final String KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT =
+            "opportunistic_network_entry_threshold_rssnr_int";
+
+    /**
+     * Controls RSRP threshold below which AlternativeNetworkService will decide whether
+     * the opportunistic network available is not good enough for internet data.
+     */
+    public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT =
+            "opportunistic_network_exit_threshold_rsrp_int";
+
+    /**
+     * Controls RSSNR threshold below which AlternativeNetworkService will decide whether
+     * the opportunistic network available is not good enough for internet data.
+     */
+    public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT =
+            "opportunistic_network_exit_threshold_rssnr_int";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -2699,6 +2727,14 @@
         sDefaults.putBoolean(KEY_CALL_WAITING_OVER_UT_WARNING_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_EMERGENCY_DIALER_SHORTCUT_BOOL, true);
+        /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_GOOD */
+        sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSRP_INT, -108);
+        /* Default value is minimum RSRP level needed for SIGNAL_STRENGTH_MODERATE */
+        sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT, -118);
+        /* Default value is minimum RSSNR level needed for SIGNAL_STRENGTH_GOOD */
+        sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_ENTRY_THRESHOLD_RSSNR_INT, 45);
+        /* Default value is minimum RSSNR level needed for SIGNAL_STRENGTH_MODERATE */
+        sDefaults.putInt(KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT, 10);
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 893dbe3..61c6b48 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -30,7 +30,7 @@
 public final class CellSignalStrengthLte extends CellSignalStrength implements Parcelable {
 
     private static final String LOG_TAG = "CellSignalStrengthLte";
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
 
     /**
      * Indicates the unknown or undetectable RSSI value in ASU.
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index c53b37d..26ec6de 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -17,6 +17,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.PersistableBundle;
 
@@ -34,6 +35,7 @@
  * Returned as the reason for a data connection failure as defined by modem and some local errors.
  * @hide
  */
+@SystemApi
 public final class DataFailCause {
     /** There is no failure */
     public static final int NONE = 0;
@@ -101,8 +103,8 @@
     public static final int PDN_CONN_DOES_NOT_EXIST = 0x36;
     /** Multiple connections to a same PDN is not allowed. */
     public static final int MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED = 0x37;
-    /** Packet Data Protocol (PDP) */
-    public static final int MAX_ACTIVE_PDP_CONTEXT_REACHED = 0x41;
+    /** Max number of Packet Data Protocol (PDP) context reached. */
+    public static final int ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED = 0x41;
     /** Unsupported APN in current public land mobile network (PLMN). */
     public static final int UNSUPPORTED_APN_IN_CURRENT_PLMN = 0x42;
     /** Invalid transaction id. */
@@ -165,22 +167,36 @@
 
     // Local errors generated by Vendor RIL
     // specified in ril.h
+    /** Data fail due to registration failure. */
     public static final int REGISTRATION_FAIL = -1;
+    /** Data fail due to GPRS registration failure. */
     public static final int GPRS_REGISTRATION_FAIL = -2;
+    /** Data call drop due to network/modem disconnect. */
     public static final int SIGNAL_LOST = -3;                        /* no retry */
+    /**
+     * Preferred technology has changed, must retry with parameters appropriate for new technology.
+     */
     public static final int PREF_RADIO_TECH_CHANGED = -4;
+    /** data call was disconnected because radio was resetting, powered off. */
     public static final int RADIO_POWER_OFF = -5;                    /* no retry */
+    /** Data call was disconnected by modem because tethered. */
     public static final int TETHERED_CALL_ACTIVE = -6;               /* no retry */
+    /** Data call fail due to unspecific errors. */
     public static final int ERROR_UNSPECIFIED = 0xFFFF;
 
     // Errors generated by the Framework
     // specified here
+    /** Unknown data failure cause. */
     public static final int UNKNOWN = 0x10000;
+    /** Data fail due to radio not unavailable. */
     public static final int RADIO_NOT_AVAILABLE = 0x10001;                   /* no retry */
+    /** @hide */
     public static final int UNACCEPTABLE_NETWORK_PARAMETER = 0x10002;        /* no retry */
+    /** @hide */
     public static final int CONNECTION_TO_DATACONNECTIONAC_BROKEN = 0x10003;
+    /** Data connection was lost. */
     public static final int LOST_CONNECTION = 0x10004;
-    /** Data was reset by framework. */
+    /** @hide */
     public static final int RESET_BY_FRAMEWORK = 0x10005;
 
     /** @hide */
@@ -216,7 +232,7 @@
             ESM_INFO_NOT_RECEIVED,
             PDN_CONN_DOES_NOT_EXIST,
             MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
-            MAX_ACTIVE_PDP_CONTEXT_REACHED,
+            ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
             UNSUPPORTED_APN_IN_CURRENT_PLMN,
             INVALID_TRANSACTION_ID,
             MESSAGE_INCORRECT_SEMANTIC,
@@ -308,8 +324,8 @@
         sFailCauseMap.put(PDN_CONN_DOES_NOT_EXIST, "PDN_CONN_DOES_NOT_EXIST");
         sFailCauseMap.put(MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED,
                 "MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED");
-        sFailCauseMap.put(MAX_ACTIVE_PDP_CONTEXT_REACHED,
-                "MAX_ACTIVE_PDP_CONTEXT_REACHED");
+        sFailCauseMap.put(ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED,
+                "ACTIVE_PDP_CONTEXT_MAX_NUMBER_REACHED");
         sFailCauseMap.put(UNSUPPORTED_APN_IN_CURRENT_PLMN,
                 "UNSUPPORTED_APN_IN_CURRENT_PLMN");
         sFailCauseMap.put(INVALID_TRANSACTION_ID, "INVALID_TRANSACTION_ID");
@@ -369,6 +385,9 @@
         sFailCauseMap.put(RESET_BY_FRAMEWORK, "RESET_BY_FRAMEWORK");
     }
 
+    private DataFailCause() {
+    }
+
     /**
      * Map of subId -> set of data call setup permanent failure for the carrier.
      */
@@ -382,6 +401,8 @@
      * @param cause data disconnect cause
      * @param subId subscription index
      * @return true if the fail cause code needs platform to trigger a modem restart.
+     *
+     * @hide
      */
     public static boolean isRadioRestartFailure(@NonNull Context context, @FailCause int cause,
                                                 int subId) {
@@ -410,6 +431,7 @@
         return false;
     }
 
+    /** @hide */
     public static boolean isPermanentFailure(@NonNull Context context, @FailCause int failCause,
                                              int subId) {
         synchronized (sPermanentFailureCache) {
@@ -469,6 +491,7 @@
         }
     }
 
+    /** @hide */
     public static boolean isEventLoggable(@FailCause int dataFailCause) {
         return (dataFailCause == OPERATOR_BARRED) || (dataFailCause == INSUFFICIENT_RESOURCES)
                 || (dataFailCause == UNKNOWN_PDP_ADDRESS_TYPE)
@@ -488,11 +511,13 @@
                 || (dataFailCause == UNACCEPTABLE_NETWORK_PARAMETER);
     }
 
+    /** @hide */
     public static String toString(@FailCause int dataFailCause) {
         int cause = getFailCause(dataFailCause);
         return (cause == UNKNOWN) ? "UNKNOWN(" + dataFailCause + ")" : sFailCauseMap.get(cause);
     }
 
+    /** @hide */
     public static int getFailCause(@FailCause int failCause) {
         if (sFailCauseMap.containsKey(failCause)) {
             return failCause;
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 9317aa7..e27b385 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -184,14 +184,17 @@
     public static final int LISTEN_PRECISE_CALL_STATE                       = 0x00000800;
 
     /**
-     * Listen for precise changes and fails on the data connection (cellular).
+     * Listen for {@link PreciseDataConnectionState} on the data connection (cellular).
+     *
      * {@more}
      * Requires Permission: {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE
      * READ_PRECISE_PHONE_STATE}
      *
      * @see #onPreciseDataConnectionStateChanged
+     *
      * @hide
      */
+    @SystemApi
     public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE            = 0x00001000;
 
     /**
@@ -564,10 +567,11 @@
 
     /**
      * Callback invoked when data connection state changes with precise information.
+     * @param dataConnectionState {@link PreciseDataConnectionState}
      *
      * @hide
      */
-    @UnsupportedAppUsage
+    @SystemApi
     public void onPreciseDataConnectionStateChanged(
             PreciseDataConnectionState dataConnectionState) {
         // default implementation empty
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 8373899..57a1826 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -16,10 +16,12 @@
 
 package android.telephony;
 
+import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.LinkProperties;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.data.ApnSetting;
 
 import java.util.Objects;
 
@@ -31,7 +33,7 @@
  * <ul>
  *   <li>Data connection state.
  *   <li>Network type of the connection.
- *   <li>APN type.
+ *   <li>APN types.
  *   <li>APN.
  *   <li>The properties of the network link.
  *   <li>Data connection fail cause.
@@ -39,14 +41,15 @@
  *
  * @hide
  */
-public class PreciseDataConnectionState implements Parcelable {
+@SystemApi
+public final class PreciseDataConnectionState implements Parcelable {
 
-    private int mState = TelephonyManager.DATA_UNKNOWN;
-    private int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
-    private String mAPNType = "";
+    private @TelephonyManager.DataState int mState = TelephonyManager.DATA_UNKNOWN;
+    private @TelephonyManager.NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    private @DataFailCause.FailCause int mFailCause = DataFailCause.NONE;
+    private @ApnSetting.ApnType int mAPNTypes = ApnSetting.TYPE_NONE;
     private String mAPN = "";
     private LinkProperties mLinkProperties = null;
-    private String mFailCause = "";
 
     /**
      * Constructor
@@ -54,11 +57,14 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public PreciseDataConnectionState(int state, int networkType, String apnType, String apn,
-                                      LinkProperties linkProperties, String failCause) {
+    public PreciseDataConnectionState(@TelephonyManager.DataState int state,
+                                      @TelephonyManager.NetworkType int networkType,
+                                      @ApnSetting.ApnType int apnTypes, String apn,
+                                      LinkProperties linkProperties,
+                                      @DataFailCause.FailCause int failCause) {
         mState = state;
         mNetworkType = networkType;
-        mAPNType = apnType;
+        mAPNTypes = apnTypes;
         mAPN = apn;
         mLinkProperties = linkProperties;
         mFailCause = failCause;
@@ -74,73 +80,52 @@
 
     /**
      * Construct a PreciseDataConnectionState object from the given parcel.
+     *
+     * @hide
      */
     private PreciseDataConnectionState(Parcel in) {
         mState = in.readInt();
         mNetworkType = in.readInt();
-        mAPNType = in.readString();
+        mAPNTypes = in.readInt();
         mAPN = in.readString();
         mLinkProperties = (LinkProperties)in.readParcelable(null);
-        mFailCause = in.readString();
+        mFailCause = in.readInt();
     }
 
     /**
-     * Get data connection state
-     *
-     * @see TelephonyManager#DATA_UNKNOWN
-     * @see TelephonyManager#DATA_DISCONNECTED
-     * @see TelephonyManager#DATA_CONNECTING
-     * @see TelephonyManager#DATA_CONNECTED
-     * @see TelephonyManager#DATA_SUSPENDED
+     * Returns the state of data connection that supported the apn types returned by
+     * {@link #getDataConnectionApnTypeBitMask()}
      */
-    @UnsupportedAppUsage
-    public int getDataConnectionState() {
+    public @TelephonyManager.DataState int getDataConnectionState() {
         return mState;
     }
 
     /**
-     * Get data connection network type
-     *
-     * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
-     * @see TelephonyManager#NETWORK_TYPE_GPRS
-     * @see TelephonyManager#NETWORK_TYPE_EDGE
-     * @see TelephonyManager#NETWORK_TYPE_UMTS
-     * @see TelephonyManager#NETWORK_TYPE_CDMA
-     * @see TelephonyManager#NETWORK_TYPE_EVDO_0
-     * @see TelephonyManager#NETWORK_TYPE_EVDO_A
-     * @see TelephonyManager#NETWORK_TYPE_1xRTT
-     * @see TelephonyManager#NETWORK_TYPE_HSDPA
-     * @see TelephonyManager#NETWORK_TYPE_HSUPA
-     * @see TelephonyManager#NETWORK_TYPE_HSPA
-     * @see TelephonyManager#NETWORK_TYPE_IDEN
-     * @see TelephonyManager#NETWORK_TYPE_EVDO_B
-     * @see TelephonyManager#NETWORK_TYPE_LTE
-     * @see TelephonyManager#NETWORK_TYPE_EHRPD
-     * @see TelephonyManager#NETWORK_TYPE_HSPAP
+     * Returns the network type associated with this data connection.
+     * @hide
      */
-    @UnsupportedAppUsage
-    public int getDataConnectionNetworkType() {
+    public @TelephonyManager.NetworkType int getDataConnectionNetworkType() {
         return mNetworkType;
     }
 
     /**
-     * Get data connection APN type
+     * Returns the data connection APN types supported by this connection and triggers
+     * {@link PreciseDataConnectionState} change.
      */
-    @UnsupportedAppUsage
-    public String getDataConnectionAPNType() {
-        return mAPNType;
+    public @ApnSetting.ApnType int getDataConnectionApnTypeBitMask() {
+        return mAPNTypes;
     }
 
     /**
-     * Get data connection APN.
+     * Returns APN {@link ApnSetting} of this data connection.
      */
-    @UnsupportedAppUsage
-    public String getDataConnectionAPN() {
+    public String getDataConnectionApn() {
         return mAPN;
     }
 
     /**
-     * Get the properties of the network link.
+     * Get the properties of the network link {@link LinkProperties}.
+     * @hide
      */
     @UnsupportedAppUsage
     public LinkProperties getDataConnectionLinkProperties() {
@@ -148,10 +133,9 @@
     }
 
     /**
-     * Get data connection fail cause, in case there was a failure.
+     * Returns data connection fail cause, in case there was a failure.
      */
-    @UnsupportedAppUsage
-    public String getDataConnectionFailCause() {
+    public @DataFailCause.FailCause int getDataConnectionFailCause() {
         return mFailCause;
     }
 
@@ -164,10 +148,10 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mState);
         out.writeInt(mNetworkType);
-        out.writeString(mAPNType);
+        out.writeInt(mAPNTypes);
         out.writeString(mAPN);
         out.writeParcelable(mLinkProperties, flags);
-        out.writeString(mFailCause);
+        out.writeInt(mFailCause);
     }
 
     public static final Parcelable.Creator<PreciseDataConnectionState> CREATOR
@@ -184,56 +168,23 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mState, mNetworkType, mAPNType, mAPN, mLinkProperties, mFailCause);
+        return Objects.hash(mState, mNetworkType, mAPNTypes, mAPN, mLinkProperties,
+                mFailCause);
     }
 
     @Override
     public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
+
+        if (!(obj instanceof PreciseDataConnectionState)) {
             return false;
         }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
+
         PreciseDataConnectionState other = (PreciseDataConnectionState) obj;
-        if (mAPN == null) {
-            if (other.mAPN != null) {
-                return false;
-            }
-        } else if (!mAPN.equals(other.mAPN)) {
-            return false;
-        }
-        if (mAPNType == null) {
-            if (other.mAPNType != null) {
-                return false;
-            }
-        } else if (!mAPNType.equals(other.mAPNType)) {
-            return false;
-        }
-        if (mFailCause == null) {
-            if (other.mFailCause != null) {
-                return false;
-            }
-        } else if (!mFailCause.equals(other.mFailCause)) {
-            return false;
-        }
-        if (mLinkProperties == null) {
-            if (other.mLinkProperties != null) {
-                return false;
-            }
-        } else if (!mLinkProperties.equals(other.mLinkProperties)) {
-            return false;
-        }
-        if (mNetworkType != other.mNetworkType) {
-            return false;
-        }
-        if (mState != other.mState) {
-            return false;
-        }
-        return true;
+        return Objects.equals(mAPN, other.mAPN) && mAPNTypes == other.mAPNTypes
+                && mFailCause == other.mFailCause
+                && Objects.equals(mLinkProperties, other.mLinkProperties)
+                && mNetworkType == other.mNetworkType
+                && mState == other.mState;
     }
 
     @Override
@@ -242,10 +193,10 @@
 
         sb.append("Data Connection state: " + mState);
         sb.append(", Network type: " + mNetworkType);
-        sb.append(", APN type: " + mAPNType);
+        sb.append(", APN types: " + ApnSetting.getApnTypesStringFromBitmask(mAPNTypes));
         sb.append(", APN: " + mAPN);
         sb.append(", Link properties: " + mLinkProperties);
-        sb.append(", Fail cause: " + mFailCause);
+        sb.append(", Fail cause: " + DataFailCause.toString(mFailCause));
 
         return sb.toString();
     }
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index ef185c5..2271069 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -369,7 +369,7 @@
     public int getLevel() {
         int level = getPrimary().getLevel();
         if (level < SIGNAL_STRENGTH_NONE_OR_UNKNOWN || level > SIGNAL_STRENGTH_GREAT) {
-            log("Invalid Level " + level + ", this=" + this);
+            loge("Invalid Level " + level + ", this=" + this);
             return SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
         }
         return getPrimary().getLevel();
@@ -657,9 +657,16 @@
     }
 
     /**
-     * log
+     * log warning
      */
     private static void log(String s) {
         Rlog.w(LOG_TAG, s);
     }
+
+    /**
+     * log error
+     */
+    private static void loge(String s) {
+        Rlog.e(LOG_TAG, s);
+    }
 }
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 0a58fa0..4a25818 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -174,6 +174,16 @@
     private boolean mIsGroupDisabled = false;
 
     /**
+     * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL
+     * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET.
+     * A profile on the eUICC can be defined as test, operational, provisioning, or unset.
+     * The profile class will be populated from the profile metadata if present. Otherwise,
+     * the profile class defaults to unset if there is no profile metadata or the subscription
+     * is not on an eUICC ({@link #isEmbedded} returns false).
+     */
+    private int mProfileClass;
+
+    /**
      * @hide
      */
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -182,7 +192,8 @@
             @Nullable UiccAccessRule[] accessRules, String cardString) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString,
-                false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID);
+                false, null, true, TelephonyManager.UNKNOWN_CARRIER_ID,
+                SubscriptionManager.PROFILE_CLASS_DEFAULT);
     }
 
     /**
@@ -192,10 +203,10 @@
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardString, boolean isOpportunistic,
-            @Nullable String groupUUID, boolean isMetered, int carrierId) {
+            @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) {
         this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
                 roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
-                isOpportunistic, groupUUID, isMetered, false, carrierId);
+                isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass);
     }
 
     /**
@@ -206,7 +217,7 @@
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
             boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered,
-            boolean isGroupDisabled, int carrierid) {
+            boolean isGroupDisabled, int carrierid, int profileClass) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
@@ -229,6 +240,7 @@
         this.mIsMetered = isMetered;
         this.mIsGroupDisabled = isGroupDisabled;
         this.mCarrierId = carrierid;
+        this.mProfileClass = profileClass;
     }
 
 
@@ -466,6 +478,15 @@
     }
 
     /**
+     * @return the profile class of this subscription.
+     * @hide
+     */
+    @SystemApi
+    public @SubscriptionManager.ProfileClass int getProfileClass() {
+        return this.mProfileClass;
+    }
+
+    /**
      * Checks whether the app with the given context is authorized to manage this subscription
      * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
      * returns true).
@@ -589,11 +610,12 @@
             boolean isMetered = source.readBoolean();
             boolean isGroupDisabled = source.readBoolean();
             int carrierid = source.readInt();
+            int profileClass = source.readInt();
 
             return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
                     nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
                     isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID,
-                    isMetered, isGroupDisabled, carrierid);
+                    isMetered, isGroupDisabled, carrierid, profileClass);
         }
 
         @Override
@@ -626,6 +648,7 @@
         dest.writeBoolean(mIsMetered);
         dest.writeBoolean(mIsGroupDisabled);
         dest.writeInt(mCarrierId);
+        dest.writeInt(mProfileClass);
     }
 
     @Override
@@ -661,7 +684,8 @@
                 + " accessRules " + Arrays.toString(mAccessRules)
                 + " cardString=" + cardStringToPrint + " cardId=" + mCardId
                 + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
-                + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled + "}";
+                + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled
+                + " profileClass=" + mProfileClass + "}";
     }
 
     @Override
@@ -669,7 +693,7 @@
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
                 mIsOpportunistic, mGroupUUID, mIsMetered, mIccId, mNumber, mMcc, mMnc,
                 mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mAccessRules,
-                mIsGroupDisabled, mCarrierId);
+                mIsGroupDisabled, mCarrierId, mProfileClass);
     }
 
     @Override
@@ -704,6 +728,7 @@
                 && Objects.equals(mCardId, toCompare.mCardId)
                 && TextUtils.equals(mDisplayName, toCompare.mDisplayName)
                 && TextUtils.equals(mCarrierName, toCompare.mCarrierName)
-                && Arrays.equals(mAccessRules, toCompare.mAccessRules);
+                && Arrays.equals(mAccessRules, toCompare.mAccessRules)
+                && mProfileClass == toCompare.mProfileClass;
     }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 3235507..fae5d30 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -22,6 +22,7 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.DurationMillisLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -62,6 +63,8 @@
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.internal.telephony.PhoneConstants;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -603,6 +606,72 @@
     public static final String IS_METERED = "is_metered";
 
     /**
+     * TelephonyProvider column name for the profile class of a subscription
+     * Only present if {@link #IS_EMBEDDED} is 1.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String PROFILE_CLASS = "profile_class";
+
+    /**
+     * Profile class of the subscription
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "PROFILE_CLASS_" }, value = {
+            PROFILE_CLASS_TESTING,
+            PROFILE_CLASS_PROVISIONING,
+            PROFILE_CLASS_OPERATIONAL,
+            PROFILE_CLASS_UNSET,
+            PROFILE_CLASS_DEFAULT
+    })
+    public @interface ProfileClass {}
+
+    /**
+     * A testing profile can be pre-loaded or downloaded onto
+     * the eUICC and provides connectivity to test equipment
+     * for the purpose of testing the device and the eUICC. It
+     * is not intended to store any operator credentials.
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_TESTING = 0;
+
+    /**
+     * A provisioning profile is pre-loaded onto the eUICC and
+     * provides connectivity to a mobile network solely for the
+     * purpose of provisioning profiles.
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_PROVISIONING = 1;
+
+    /**
+     * An operational profile can be pre-loaded or downloaded
+     * onto the eUICC and provides services provided by the
+     * operator.
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_OPERATIONAL = 2;
+
+    /**
+     * The profile class is unset. This occurs when profile class
+     * info is not available. The subscription either has no profile
+     * metadata or the profile metadata did not encode profile class.
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_UNSET = -1;
+
+    /**
+     * Default profile class
+     * @hide
+     */
+    @SystemApi
+    public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+
+    /**
      * Broadcast Action: The user has changed one of the default subs related to
      * data, phone calls, or sms</p>
      *
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 739c80f..1d8ee79 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4558,9 +4558,18 @@
       }
     }
 
-    /** Data connection state: Unknown.  Used before we know the state.
-     * @hide
-     */
+    /** @hide */
+    @IntDef(prefix = {"DATA_"}, value = {
+            DATA_UNKNOWN,
+            DATA_DISCONNECTED,
+            DATA_CONNECTING,
+            DATA_CONNECTED,
+            DATA_SUSPENDED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DataState{}
+
+    /** Data connection state: Unknown.  Used before we know the state. */
     public static final int DATA_UNKNOWN        = -1;
     /** Data connection state: Disconnected. IP traffic not available. */
     public static final int DATA_DISCONNECTED   = 0;
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 02a6f31..5632c63 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -68,7 +68,7 @@
             int backgroundCallState);
     void notifyDisconnectCause(int disconnectCause, int preciseDisconnectCause);
     void notifyPreciseDataConnectionFailed(String apnType, String apn,
-            String failCause);
+            int failCause);
     void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
     void notifySrvccStateChanged(in int subId, in int lteState);
     void notifySimActivationStateChangedForPhoneId(in int phoneId, in int subId,
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 37158e5..e1d6e01 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -25,7 +25,8 @@
         "android.test.mock",
     ],
 
+    srcs_lib: "framework",
+    srcs_lib_whitelist_dirs: ["core/java"],
     srcs_lib_whitelist_pkgs: ["android"],
-    metalava_enabled: false,
     compile_dex: true,
 }
diff --git a/tests/net/Android.mk b/tests/net/Android.mk
index 9d1edbf..f6f35fd 100644
--- a/tests/net/Android.mk
+++ b/tests/net/Android.mk
@@ -18,6 +18,7 @@
     mockito-target-minus-junit4 \
     platform-test-annotations \
     services.core \
+    services.ipmemorystore \
     services.net
 
 LOCAL_JAVA_LIBRARIES := \
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
new file mode 100644
index 0000000..eae9710
--- /dev/null
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.net;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpMemoryStoreTest {
+    @Mock
+    Context mMockContext;
+    @Mock
+    IIpMemoryStore mMockService;
+    IpMemoryStore mStore;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mStore = new IpMemoryStore(mMockContext, mMockService);
+    }
+
+    @Test
+    public void testNetworkAttributes() {
+        // TODO : implement this
+    }
+
+    @Test
+    public void testPrivateData() {
+        // TODO : implement this
+    }
+
+    @Test
+    public void testFindL2Key() {
+        // TODO : implement this
+    }
+
+    @Test
+    public void testIsSameNetwork() {
+        // TODO : implement this
+    }
+
+}
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index 9695e9a..f82b380 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -53,6 +53,8 @@
     private static InetAddress DNS1 = NetworkUtils.numericToInetAddress("75.208.7.1");
     private static InetAddress DNS2 = NetworkUtils.numericToInetAddress("69.78.7.1");
     private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
+    private static InetAddress PCSCFV6 =  NetworkUtils.numericToInetAddress(
+            "2001:0db8:85a3:0000:0000:8a2e:0370:1");
     private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
     private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
     private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
@@ -86,6 +88,9 @@
         assertTrue(source.isIdenticalValidatedPrivateDnses(target));
         assertTrue(target.isIdenticalValidatedPrivateDnses(source));
 
+        assertTrue(source.isIdenticalPcscfs(target));
+        assertTrue(target.isIdenticalPcscfs(source));
+
         assertTrue(source.isIdenticalRoutes(target));
         assertTrue(target.isIdenticalRoutes(source));
 
@@ -128,6 +133,8 @@
         // set 2 dnses
         source.addDnsServer(DNS1);
         source.addDnsServer(DNS2);
+        // set 1 pcscf
+        source.addPcscfServer(PCSCFV6);
         // set 2 gateways
         source.addRoute(new RouteInfo(GATEWAY1));
         source.addRoute(new RouteInfo(GATEWAY2));
@@ -141,6 +148,7 @@
         target.addLinkAddress(LINKADDRV6);
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
+        target.addPcscfServer(PCSCFV6);
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
@@ -154,6 +162,7 @@
         target.addLinkAddress(LINKADDRV6);
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
+        target.addPcscfServer(PCSCFV6);
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
@@ -167,6 +176,7 @@
         target.addLinkAddress(LINKADDRV6);
         target.addDnsServer(DNS1);
         target.addDnsServer(DNS2);
+        target.addPcscfServer(PCSCFV6);
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
@@ -179,6 +189,21 @@
         // change dnses
         target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
         target.addDnsServer(DNS2);
+        target.addPcscfServer(PCSCFV6);
+        target.addRoute(new RouteInfo(GATEWAY1));
+        target.addRoute(new RouteInfo(GATEWAY2));
+        target.setMtu(MTU);
+        assertFalse(source.equals(target));
+
+        target.clear();
+        target.setInterfaceName(NAME);
+        target.addLinkAddress(LINKADDRV4);
+        target.addLinkAddress(LINKADDRV6);
+        target.addDnsServer(NetworkUtils.numericToInetAddress("75.208.7.2"));
+        target.addDnsServer(DNS2);
+        // change pcscf
+        target.addPcscfServer(NetworkUtils.numericToInetAddress(
+            "2001::1"));
         target.addRoute(new RouteInfo(GATEWAY1));
         target.addRoute(new RouteInfo(GATEWAY2));
         target.setMtu(MTU);
diff --git a/tests/net/java/android/net/ip/IpServerTest.java b/tests/net/java/android/net/ip/IpServerTest.java
index 0178228..c3162af 100644
--- a/tests/net/java/android/net/ip/IpServerTest.java
+++ b/tests/net/java/android/net/ip/IpServerTest.java
@@ -22,20 +22,26 @@
 import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.NetworkUtils.intToInet4AddressHTH;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.ip.IpServer.STATE_AVAILABLE;
 import static android.net.ip.IpServer.STATE_TETHERED;
 import static android.net.ip.IpServer.STATE_UNAVAILABLE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -48,8 +54,9 @@
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.RouteInfo;
-import android.net.dhcp.DhcpServer;
-import android.net.dhcp.DhcpServingParams;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServer;
+import android.net.dhcp.IDhcpServerCallbacks;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
 import android.net.util.SharedLog;
@@ -82,16 +89,18 @@
     private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
             IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
 
+    private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
+
     @Mock private INetworkManagementService mNMService;
     @Mock private INetworkStatsService mStatsService;
     @Mock private IpServer.Callback mCallback;
     @Mock private InterfaceConfiguration mInterfaceConfiguration;
     @Mock private SharedLog mSharedLog;
-    @Mock private DhcpServer mDhcpServer;
+    @Mock private IDhcpServer mDhcpServer;
     @Mock private RouterAdvertisementDaemon mRaDaemon;
     @Mock private IpServer.Dependencies mDependencies;
 
-    @Captor private ArgumentCaptor<DhcpServingParams> mDhcpParamsCaptor;
+    @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
 
     private final TestLooper mLooper = new TestLooper();
     private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
@@ -112,8 +121,18 @@
         mLooper.dispatchAll();
         reset(mNMService, mStatsService, mCallback);
         when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
-        when(mDependencies.makeDhcpServer(
-                any(), any(), mDhcpParamsCaptor.capture(), any())).thenReturn(mDhcpServer);
+
+        doAnswer(inv -> {
+            final IDhcpServerCallbacks cb = inv.getArgument(2);
+            new Thread(() -> {
+                try {
+                    cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
+                } catch (RemoteException e) {
+                    fail(e.getMessage());
+                }
+            }).run();
+            return null;
+        }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
         when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
         when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
 
@@ -399,21 +418,20 @@
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
 
-        verify(mDependencies, never()).makeDhcpServer(any(), any(), any(), any());
+        verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
     }
 
-    private void assertDhcpStarted(IpPrefix expectedPrefix) {
-        verify(mDependencies, times(1)).makeDhcpServer(
-                eq(mLooper.getLooper()), eq(IFACE_NAME), any(), eq(mSharedLog));
-        verify(mDhcpServer, times(1)).start();
-        final DhcpServingParams params = mDhcpParamsCaptor.getValue();
+    private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
+        verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
+        verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any());
+        final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
         // Last address byte is random
-        assertTrue(expectedPrefix.contains(params.serverAddr.getAddress()));
-        assertEquals(expectedPrefix.getPrefixLength(), params.serverAddr.getPrefixLength());
-        assertEquals(1, params.defaultRouters.size());
-        assertEquals(params.serverAddr.getAddress(), params.defaultRouters.iterator().next());
-        assertEquals(1, params.dnsServers.size());
-        assertEquals(params.serverAddr.getAddress(), params.dnsServers.iterator().next());
+        assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
+        assertEquals(expectedPrefix.getPrefixLength(), params.serverAddrPrefixLength);
+        assertEquals(1, params.defaultRouters.length);
+        assertEquals(params.serverAddr, params.defaultRouters[0]);
+        assertEquals(1, params.dnsServers.length);
+        assertEquals(params.serverAddr, params.dnsServers[0]);
         assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs);
     }
 
@@ -458,7 +476,7 @@
             addr4 = addr;
             break;
         }
-        assertTrue("missing IPv4 address", addr4 != null);
+        assertNotNull("missing IPv4 address", addr4);
 
         // Assert the presence of the associated directly connected route.
         final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName());
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
new file mode 100644
index 0000000..a9f9758
--- /dev/null
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -0,0 +1,113 @@
+/*
+ * 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.net.ipmemorystore;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ParcelableTests {
+    @Test
+    public void testNetworkAttributesParceling() throws Exception {
+        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+        NetworkAttributes in = builder.build();
+        assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+        builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
+        // groupHint stays null this time around
+        builder.setDnsAddresses(Collections.emptyList());
+        builder.setMtu(18);
+        in = builder.build();
+        assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+        builder.setAssignedV4Address((Inet4Address) Inet4Address.getByName("6.7.8.9"));
+        builder.setGroupHint("groupHint");
+        builder.setDnsAddresses(Arrays.asList(
+                InetAddress.getByName("ACA1:652B:0911:DE8F:1200:115E:913B:AA2A"),
+                InetAddress.getByName("6.7.8.9")));
+        builder.setMtu(1_000_000);
+        in = builder.build();
+        assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+        builder.setMtu(null);
+        in = builder.build();
+        assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+    }
+
+    @Test
+    public void testPrivateDataParceling() throws Exception {
+        final Blob in = new Blob();
+        in.data = new byte[] {89, 111, 108, 111};
+        final Blob out = parcelingRoundTrip(in);
+        // Object.equals on byte[] tests the references
+        assertEquals(in.data.length, out.data.length);
+        assertTrue(Arrays.equals(in.data, out.data));
+    }
+
+    @Test
+    public void testSameL3NetworkResponseParceling() throws Exception {
+        final SameL3NetworkResponseParcelable parcelable = new SameL3NetworkResponseParcelable();
+        parcelable.l2Key1 = "key 1";
+        parcelable.l2Key2 = "key 2";
+        parcelable.confidence = 0.43f;
+
+        final SameL3NetworkResponse in = new SameL3NetworkResponse(parcelable);
+        assertEquals("key 1", in.l2Key1);
+        assertEquals("key 2", in.l2Key2);
+        assertEquals(0.43f, in.confidence, 0.01f /* delta */);
+
+        final SameL3NetworkResponse out =
+                new SameL3NetworkResponse(parcelingRoundTrip(in.toParcelable()));
+
+        assertEquals(in, out);
+        assertEquals(in.l2Key1, out.l2Key1);
+        assertEquals(in.l2Key2, out.l2Key2);
+        assertEquals(in.confidence, out.confidence, 0.01f /* delta */);
+    }
+
+    private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception {
+        final Parcel p = Parcel.obtain();
+        in.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        final byte[] marshalledData = p.marshall();
+        p.recycle();
+
+        final Parcel q = Parcel.obtain();
+        q.unmarshall(marshalledData, 0, marshalledData.length);
+        q.setDataPosition(0);
+
+        final Parcelable.Creator<T> creator = (Parcelable.Creator<T>)
+                in.getClass().getField("CREATOR").get(null); // static object, so null receiver
+        final T unmarshalled = (T) creator.createFromParcel(q);
+        q.recycle();
+        return unmarshalled;
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 71529fd..bf39644 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -26,6 +26,8 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
 import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
+import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -69,17 +71,19 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.eq;
 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.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -89,7 +93,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Resources;
-import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.ConnectivityManager.PacketKeepalive;
@@ -97,6 +100,8 @@
 import android.net.ConnectivityManager.TooManyRequestsException;
 import android.net.ConnectivityThread;
 import android.net.INetd;
+import android.net.INetworkMonitor;
+import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
@@ -114,12 +119,14 @@
 import android.net.NetworkMisc;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.NetworkStack;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.StringNetworkSpecifier;
 import android.net.UidRange;
-import android.net.captiveportal.CaptivePortalProbeResult;
 import android.net.metrics.IpConnectivityLog;
+import android.net.shared.NetworkMonitorUtils;
+import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
 import android.os.ConditionVariable;
 import android.os.Handler;
@@ -148,12 +155,9 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.connectivity.ConnectivityConstants;
 import com.android.server.connectivity.DefaultNetworkMetrics;
-import com.android.server.connectivity.DnsManager;
 import com.android.server.connectivity.IpConnectivityMetrics;
 import com.android.server.connectivity.MockableSystemProperties;
 import com.android.server.connectivity.Nat464Xlat;
-import com.android.server.connectivity.NetworkAgentInfo;
-import com.android.server.connectivity.NetworkMonitor;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.NetworkPinner;
@@ -168,6 +172,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
 
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -230,6 +235,7 @@
     @Mock INetworkStatsService mStatsService;
     @Mock INetworkPolicyManager mNpm;
     @Mock INetd mMockNetd;
+    @Mock NetworkStack mNetworkStack;
 
     private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class);
 
@@ -299,6 +305,7 @@
         public Object getSystemService(String name) {
             if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
             if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
+            if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
             return super.getSystemService(name);
         }
 
@@ -386,7 +393,7 @@
     }
 
     private class MockNetworkAgent {
-        private final WrappedNetworkMonitor mWrappedNetworkMonitor;
+        private final INetworkMonitor mNetworkMonitor;
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
         private final HandlerThread mHandlerThread;
@@ -402,6 +409,26 @@
         // mNetworkStatusReceived.
         private String mRedirectUrl;
 
+        private INetworkMonitorCallbacks mNmCallbacks;
+        private int mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+        private String mNmValidationRedirectUrl = null;
+        private boolean mNmProvNotificationRequested = false;
+
+        void setNetworkValid() {
+            mNmValidationResult = NETWORK_TEST_RESULT_VALID;
+            mNmValidationRedirectUrl = null;
+        }
+
+        void setNetworkInvalid() {
+            mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+            mNmValidationRedirectUrl = null;
+        }
+
+        void setNetworkPortal(String redirectUrl) {
+            setNetworkInvalid();
+            mNmValidationRedirectUrl = redirectUrl;
+        }
+
         MockNetworkAgent(int transport) {
             this(transport, new LinkProperties());
         }
@@ -434,6 +461,29 @@
             }
             mHandlerThread = new HandlerThread("Mock-" + typeName);
             mHandlerThread.start();
+
+            mNetworkMonitor = mock(INetworkMonitor.class);
+            final Answer validateAnswer = inv -> {
+                new Thread(this::onValidationRequested).start();
+                return null;
+            };
+
+            try {
+                doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected();
+                doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt());
+            } catch (RemoteException e) {
+                fail(e.getMessage());
+            }
+
+            final ArgumentCaptor<Network> nmNetworkCaptor =
+                    ArgumentCaptor.forClass(Network.class);
+            final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor =
+                    ArgumentCaptor.forClass(INetworkMonitorCallbacks.class);
+            doNothing().when(mNetworkStack).makeNetworkMonitor(
+                    nmNetworkCaptor.capture(),
+                    any() /* name */,
+                    nmCbCaptor.capture());
+
             mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext,
                     "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities,
                     linkProperties, mScore, new NetworkMisc()) {
@@ -465,10 +515,40 @@
                     mPreventReconnectReceived.open();
                 }
             };
+
+            assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId);
+            mNmCallbacks = nmCbCaptor.getValue();
+
+            try {
+                mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
+            } catch (RemoteException e) {
+                fail(e.getMessage());
+            }
+
             // Waits for the NetworkAgent to be registered, which includes the creation of the
             // NetworkMonitor.
             waitForIdle();
-            mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
+        }
+
+        private void onValidationRequested() {
+            try {
+                if (mNmProvNotificationRequested
+                        && mNmValidationResult == NETWORK_TEST_RESULT_VALID) {
+                    mNmCallbacks.hideProvisioningNotification();
+                    mNmProvNotificationRequested = false;
+                }
+
+                mNmCallbacks.notifyNetworkTested(
+                        mNmValidationResult, mNmValidationRedirectUrl);
+
+                if (mNmValidationRedirectUrl != null) {
+                    mNmCallbacks.showProvisioningNotification(
+                            "test_provisioning_notif_action");
+                    mNmProvNotificationRequested = true;
+                }
+            } catch (RemoteException e) {
+                fail(e.getMessage());
+            }
         }
 
         public void adjustScore(int change) {
@@ -539,7 +619,7 @@
             NetworkCallback callback = null;
             final ConditionVariable validatedCv = new ConditionVariable();
             if (validated) {
-                mWrappedNetworkMonitor.gen204ProbeResult = 204;
+                setNetworkValid();
                 NetworkRequest request = new NetworkRequest.Builder()
                         .addTransportType(mNetworkCapabilities.getTransportTypes()[0])
                         .clearCapabilities()
@@ -564,15 +644,14 @@
             if (validated) {
                 // Wait for network to validate.
                 waitFor(validatedCv);
-                mWrappedNetworkMonitor.gen204ProbeResult = 500;
+                setNetworkInvalid();
             }
 
             if (callback != null) mCm.unregisterNetworkCallback(callback);
         }
 
         public void connectWithCaptivePortal(String redirectUrl) {
-            mWrappedNetworkMonitor.gen204ProbeResult = 200;
-            mWrappedNetworkMonitor.gen204ProbeRedirectUrl = redirectUrl;
+            setNetworkPortal(redirectUrl);
             connect(false);
         }
 
@@ -603,10 +682,6 @@
             return mDisconnected;
         }
 
-        public WrappedNetworkMonitor getWrappedNetworkMonitor() {
-            return mWrappedNetworkMonitor;
-        }
-
         public void sendLinkProperties(LinkProperties lp) {
             mNetworkAgent.sendLinkProperties(lp);
         }
@@ -880,28 +955,6 @@
         }
     }
 
-    // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
-    private class WrappedNetworkMonitor extends NetworkMonitor {
-        public final Handler connectivityHandler;
-        // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
-        public int gen204ProbeResult = 500;
-        public String gen204ProbeRedirectUrl = null;
-
-        public WrappedNetworkMonitor(Context context, Handler handler,
-                NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest,
-                IpConnectivityLog log) {
-            super(context, handler, networkAgentInfo, defaultRequest, log,
-                    NetworkMonitor.Dependencies.DEFAULT);
-            connectivityHandler = handler;
-        }
-
-        @Override
-        protected CaptivePortalProbeResult isCaptivePortal() {
-            if (!mIsCaptivePortalCheckEnabled) { return new CaptivePortalProbeResult(204); }
-            return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl, null);
-        }
-    }
-
     private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
         public volatile boolean configRestrictsAvoidBadWifi;
         public volatile int configMeteredMultipathPreference;
@@ -923,7 +976,6 @@
 
     private class WrappedConnectivityService extends ConnectivityService {
         public WrappedMultinetworkPolicyTracker wrappedMultinetworkPolicyTracker;
-        private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
         private MockableSystemProperties mSystemProperties;
 
         public WrappedConnectivityService(Context context, INetworkManagementService netManager,
@@ -971,15 +1023,6 @@
             }
         }
 
-        @Override
-        public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
-                NetworkAgentInfo nai, NetworkRequest defaultRequest) {
-            final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(
-                    context, handler, nai, defaultRequest, mock(IpConnectivityLog.class));
-            mLastCreatedNetworkMonitor = monitor;
-            return monitor;
-        }
-
         public Nat464Xlat getNat464Xlat(MockNetworkAgent mna) {
             return getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
         }
@@ -1017,10 +1060,6 @@
         protected void registerNetdEventCallback() {
         }
 
-        public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
-            return mLastCreatedNetworkMonitor;
-        }
-
         public void mockVpn(int uid) {
             synchronized (mVpns) {
                 int userId = UserHandle.getUserId(uid);
@@ -2439,7 +2478,7 @@
 
         // Make captive portal disappear then revalidate.
         // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+        mWiFiNetworkAgent.setNetworkValid();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
@@ -2448,13 +2487,13 @@
 
         // Break network connectivity.
         // Expect NET_CAPABILITY_VALIDATED onLost callback.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
+        mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
     }
 
     @Test
-    public void testCaptivePortalApp() {
+    public void testCaptivePortalApp() throws RemoteException {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
                 .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
@@ -2477,21 +2516,19 @@
         mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
 
         // Turn into a captive portal.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302;
+        mWiFiNetworkAgent.setNetworkPortal("http://example.com");
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
         validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
-        // Check that startCaptivePortalApp sends the expected intent.
+        // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
         mCm.startCaptivePortalApp(wifiNetwork);
-        Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
-        assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction());
-        assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK));
+        verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
+                .launchCaptivePortalApp();
 
-        // Have the app report that the captive portal is dismissed, and check that we revalidate.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
-        CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
-        c.reportCaptivePortalDismissed();
+        // Report that the captive portal is dismissed, and check that callbacks are fired
+        mWiFiNetworkAgent.setNetworkValid();
+        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
         validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
         captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
 
@@ -2524,20 +2561,6 @@
         waitFor(avoidCv);
 
         assertNoCallbacks(captivePortalCallback, validatedCallback);
-
-        // Now test ignore mode.
-        setCaptivePortalMode(Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE);
-
-        // Bring up a network with a captive portal.
-        // Since we're ignoring captive portals, the network will validate.
-        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
-        String secondRedirectUrl = "http://example.com/secondPath";
-        mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
-
-        // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
-        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
-        // But there should be no CaptivePortal callback.
-        captivePortalCallback.assertNoCallback();
     }
 
     private NetworkRequest.Builder newWifiRequestBuilder() {
@@ -3169,7 +3192,7 @@
         Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+        mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -3213,7 +3236,7 @@
         wifiNetwork = mWiFiNetworkAgent.getNetwork();
 
         // Fail validation on wifi and expect the dialog to appear.
-        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+        mWiFiNetworkAgent.setNetworkInvalid();
         mCm.reportNetworkConnectivity(wifiNetwork, false);
         defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
         validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
@@ -4002,11 +4025,9 @@
         final String TLS_SERVER6 = "2001:db8:53::53";
         final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) };
         final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 };
-        final Handler h = mCellNetworkAgent.getWrappedNetworkMonitor().connectivityHandler;
-        h.sendMessage(h.obtainMessage(
-                NetworkMonitor.EVENT_PRIVATE_DNS_CONFIG_RESOLVED, 0,
-                mCellNetworkAgent.getNetwork().netId,
-                new DnsManager.PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS)));
+        mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved(
+                new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel());
+
         waitForIdle();
         verify(mNetworkManagementService, atLeastOnce()).setDnsConfigurationForNetwork(
                 anyInt(), mStringArrayCaptor.capture(), any(), any(),
@@ -4294,6 +4315,12 @@
         ranges.add(new UidRange(uid, uid));
         mMockVpn.setNetworkAgent(vpnNetworkAgent);
         mMockVpn.setUids(ranges);
+        // VPN networks do not satisfy the default request and are automatically validated
+        // by NetworkMonitor
+        assertFalse(NetworkMonitorUtils.isValidationRequired(
+                mCm.getDefaultRequest().networkCapabilities, vpnNetworkAgent.mNetworkCapabilities));
+        vpnNetworkAgent.setNetworkValid();
+
         vpnNetworkAgent.connect(false);
         mMockVpn.connect();
 
diff --git a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
index 01b468a..38322e9 100644
--- a/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/DnsManagerTest.java
@@ -17,7 +17,6 @@
 package com.android.server.connectivity;
 
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
-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.PRIVATE_DNS_DEFAULT_MODE;
 import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
@@ -29,13 +28,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.when;
 
-import android.content.ContentResolver;
 import android.content.Context;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.RouteInfo;
+import android.net.shared.PrivateDnsConfig;
 import android.os.INetworkManagementService;
 import android.provider.Settings;
 import android.support.test.filters.SmallTest;
@@ -43,18 +42,16 @@
 import android.test.mock.MockContentResolver;
 
 import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.server.connectivity.DnsManager.PrivateDnsConfig;
-import com.android.server.connectivity.MockableSystemProperties;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.net.InetAddress;
 import java.util.Arrays;
 
-import org.junit.runner.RunWith;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
 /**
  * Tests for {@link DnsManager}.
  *
@@ -133,7 +130,7 @@
                 PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
         Settings.Global.putString(mContentResolver, PRIVATE_DNS_SPECIFIER, "strictmode.com");
         mDnsManager.updatePrivateDns(new Network(TEST_NETID),
-                new DnsManager.PrivateDnsConfig("strictmode.com", new InetAddress[] {
+                new PrivateDnsConfig("strictmode.com", new InetAddress[] {
                     InetAddress.parseNumericAddress("6.6.6.6"),
                     InetAddress.parseNumericAddress("2001:db8:66:66::1")
                     }));
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 354cf2f..4c52d81 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -23,10 +23,10 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.reset;
 
 import android.app.PendingIntent;
 import android.content.Context;
@@ -36,18 +36,18 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
-import android.support.test.runner.AndroidJUnit4;
+import android.net.NetworkStack;
 import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
 import android.text.format.DateUtils;
 
 import com.android.internal.R;
 import com.android.server.ConnectivityService;
-import com.android.server.connectivity.NetworkNotificationManager;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 
-import org.junit.runner.RunWith;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -70,13 +70,16 @@
     @Mock NetworkMisc mMisc;
     @Mock NetworkNotificationManager mNotifier;
     @Mock Resources mResources;
+    @Mock NetworkStack mNetworkStack;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         when(mCtx.getResources()).thenReturn(mResources);
         when(mCtx.getPackageName()).thenReturn("com.android.server.connectivity");
-        when(mConnService.createNetworkMonitor(any(), any(), any(), any())).thenReturn(null);
+        when(mCtx.getSystemServiceName(NetworkStack.class))
+                .thenReturn(Context.NETWORK_STACK_SERVICE);
+        when(mCtx.getSystemService(Context.NETWORK_STACK_SERVICE)).thenReturn(mNetworkStack);
 
         mMonitor = new TestableLingerMonitor(mCtx, mNotifier, HIGH_DAILY_LIMIT, HIGH_RATE_LIMIT);
     }
@@ -349,7 +352,7 @@
         caps.addCapability(0);
         caps.addTransportType(transport);
         NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
-                caps, 50, mCtx, null, mMisc, null, mConnService);
+                caps, 50, mCtx, null, mMisc, mConnService);
         nai.everValidated = true;
         return nai;
     }
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index e6b43d2..1ea83c2 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -27,6 +27,7 @@
 import static android.net.ConnectivityManager.TETHERING_WIFI;
 import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
 import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -37,6 +38,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.Matchers.anyInt;
@@ -47,6 +49,8 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -74,8 +78,9 @@
 import android.net.NetworkState;
 import android.net.NetworkUtils;
 import android.net.RouteInfo;
-import android.net.dhcp.DhcpServer;
-import android.net.dhcp.DhcpServingParams;
+import android.net.dhcp.DhcpServerCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServer;
 import android.net.ip.IpServer;
 import android.net.ip.RouterAdvertisementDaemon;
 import android.net.util.InterfaceParams;
@@ -86,7 +91,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.INetworkManagementService;
-import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -129,6 +133,8 @@
     private static final String TEST_USB_IFNAME = "test_rndis0";
     private static final String TEST_WLAN_IFNAME = "test_wlan0";
 
+    private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
+
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
     @Mock private INetworkManagementService mNMService;
@@ -143,9 +149,11 @@
     @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
     @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator;
     @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
-    @Mock private DhcpServer mDhcpServer;
+    @Mock private IDhcpServer mDhcpServer;
     @Mock private INetd mNetd;
 
+    private final MockIpServerDependencies mIpServerDependencies =
+            spy(new MockIpServerDependencies());
     private final MockTetheringDependencies mTetheringDependencies =
             new MockTetheringDependencies();
 
@@ -185,6 +193,47 @@
         }
     }
 
+    public class MockIpServerDependencies extends IpServer.Dependencies {
+        MockIpServerDependencies() {
+            super(null);
+        }
+
+        @Override
+        public RouterAdvertisementDaemon getRouterAdvertisementDaemon(
+                InterfaceParams ifParams) {
+            return mRouterAdvertisementDaemon;
+        }
+
+        @Override
+        public InterfaceParams getInterfaceParams(String ifName) {
+            assertTrue("Non-mocked interface " + ifName,
+                    ifName.equals(TEST_USB_IFNAME)
+                            || ifName.equals(TEST_WLAN_IFNAME)
+                            || ifName.equals(TEST_MOBILE_IFNAME));
+            final String[] ifaces = new String[] {
+                    TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME };
+            return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
+                    MacAddress.ALL_ZEROS_ADDRESS);
+        }
+
+        @Override
+        public INetd getNetdService() {
+            return mNetd;
+        }
+
+        @Override
+        public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
+                DhcpServerCallbacks cb) {
+            new Thread(() -> {
+                try {
+                    cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
+                } catch (RemoteException e) {
+                    fail(e.getMessage());
+                }
+            }).run();
+        }
+    }
+
     public class MockTetheringDependencies extends TetheringDependencies {
         StateMachine upstreamNetworkMonitorMasterSM;
         ArrayList<IpServer> ipv6CoordinatorNotifyList;
@@ -216,35 +265,8 @@
         }
 
         @Override
-        public IpServer.Dependencies getIpServerDependencies() {
-            return new IpServer.Dependencies() {
-                @Override
-                public RouterAdvertisementDaemon getRouterAdvertisementDaemon(
-                        InterfaceParams ifParams) {
-                    return mRouterAdvertisementDaemon;
-                }
-
-                @Override
-                public InterfaceParams getInterfaceParams(String ifName) {
-                    final String[] ifaces = new String[] {
-                            TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME };
-                    final int index = ArrayUtils.indexOf(ifaces, ifName);
-                    assertTrue("Non-mocked interface: " + ifName, index >= 0);
-                    return new InterfaceParams(ifName, index + IFINDEX_OFFSET,
-                            MacAddress.ALL_ZEROS_ADDRESS);
-                }
-
-                @Override
-                public INetd getNetdService() {
-                    return mNetd;
-                }
-
-                @Override
-                public DhcpServer makeDhcpServer(Looper looper, String ifName,
-                        DhcpServingParams params, SharedLog log) {
-                    return mDhcpServer;
-                }
-            };
+        public IpServer.Dependencies getIpServerDependencies(Context context) {
+            return mIpServerDependencies;
         }
 
         @Override
@@ -546,7 +568,7 @@
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
-        verify(mDhcpServer, times(1)).start();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
     }
 
     @Test
@@ -557,7 +579,7 @@
         runUsbTethering(upstreamState);
         sendIPv6TetherUpdates(upstreamState);
 
-        verify(mDhcpServer, never()).start();
+        verify(mIpServerDependencies, never()).makeDhcpServer(any(), any(), any());
     }
 
     @Test
@@ -581,7 +603,7 @@
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mRouterAdvertisementDaemon, times(1)).start();
-        verify(mDhcpServer, times(1)).start();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
 
         sendIPv6TetherUpdates(upstreamState);
         verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -595,7 +617,7 @@
 
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mDhcpServer, times(1)).start();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
         verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME,
                 TEST_XLAT_MOBILE_IFNAME);
@@ -612,7 +634,7 @@
         runUsbTethering(upstreamState);
 
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mDhcpServer, times(1)).start();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
         verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
 
         // Then 464xlat comes up
@@ -636,7 +658,7 @@
         verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
         // DHCP not restarted on downstream (still times(1))
-        verify(mDhcpServer, times(1)).start();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
new file mode 100644
index 0000000..859a54d
--- /dev/null
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link IpMemoryStoreServiceTest}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IpMemoryStoreServiceTest {
+    @Mock
+    Context mMockContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testNetworkAttributes() {
+        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+        // TODO : implement this
+    }
+
+    @Test
+    public void testPrivateData() {
+        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+        // TODO : implement this
+    }
+
+    @Test
+    public void testFindL2Key() {
+        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+        // TODO : implement this
+    }
+
+    @Test
+    public void testIsSameNetwork() {
+        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
+        // TODO : implement this
+    }
+}
diff --git a/tools/bit/command.h b/tools/bit/command.h
index fb44900..dd7103e 100644
--- a/tools/bit/command.h
+++ b/tools/bit/command.h
@@ -25,7 +25,7 @@
 
 struct Command
 {
-    Command(const string& prog);
+    explicit Command(const string& prog);
     ~Command();
 
     void AddArg(const string& arg);
diff --git a/tools/stats_log_api_gen/main.cpp b/tools/stats_log_api_gen/main.cpp
index 2478b91..27e77fe 100644
--- a/tools/stats_log_api_gen/main.cpp
+++ b/tools/stats_log_api_gen/main.cpp
@@ -69,7 +69,7 @@
         case JAVA_TYPE_STRING:
             return "char const*";
         case JAVA_TYPE_BYTE_ARRAY:
-            return "char const*";
+            return "const BytesField&";
         default:
             return "UNKNOWN";
     }
@@ -272,9 +272,6 @@
                                  chainField.name.c_str(), chainField.name.c_str());
                     }
                 }
-            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-                fprintf(out, ", %s arg%d, size_t arg%d_length",
-                        cpp_type_name(*arg), argIndex, argIndex);
             } else {
                 fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
             }
@@ -319,7 +316,8 @@
                 fprintf(out, "    event.end();\n\n");
             } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
                 fprintf(out,
-                        "    event.AppendCharArray(arg%d, arg%d_length);\n",
+                        "    event.AppendCharArray(arg%d.arg, "
+                        "arg%d.arg_length);\n",
                         argIndex, argIndex);
             } else {
                 if (*arg == JAVA_TYPE_STRING) {
@@ -361,9 +359,6 @@
                                 chainField.name.c_str(), chainField.name.c_str());
                    }
                }
-           } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-               fprintf(out, ", %s arg%d, size_t arg%d_length",
-                       cpp_type_name(*arg), argIndex, argIndex);
            } else {
                fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
            }
@@ -390,8 +385,6 @@
                                 chainField.name.c_str(), chainField.name.c_str());
                    }
                }
-           } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-               fprintf(out, ", arg%d, arg%d_length", argIndex, argIndex);
            } else {
                fprintf(out, ", arg%d", argIndex);
            }
@@ -445,7 +438,14 @@
                 fprintf(out, "        arg%d = \"\";\n", argIndex);
                 fprintf(out, "    }\n");
             }
-            fprintf(out, "    event << arg%d;\n", argIndex);
+            if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                fprintf(out,
+                        "    event.AppendCharArray(arg%d.arg, "
+                        "arg%d.arg_length);",
+                        argIndex, argIndex);
+            } else {
+                fprintf(out, "    event << arg%d;\n", argIndex);
+            }
             if (argIndex == 2) {
                 fprintf(out, "    event.end();\n\n");
                 fprintf(out, "    event.end();\n\n");
@@ -525,7 +525,9 @@
 static void write_cpp_usage(
     FILE* out, const string& method_name, const string& atom_code_name,
     const AtomDecl& atom, const AtomDecl &attributionDecl) {
-    fprintf(out, "     * Usage: %s(StatsLog.%s", method_name.c_str(), atom_code_name.c_str());
+    fprintf(out, "     * Usage: %s(StatsLog.%s", method_name.c_str(),
+            atom_code_name.c_str());
+
     for (vector<AtomField>::const_iterator field = atom.fields.begin();
             field != atom.fields.end(); field++) {
         if (field->javaType == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -540,10 +542,6 @@
                          chainField.name.c_str(), chainField.name.c_str());
                 }
             }
-        } else if (field->javaType == JAVA_TYPE_BYTE_ARRAY) {
-            fprintf(out, ", %s %s, size_t %s_length",
-                    cpp_type_name(field->javaType), field->name.c_str(),
-                    field->name.c_str());
         } else {
             fprintf(out, ", %s %s", cpp_type_name(field->javaType), field->name.c_str());
         }
@@ -571,9 +569,6 @@
                             chainField.name.c_str(), chainField.name.c_str());
                     }
                 }
-            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-                fprintf(out, ", %s arg%d, size_t arg%d_length",
-                        cpp_type_name(*arg), argIndex, argIndex);
             } else {
                 fprintf(out, ", %s arg%d", cpp_type_name(*arg), argIndex);
             }
@@ -640,6 +635,15 @@
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
+    fprintf(out, "struct BytesField {\n");
+    fprintf(out,
+            "  BytesField(char const* array, size_t len) : arg(array), "
+            "arg_length(len) {}\n");
+    fprintf(out, "  char const* arg;\n");
+    fprintf(out, "  size_t arg_length;\n");
+    fprintf(out, "};\n");
+    fprintf(out, "\n");
+
     fprintf(out, "struct StateAtomFieldOptions {\n");
     fprintf(out, "  std::vector<int> primaryFields;\n");
     fprintf(out, "  int exclusiveField;\n");
@@ -705,6 +709,7 @@
     const AtomDecl &attributionDecl) {
     for (set<vector<java_type_t>>::const_iterator signature = signatures.begin();
         signature != signatures.end(); signature++) {
+        fprintf(out, "    /** @hide */\n");
         fprintf(out, "    public static native int %s(int code", method_name.c_str());
         int argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
@@ -748,6 +753,7 @@
         }
 
         // Method header (signature)
+        fprintf(out, "    /** @hide */\n");
         fprintf(out, "    public static void write(int code");
         int argIndex = 1;
         for (vector<java_type_t>::const_iterator arg = signature->begin();
@@ -827,6 +833,7 @@
         if (non_chained_decl != atom_code_to_non_chained_decl_map.end()) {
             write_java_usage(out, "write_non_chained", constant, *non_chained_decl->second);
         }
+        fprintf(out, "     * @hide\n");
         fprintf(out, "     */\n");
         fprintf(out, "    public static final int %s = %d;\n", constant.c_str(), atom->code);
     }
@@ -843,6 +850,7 @@
                     field->name.c_str());
                 for (map<int, string>::const_iterator value = field->enumValues.begin();
                     value != field->enumValues.end(); value++) {
+                    fprintf(out, "    /** @hide */\n");
                     fprintf(out, "    public static final int %s__%s__%s = %d;\n",
                         make_constant_name(atom->message).c_str(),
                         make_constant_name(field->name).c_str(),
@@ -1054,6 +1062,11 @@
                 fprintf(out, "        str%d = NULL;\n", argIndex);
                 fprintf(out, "    }\n");
 
+                fprintf(out,
+                        "    android::util::BytesField bytesField%d(str%d, "
+                        "str%d_length);",
+                        argIndex, argIndex, argIndex);
+
             } else if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
                 hadStringOrChain = true;
                 for (auto chainField : attributionDecl.fields) {
@@ -1106,7 +1119,8 @@
 
         // stats_write call
         argIndex = 1;
-        fprintf(out, "   int ret =  android::util::%s(code", cpp_method_name.c_str());
+        fprintf(out, "\n    int ret =  android::util::%s(code",
+                cpp_method_name.c_str());
         for (vector<java_type_t>::const_iterator arg = signature->begin();
                 arg != signature->end(); arg++) {
             if (*arg == JAVA_TYPE_ATTRIBUTION_CHAIN) {
@@ -1119,16 +1133,12 @@
                         fprintf(out, ", %s_vec", chainField.name.c_str());
                     }
                 }
+            } else if (*arg == JAVA_TYPE_BYTE_ARRAY) {
+                fprintf(out, ", bytesField%d", argIndex);
             } else {
-                const char* argName = (*arg == JAVA_TYPE_STRING ||
-                                       *arg == JAVA_TYPE_BYTE_ARRAY)
-                                              ? "str"
-                                              : "arg";
+                const char* argName =
+                        (*arg == JAVA_TYPE_STRING) ? "str" : "arg";
                 fprintf(out, ", (%s)%s%d", cpp_type_name(*arg), argName, argIndex);
-
-                if (*arg == JAVA_TYPE_BYTE_ARRAY) {
-                    fprintf(out, ", %s%d_length", argName, argIndex);
-                }
             }
             argIndex++;
         }
diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h
index f14bbfd..bddd981 100644
--- a/tools/streaming_proto/Errors.h
+++ b/tools/streaming_proto/Errors.h
@@ -11,7 +11,7 @@
 struct Error
 {
     Error();
-    explicit Error(const Error& that);
+    Error(const Error& that);
     Error(const string& filename, int lineno, const char* message);
 
     string filename;