Merge "Introduce link classes for media mainline module"
diff --git a/Android.bp b/Android.bp
index f032e62..286be82 100644
--- a/Android.bp
+++ b/Android.bp
@@ -882,20 +882,28 @@
     name: "networkstack-aidl-interfaces",
     local_include_dir: "core/java",
     srcs: [
+        "core/java/android/net/ApfCapabilitiesParcelable.aidl",
+        "core/java/android/net/DhcpResultsParcelable.aidl",
         "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/InitialConfigurationParcelable.aidl",
         "core/java/android/net/IpPrefixParcelable.aidl",
         "core/java/android/net/LinkAddressParcelable.aidl",
         "core/java/android/net/LinkPropertiesParcelable.aidl",
+        "core/java/android/net/NetworkParcelable.aidl",
         "core/java/android/net/PrivateDnsConfigParcel.aidl",
+        "core/java/android/net/ProvisioningConfigurationParcelable.aidl",
         "core/java/android/net/ProxyInfoParcelable.aidl",
         "core/java/android/net/RouteInfoParcelable.aidl",
+        "core/java/android/net/StaticIpConfigurationParcelable.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/ip/IIpClient.aidl",
+        "core/java/android/net/ip/IIpClientCallbacks.aidl",
         "core/java/android/net/ipmemorystore/**/*.aidl",
     ],
     api_dir: "aidl/networkstack",
@@ -1253,6 +1261,7 @@
         ":non_openjdk_javadoc_files",
         ":android_icu4j_src_files_for_docs",
         ":conscrypt_public_api_files",
+        ":media2-srcs",
         "test-mock/src/**/*.java",
         "test-runner/src/**/*.java",
     ],
@@ -1314,6 +1323,7 @@
         ":non_openjdk_javadoc_files",
         ":android_icu4j_src_files_for_docs",
         ":conscrypt_public_api_files",
+        ":media2-srcs",
     ],
     srcs_lib: "framework",
     srcs_lib_whitelist_dirs: frameworks_base_subdirs,
diff --git a/Android.mk b/Android.mk
index 9f7bf99..e405345 100644
--- a/Android.mk
+++ b/Android.mk
@@ -87,14 +87,11 @@
     frameworks/base/config/hiddenapi-greylist-max-p.txt \
     frameworks/base/config/hiddenapi-greylist-max-o.txt \
     frameworks/base/config/hiddenapi-force-blacklist.txt \
-    $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \
-    $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \
+    $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) \
     $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \
     $(SOONG_HIDDENAPI_FLAGS)
 	frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \
-	    --public $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \
-	    --private $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \
-	    --csv $(PRIVATE_FLAGS_INPUTS) \
+	    --csv $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) $(PRIVATE_FLAGS_INPUTS) \
 	    --greylist frameworks/base/config/hiddenapi-greylist.txt \
 	    --greylist-ignore-conflicts $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \
 	    --greylist-max-p frameworks/base/config/hiddenapi-greylist-max-p.txt \
diff --git a/api/current.txt b/api/current.txt
index d378416..96508c7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24533,6 +24533,7 @@
     ctor public MediaController2(@NonNull android.content.Context, @NonNull android.media.Session2Token, @NonNull java.util.concurrent.Executor, @NonNull android.media.MediaController2.ControllerCallback);
     method public void cancelSessionCommand(@NonNull Object);
     method public void close();
+    method public android.media.Session2Token getConnectedSessionToken();
     method public boolean isPlaybackActive();
     method @NonNull public Object sendSessionCommand(@NonNull android.media.Session2Command, @Nullable android.os.Bundle);
   }
@@ -28895,6 +28896,7 @@
     method public android.net.IpPrefix getDestination();
     method public java.net.InetAddress getGateway();
     method public String getInterface();
+    method public boolean hasGateway();
     method public boolean isDefaultRoute();
     method public boolean matches(java.net.InetAddress);
     method public void writeToParcel(android.os.Parcel, int);
@@ -42228,6 +42230,8 @@
     method public static String gaiName(int);
     field public static final int AF_INET;
     field public static final int AF_INET6;
+    field public static final int AF_NETLINK;
+    field public static final int AF_PACKET;
     field public static final int AF_UNIX;
     field public static final int AF_UNSPEC;
     field public static final int AI_ADDRCONFIG;
@@ -42237,6 +42241,7 @@
     field public static final int AI_NUMERICSERV;
     field public static final int AI_PASSIVE;
     field public static final int AI_V4MAPPED;
+    field public static final int ARPHRD_ETHER;
     field public static final int CAP_AUDIT_CONTROL;
     field public static final int CAP_AUDIT_WRITE;
     field public static final int CAP_BLOCK_SUSPEND;
@@ -42360,6 +42365,10 @@
     field public static final int ESPIPE;
     field public static final int ESRCH;
     field public static final int ESTALE;
+    field public static final int ETH_P_ALL;
+    field public static final int ETH_P_ARP;
+    field public static final int ETH_P_IP;
+    field public static final int ETH_P_IPV6;
     field public static final int ETIME;
     field public static final int ETIMEDOUT;
     field public static final int ETXTBSY;
@@ -42457,6 +42466,8 @@
     field public static final int MS_ASYNC;
     field public static final int MS_INVALIDATE;
     field public static final int MS_SYNC;
+    field public static final int NETLINK_INET_DIAG;
+    field public static final int NETLINK_ROUTE;
     field public static final int NI_DGRAM;
     field public static final int NI_NAMEREQD;
     field public static final int NI_NOFQDN;
@@ -42493,6 +42504,7 @@
     field public static final int PR_GET_DUMPABLE;
     field public static final int PR_SET_DUMPABLE;
     field public static final int PR_SET_NO_NEW_PRIVS;
+    field public static final int RTMGRP_NEIGH;
     field public static final int RT_SCOPE_HOST;
     field public static final int RT_SCOPE_LINK;
     field public static final int RT_SCOPE_NOWHERE;
@@ -46782,15 +46794,15 @@
     method public void setFlags(int);
     method public void updateDrawState(android.text.TextPaint);
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
+    field @Deprecated public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
     field public static final android.os.Parcelable.Creator<android.text.style.SuggestionSpan> CREATOR;
     field public static final int FLAG_AUTO_CORRECTION = 4; // 0x4
     field public static final int FLAG_EASY_CORRECT = 1; // 0x1
     field public static final int FLAG_MISSPELLED = 2; // 0x2
     field public static final int SUGGESTIONS_MAX_SIZE = 5; // 0x5
-    field public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
-    field public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
-    field public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
+    field @Deprecated public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
+    field @Deprecated public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
+    field @Deprecated public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
   }
 
   public class SuperscriptSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
diff --git a/api/system-current.txt b/api/system-current.txt
index 810b237..e593e14 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3894,6 +3894,12 @@
 
 package android.net {
 
+  public class CaptivePortal implements android.os.Parcelable {
+    field public static final int APP_RETURN_DISMISSED = 0; // 0x0
+    field public static final int APP_RETURN_UNWANTED = 1; // 0x1
+    field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
+  }
+
   public class ConnectivityManager {
     method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl();
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
@@ -3901,6 +3907,8 @@
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
     method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
+    field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+    field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
     field public static final int TETHERING_BLUETOOTH = 2; // 0x2
     field public static final int TETHERING_USB = 1; // 0x1
     field public static final int TETHERING_WIFI = 0; // 0x0
@@ -3946,6 +3954,10 @@
   public class LinkAddress implements android.os.Parcelable {
     ctor public LinkAddress(java.net.InetAddress, int);
     ctor public LinkAddress(String);
+    method public boolean isGlobalPreferred();
+    method public boolean isIPv4();
+    method public boolean isIPv6();
+    method public boolean isSameAddressAs(android.net.LinkAddress);
   }
 
   public final class LinkProperties implements android.os.Parcelable {
@@ -3960,6 +3972,10 @@
     method public void setMtu(int);
   }
 
+  public class Network implements android.os.Parcelable {
+    method public android.net.Network getPrivateDnsBypassingCopy();
+  }
+
   public final class NetworkCapabilities implements android.os.Parcelable {
     method public int getSignalStrength();
     field public static final int NET_CAPABILITY_OEM_PAID = 22; // 0x16
@@ -4001,6 +4017,13 @@
     field public static final String EXTRA_PACKAGE_NAME = "packageName";
   }
 
+  public final class RouteInfo implements android.os.Parcelable {
+    method public int getType();
+    field public static final int RTN_THROW = 9; // 0x9
+    field public static final int RTN_UNICAST = 1; // 0x1
+    field public static final int RTN_UNREACHABLE = 7; // 0x7
+  }
+
   public class RssiCurve implements android.os.Parcelable {
     ctor public RssiCurve(int, int, byte[]);
     ctor public RssiCurve(int, int, byte[], int);
@@ -5376,8 +5399,23 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
     field public static final String APP_STANDBY_ENABLED = "app_standby_enabled";
     field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages";
+    field public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS = "captive_portal_fallback_probe_specs";
+    field public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
+    field public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
+    field public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
+    field public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+    field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+    field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+    field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+    field public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS = "captive_portal_other_fallback_urls";
+    field public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
+    field public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
     field public static final String CARRIER_APP_NAMES = "carrier_app_names";
     field public static final String CARRIER_APP_WHITELIST = "carrier_app_whitelist";
+    field public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = "data_stall_consecutive_dns_timeout_threshold";
+    field public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type";
+    field public static final String DATA_STALL_MIN_EVALUATE_INTERVAL = "data_stall_min_evaluate_interval";
+    field public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD = "data_stall_valid_dns_time_threshold";
     field public static final String DEFAULT_SM_DP_PLUS = "default_sm_dp_plus";
     field public static final String DEVICE_DEMO_MODE = "device_demo_mode";
     field public static final String DEVICE_PROVISIONING_MOBILE_DATA_ENABLED = "device_provisioning_mobile_data";
diff --git a/api/test-current.txt b/api/test-current.txt
index fb25d13..b829758 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -780,15 +780,44 @@
 
 package android.net {
 
+  public class CaptivePortal implements android.os.Parcelable {
+    field public static final int APP_RETURN_DISMISSED = 0; // 0x0
+    field public static final int APP_RETURN_UNWANTED = 1; // 0x1
+    field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
+  }
+
+  public class ConnectivityManager {
+    field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
+    field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
+  }
+
   public final class IpSecManager {
     field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
   }
 
+  public class LinkAddress implements android.os.Parcelable {
+    method public boolean isGlobalPreferred();
+    method public boolean isIPv4();
+    method public boolean isIPv6();
+    method public boolean isSameAddressAs(android.net.LinkAddress);
+  }
+
+  public class Network implements android.os.Parcelable {
+    method public android.net.Network getPrivateDnsBypassingCopy();
+  }
+
   public final class NetworkCapabilities implements android.os.Parcelable {
     method public int[] getCapabilities();
     method public int[] getTransportTypes();
   }
 
+  public final class RouteInfo implements android.os.Parcelable {
+    method public int getType();
+    field public static final int RTN_THROW = 9; // 0x9
+    field public static final int RTN_UNICAST = 1; // 0x1
+    field public static final int RTN_UNREACHABLE = 7; // 0x7
+  }
+
   public class TrafficStats {
     method public static long getLoopbackRxBytes();
     method public static long getLoopbackRxPackets();
@@ -1277,6 +1306,21 @@
     field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages";
     field public static final String AUTOFILL_SMART_SUGGESTION_EMULATION_FLAGS = "autofill_smart_suggestion_emulation_flags";
     field public static final String AUTOMATIC_POWER_SAVER_MODE = "automatic_power_saver_mode";
+    field public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS = "captive_portal_fallback_probe_specs";
+    field public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
+    field public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
+    field public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
+    field public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+    field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+    field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+    field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+    field public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS = "captive_portal_other_fallback_urls";
+    field public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
+    field public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
+    field public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD = "data_stall_consecutive_dns_timeout_threshold";
+    field public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type";
+    field public static final String DATA_STALL_MIN_EVALUATE_INTERVAL = "data_stall_min_evaluate_interval";
+    field public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD = "data_stall_valid_dns_time_threshold";
     field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
     field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
     field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
diff --git a/core/java/android/net/ApfCapabilitiesParcelable.aidl b/core/java/android/net/ApfCapabilitiesParcelable.aidl
new file mode 100644
index 0000000..f0645d2
--- /dev/null
+++ b/core/java/android/net/ApfCapabilitiesParcelable.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.net;
+
+parcelable ApfCapabilitiesParcelable {
+    int apfVersionSupported;
+    int maximumApfProgramSize;
+    int apfPacketFormat;
+}
\ No newline at end of file
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index ee05f28..4047068 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -15,6 +15,8 @@
  */
 package android.net;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,10 +30,16 @@
  */
 public class CaptivePortal implements Parcelable {
     /** @hide */
+    @SystemApi
+    @TestApi
     public static final int APP_RETURN_DISMISSED    = 0;
     /** @hide */
+    @SystemApi
+    @TestApi
     public static final int APP_RETURN_UNWANTED     = 1;
     /** @hide */
+    @SystemApi
+    @TestApi
     public static final int APP_RETURN_WANTED_AS_IS = 2;
 
     private final IBinder mBinder;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index abc00fe..cee3a40 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -23,6 +23,7 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -255,6 +256,8 @@
      * portal login activity.
      * {@hide}
      */
+    @SystemApi
+    @TestApi
     public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC =
             "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
 
@@ -262,6 +265,8 @@
      * Key for passing a user agent string to the captive portal login activity.
      * {@hide}
      */
+    @SystemApi
+    @TestApi
     public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT =
             "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
 
diff --git a/core/java/android/net/DhcpResultsParcelable.aidl b/core/java/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..cf5629b
--- /dev/null
+++ b/core/java/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,27 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.StaticIpConfigurationParcelable;
+
+parcelable DhcpResultsParcelable {
+    StaticIpConfigurationParcelable baseConfiguration;
+    int leaseDuration;
+    int mtu;
+    String serverAddress;
+    String vendorInfo;
+}
\ No newline at end of file
diff --git a/core/java/android/net/InitialConfigurationParcelable.aidl b/core/java/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..bdda355
--- /dev/null
+++ b/core/java/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.net;
+
+import android.net.IpPrefixParcelable;
+import android.net.LinkAddressParcelable;
+
+parcelable InitialConfigurationParcelable {
+    LinkAddressParcelable[] ipAddresses;
+    IpPrefixParcelable[] directlyConnectedRoutes;
+    String[] dnsServers;
+    String gateway;
+}
\ No newline at end of file
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index b40f15a..a536d08 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -26,6 +26,7 @@
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
@@ -117,7 +118,8 @@
      * @return true if the address is IPv6.
      * @hide
      */
-    @UnsupportedAppUsage
+    @TestApi
+    @SystemApi
     public boolean isIPv6() {
         return address instanceof Inet6Address;
     }
@@ -126,6 +128,8 @@
      * @return true if the address is IPv4 or is a mapped IPv4 address.
      * @hide
      */
+    @TestApi
+    @SystemApi
     public boolean isIPv4() {
         return address instanceof Inet4Address;
     }
@@ -263,7 +267,8 @@
      * otherwise.
      * @hide
      */
-    @UnsupportedAppUsage
+    @TestApi
+    @SystemApi
     public boolean isSameAddressAs(LinkAddress other) {
         return address.equals(other.address) && prefixLength == other.prefixLength;
     }
@@ -310,6 +315,8 @@
      * Returns true if this {@code LinkAddress} is global scope and preferred.
      * @hide
      */
+    @TestApi
+    @SystemApi
     public boolean isGlobalPreferred() {
         /**
          * Note that addresses flagged as IFA_F_OPTIMISTIC are
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 4e551756..77d83f5 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -52,6 +52,8 @@
 
     /**
      * The MacAddress zero MAC address.
+     *
+     * <p>Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
      * @hide
      */
     @UnsupportedAppUsage
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index bf2344d..2c831de 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -16,6 +16,8 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -157,6 +159,8 @@
      *
      * @hide
      */
+    @TestApi
+    @SystemApi
     public Network getPrivateDnsBypassingCopy() {
         return new Network(netId, true);
     }
diff --git a/core/java/android/net/NetworkParcelable.aidl b/core/java/android/net/NetworkParcelable.aidl
new file mode 100644
index 0000000..c26352e
--- /dev/null
+++ b/core/java/android/net/NetworkParcelable.aidl
@@ -0,0 +1,22 @@
+/*
+**
+** 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.net;
+
+parcelable NetworkParcelable {
+    long networkHandle;
+}
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index 2eac6de..af043ee 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -50,6 +50,8 @@
 
     public static final String NETWORKSTACK_PACKAGE_NAME = "com.android.mainline.networkstack";
 
+    private static final int NETWORKSTACK_TIMEOUT_MS = 10_000;
+
     @NonNull
     @GuardedBy("mPendingNetStackRequests")
     private final ArrayList<NetworkStackCallback> mPendingNetStackRequests = new ArrayList<>();
@@ -57,6 +59,8 @@
     @GuardedBy("mPendingNetStackRequests")
     private INetworkStackConnector mConnector;
 
+    private volatile boolean mNetworkStackStartRequested = false;
+
     private interface NetworkStackCallback {
         void onNetworkStackConnected(INetworkStackConnector connector);
     }
@@ -134,6 +138,7 @@
      * started.
      */
     public void start(Context context) {
+        mNetworkStackStartRequested = true;
         // Try to bind in-process if the library is available
         IBinder connector = null;
         try {
@@ -170,15 +175,54 @@
         }
     }
 
-    // TODO: use this method to obtain the connector when implementing network stack operations
+    /**
+     * For non-system server clients, get the connector registered by the system server.
+     */
+    private INetworkStackConnector getRemoteConnector() {
+        // Block until the NetworkStack connector is registered in ServiceManager.
+        // <p>This is only useful for non-system processes that do not have a way to be notified of
+        // registration completion. Adding a callback system would be too heavy weight considering
+        // that the connector is registered on boot, so it is unlikely that a client would request
+        // it before it is registered.
+        // TODO: consider blocking boot on registration and simplify much of the logic in this class
+        IBinder connector;
+        try {
+            final long before = System.currentTimeMillis();
+            while ((connector = ServiceManager.getService(Context.NETWORK_STACK_SERVICE)) == null) {
+                Thread.sleep(20);
+                if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
+                    Slog.e(TAG, "Timeout waiting for NetworkStack connector");
+                    return null;
+                }
+            }
+        } catch (InterruptedException e) {
+            Slog.e(TAG, "Error waiting for NetworkStack connector", e);
+            return null;
+        }
+
+        return INetworkStackConnector.Stub.asInterface(connector);
+    }
+
     private void requestConnector(@NonNull NetworkStackCallback request) {
         // TODO: PID check.
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+        final int caller = Binder.getCallingUid();
+        if (caller != Process.SYSTEM_UID && caller != Process.BLUETOOTH_UID) {
             // Don't even attempt to obtain the connector and give a nice error message
             throw new SecurityException(
                     "Only the system server should try to bind to the network stack.");
         }
 
+        if (!mNetworkStackStartRequested) {
+            // The network stack is not being started in this process, e.g. this process is not
+            // the system server. Get a remote connector registered by the system server.
+            final INetworkStackConnector connector = getRemoteConnector();
+            synchronized (mPendingNetStackRequests) {
+                mConnector = connector;
+            }
+            request.onNetworkStackConnected(connector);
+            return;
+        }
+
         final INetworkStackConnector connector;
         synchronized (mPendingNetStackRequests) {
             connector = mConnector;
diff --git a/core/java/android/net/ProvisioningConfigurationParcelable.aidl b/core/java/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..2a144f2
--- /dev/null
+++ b/core/java/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,38 @@
+/*
+**
+** 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.net;
+
+import android.net.ApfCapabilitiesParcelable;
+import android.net.InitialConfigurationParcelable;
+import android.net.NetworkParcelable;
+import android.net.StaticIpConfigurationParcelable;
+
+parcelable ProvisioningConfigurationParcelable {
+    boolean enableIPv4;
+    boolean enableIPv6;
+    boolean usingMultinetworkPolicyTracker;
+    boolean usingIpReachabilityMonitor;
+    int requestedPreDhcpActionMs;
+    InitialConfigurationParcelable initialConfig;
+    StaticIpConfigurationParcelable staticIpConfig;
+    ApfCapabilitiesParcelable apfCapabilities;
+    int provisioningTimeoutMs;
+    int ipv6AddrGenMode;
+    NetworkParcelable network;
+    String displayName;
+}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 37ab9ff..6bf2c67 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -16,16 +16,17 @@
 
 package android.net;
 
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.net.UnknownHostException;
-import java.net.InetAddress;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
-
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.Collection;
 import java.util.Objects;
 
@@ -67,12 +68,18 @@
 
 
     /** Unicast route. @hide */
+    @SystemApi
+    @TestApi
     public static final int RTN_UNICAST = 1;
 
     /** Unreachable route. @hide */
+    @SystemApi
+    @TestApi
     public static final int RTN_UNREACHABLE = 7;
 
     /** Throw route. @hide */
+    @SystemApi
+    @TestApi
     public static final int RTN_THROW = 9;
 
     /**
@@ -317,6 +324,8 @@
      *
      * @hide
      */
+    @TestApi
+    @SystemApi
     public int getType() {
         return mType;
     }
@@ -362,9 +371,7 @@
      * ({@code false}).
      *
      * @return {@code true} if a gateway is specified
-     * @hide
      */
-    @UnsupportedAppUsage
     public boolean hasGateway() {
         return mHasGateway;
     }
diff --git a/core/java/android/net/StaticIpConfigurationParcelable.aidl b/core/java/android/net/StaticIpConfigurationParcelable.aidl
new file mode 100644
index 0000000..45dc021
--- /dev/null
+++ b/core/java/android/net/StaticIpConfigurationParcelable.aidl
@@ -0,0 +1,27 @@
+/*
+**
+** 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.net;
+
+import android.net.LinkAddressParcelable;
+
+parcelable StaticIpConfigurationParcelable {
+    LinkAddressParcelable ipAddress;
+    String gateway;
+    String[] dnsServers;
+    String domains;
+}
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java
similarity index 78%
rename from services/net/java/android/net/apf/ApfCapabilities.java
rename to core/java/android/net/apf/ApfCapabilities.java
index dec8ca2..f28cdc9 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/core/java/android/net/apf/ApfCapabilities.java
@@ -38,18 +38,28 @@
      */
     public final int apfPacketFormat;
 
-    public ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat)
-    {
+    public ApfCapabilities(
+            int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) {
         this.apfVersionSupported = apfVersionSupported;
         this.maximumApfProgramSize = maximumApfProgramSize;
         this.apfPacketFormat = apfPacketFormat;
     }
 
+    @Override
     public String toString() {
         return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
                 apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof  ApfCapabilities)) return false;
+        final ApfCapabilities other = (ApfCapabilities) obj;
+        return apfVersionSupported == other.apfVersionSupported
+                && maximumApfProgramSize == other.maximumApfProgramSize
+                && apfPacketFormat == other.apfPacketFormat;
+    }
+
     /**
      * Returns true if the APF interpreter advertises support for the data buffer access opcodes
      * LDDW and STDW.
diff --git a/core/java/android/net/ip/IIpClient.aidl b/core/java/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..7769ec2
--- /dev/null
+++ b/core/java/android/net/ip/IIpClient.aidl
@@ -0,0 +1,32 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package android.net.ip;
+
+import android.net.ProxyInfoParcelable;
+import android.net.ProvisioningConfigurationParcelable;
+
+/** @hide */
+oneway interface IIpClient {
+    void completedPreDhcpAction();
+    void confirmConfiguration();
+    void readPacketFilterComplete(in byte[] data);
+    void shutdown();
+    void startProvisioning(in ProvisioningConfigurationParcelable req);
+    void stop();
+    void setTcpBufferSizes(in String tcpBufferSizes);
+    void setHttpProxy(in ProxyInfoParcelable proxyInfo);
+    void setMulticastFilter(boolean enabled);
+}
diff --git a/core/java/android/net/ip/IIpClientCallbacks.aidl b/core/java/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..f077e3b
--- /dev/null
+++ b/core/java/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,66 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package android.net.ip;
+
+import android.net.LinkPropertiesParcelable;
+import android.net.ip.IIpClient;
+import android.net.DhcpResultsParcelable;
+
+/** @hide */
+oneway interface IIpClientCallbacks {
+    void onIpClientCreated(in IIpClient ipClient);
+
+    void onPreDhcpAction();
+    void onPostDhcpAction();
+
+    // This is purely advisory and not an indication of provisioning
+    // success or failure.  This is only here for callers that want to
+    // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
+    // DHCPv4 or static IPv4 configuration failure or success can be
+    // determined by whether or not the passed-in DhcpResults object is
+    // null or not.
+    void onNewDhcpResults(in DhcpResultsParcelable dhcpResults);
+
+    void onProvisioningSuccess(in LinkPropertiesParcelable newLp);
+    void onProvisioningFailure(in LinkPropertiesParcelable newLp);
+
+    // Invoked on LinkProperties changes.
+    void onLinkPropertiesChange(in LinkPropertiesParcelable newLp);
+
+    // Called when the internal IpReachabilityMonitor (if enabled) has
+    // detected the loss of a critical number of required neighbors.
+    void onReachabilityLost(in String logMsg);
+
+    // Called when the IpClient state machine terminates.
+    void onQuit();
+
+    // Install an APF program to filter incoming packets.
+    void installPacketFilter(in byte[] filter);
+
+    // Asynchronously read back the APF program & data buffer from the wifi driver.
+    // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+    // buffer. In response to this request, the driver returns the data buffer asynchronously
+    // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+    void startReadPacketFilter();
+
+    // If multicast filtering cannot be accomplished with APF, this function will be called to
+    // actuate multicast filtering using another means.
+    void setFallbackMulticastFilter(boolean enabled);
+
+    // Enabled/disable Neighbor Discover offload functionality. This is
+    // called, for example, whenever 464xlat is being started or stopped.
+    void setNeighborDiscoveryOffload(boolean enable);
+}
\ No newline at end of file
diff --git a/core/java/android/net/ip/IpClientCallbacks.java b/core/java/android/net/ip/IpClientCallbacks.java
new file mode 100644
index 0000000..db01ae4
--- /dev/null
+++ b/core/java/android/net/ip/IpClientCallbacks.java
@@ -0,0 +1,119 @@
+/*
+ * 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.net.ip;
+
+import android.net.DhcpResults;
+import android.net.LinkProperties;
+
+/**
+ * Callbacks for handling IpClient events.
+ *
+ * This is a convenience class to allow clients not to override all methods of IIpClientCallbacks,
+ * and avoid unparceling arguments.
+ * These methods are called asynchronously on a Binder thread, as IpClient lives in a different
+ * process.
+ * @hide
+ */
+public class IpClientCallbacks {
+
+    /**
+     * Callback called upon IpClient creation.
+     *
+     * @param ipClient The Binder token to communicate with IpClient.
+     */
+    public void onIpClientCreated(IIpClient ipClient) {}
+
+    /**
+     * Callback called prior to DHCP discovery/renewal.
+     *
+     * <p>In order to receive onPreDhcpAction(), call #withPreDhcpAction() when constructing a
+     * ProvisioningConfiguration.
+     *
+     * <p>Implementations of onPreDhcpAction() must call IpClient#completedPreDhcpAction() to
+     * indicate that DHCP is clear to proceed.
+      */
+    public void onPreDhcpAction() {}
+
+    /**
+     * Callback called after DHCP discovery/renewal.
+     */
+    public void onPostDhcpAction() {}
+
+    /**
+     * Callback called when new DHCP results are available.
+     *
+     * <p>This is purely advisory and not an indication of provisioning success or failure.  This is
+     * only here for callers that want to expose DHCPv4 results to other APIs
+     * (e.g., WifiInfo#setInetAddress).
+     *
+     * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+     * the passed-in DhcpResults object is null.
+     */
+    public void onNewDhcpResults(DhcpResults dhcpResults) {}
+
+    /**
+     * Indicates that provisioning was successful.
+     */
+    public void onProvisioningSuccess(LinkProperties newLp) {}
+
+    /**
+     * Indicates that provisioning failed.
+     */
+    public void onProvisioningFailure(LinkProperties newLp) {}
+
+    /**
+     * Invoked on LinkProperties changes.
+     */
+    public void onLinkPropertiesChange(LinkProperties newLp) {}
+
+    /**Called when the internal IpReachabilityMonitor (if enabled) has
+     * detected the loss of a critical number of required neighbors.
+     */
+    public void onReachabilityLost(String logMsg) {}
+
+    /**
+     * Called when the IpClient state machine terminates.
+     */
+    public void onQuit() {}
+
+    /**
+     * Called to indicate that a new APF program must be installed to filter incoming packets.
+     */
+    public void installPacketFilter(byte[] filter) {}
+
+    /**
+     * Called to indicate that the APF Program & data buffer must be read asynchronously from the
+     * wifi driver.
+     *
+     * <p>Due to Wifi HAL limitations, the current implementation only supports dumping the entire
+     * buffer. In response to this request, the driver returns the data buffer asynchronously
+     * by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
+     */
+    public void startReadPacketFilter() {}
+
+    /**
+     * If multicast filtering cannot be accomplished with APF, this function will be called to
+     * actuate multicast filtering using another means.
+     */
+    public void setFallbackMulticastFilter(boolean enabled) {}
+
+    /**
+     * Enabled/disable Neighbor Discover offload functionality. This is called, for example,
+     * whenever 464xlat is being started or stopped.
+     */
+    public void setNeighborDiscoveryOffload(boolean enable) {}
+}
diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java
index 95e5042..cacd42d 100644
--- a/core/java/android/net/ipmemorystore/Status.java
+++ b/core/java/android/net/ipmemorystore/Status.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * A parcelable status representing the result of an operation.
  * Parcels as StatusParceled.
@@ -26,7 +28,10 @@
 public class Status {
     public static final int SUCCESS = 0;
 
-    public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1;
+    public static final int ERROR_GENERIC = -1;
+    public static final int ERROR_ILLEGAL_ARGUMENT = -2;
+    public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -3;
+    public static final int ERROR_STORAGE = -4;
 
     public final int resultCode;
 
@@ -34,7 +39,8 @@
         this.resultCode = resultCode;
     }
 
-    Status(@NonNull final StatusParcelable parcelable) {
+    @VisibleForTesting
+    public Status(@NonNull final StatusParcelable parcelable) {
         this(parcelable.resultCode);
     }
 
@@ -55,7 +61,12 @@
     public String toString() {
         switch (resultCode) {
             case SUCCESS: return "SUCCESS";
+            case ERROR_GENERIC: return "GENERIC ERROR";
+            case ERROR_ILLEGAL_ARGUMENT: return "ILLEGAL ARGUMENT";
             case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED";
+            // "DB storage error" is not very helpful but SQLite does not provide specific error
+            // codes upon store failure. Thus this indicates SQLite returned some error upon store
+            case ERROR_STORAGE: return "DATABASE STORAGE ERROR";
             default: return "Unknown value ?!";
         }
     }
diff --git a/core/java/android/net/ipmemorystore/Utils.java b/core/java/android/net/ipmemorystore/Utils.java
index 73d8c83..b361aca 100644
--- a/core/java/android/net/ipmemorystore/Utils.java
+++ b/core/java/android/net/ipmemorystore/Utils.java
@@ -17,18 +17,25 @@
 package android.net.ipmemorystore;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 /** {@hide} */
 public class Utils {
     /** Pretty print */
-    public static String blobToString(final Blob blob) {
-        final StringBuilder sb = new StringBuilder("Blob : [");
-        if (blob.data.length <= 24) {
-            appendByteArray(sb, blob.data, 0, blob.data.length);
+    public static String blobToString(@Nullable final Blob blob) {
+        return "Blob : " + byteArrayToString(null == blob ? null : blob.data);
+    }
+
+    /** Pretty print */
+    public static String byteArrayToString(@Nullable final byte[] data) {
+        if (null == data) return "null";
+        final StringBuilder sb = new StringBuilder("[");
+        if (data.length <= 24) {
+            appendByteArray(sb, data, 0, data.length);
         } else {
-            appendByteArray(sb, blob.data, 0, 16);
+            appendByteArray(sb, data, 0, 16);
             sb.append("...");
-            appendByteArray(sb, blob.data, blob.data.length - 8, blob.data.length);
+            appendByteArray(sb, data, data.length - 8, data.length);
         }
         sb.append("]");
         return sb.toString();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d5de13c..8ef7ae1 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10816,6 +10816,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
 
         /**
@@ -10824,6 +10826,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
 
         /**
@@ -10832,6 +10836,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
 
         /**
@@ -10841,6 +10847,8 @@
          * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
 
         /**
@@ -10869,6 +10877,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
 
         /**
@@ -10877,6 +10887,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
 
         /**
@@ -10885,6 +10897,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
 
         /**
@@ -10893,6 +10907,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS =
                 "captive_portal_other_fallback_urls";
 
@@ -10902,6 +10918,8 @@
          * by "@@,@@".
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS =
                 "captive_portal_fallback_probe_specs";
 
@@ -10912,6 +10930,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
 
         /**
@@ -10920,6 +10940,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
 
         /**
@@ -10929,6 +10951,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD =
                 "data_stall_consecutive_dns_timeout_threshold";
 
@@ -10937,6 +10961,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String DATA_STALL_MIN_EVALUATE_INTERVAL =
                 "data_stall_min_evaluate_interval";
 
@@ -10946,6 +10972,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String DATA_STALL_VALID_DNS_TIME_THRESHOLD =
                 "data_stall_valid_dns_time_threshold";
 
@@ -10955,6 +10983,8 @@
          *
          * @hide
          */
+        @SystemApi
+        @TestApi
         public static final String DATA_STALL_EVALUATION_TYPE = "data_stall_evaluation_type";
 
         /**
@@ -11103,6 +11133,10 @@
           */
         public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
 
+
+        /** {@hide} */
+        public static final String
+                BLUETOOTH_BTSNOOP_DEFAULT_MODE = "bluetooth_btsnoop_default_mode";
         /** {@hide} */
         public static final String
                 BLUETOOTH_HEADSET_PRIORITY_PREFIX = "bluetooth_headset_priority_";
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 7a58681..dd073e9 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -21,7 +21,6 @@
 import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.content.Intent;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.os.Parcel;
@@ -31,7 +30,6 @@
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.util.Log;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.TextView;
 
 import java.util.Arrays;
@@ -72,9 +70,37 @@
      */
     public static final int FLAG_AUTO_CORRECTION = 0x0004;
 
+    /**
+     * This action is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
+     *
+     * @deprecated For IMEs to receive this kind of user interaction signals, implement IMEs' own
+     *             suggestion picker UI instead of relying on {@link SuggestionSpan}. To retrieve
+     *             bounding boxes for each character of the composing text, use
+     *             {@link android.view.inputmethod.CursorAnchorInfo}.
+     */
+    @Deprecated
     public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
+
+    /**
+     * This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
+     *
+     * @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
+     */
+    @Deprecated
     public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
+    /**
+     * This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
+     *
+     * @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
+     */
+    @Deprecated
     public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
+    /**
+     * This is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
+     *
+     * @deprecated See {@link #ACTION_SUGGESTION_PICKED}.
+     */
+    @Deprecated
     public static final String SUGGESTION_SPAN_PICKED_HASHCODE = "hashcode";
 
     public static final int SUGGESTIONS_MAX_SIZE = 5;
@@ -97,8 +123,6 @@
     private final String mLocaleStringForCompatibility;
     @NonNull
     private final String mLanguageTag;
-    private final String mNotificationTargetClassName;
-    private final String mNotificationTargetPackageName;
     private final int mHashCode;
 
     @UnsupportedAppUsage
@@ -137,7 +161,9 @@
      * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. Null values not permitted.
      * @param flags Additional flags indicating how this span is handled in TextView
      * @param notificationTargetClass if not null, this class will get notified when the user
-     * selects one of the suggestions.
+     *                                selects one of the suggestions.  On Android
+     *                                {@link android.os.Build.VERSION_CODES#Q} and later this
+     *                                parameter is always ignored.
      */
     public SuggestionSpan(Context context, Locale locale, String[] suggestions, int flags,
             Class<?> notificationTargetClass) {
@@ -156,20 +182,7 @@
         }
         mLocaleStringForCompatibility = sourceLocale == null ? "" : sourceLocale.toString();
         mLanguageTag = sourceLocale == null ? "" : sourceLocale.toLanguageTag();
-
-        if (context != null) {
-            mNotificationTargetPackageName = context.getPackageName();
-        } else {
-            mNotificationTargetPackageName = null;
-        }
-
-        if (notificationTargetClass != null) {
-            mNotificationTargetClassName = notificationTargetClass.getCanonicalName();
-        } else {
-            mNotificationTargetClassName = "";
-        }
-        mHashCode = hashCodeInternal(mSuggestions, mLanguageTag, mLocaleStringForCompatibility,
-                mNotificationTargetClassName);
+        mHashCode = hashCodeInternal(mSuggestions, mLanguageTag, mLocaleStringForCompatibility);
 
         initStyle(context);
     }
@@ -215,8 +228,6 @@
         mFlags = src.readInt();
         mLocaleStringForCompatibility = src.readString();
         mLanguageTag = src.readString();
-        mNotificationTargetClassName = src.readString();
-        mNotificationTargetPackageName = src.readString();
         mHashCode = src.readInt();
         mEasyCorrectUnderlineColor = src.readInt();
         mEasyCorrectUnderlineThickness = src.readFloat();
@@ -260,17 +271,15 @@
     }
 
     /**
-     * @return The name of the class to notify. The class of the original IME package will receive
-     * a notification when the user selects one of the suggestions. The notification will include
-     * the original string, the suggested replacement string as well as the hashCode of this span.
-     * The class will get notified by an intent that has those information.
-     * This is an internal API because only the framework should know the class name.
+     * @return {@code null}.
      *
      * @hide
+     * @deprecated Do not use. Always returns {@code null}.
      */
+    @Deprecated
     @UnsupportedAppUsage
     public String getNotificationTargetClassName() {
-        return mNotificationTargetClassName;
+        return null;
     }
 
     public int getFlags() {
@@ -297,8 +306,6 @@
         dest.writeInt(mFlags);
         dest.writeString(mLocaleStringForCompatibility);
         dest.writeString(mLanguageTag);
-        dest.writeString(mNotificationTargetClassName);
-        dest.writeString(mNotificationTargetPackageName);
         dest.writeInt(mHashCode);
         dest.writeInt(mEasyCorrectUnderlineColor);
         dest.writeFloat(mEasyCorrectUnderlineThickness);
@@ -332,9 +339,9 @@
     }
 
     private static int hashCodeInternal(String[] suggestions, @NonNull String languageTag,
-            @NonNull String localeStringForCompatibility, String notificationTargetClassName) {
+            @NonNull String localeStringForCompatibility) {
         return Arrays.hashCode(new Object[] {Long.valueOf(SystemClock.uptimeMillis()), suggestions,
-                languageTag, localeStringForCompatibility, notificationTargetClassName});
+                languageTag, localeStringForCompatibility});
     }
 
     public static final Parcelable.Creator<SuggestionSpan> CREATOR =
@@ -390,39 +397,14 @@
     }
 
     /**
-     * Notifies a suggestion selection.
+     * Does nothing.
      *
+     * @deprecated this is deprecated in {@link android.os.Build.VERSION_CODES#Q}.
      * @hide
      */
     @UnsupportedAppUsage
+    @Deprecated
     public void notifySelection(Context context, String original, int index) {
-        final Intent intent = new Intent();
-
-        if (context == null || mNotificationTargetClassName == null) {
-            return;
-        }
-        // Ensures that only a class in the original IME package will receive the
-        // notification.
-        if (mSuggestions == null || index < 0 || index >= mSuggestions.length) {
-            Log.w(TAG, "Unable to notify the suggestion as the index is out of range index=" + index
-                    + " length=" + mSuggestions.length);
-            return;
-        }
-
-        // The package name is not mandatory (legacy from JB), and if the package name
-        // is missing, we try to notify the suggestion through the input method manager.
-        if (mNotificationTargetPackageName != null) {
-            intent.setClassName(mNotificationTargetPackageName, mNotificationTargetClassName);
-            intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
-            intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, original);
-            intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, mSuggestions[index]);
-            intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, hashCode());
-            context.sendBroadcast(intent);
-        } else {
-            InputMethodManager imm = context.getSystemService(InputMethodManager.class);
-            if (imm != null) {
-                imm.notifySuggestionPicked(this, original, index);
-            }
-        }
+        Log.w(TAG, "notifySelection() is deprecated.  Does nothing.");
     }
 }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index b3da727..9ac0ca9 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -387,7 +387,8 @@
      */
     @NonNull
     public WindowInsets consumeDisplayCutout() {
-        return new WindowInsets(mTypeInsetsMap, mTypeMaxInsetsMap,
+        return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap,
+                mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
                 mIsRound, mAlwaysConsumeNavBar,
                 null /* displayCutout */);
     }
@@ -872,7 +873,7 @@
         @NonNull
         public Builder setStableInsets(@NonNull Insets stableInsets) {
             Preconditions.checkNotNull(stableInsets);
-            assignCompatInsets(mTypeInsetsMap, stableInsets.toRect());
+            assignCompatInsets(mTypeMaxInsetsMap, stableInsets.toRect());
             mStableInsetsConsumed = false;
             return this;
         }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 1ba7d8e..86c5f18 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -980,24 +980,30 @@
         InputMethodPrivilegedOperationsRegistry.get(imeToken).updateStatusIcon(null, 0);
     }
 
-    /** @hide */
+    /**
+     * This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
+     *
+     * @param spans will be ignored.
+     *
+     * @deprecated Do not use.
+     * @hide
+     */
+    @Deprecated
     @UnsupportedAppUsage
     public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
-        try {
-            mService.registerSuggestionSpansForNotification(spans);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        Log.w(TAG, "registerSuggestionSpansForNotification() is deprecated.  Does nothing.");
     }
 
-    /** @hide */
+    /**
+     * This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
+     *
+     * @deprecated Do not use.
+     * @hide
+     */
+    @Deprecated
     @UnsupportedAppUsage
     public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
-        try {
-            mService.notifySuggestionPicked(span, originalString, index);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        Log.w(TAG, "notifySuggestionPicked() is deprecated.  Does nothing.");
     }
 
     /**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 55364ec..4a60b6a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2901,10 +2901,6 @@
             }
         }
 
-        // Notify source IME of the suggestion pick. Do this before swapping texts.
-        targetSuggestionSpan.notifySelection(
-                mTextView.getContext(), originalText, suggestionInfo.mSuggestionIndex);
-
         // Swap text content between actual text and Suggestion span
         final int suggestionStart = suggestionInfo.mSuggestionStart;
         final int suggestionEnd = suggestionInfo.mSuggestionEnd;
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 70f4ed2..356d178 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -17,7 +17,6 @@
 package com.android.internal.view;
 
 import android.os.ResultReceiver;
-import android.text.style.SuggestionSpan;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 import android.view.inputmethod.EditorInfo;
@@ -65,8 +64,6 @@
             int displayId);
     void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
     boolean isInputMethodPickerShownForTest();
-    void registerSuggestionSpansForNotification(in SuggestionSpan[] spans);
-    boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index);
     InputMethodSubtype getCurrentInputMethodSubtype();
     boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
     void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 7f7528d..78688ed 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -18,9 +18,7 @@
 
 import android.os.Bundle;
 import android.text.Editable;
-import android.text.Spanned;
 import android.text.method.KeyListener;
-import android.text.style.SuggestionSpan;
 import android.util.Log;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.CompletionInfo;
@@ -173,12 +171,6 @@
         if (mTextView == null) {
             return super.commitText(text, newCursorPosition);
         }
-        if (text instanceof Spanned) {
-            Spanned spanned = ((Spanned) text);
-            SuggestionSpan[] spans = spanned.getSpans(0, text.length(), SuggestionSpan.class);
-            mIMM.registerSuggestionSpansForNotification(spans);
-        }
-
         mTextView.resetErrorChangedFlag();
         boolean success = super.commitText(text, newCursorPosition);
         mTextView.hideErrorIfUnchanged();
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 9e32206..0d8ede7 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -488,13 +488,14 @@
 
 static jint
 android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz, jint device, jint state, jstring device_address, jstring device_name,
-                                                   jint codec __unused)
+                                                   jint codec)
 {
     const char *c_address = env->GetStringUTFChars(device_address, NULL);
     const char *c_name = env->GetStringUTFChars(device_name, NULL);
     int status = check_AudioSystem_Command(AudioSystem::setDeviceConnectionState(static_cast <audio_devices_t>(device),
                                           static_cast <audio_policy_dev_state_t>(state),
-                                          c_address, c_name));
+                                          c_address, c_name,
+                                          static_cast <audio_format_t>(codec)));
     env->ReleaseStringUTFChars(device_address, c_address);
     env->ReleaseStringUTFChars(device_name, c_name);
     return (jint) status;
@@ -512,12 +513,12 @@
 
 static jint
 android_media_AudioSystem_handleDeviceConfigChange(JNIEnv *env, jobject thiz, jint device, jstring device_address, jstring device_name,
-                                                   jint codec __unused)
+                                                   jint codec)
 {
     const char *c_address = env->GetStringUTFChars(device_address, NULL);
     const char *c_name = env->GetStringUTFChars(device_name, NULL);
     int status = check_AudioSystem_Command(AudioSystem::handleDeviceConfigChange(static_cast <audio_devices_t>(device),
-                                          c_address, c_name));
+                                          c_address, c_name, static_cast <audio_format_t>(codec)));
     env->ReleaseStringUTFChars(device_address, c_address);
     env->ReleaseStringUTFChars(device_name, c_name);
     return (jint) status;
@@ -2038,10 +2039,8 @@
         return (jint)AUDIO_JAVA_BAD_VALUE;
     }
     std::vector<audio_format_t> encodingFormats;
-    //FIXME: enable when native implementaiton is merged
-    //status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP(
-    //                      &encodingFormats);
-    status_t status = NO_ERROR;
+    status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP(
+                          &encodingFormats);
     if (status != NO_ERROR) {
         ALOGE("%s: error %d", __FUNCTION__, status);
         jStatus = nativeToJavaStatus(status);
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 41e00b9..d60d1a6 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -34,6 +34,7 @@
 // Static whitelist of open paths that the zygote is allowed to keep open.
 static const char* kPathWhitelist[] = {
   "/apex/com.android.conscrypt/javalib/conscrypt.jar",
+  "/apex/com.android.media/javalib/updatable-media.jar",
   "/dev/null",
   "/dev/socket/zygote",
   "/dev/socket/zygote_secondary",
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 0fb1c7c..87ad3d1 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -145,6 +145,7 @@
                     Settings.Global.BLOCKED_SLICES,
                     Settings.Global.BLOCKING_HELPER_DISMISS_TO_VIEW_RATIO_LIMIT,
                     Settings.Global.BLOCKING_HELPER_STREAK_LIMIT,
+                    Settings.Global.BLUETOOTH_BTSNOOP_DEFAULT_MODE,
                     Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
                     Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
                     Settings.Global.BLUETOOTH_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX,
diff --git a/media/Android.bp b/media/Android.bp
index 8ebc91a..0eb86ac 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -16,8 +16,7 @@
 }
 
 java_library {
-    // TODO: include media2.jar in the media apex and add it to the bootclasspath.
-    name: "media2",
+    name: "updatable-media",
 
     srcs: [
         ":media2-srcs",
@@ -28,6 +27,8 @@
         "mediaplayer2-protos",
     ],
 
+    installable: true,
+
     // Make sure that the implementaion only relies on SDK or system APIs.
     sdk_version: "system_current",
 }
@@ -35,19 +36,19 @@
 filegroup {
     name: "media2-srcs",
     srcs: [
-        "java/android/media/CloseGuard.java",
-        "java/android/media/DataSourceCallback.java",
-        "java/android/media/DataSourceDesc.java",
-        "java/android/media/UriDataSourceDesc.java",
-        "java/android/media/FileDataSourceDesc.java",
-        "java/android/media/CallbackDataSourceDesc.java",
-        "java/android/media/VideoSize.java",
-        "java/android/media/Media2Utils.java",
-        "java/android/media/MediaPlayer2Utils.java",
-        "java/android/media/MediaPlayer2.java",
-        "java/android/media/Media2HTTPService.java",
-        "java/android/media/Media2HTTPConnection.java",
-        "java/android/media/RoutingDelegate.java",
-        "java/android/media/BufferingParams.java",
+        "apex/java/android/media/CloseGuard.java",
+        "apex/java/android/media/DataSourceCallback.java",
+        "apex/java/android/media/DataSourceDesc.java",
+        "apex/java/android/media/UriDataSourceDesc.java",
+        "apex/java/android/media/FileDataSourceDesc.java",
+        "apex/java/android/media/CallbackDataSourceDesc.java",
+        "apex/java/android/media/VideoSize.java",
+        "apex/java/android/media/Media2Utils.java",
+        "apex/java/android/media/MediaPlayer2Utils.java",
+        "apex/java/android/media/MediaPlayer2.java",
+        "apex/java/android/media/Media2HTTPService.java",
+        "apex/java/android/media/Media2HTTPConnection.java",
+        "apex/java/android/media/RoutingDelegate.java",
+        "apex/java/android/media/BufferingParams.java",
     ],
 }
diff --git a/media/java/android/media/BufferingParams.java b/media/apex/java/android/media/BufferingParams.java
similarity index 100%
rename from media/java/android/media/BufferingParams.java
rename to media/apex/java/android/media/BufferingParams.java
diff --git a/media/java/android/media/CallbackDataSourceDesc.java b/media/apex/java/android/media/CallbackDataSourceDesc.java
similarity index 100%
rename from media/java/android/media/CallbackDataSourceDesc.java
rename to media/apex/java/android/media/CallbackDataSourceDesc.java
diff --git a/media/java/android/media/CloseGuard.java b/media/apex/java/android/media/CloseGuard.java
similarity index 100%
rename from media/java/android/media/CloseGuard.java
rename to media/apex/java/android/media/CloseGuard.java
diff --git a/media/java/android/media/DataSourceCallback.java b/media/apex/java/android/media/DataSourceCallback.java
similarity index 100%
rename from media/java/android/media/DataSourceCallback.java
rename to media/apex/java/android/media/DataSourceCallback.java
diff --git a/media/java/android/media/DataSourceDesc.java b/media/apex/java/android/media/DataSourceDesc.java
similarity index 100%
rename from media/java/android/media/DataSourceDesc.java
rename to media/apex/java/android/media/DataSourceDesc.java
diff --git a/media/java/android/media/FileDataSourceDesc.java b/media/apex/java/android/media/FileDataSourceDesc.java
similarity index 100%
rename from media/java/android/media/FileDataSourceDesc.java
rename to media/apex/java/android/media/FileDataSourceDesc.java
diff --git a/media/java/android/media/Media2HTTPConnection.java b/media/apex/java/android/media/Media2HTTPConnection.java
similarity index 100%
rename from media/java/android/media/Media2HTTPConnection.java
rename to media/apex/java/android/media/Media2HTTPConnection.java
diff --git a/media/java/android/media/Media2HTTPService.java b/media/apex/java/android/media/Media2HTTPService.java
similarity index 100%
rename from media/java/android/media/Media2HTTPService.java
rename to media/apex/java/android/media/Media2HTTPService.java
diff --git a/media/java/android/media/Media2Utils.java b/media/apex/java/android/media/Media2Utils.java
similarity index 100%
rename from media/java/android/media/Media2Utils.java
rename to media/apex/java/android/media/Media2Utils.java
diff --git a/media/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
similarity index 100%
rename from media/java/android/media/MediaPlayer2.java
rename to media/apex/java/android/media/MediaPlayer2.java
diff --git a/media/java/android/media/MediaPlayer2Utils.java b/media/apex/java/android/media/MediaPlayer2Utils.java
similarity index 100%
rename from media/java/android/media/MediaPlayer2Utils.java
rename to media/apex/java/android/media/MediaPlayer2Utils.java
diff --git a/media/java/android/media/RoutingDelegate.java b/media/apex/java/android/media/RoutingDelegate.java
similarity index 100%
rename from media/java/android/media/RoutingDelegate.java
rename to media/apex/java/android/media/RoutingDelegate.java
diff --git a/media/java/android/media/UriDataSourceDesc.java b/media/apex/java/android/media/UriDataSourceDesc.java
similarity index 100%
rename from media/java/android/media/UriDataSourceDesc.java
rename to media/apex/java/android/media/UriDataSourceDesc.java
diff --git a/media/java/android/media/VideoSize.java b/media/apex/java/android/media/VideoSize.java
similarity index 100%
rename from media/java/android/media/VideoSize.java
rename to media/apex/java/android/media/VideoSize.java
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index 7c5335b..dd97195 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -152,6 +152,7 @@
                     // No-op
                 }
             }
+            mConnectedToken = null;
             mPendingCommands.clear();
             mRequestedCommandSeqNumbers.clear();
             mCallbackExecutor.execute(() -> {
@@ -162,6 +163,22 @@
     }
 
     /**
+     * Returns {@link Session2Token} of the connected session.
+     * If it is not connected yet, it returns {@code null}.
+     * <p>
+     * This may differ with the {@link Session2Token} from the constructor. For example, if the
+     * controller is created with the token for MediaSession2Service, this would return
+     * token for the {@link MediaSession2} in the service.
+     *
+     * @return Session2Token of the connected session, or {@code null} if not connected
+     */
+    public Session2Token getConnectedSessionToken() {
+        synchronized (mLock) {
+            return mConnectedToken;
+        }
+    }
+
+    /**
      * Returns whether the session's playback is active.
      *
      * @return {@code true} if playback active. {@code false} otherwise.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 0198470..48dbf55 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -84,7 +84,7 @@
 }
 
 cc_library_shared {
-    name: "libmediaplayer2_jni",
+    name: "libmedia2_jni",
 
     srcs: [
         "android_media_DataSourceCallback.cpp",
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
deleted file mode 100644
index 1d7d6de..0000000
--- a/media/jni/Android.mk
+++ /dev/null
@@ -1,30 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_CFLAGS := -Wall -Werror
-LOCAL_SRC_FILES := \
-    Media2Jni.cpp \
-
-# TODO: Move libmedia2_jni from system to media apex. Currently, libraries defined in
-#       Android.mk is not visible in apex build.
-LOCAL_MODULE:= libmedia2_jni
-LOCAL_SHARED_LIBRARIES := libdl liblog
-
-sanitizer_runtime_libraries := $(call normalize-path-list,$(addsuffix .so,\
-  $(ADDRESS_SANITIZER_RUNTIME_LIBRARY) \
-  $(UBSAN_RUNTIME_LIBRARY) \
-  $(TSAN_RUNTIME_LIBRARY)))
-
-# $(info Sanitizer:  $(sanitizer_runtime_libraries))
-
-ndk_libraries := $(call normalize-path-list,$(addprefix lib,$(addsuffix .so,\
-  $(NDK_PREBUILT_SHARED_LIBRARIES))))
-
-# $(info NDK:  $(ndk_libraries))
-
-LOCAL_CFLAGS += -DLINKED_LIBRARIES='"$(sanitizer_runtime_libraries):$(ndk_libraries)"'
-
-sanitizer_runtime_libraries :=
-ndk_libraries :=
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/media/jni/Media2Jni.cpp b/media/jni/Media2Jni.cpp
deleted file mode 100644
index 6c0a65c..0000000
--- a/media/jni/Media2Jni.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
-**
-** Copyright 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.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaPlayer2-JNI"
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <android/dlext.h>
-#include <dirent.h>
-#include <dlfcn.h>
-#include <errno.h>
-#include <string.h>
-
-extern "C" {
-  // Copied from GraphicsEnv.cpp
-  // TODO(b/37049319) Get this from a header once one exists
-  android_namespace_t* android_create_namespace(const char* name,
-                                                const char* ld_library_path,
-                                                const char* default_library_path,
-                                                uint64_t type,
-                                                const char* permitted_when_isolated_path,
-                                                android_namespace_t* parent);
-  bool android_link_namespaces(android_namespace_t* from,
-                               android_namespace_t* to,
-                               const char* shared_libs_sonames);
-  enum {
-     ANDROID_NAMESPACE_TYPE_ISOLATED = 1,
-  };
-
-}  // extern "C"
-
-static const char kApexLibPath[] =  "/apex/com.android.media/lib"
-#ifdef __LP64__
-    "64"
-#endif
-    "/";
-static const char kMediaPlayer2LibPath[] =  "/apex/com.android.media/lib"
-#ifdef __LP64__
-    "64"
-#endif
-    "/libmediaplayer2_jni.so";
-
-typedef jint (*Media2JniOnLoad)(JavaVM*, void*);
-
-JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
-{
-    android_namespace_t *media2Ns = android_create_namespace("media2",
-            nullptr,  // ld_library_path
-            kApexLibPath,
-            ANDROID_NAMESPACE_TYPE_ISOLATED,
-            nullptr,  // permitted_when_isolated_path
-            nullptr); // parent
-    if (!android_link_namespaces(media2Ns, nullptr, LINKED_LIBRARIES)) {
-        ALOGE("Failed to link namespace. Failed to load extractor plug-ins in apex.");
-        return -1;
-    }
-    const android_dlextinfo dlextinfo = {
-        .flags = ANDROID_DLEXT_USE_NAMESPACE,
-        .library_namespace = media2Ns,
-    };
-    // load libmediaplayer2_jni and call JNI_OnLoad.
-    void *libHandle = android_dlopen_ext(kMediaPlayer2LibPath, RTLD_NOW | RTLD_LOCAL, &dlextinfo);
-    if (libHandle == NULL) {
-        ALOGW("couldn't dlopen(%s) %s", kMediaPlayer2LibPath, strerror(errno));
-        return -1;
-    }
-    Media2JniOnLoad media2JniOnLoad = (Media2JniOnLoad) dlsym(libHandle, "JNI_OnLoad");
-    if (!media2JniOnLoad) {
-        ALOGW("%s does not contain JNI_OnLoad()", kMediaPlayer2LibPath);
-        dlclose(libHandle);
-        return -1;
-    }
-    return media2JniOnLoad(vm, reserved);
-}
diff --git a/packages/NetworkStack/src/com/android/server/NetworkStackService.java b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
index 057012d..cca71e7 100644
--- a/packages/NetworkStack/src/com/android/server/NetworkStackService.java
+++ b/packages/NetworkStack/src/com/android/server/NetworkStackService.java
@@ -20,6 +20,7 @@
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
 
+import static com.android.server.util.PermissionUtil.checkDumpPermission;
 import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
 
 import android.annotation.NonNull;
@@ -139,7 +140,7 @@
         @Override
         protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
                 @Nullable String[] args) {
-            checkNetworkStackCallingPermission();
+            checkDumpPermission();
             final IndentingPrintWriter pw = new IndentingPrintWriter(fout, "  ");
             pw.println("NetworkStack logs:");
             mLog.dump(fd, pw, args);
diff --git a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
index 733f873..82bf038 100644
--- a/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
+++ b/packages/NetworkStack/src/com/android/server/util/PermissionUtil.java
@@ -31,8 +31,21 @@
      */
     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());
+        final int caller = getCallingUid();
+        if (caller != Process.SYSTEM_UID && caller != Process.BLUETOOTH_UID) {
+            throw new SecurityException("Invalid caller: " + caller);
+        }
+    }
+
+    /**
+     * Check that the caller is allowed to dump the network stack, e.g. dumpsys.
+     * @throws SecurityException The caller is not allowed to dump the network stack.
+     */
+    public static void checkDumpPermission() {
+        final int caller = getCallingUid();
+        if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID
+                && caller != Process.SHELL_UID) {
+            throw new SecurityException("No dump permissions for caller: " + caller);
         }
     }
 
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 09fa284..ed3c11c 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -102,6 +102,20 @@
 
 
     <!-- Bluetooth settings -->
+    <!-- Titles for Bluetooth HCI Snoop Logging -->
+    <string-array name="bt_hci_snoop_log_entries">
+        <item>Disabled</item>
+        <item>Enabled Filtered</item>
+        <item>Enabled</item>
+    </string-array>
+
+    <!-- Values for Bluetooth HCI Snoop Logging -->
+    <string-array name="bt_hci_snoop_log_values" translatable="false">
+        <item>disabled</item>
+        <item>filtered</item>
+        <item>full</item>
+    </string-array>
+
     <!-- Titles for Bluetooth AVRCP Versions -->
     <string-array name="bluetooth_avrcp_versions">
         <item>AVRCP 1.4 (Default)</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 16bfcc9..c8f8d73 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -591,8 +591,8 @@
     <string name="keep_screen_on_summary">Screen will never sleep while charging</string>
     <!-- Setting Checkbox title whether to enable Bluetooth HCI snoop log -->
     <string name="bt_hci_snoop_log">Enable Bluetooth HCI snoop log</string>
-    <!-- setting Checkbox summary whether to capture all Bluetooth HCI packets in a file -->
-    <string name="bt_hci_snoop_log_summary">Capture all Bluetooth HCI packets in a file (Toggle Bluetooth after changing this setting)</string>
+    <!-- setting Checkbox summary whether to capture all Bluetooth HCI packets in a file [CHAR_LIMIT=100] -->
+    <string name="bt_hci_snoop_log_summary">Capture Bluetooth packets. (Toggle Bluetooth after changing this setting)</string>
     <!-- setting Checkbox title whether to enable OEM unlock [CHAR_LIMIT=35] -->
     <string name="oem_unlock_enable">OEM unlocking</string>
     <!-- setting Checkbox summary whether to enable OEM unlock [CHAR_LIMIT=50] -->
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dc03369..be910d4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -58,6 +58,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -184,6 +185,7 @@
 
     @GuardedBy("mService")
     final void updateOomAdjLocked() {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "updateOomAdj");
         mService.mOomAdjProfiler.oomAdjStarted();
         final ProcessRecord TOP_APP = mService.getTopAppLocked();
         final long now = SystemClock.uptimeMillis();
@@ -568,6 +570,7 @@
             }
         }
         mService.mOomAdjProfiler.oomAdjEnded();
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9041693..6c3cc58 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1243,7 +1243,7 @@
                             // Uh oh, current input method is no longer around!
                             // Pick another one...
                             Slog.i(TAG, "Current input method removed: " + curInputMethodId);
-                            updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
+                            updateSystemUiLocked(0 /* vis */, mBackDisposition);
                             if (!chooseNewDefaultIMELocked()) {
                                 changed = true;
                                 curIm = null;
@@ -1572,7 +1572,7 @@
                 if (mStatusBar != null) {
                     mStatusBar.setIconVisibility(mSlotIme, false);
                 }
-                updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
+                updateSystemUiLocked(mImeWindowVis, mBackDisposition);
                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
                         com.android.internal.R.bool.show_ongoing_ime_switcher);
                 if (mShowOngoingImeSwitcherForPhones) {
@@ -1654,16 +1654,11 @@
      * @return true if and only if non-null valid token is specified.
      */
     @GuardedBy("mMethodMap")
-    private boolean calledWithValidTokenLocked(@Nullable IBinder token) {
-        if (token == null && Binder.getCallingPid() == Process.myPid()) {
-            if (DEBUG) {
-                // TODO(b/34851776): Basically it's the caller's fault if we reach here.
-                Slog.d(TAG, "Bug 34851776 is detected callers=" + Debug.getCallers(10));
-            }
-            return false;
+    private boolean calledWithValidTokenLocked(@NonNull IBinder token) {
+        if (token == null) {
+            throw new InvalidParameterException("token must not be null.");
         }
-        if (token == null || token != mCurToken) {
-            // TODO(b/34886274): The semantics of this verification is actually not well-defined.
+        if (token != mCurToken) {
             Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
                     + " uid:" + Binder.getCallingUid() + " token:" + token);
             return false;
@@ -2260,7 +2255,7 @@
                     sessionState.session.finishSession();
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Session failed to close due to remote exception", e);
-                    updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
+                    updateSystemUiLocked(0 /* vis */, mBackDisposition);
                 }
                 sessionState.session = null;
             }
@@ -2419,14 +2414,14 @@
 
     @BinderThread
     @SuppressWarnings("deprecation")
-    private void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
+    private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) {
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
             }
             mImeWindowVis = vis;
             mBackDisposition = backDisposition;
-            updateSystemUiLocked(mCurToken, vis, backDisposition);
+            updateSystemUiLocked(vis, backDisposition);
         }
 
         final boolean dismissImeOnBackKeyPressed;
@@ -2447,7 +2442,7 @@
     }
 
     @BinderThread
-    private void reportStartInput(IBinder token, IBinder startInputToken) {
+    private void reportStartInput(@NonNull IBinder token, IBinder startInputToken) {
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
@@ -2461,11 +2456,10 @@
     }
 
     // Caution! This method is called in this class. Handle multi-user carefully
-    private void updateSystemUiLocked(IBinder token, int vis, int backDisposition) {
-        if (!calledWithValidTokenLocked(token)) {
+    private void updateSystemUiLocked(int vis, int backDisposition) {
+        if (mCurToken == null) {
             return;
         }
-
         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
         // all updateSystemUi happens on system previlege.
         final long ident = Binder.clearCallingIdentity();
@@ -2477,7 +2471,7 @@
             // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
             if (mStatusBar != null) {
-                mStatusBar.setImeWindowStatus(token, vis, backDisposition,
+                mStatusBar.setImeWindowStatus(mCurToken, vis, backDisposition,
                         needsToShowImeSwitcher);
             }
             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
@@ -2519,54 +2513,6 @@
         }
     }
 
-    @Override
-    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
-        synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
-                return;
-            }
-            final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
-            for (int i = 0; i < spans.length; ++i) {
-                SuggestionSpan ss = spans[i];
-                if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
-                    mSecureSuggestionSpans.put(ss, currentImi);
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
-        synchronized (mMethodMap) {
-            if (!calledFromValidUserLocked(!PER_PROFILE_IME_ENABLED)) {
-                return false;
-            }
-            final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
-            // TODO: Do not send the intent if the process of the targetImi is already dead.
-            if (targetImi != null) {
-                final String[] suggestions = span.getSuggestions();
-                if (index < 0 || index >= suggestions.length) return false;
-                final String className = span.getNotificationTargetClassName();
-                final Intent intent = new Intent();
-                // Ensures that only a class in the original IME package will receive the
-                // notification.
-                intent.setClassName(targetImi.getPackageName(), className);
-                intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
-                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
-                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
-                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
-                final long ident = Binder.clearCallingIdentity();
-                try {
-                    mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
-                } finally {
-                    Binder.restoreCallingIdentity(ident);
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
     void updateFromSettingsLocked(boolean enabledMayChange) {
         updateInputMethodsFromSettingsLocked(enabledMayChange);
         updateKeyboardFromSettingsLocked();
@@ -2667,7 +2613,7 @@
                 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
                 if (mCurMethod != null) {
                     try {
-                        updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
+                        updateSystemUiLocked(mImeWindowVis, mBackDisposition);
                         mCurMethod.changeInputMethodSubtype(newSubtype);
                     } catch (RemoteException e) {
                         Slog.w(TAG, "Failed to call changeInputMethodSubtype");
@@ -3183,7 +3129,7 @@
     }
 
     @BinderThread
-    private void setInputMethod(IBinder token, String id) {
+    private void setInputMethod(@NonNull IBinder token, String id) {
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
@@ -3193,7 +3139,8 @@
     }
 
     @BinderThread
-    private void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+    private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
+            InputMethodSubtype subtype) {
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
@@ -3222,7 +3169,7 @@
     }
 
     @BinderThread
-    private boolean switchToPreviousInputMethod(IBinder token) {
+    private boolean switchToPreviousInputMethod(@NonNull IBinder token) {
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return false;
@@ -3294,7 +3241,7 @@
     }
 
     @BinderThread
-    private boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+    private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme) {
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return false;
@@ -3712,7 +3659,7 @@
     private void handleSetInteractive(final boolean interactive) {
         synchronized (mMethodMap) {
             mIsInteractive = interactive;
-            updateSystemUiLocked(mCurToken, interactive ? mImeWindowVis : 0, mBackDisposition);
+            updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition);
 
             // Inform the current client of the change in active status
             if (mCurClient != null && mCurClient.client != null) {
@@ -4043,7 +3990,7 @@
             attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
             attrs.setTitle("Select input method");
             w.setAttributes(attrs);
-            updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
+            updateSystemUiLocked(mImeWindowVis, mBackDisposition);
             mSwitchingDialog.show();
         }
     }
@@ -4103,7 +4050,7 @@
             mSwitchingDialogTitleView = null;
         }
 
-        updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
+        updateSystemUiLocked(mImeWindowVis, mBackDisposition);
         mDialogBuilder = null;
         mIms = null;
     }
@@ -4381,7 +4328,7 @@
     }
 
     @BinderThread
-    private void reportFullscreenMode(IBinder token, boolean fullscreen) {
+    private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen) {
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
@@ -4816,8 +4763,10 @@
     private static final class InputMethodPrivilegedOperationsImpl
             extends IInputMethodPrivilegedOperations.Stub {
         private final InputMethodManagerService mImms;
+        @NonNull
         private final IBinder mToken;
-        InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms, IBinder token) {
+        InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
+                @NonNull IBinder token) {
             mImms = imms;
             mToken = token;
         }
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index f304ceb..6f0c5e8 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -53,7 +53,6 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Slog;
@@ -1525,20 +1524,6 @@
 
         @BinderThread
         @Override
-        public void registerSuggestionSpansForNotification(SuggestionSpan[] suggestionSpans) {
-            reportNotSupported();
-        }
-
-        @BinderThread
-        @Override
-        public boolean notifySuggestionPicked(
-                SuggestionSpan span, String originalString, int index) {
-            reportNotSupported();
-            return false;
-        }
-
-        @BinderThread
-        @Override
         public InputMethodSubtype getCurrentInputMethodSubtype() {
             reportNotSupported();
             return null;
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 8c274e4..bffd60b 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -962,12 +962,64 @@
 
     @Override
     public long getIfaceStats(String iface, int type) {
-        return nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
+        long nativeIfaceStats = nativeGetIfaceStat(iface, type, checkBpfStatsEnable());
+        if (nativeIfaceStats == -1) {
+            return nativeIfaceStats;
+        } else {
+            // When tethering offload is in use, nativeIfaceStats does not contain usage from
+            // offload, add it back here.
+            // When tethering offload is not in use, nativeIfaceStats contains tethering usage.
+            // this does not cause double-counting of tethering traffic, because
+            // NetdTetheringStatsProvider returns zero NetworkStats
+            // when called with STATS_PER_IFACE.
+            return nativeIfaceStats + getTetherStats(iface, type);
+        }
     }
 
     @Override
     public long getTotalStats(int type) {
-        return nativeGetTotalStat(type, checkBpfStatsEnable());
+        long nativeTotalStats = nativeGetTotalStat(type, checkBpfStatsEnable());
+        if (nativeTotalStats == -1) {
+            return nativeTotalStats;
+        } else {
+            // Refer to comment in getIfaceStats
+            return nativeTotalStats + getTetherStats(IFACE_ALL, type);
+        }
+    }
+
+    private long getTetherStats(String iface, int type) {
+        final NetworkStats tetherSnapshot;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error get TetherStats: " + e);
+            return 0;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        HashSet<String> limitIfaces;
+        if (iface == IFACE_ALL) {
+            limitIfaces = null;
+        } else {
+            limitIfaces = new HashSet<String>();
+            limitIfaces.add(iface);
+        }
+        NetworkStats.Entry entry = tetherSnapshot.getTotal(null, limitIfaces);
+        if (LOGD) Slog.d(TAG, "TetherStats: iface=" + iface + " type=" + type +
+                " entry=" + entry);
+        switch (type) {
+            case 0: // TYPE_RX_BYTES
+                return entry.rxBytes;
+            case 1: // TYPE_RX_PACKETS
+                return entry.rxPackets;
+            case 2: // TYPE_TX_BYTES
+                return entry.txBytes;
+            case 3: // TYPE_TX_PACKETS
+                return entry.txPackets;
+            default:
+                return 0;
+        }
     }
 
     private boolean checkBpfStatsEnable() {
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 33ff194..434084c 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -64,15 +64,11 @@
     public void layout(Rect outer, Rect inner, Point surfaceOrigin) {
         mOuter.set(outer);
         mInner.set(inner);
-        mOuter.offset(-surfaceOrigin.x, -surfaceOrigin.y);
-        mInner.offset(-surfaceOrigin.x, -surfaceOrigin.y);
-        outer = mOuter;
-        inner = mInner;
 
-        mTop.layout(outer.left, outer.top, inner.right, inner.top);
-        mLeft.layout(outer.left, inner.top, inner.left, outer.bottom);
-        mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom);
-        mRight.layout(inner.right, outer.top, outer.right, inner.bottom);
+        mTop.layout(outer.left, outer.top, inner.right, inner.top, surfaceOrigin);
+        mLeft.layout(outer.left, inner.top, inner.left, outer.bottom, surfaceOrigin);
+        mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin);
+        mRight.layout(inner.right, outer.top, outer.right, inner.bottom, surfaceOrigin);
     }
 
 
@@ -137,20 +133,18 @@
         private final String mType;
         private SurfaceControl mSurface;
 
-        private final Rect mSurfaceFrame = new Rect();
-        private final Rect mLayoutFrame = new Rect();
+        private final Rect mSurfaceFrameRelative = new Rect();
+        private final Rect mLayoutFrameGlobal = new Rect();
+        private final Rect mLayoutFrameRelative = new Rect();
 
         public LetterboxSurface(String type) {
             mType = type;
         }
 
-        public void layout(int left, int top, int right, int bottom) {
-            if (mLayoutFrame.left == left && mLayoutFrame.top == top
-                    && mLayoutFrame.right == right && mLayoutFrame.bottom == bottom) {
-                // Nothing changed.
-                return;
-            }
-            mLayoutFrame.set(left, top, right, bottom);
+        public void layout(int left, int top, int right, int bottom, Point surfaceOrigin) {
+            mLayoutFrameGlobal.set(left, top, right, bottom);
+            mLayoutFrameRelative.set(mLayoutFrameGlobal);
+            mLayoutFrameRelative.offset(-surfaceOrigin.x, -surfaceOrigin.y);
         }
 
         private void createSurface() {
@@ -168,32 +162,37 @@
         }
 
         public int getWidth() {
-            return Math.max(0, mLayoutFrame.width());
+            return Math.max(0, mLayoutFrameGlobal.width());
         }
 
         public int getHeight() {
-            return Math.max(0, mLayoutFrame.height());
+            return Math.max(0, mLayoutFrameGlobal.height());
         }
 
+        /**
+         * Returns if the given {@code rect} overlaps with this letterbox piece.
+         * @param rect the area to check for overlap in global coordinates
+         */
         public boolean isOverlappingWith(Rect rect) {
-            if (getWidth() <= 0 || getHeight() <= 0) {
+            if (mLayoutFrameGlobal.isEmpty()) {
                 return false;
             }
-            return Rect.intersects(rect, mLayoutFrame);
+            return Rect.intersects(rect, mLayoutFrameGlobal);
         }
 
         public void applySurfaceChanges(SurfaceControl.Transaction t) {
-            if (mSurfaceFrame.equals(mLayoutFrame)) {
+            if (mSurfaceFrameRelative.equals(mLayoutFrameRelative)) {
                 // Nothing changed.
                 return;
             }
-            mSurfaceFrame.set(mLayoutFrame);
-            if (!mSurfaceFrame.isEmpty()) {
+            mSurfaceFrameRelative.set(mLayoutFrameRelative);
+            if (!mSurfaceFrameRelative.isEmpty()) {
                 if (mSurface == null) {
                     createSurface();
                 }
-                t.setPosition(mSurface, mSurfaceFrame.left, mSurfaceFrame.top);
-                t.setWindowCrop(mSurface, mSurfaceFrame.width(), mSurfaceFrame.height());
+                t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
+                t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
+                        mSurfaceFrameRelative.height());
                 t.show(mSurface);
             } else if (mSurface != null) {
                 t.hide(mSurface);
@@ -201,7 +200,7 @@
         }
 
         public boolean needsApplySurfaceChanges() {
-            return !mSurfaceFrame.equals(mLayoutFrame);
+            return !mSurfaceFrameRelative.equals(mLayoutFrameRelative);
         }
     }
 }
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
index eaab650..238f077 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java
@@ -17,9 +17,24 @@
 package com.android.server.net.ipmemorystore;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentValues;
 import android.content.Context;
+import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteOpenHelper;
+import android.net.NetworkUtils;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.Status;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Encapsulating class for using the SQLite database backing the memory store.
@@ -30,6 +45,8 @@
  * @hide
  */
 public class IpMemoryStoreDatabase {
+    private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName();
+
     /**
      * Contract class for the Network Attributes table.
      */
@@ -57,7 +74,7 @@
         public static final String COLTYPE_DNSADDRESSES = "BLOB";
 
         public static final String COLNAME_MTU = "mtu";
-        public static final String COLTYPE_MTU = "INTEGER";
+        public static final String COLTYPE_MTU = "INTEGER DEFAULT -1";
 
         public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS "
                 + TABLENAME                 + " ("
@@ -108,7 +125,7 @@
     /** The SQLite DB helper */
     public static class DbHelper extends SQLiteOpenHelper {
         // Update this whenever changing the schema.
-        private static final int SCHEMA_VERSION = 1;
+        private static final int SCHEMA_VERSION = 2;
         private static final String DATABASE_FILENAME = "IpMemoryStore.db";
 
         public DbHelper(@NonNull final Context context) {
@@ -140,4 +157,216 @@
             onCreate(db);
         }
     }
+
+    @NonNull
+    private static byte[] encodeAddressList(@NonNull final List<InetAddress> addresses) {
+        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+        for (final InetAddress address : addresses) {
+            final byte[] b = address.getAddress();
+            os.write(b.length);
+            os.write(b, 0, b.length);
+        }
+        return os.toByteArray();
+    }
+
+    @NonNull
+    private static ArrayList<InetAddress> decodeAddressList(@NonNull final byte[] encoded) {
+        final ByteArrayInputStream is = new ByteArrayInputStream(encoded);
+        final ArrayList<InetAddress> addresses = new ArrayList<>();
+        int d = -1;
+        while ((d = is.read()) != -1) {
+            final byte[] bytes = new byte[d];
+            is.read(bytes, 0, d);
+            try {
+                addresses.add(InetAddress.getByAddress(bytes));
+            } catch (UnknownHostException e) { /* Hopefully impossible */ }
+        }
+        return addresses;
+    }
+
+    // Convert a NetworkAttributes object to content values to store them in a table compliant
+    // with the contract defined in NetworkAttributesContract.
+    @NonNull
+    private static ContentValues toContentValues(@NonNull final String key,
+            @Nullable final NetworkAttributes attributes, final long expiry) {
+        final ContentValues values = new ContentValues();
+        values.put(NetworkAttributesContract.COLNAME_L2KEY, key);
+        values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry);
+        if (null != attributes) {
+            if (null != attributes.assignedV4Address) {
+                values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS,
+                        NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address));
+            }
+            if (null != attributes.groupHint) {
+                values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint);
+            }
+            if (null != attributes.dnsAddresses) {
+                values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES,
+                        encodeAddressList(attributes.dnsAddresses));
+            }
+            if (null != attributes.mtu) {
+                values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu);
+            }
+        }
+        return values;
+    }
+
+    // Convert a byte array into content values to store it in a table compliant with the
+    // contract defined in PrivateDataContract.
+    @NonNull
+    private static ContentValues toContentValues(@NonNull final String key,
+            @NonNull final String clientId, @NonNull final String name,
+            @NonNull final byte[] data) {
+        final ContentValues values = new ContentValues();
+        values.put(PrivateDataContract.COLNAME_L2KEY, key);
+        values.put(PrivateDataContract.COLNAME_CLIENT, clientId);
+        values.put(PrivateDataContract.COLNAME_DATANAME, name);
+        values.put(PrivateDataContract.COLNAME_DATA, data);
+        return values;
+    }
+
+    private static final String[] EXPIRY_COLUMN = new String[] {
+        NetworkAttributesContract.COLNAME_EXPIRYDATE
+    };
+    static final int EXPIRY_ERROR = -1; // Legal values for expiry are positive
+
+    static final String SELECT_L2KEY = NetworkAttributesContract.COLNAME_L2KEY + " = ?";
+
+    // Returns the expiry date of the specified row, or one of the error codes above if the
+    // row is not found or some other error
+    static long getExpiry(@NonNull final SQLiteDatabase db, @NonNull final String key) {
+        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+                EXPIRY_COLUMN, // columns
+                SELECT_L2KEY, // selection
+                new String[] { key }, // selectionArgs
+                null, // groupBy
+                null, // having
+                null // orderBy
+        );
+        // L2KEY is the primary key ; it should not be possible to get more than one
+        // result here. 0 results means the key was not found.
+        if (cursor.getCount() != 1) return EXPIRY_ERROR;
+        cursor.moveToFirst();
+        return cursor.getLong(0); // index in the EXPIRY_COLUMN array
+    }
+
+    static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive
+
+    // Returns the relevance of the specified row, or one of the error codes above if the
+    // row is not found or some other error
+    static int getRelevance(@NonNull final SQLiteDatabase db, @NonNull final String key) {
+        final long expiry = getExpiry(db, key);
+        return expiry < 0 ? (int) expiry : RelevanceUtils.computeRelevanceForNow(expiry);
+    }
+
+    // If the attributes are null, this will only write the expiry.
+    // Returns an int out of Status.{SUCCESS,ERROR_*}
+    static int storeNetworkAttributes(@NonNull final SQLiteDatabase db, @NonNull final String key,
+            final long expiry, @Nullable final NetworkAttributes attributes) {
+        final ContentValues cv = toContentValues(key, attributes, expiry);
+        db.beginTransaction();
+        try {
+            // Unfortunately SQLite does not have any way to do INSERT OR UPDATE. Options are
+            // to either insert with on conflict ignore then update (like done here), or to
+            // construct a custom SQL INSERT statement with nested select.
+            final long resultId = db.insertWithOnConflict(NetworkAttributesContract.TABLENAME,
+                    null, cv, SQLiteDatabase.CONFLICT_IGNORE);
+            if (resultId < 0) {
+                db.update(NetworkAttributesContract.TABLENAME, cv, SELECT_L2KEY, new String[]{key});
+            }
+            db.setTransactionSuccessful();
+            return Status.SUCCESS;
+        } catch (SQLiteException e) {
+            // No space left on disk or something
+            Log.e(TAG, "Could not write to the memory store", e);
+        } finally {
+            db.endTransaction();
+        }
+        return Status.ERROR_STORAGE;
+    }
+
+    // Returns an int out of Status.{SUCCESS,ERROR_*}
+    static int storeBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
+            @NonNull final String clientId, @NonNull final String name,
+            @NonNull final byte[] data) {
+        final long res = db.insertWithOnConflict(PrivateDataContract.TABLENAME, null,
+                toContentValues(key, clientId, name, data), SQLiteDatabase.CONFLICT_REPLACE);
+        return (res == -1) ? Status.ERROR_STORAGE : Status.SUCCESS;
+    }
+
+    @Nullable
+    static NetworkAttributes retrieveNetworkAttributes(@NonNull final SQLiteDatabase db,
+            @NonNull final String key) {
+        final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME,
+                null, // columns, null means everything
+                NetworkAttributesContract.COLNAME_L2KEY + " = ?", // selection
+                new String[] { key }, // selectionArgs
+                null, // groupBy
+                null, // having
+                null); // orderBy
+        // L2KEY is the primary key ; it should not be possible to get more than one
+        // result here. 0 results means the key was not found.
+        if (cursor.getCount() != 1) return null;
+        cursor.moveToFirst();
+
+        // Make sure the data hasn't expired
+        final long expiry = cursor.getLong(
+                cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE));
+        if (expiry < System.currentTimeMillis()) return null;
+
+        final NetworkAttributes.Builder builder = new NetworkAttributes.Builder();
+        final int assignedV4AddressInt = getInt(cursor,
+                NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0);
+        final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT);
+        final byte[] dnsAddressesBlob =
+                getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES);
+        final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1);
+        if (0 != assignedV4AddressInt) {
+            builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt));
+        }
+        builder.setGroupHint(groupHint);
+        if (null != dnsAddressesBlob) {
+            builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob));
+        }
+        if (mtu >= 0) {
+            builder.setMtu(mtu);
+        }
+        return builder.build();
+    }
+
+    private static final String[] DATA_COLUMN = new String[] {
+            PrivateDataContract.COLNAME_DATA
+    };
+    @Nullable
+    static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
+            @NonNull final String clientId, @NonNull final String name) {
+        final Cursor cursor = db.query(PrivateDataContract.TABLENAME,
+                DATA_COLUMN, // columns
+                PrivateDataContract.COLNAME_L2KEY + " = ? AND " // selection
+                + PrivateDataContract.COLNAME_CLIENT + " = ? AND "
+                + PrivateDataContract.COLNAME_DATANAME + " = ?",
+                new String[] { key, clientId, name }, // selectionArgs
+                null, // groupBy
+                null, // having
+                null); // orderBy
+        // The query above is querying by (composite) primary key, so it should not be possible to
+        // get more than one result here. 0 results means the key was not found.
+        if (cursor.getCount() != 1) return null;
+        cursor.moveToFirst();
+        return cursor.getBlob(0); // index in the DATA_COLUMN array
+    }
+
+    // Helper methods
+    static String getString(final Cursor cursor, final String columnName) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return (columnIndex >= 0) ? cursor.getString(columnIndex) : null;
+    }
+    static byte[] getBlob(final Cursor cursor, final String columnName) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null;
+    }
+    static int getInt(final Cursor cursor, final String columnName, final int defaultValue) {
+        final int columnIndex = cursor.getColumnIndex(columnName);
+        return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue;
+    }
 }
diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
index 55a72190..444b299 100644
--- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
+++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java
@@ -16,6 +16,13 @@
 
 package com.android.server.net.ipmemorystore;
 
+import static android.net.ipmemorystore.Status.ERROR_DATABASE_CANNOT_BE_OPENED;
+import static android.net.ipmemorystore.Status.ERROR_GENERIC;
+import static android.net.ipmemorystore.Status.ERROR_ILLEGAL_ARGUMENT;
+import static android.net.ipmemorystore.Status.SUCCESS;
+
+import static com.android.server.net.ipmemorystore.IpMemoryStoreDatabase.EXPIRY_ERROR;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -28,7 +35,12 @@
 import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
 import android.net.ipmemorystore.IOnSameNetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.Status;
+import android.net.ipmemorystore.StatusParcelable;
+import android.net.ipmemorystore.Utils;
+import android.os.RemoteException;
 import android.util.Log;
 
 import java.util.concurrent.ExecutorService;
@@ -45,6 +57,7 @@
 public class IpMemoryStoreService extends IIpMemoryStore.Stub {
     private static final String TAG = IpMemoryStoreService.class.getSimpleName();
     private static final int MAX_CONCURRENT_THREADS = 4;
+    private static final boolean DBG = true;
 
     @NonNull
     final Context mContext;
@@ -114,6 +127,11 @@
         if (mDb != null) mDb.close();
     }
 
+    /** Helper function to make a status object */
+    private StatusParcelable makeStatus(final int code) {
+        return new Status(code).toParcelable();
+    }
+
     /**
      * Store network attributes for a given L2 key.
      *
@@ -128,11 +146,27 @@
      * 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.
      */
+    // Note that while l2Key and attributes are non-null in spirit, they are received from
+    // another process. If the remote process decides to ignore everything and send null, this
+    // process should still not crash.
     @Override
-    public void storeNetworkAttributes(@NonNull final String l2Key,
-            @NonNull final NetworkAttributesParcelable attributes,
+    public void storeNetworkAttributes(@Nullable final String l2Key,
+            @Nullable final NetworkAttributesParcelable attributes,
             @Nullable final IOnStatusListener listener) {
-        // TODO : implement this.
+        // Because the parcelable is 100% mutable, the thread may not see its members initialized.
+        // Therefore either an immutable object is created on this same thread before it's passed
+        // to the executor, or there need to be a write barrier here and a read barrier in the
+        // remote thread.
+        final NetworkAttributes na = null == attributes ? null : new NetworkAttributes(attributes);
+        mExecutor.execute(() -> {
+            try {
+                final int code = storeNetworkAttributesAndBlobSync(l2Key, na,
+                        null /* clientId */, null /* name */, null /* data */);
+                if (null != listener) listener.onComplete(makeStatus(code));
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
     }
 
     /**
@@ -141,16 +175,63 @@
      * @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 blob 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,
+    public void storeBlob(@Nullable final String l2Key, @Nullable final String clientId,
+            @Nullable final String name, @Nullable final Blob blob,
             @Nullable final IOnStatusListener listener) {
-        // TODO : implement this.
+        final byte[] data = null == blob ? null : blob.data;
+        mExecutor.execute(() -> {
+            try {
+                final int code = storeNetworkAttributesAndBlobSync(l2Key,
+                        null /* NetworkAttributes */, clientId, name, data);
+                if (null != listener) listener.onComplete(makeStatus(code));
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
+    }
+
+    /**
+     * Helper method for storeNetworkAttributes and storeBlob.
+     *
+     * Either attributes or none of clientId, name and data may be null. This will write the
+     * passed data if non-null, and will write attributes if non-null, but in any case it will
+     * bump the relevance up.
+     * Returns a success code from Status.
+     */
+    private int storeNetworkAttributesAndBlobSync(@Nullable final String l2Key,
+            @Nullable final NetworkAttributes attributes,
+            @Nullable final String clientId,
+            @Nullable final String name, @Nullable final byte[] data) {
+        if (null == l2Key) return ERROR_ILLEGAL_ARGUMENT;
+        if (null == attributes && null == data) return ERROR_ILLEGAL_ARGUMENT;
+        if (null != data && (null == clientId || null == name)) return ERROR_ILLEGAL_ARGUMENT;
+        if (null == mDb) return ERROR_DATABASE_CANNOT_BE_OPENED;
+        try {
+            final long oldExpiry = IpMemoryStoreDatabase.getExpiry(mDb, l2Key);
+            final long newExpiry = RelevanceUtils.bumpExpiryDate(
+                    oldExpiry == EXPIRY_ERROR ? System.currentTimeMillis() : oldExpiry);
+            final int errorCode =
+                    IpMemoryStoreDatabase.storeNetworkAttributes(mDb, l2Key, newExpiry, attributes);
+            // If no blob to store, the client is interested in the result of storing the attributes
+            if (null == data) return errorCode;
+            // Otherwise it's interested in the result of storing the blob
+            return IpMemoryStoreDatabase.storeBlob(mDb, l2Key, clientId, name, data);
+        } catch (Exception e) {
+            if (DBG) {
+                Log.e(TAG, "Exception while storing for key {" + l2Key
+                        + "} ; NetworkAttributes {" + (null == attributes ? "null" : attributes)
+                        + "} ; clientId {" + (null == clientId ? "null" : clientId)
+                        + "} ; name {" + (null == name ? "null" : name)
+                        + "} ; data {" + Utils.byteArrayToString(data) + "}", e);
+            }
+        }
+        return ERROR_GENERIC;
     }
 
     /**
@@ -198,9 +279,32 @@
      *         the query.
      */
     @Override
-    public void retrieveNetworkAttributes(@NonNull final String l2Key,
-            @NonNull final IOnNetworkAttributesRetrieved listener) {
-        // TODO : implement this.
+    public void retrieveNetworkAttributes(@Nullable final String l2Key,
+            @Nullable final IOnNetworkAttributesRetrieved listener) {
+        if (null == listener) return;
+        mExecutor.execute(() -> {
+            try {
+                if (null == l2Key) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null);
+                    return;
+                }
+                if (null == mDb) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
+                            null);
+                    return;
+                }
+                try {
+                    final NetworkAttributes attributes =
+                            IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key);
+                    listener.onL2KeyResponse(makeStatus(SUCCESS), l2Key,
+                            null == attributes ? null : attributes.toParcelable());
+                } catch (final Exception e) {
+                    listener.onL2KeyResponse(makeStatus(ERROR_GENERIC), l2Key, null);
+                }
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
     }
 
     /**
@@ -217,6 +321,28 @@
     @Override
     public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
             @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
-        // TODO : implement this.
+        if (null == listener) return;
+        mExecutor.execute(() -> {
+            try {
+                if (null == l2Key) {
+                    listener.onBlobRetrieved(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, name, null);
+                    return;
+                }
+                if (null == mDb) {
+                    listener.onBlobRetrieved(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key,
+                            name, null);
+                    return;
+                }
+                try {
+                    final Blob b = new Blob();
+                    b.data = IpMemoryStoreDatabase.retrieveBlob(mDb, l2Key, clientId, name);
+                    listener.onBlobRetrieved(makeStatus(SUCCESS), l2Key, name, b);
+                } catch (final Exception e) {
+                    listener.onBlobRetrieved(makeStatus(ERROR_GENERIC), l2Key, name, null);
+                }
+            } catch (final RemoteException e) {
+                // Client at the other end died
+            }
+        });
     }
 }
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index f037905..d8e2869 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -16,8 +16,19 @@
 
 package android.net.apf;
 
-import static android.net.util.NetworkConstants.*;
-import static android.system.OsConstants.*;
+import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static android.net.util.NetworkConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static android.net.util.NetworkConstants.ICMPV6_ROUTER_SOLICITATION;
+import static android.system.OsConstants.AF_PACKET;
+import static android.system.OsConstants.ARPHRD_ETHER;
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_RAW;
+
 import static com.android.internal.util.BitUtils.bytesToBEInt;
 import static com.android.internal.util.BitUtils.getUint16;
 import static com.android.internal.util.BitUtils.getUint32;
@@ -34,7 +45,7 @@
 import android.net.NetworkUtils;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClient;
+import android.net.ip.IpClientCallbacks;
 import android.net.metrics.ApfProgramEvent;
 import android.net.metrics.ApfStats;
 import android.net.metrics.IpConnectivityLog;
@@ -48,10 +59,14 @@
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Pair;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
+
+import libcore.io.IoBridge;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.Inet4Address;
@@ -63,7 +78,6 @@
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
-import libcore.io.IoBridge;
 
 /**
  * For networks that support packet filtering via APF programs, {@code ApfFilter}
@@ -308,7 +322,7 @@
     private static final int APF_MAX_ETH_TYPE_BLACK_LIST_LEN = 20;
 
     private final ApfCapabilities mApfCapabilities;
-    private final IpClient.Callback mIpClientCallback;
+    private final IpClientCallbacks mIpClientCallback;
     private final InterfaceParams mInterfaceParams;
     private final IpConnectivityLog mMetricsLog;
 
@@ -349,7 +363,7 @@
 
     @VisibleForTesting
     ApfFilter(Context context, ApfConfiguration config, InterfaceParams ifParams,
-            IpClient.Callback ipClientCallback, IpConnectivityLog log) {
+            IpClientCallbacks ipClientCallback, IpConnectivityLog log) {
         mApfCapabilities = config.apfCapabilities;
         mIpClientCallback = ipClientCallback;
         mInterfaceParams = ifParams;
@@ -1390,7 +1404,7 @@
      * filtering using APF programs.
      */
     public static ApfFilter maybeCreate(Context context, ApfConfiguration config,
-            InterfaceParams ifParams, IpClient.Callback ipClientCallback) {
+            InterfaceParams ifParams, IpClientCallbacks ipClientCallback) {
         if (context == null || config == null || ifParams == null) return null;
         ApfCapabilities apfCapabilities =  config.apfCapabilities;
         if (apfCapabilities == null) return null;
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 0176dd4..ff4e280 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -16,19 +16,19 @@
 
 package android.net.ip;
 
-import com.android.internal.util.HexDump;
-import com.android.internal.util.MessageUtils;
-import com.android.internal.util.WakeupMessage;
+import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
 
 import android.content.Context;
 import android.net.DhcpResults;
 import android.net.INetd;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
-import android.net.LinkProperties.ProvisioningChange;
 import android.net.LinkProperties;
+import android.net.LinkProperties.ProvisioningChange;
 import android.net.Network;
+import android.net.ProvisioningConfigurationParcelable;
 import android.net.ProxyInfo;
+import android.net.ProxyInfoParcelable;
 import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.apf.ApfCapabilities;
@@ -36,10 +36,10 @@
 import android.net.dhcp.DhcpClient;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
+import android.net.shared.InitialConfiguration;
 import android.net.util.InterfaceParams;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.NetdService;
-import android.net.util.NetworkConstants;
 import android.net.util.SharedLog;
 import android.os.ConditionVariable;
 import android.os.INetworkManagementService;
@@ -52,29 +52,24 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.IState;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.MessageUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.net.NetlinkTracker;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
 import java.net.InetAddress;
-import java.net.SocketException;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
 import java.util.List;
-import java.util.Set;
-import java.util.StringJoiner;
+import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.Predicate;
@@ -134,83 +129,17 @@
     }
 
     /**
-     * Callbacks for handling IpClient events.
-     *
-     * These methods are called by IpClient on its own thread. Implementations
-     * of this class MUST NOT carry out long-running computations or hold locks
-     * for which there might be contention with other code calling public
-     * methods of the same IpClient instance.
+     * TODO: remove after migrating clients to use IpClientCallbacks directly
+     * @see IpClientCallbacks
      */
-    public static class Callback {
-        // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
-        // when constructing a ProvisioningConfiguration.
-        //
-        // Implementations of onPreDhcpAction() must call
-        // IpClient#completedPreDhcpAction() to indicate that DHCP is clear
-        // to proceed.
-        public void onPreDhcpAction() {}
-        public void onPostDhcpAction() {}
+    public static class Callback extends IpClientCallbacks {}
 
-        // This is purely advisory and not an indication of provisioning
-        // success or failure.  This is only here for callers that want to
-        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
-        // DHCPv4 or static IPv4 configuration failure or success can be
-        // determined by whether or not the passed-in DhcpResults object is
-        // null or not.
-        public void onNewDhcpResults(DhcpResults dhcpResults) {}
-
-        public void onProvisioningSuccess(LinkProperties newLp) {}
-        public void onProvisioningFailure(LinkProperties newLp) {}
-
-        // Invoked on LinkProperties changes.
-        public void onLinkPropertiesChange(LinkProperties newLp) {}
-
-        // Called when the internal IpReachabilityMonitor (if enabled) has
-        // detected the loss of a critical number of required neighbors.
-        public void onReachabilityLost(String logMsg) {}
-
-        // Called when the IpClient state machine terminates.
-        public void onQuit() {}
-
-        // Install an APF program to filter incoming packets.
-        public void installPacketFilter(byte[] filter) {}
-
-        // Asynchronously read back the APF program & data buffer from the wifi driver.
-        // Due to Wifi HAL limitations, the current implementation only supports dumping the entire
-        // buffer. In response to this request, the driver returns the data buffer asynchronously
-        // by sending an IpClient#EVENT_READ_PACKET_FILTER_COMPLETE message.
-        public void startReadPacketFilter() {}
-
-        // If multicast filtering cannot be accomplished with APF, this function will be called to
-        // actuate multicast filtering using another means.
-        public void setFallbackMulticastFilter(boolean enabled) {}
-
-        // Enabled/disable Neighbor Discover offload functionality. This is
-        // called, for example, whenever 464xlat is being started or stopped.
-        public void setNeighborDiscoveryOffload(boolean enable) {}
-    }
-
-    public static class WaitForProvisioningCallback extends Callback {
-        private final ConditionVariable mCV = new ConditionVariable();
-        private LinkProperties mCallbackLinkProperties;
-
-        public LinkProperties waitForProvisioning() {
-            mCV.block();
-            return mCallbackLinkProperties;
-        }
-
-        @Override
-        public void onProvisioningSuccess(LinkProperties newLp) {
-            mCallbackLinkProperties = newLp;
-            mCV.open();
-        }
-
-        @Override
-        public void onProvisioningFailure(LinkProperties newLp) {
-            mCallbackLinkProperties = null;
-            mCV.open();
-        }
-    }
+    /**
+     * TODO: remove once clients are migrated to IpClientUtil.WaitForProvisioningCallback
+     * @see IpClientUtil.WaitForProvisioningCallbacks
+     */
+    public static class WaitForProvisioningCallback
+            extends IpClientUtil.WaitForProvisioningCallbacks {}
 
     // Use a wrapper class to log in order to ensure complete and detailed
     // logging. This method is lighter weight than annotations/reflection
@@ -232,12 +161,12 @@
     // once passed on to the callback they may be modified by another thread.
     //
     // TODO: Find an lighter weight approach.
-    private class LoggingCallbackWrapper extends Callback {
+    private class LoggingCallbackWrapper extends IpClientCallbacks {
         private static final String PREFIX = "INVOKE ";
-        private final Callback mCallback;
+        private final IpClientCallbacks mCallback;
 
-        public LoggingCallbackWrapper(Callback callback) {
-            mCallback = (callback != null) ? callback : new Callback();
+        LoggingCallbackWrapper(IpClientCallbacks callback) {
+            mCallback = (callback != null) ? callback : new IpClientCallbacks();
         }
 
         private void log(String msg) {
@@ -307,288 +236,102 @@
     }
 
     /**
-     * This class encapsulates parameters to be passed to
-     * IpClient#startProvisioning(). A defensive copy is made by IpClient
-     * and the values specified herein are in force until IpClient#stop()
-     * is called.
-     *
-     * Example use:
-     *
-     *     final ProvisioningConfiguration config =
-     *             mIpClient.buildProvisioningConfiguration()
-     *                     .withPreDhcpAction()
-     *                     .withProvisioningTimeoutMs(36 * 1000)
-     *                     .build();
-     *     mIpClient.startProvisioning(config);
-     *     ...
-     *     mIpClient.stop();
-     *
-     * The specified provisioning configuration will only be active until
-     * IpClient#stop() is called. Future calls to IpClient#startProvisioning()
-     * must specify the configuration again.
+     * TODO: remove after migrating clients to use the shared configuration class directly.
+     * @see android.net.shared.ProvisioningConfiguration
      */
-    public static class ProvisioningConfiguration {
-        // TODO: Delete this default timeout once those callers that care are
-        // fixed to pass in their preferred timeout.
-        //
-        // We pick 36 seconds so we can send DHCP requests at
-        //
-        //     t=0, t=2, t=6, t=14, t=30
-        //
-        // allowing for 10% jitter.
-        private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
-
-        public static class Builder {
-            private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
-
-            public Builder withoutIPv4() {
-                mConfig.mEnableIPv4 = false;
-                return this;
-            }
-
-            public Builder withoutIPv6() {
-                mConfig.mEnableIPv6 = false;
-                return this;
-            }
-
-            public Builder withoutMultinetworkPolicyTracker() {
-                mConfig.mUsingMultinetworkPolicyTracker = false;
-                return this;
-            }
-
-            public Builder withoutIpReachabilityMonitor() {
-                mConfig.mUsingIpReachabilityMonitor = false;
-                return this;
-            }
-
-            public Builder withPreDhcpAction() {
-                mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
-                return this;
-            }
-
-            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
-                mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
-                return this;
-            }
-
-            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
-                mConfig.mInitialConfig = initialConfig;
-                return this;
-            }
-
-            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
-                mConfig.mStaticIpConfig = staticConfig;
-                return this;
-            }
-
-            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
-                mConfig.mApfCapabilities = apfCapabilities;
-                return this;
-            }
-
-            public Builder withProvisioningTimeoutMs(int timeoutMs) {
-                mConfig.mProvisioningTimeoutMs = timeoutMs;
-                return this;
-            }
-
-            public Builder withRandomMacAddress() {
-                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
-                return this;
-            }
-
-            public Builder withStableMacAddress() {
-                mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-                return this;
-            }
-
-            public Builder withNetwork(Network network) {
-                mConfig.mNetwork = network;
-                return this;
-            }
-
-            public Builder withDisplayName(String displayName) {
-                mConfig.mDisplayName = displayName;
-                return this;
-            }
-
-            public ProvisioningConfiguration build() {
-                return new ProvisioningConfiguration(mConfig);
-            }
-        }
-
-        /* package */ boolean mEnableIPv4 = true;
-        /* package */ boolean mEnableIPv6 = true;
-        /* package */ boolean mUsingMultinetworkPolicyTracker = true;
-        /* package */ boolean mUsingIpReachabilityMonitor = true;
-        /* package */ int mRequestedPreDhcpActionMs;
-        /* package */ InitialConfiguration mInitialConfig;
-        /* package */ StaticIpConfiguration mStaticIpConfig;
-        /* package */ ApfCapabilities mApfCapabilities;
-        /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
-        /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
-        /* package */ Network mNetwork = null;
-        /* package */ String mDisplayName = null;
-
-        public ProvisioningConfiguration() {} // used by Builder
-
-        public ProvisioningConfiguration(ProvisioningConfiguration other) {
-            mEnableIPv4 = other.mEnableIPv4;
-            mEnableIPv6 = other.mEnableIPv6;
-            mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
-            mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
-            mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
-            mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
-            mStaticIpConfig = other.mStaticIpConfig;
-            mApfCapabilities = other.mApfCapabilities;
-            mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
-            mIPv6AddrGenMode = other.mIPv6AddrGenMode;
-            mNetwork = other.mNetwork;
-            mDisplayName = other.mDisplayName;
-        }
-
-        @Override
-        public String toString() {
-            return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
-                    .add("mEnableIPv4: " + mEnableIPv4)
-                    .add("mEnableIPv6: " + mEnableIPv6)
-                    .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
-                    .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
-                    .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
-                    .add("mInitialConfig: " + mInitialConfig)
-                    .add("mStaticIpConfig: " + mStaticIpConfig)
-                    .add("mApfCapabilities: " + mApfCapabilities)
-                    .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
-                    .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
-                    .add("mNetwork: " + mNetwork)
-                    .add("mDisplayName: " + mDisplayName)
-                    .toString();
-        }
-
-        public boolean isValid() {
-            return (mInitialConfig == null) || mInitialConfig.isValid();
-        }
-    }
-
-    public static class InitialConfiguration {
-        public final Set<LinkAddress> ipAddresses = new HashSet<>();
-        public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
-        public final Set<InetAddress> dnsServers = new HashSet<>();
-        public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config
-
-        public static InitialConfiguration copy(InitialConfiguration config) {
-            if (config == null) {
-                return null;
-            }
-            InitialConfiguration configCopy = new InitialConfiguration();
-            configCopy.ipAddresses.addAll(config.ipAddresses);
-            configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
-            configCopy.dnsServers.addAll(config.dnsServers);
-            return configCopy;
-        }
-
-        @Override
-        public String toString() {
-            return String.format(
-                    "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)",
-                    join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
-                    join(", ", dnsServers), gateway);
-        }
-
-        public boolean isValid() {
-            if (ipAddresses.isEmpty()) {
-                return false;
-            }
-
-            // For every IP address, there must be at least one prefix containing that address.
-            for (LinkAddress addr : ipAddresses) {
-                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
-                    return false;
-                }
-            }
-            // For every dns server, there must be at least one prefix containing that address.
-            for (InetAddress addr : dnsServers) {
-                if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
-                    return false;
-                }
-            }
-            // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
-            // (read: compliant with RFC4291#section2.5.4).
-            if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
-                return false;
-            }
-            // If directlyConnectedRoutes contains an IPv6 default route
-            // then ipAddresses MUST contain at least one non-ULA GUA.
-            if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
-                    && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
-                return false;
-            }
-            // The prefix length of routes in directlyConnectedRoutes be within reasonable
-            // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
-            if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
-                return false;
-            }
-            // There no more than one IPv4 address
-            if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) {
-                return false;
-            }
-
-            return true;
+    public static class ProvisioningConfiguration
+            extends android.net.shared.ProvisioningConfiguration {
+        public ProvisioningConfiguration(android.net.shared.ProvisioningConfiguration other) {
+            super(other);
         }
 
         /**
-         * @return true if the given list of addressess and routes satisfies provisioning for this
-         * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
-         * because addresses and routes seen by Netlink will contain additional fields like flags,
-         * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
-         * provisioning check always fails.
-         *
-         * If the given list of routes is null, only addresses are taken into considerations.
+         * @see android.net.shared.ProvisioningConfiguration.Builder
          */
-        public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
-            if (ipAddresses.isEmpty()) {
-                return false;
+        public static class Builder extends android.net.shared.ProvisioningConfiguration.Builder {
+            // Override all methods to have a return type matching this Builder
+            @Override
+            public Builder withoutIPv4() {
+                super.withoutIPv4();
+                return this;
             }
 
-            for (LinkAddress addr : ipAddresses) {
-                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
-                    return false;
-                }
+            @Override
+            public Builder withoutIPv6() {
+                super.withoutIPv6();
+                return this;
             }
 
-            if (routes != null) {
-                for (IpPrefix prefix : directlyConnectedRoutes) {
-                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
-                        return false;
-                    }
-                }
+            @Override
+            public Builder withoutMultinetworkPolicyTracker() {
+                super.withoutMultinetworkPolicyTracker();
+                return this;
             }
 
-            return true;
-        }
+            @Override
+            public Builder withoutIpReachabilityMonitor() {
+                super.withoutIpReachabilityMonitor();
+                return this;
+            }
 
-        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
-            return !route.hasGateway() && prefix.equals(route.getDestination());
-        }
+            @Override
+            public Builder withPreDhcpAction() {
+                super.withPreDhcpAction();
+                return this;
+            }
 
-        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
-            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
-        }
+            @Override
+            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+                super.withPreDhcpAction(dhcpActionTimeoutMs);
+                return this;
+            }
 
-        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
-            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
-        }
+            @Override
+            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+                super.withStaticConfiguration(staticConfig);
+                return this;
+            }
 
-        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
-            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
-                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
-        }
+            @Override
+            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+                super.withApfCapabilities(apfCapabilities);
+                return this;
+            }
 
-        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
-            return prefix.getAddress().equals(Inet6Address.ANY);
-        }
+            @Override
+            public Builder withProvisioningTimeoutMs(int timeoutMs) {
+                super.withProvisioningTimeoutMs(timeoutMs);
+                return this;
+            }
 
-        private static boolean isIPv6GUA(LinkAddress addr) {
-            return addr.isIPv6() && addr.isGlobalPreferred();
+            @Override
+            public Builder withRandomMacAddress() {
+                super.withRandomMacAddress();
+                return this;
+            }
+
+            @Override
+            public Builder withStableMacAddress() {
+                super.withStableMacAddress();
+                return this;
+            }
+
+            @Override
+            public Builder withNetwork(Network network) {
+                super.withNetwork(network);
+                return this;
+            }
+
+            @Override
+            public Builder withDisplayName(String displayName) {
+                super.withDisplayName(displayName);
+                return this;
+            }
+
+            @Override
+            public ProvisioningConfiguration build() {
+                return new ProvisioningConfiguration(mConfig);
+            }
         }
     }
 
@@ -638,7 +381,7 @@
     private final String mInterfaceName;
     private final String mClatInterfaceName;
     @VisibleForTesting
-    protected final Callback mCallback;
+    protected final IpClientCallbacks mCallback;
     private final Dependencies mDependencies;
     private final CountDownLatch mShutdownLatch;
     private final INetworkManagementService mNwService;
@@ -657,7 +400,7 @@
      * Non-final member variables accessed only from within our StateMachine.
      */
     private LinkProperties mLinkProperties;
-    private ProvisioningConfiguration mConfiguration;
+    private android.net.shared.ProvisioningConfiguration mConfiguration;
     private MultinetworkPolicyTracker mMultinetworkPolicyTracker;
     private IpReachabilityMonitor mIpReachabilityMonitor;
     private DhcpClient mDhcpClient;
@@ -691,7 +434,7 @@
         }
     }
 
-    public IpClient(Context context, String ifName, Callback callback) {
+    public IpClient(Context context, String ifName, IpClientCallbacks callback) {
         this(context, ifName, callback, new Dependencies());
     }
 
@@ -699,7 +442,7 @@
      * An expanded constructor, useful for dependency injection.
      * TODO: migrate all test users to mock IpClient directly and remove this ctor.
      */
-    public IpClient(Context context, String ifName, Callback callback,
+    public IpClient(Context context, String ifName, IpClientCallbacks callback,
             INetworkManagementService nwService) {
         this(context, ifName, callback, new Dependencies() {
             @Override
@@ -708,7 +451,7 @@
     }
 
     @VisibleForTesting
-    IpClient(Context context, String ifName, Callback callback, Dependencies deps) {
+    IpClient(Context context, String ifName, IpClientCallbacks callback, Dependencies deps) {
         super(IpClient.class.getSimpleName() + "." + ifName);
         Preconditions.checkNotNull(ifName);
         Preconditions.checkNotNull(callback);
@@ -795,6 +538,57 @@
         startStateMachineUpdaters();
     }
 
+    /**
+     * Make a IIpClient connector to communicate with this IpClient.
+     */
+    public IIpClient makeConnector() {
+        return new IpClientConnector();
+    }
+
+    class IpClientConnector extends IIpClient.Stub {
+        @Override
+        public void completedPreDhcpAction() {
+            IpClient.this.completedPreDhcpAction();
+        }
+        @Override
+        public void confirmConfiguration() {
+            IpClient.this.confirmConfiguration();
+        }
+        @Override
+        public void readPacketFilterComplete(byte[] data) {
+            IpClient.this.readPacketFilterComplete(data);
+        }
+        @Override
+        public void shutdown() {
+            IpClient.this.shutdown();
+        }
+        @Override
+        public void startProvisioning(ProvisioningConfigurationParcelable req) {
+            IpClient.this.startProvisioning(
+                    android.net.shared.ProvisioningConfiguration.fromStableParcelable(req));
+        }
+        @Override
+        public void stop() {
+            IpClient.this.stop();
+        }
+        @Override
+        public void setTcpBufferSizes(String tcpBufferSizes) {
+            IpClient.this.setTcpBufferSizes(tcpBufferSizes);
+        }
+        @Override
+        public void setHttpProxy(ProxyInfoParcelable proxyInfo) {
+            IpClient.this.setHttpProxy(fromStableParcelable(proxyInfo));
+        }
+        @Override
+        public void setMulticastFilter(boolean enabled) {
+            IpClient.this.setMulticastFilter(enabled);
+        }
+        // TODO: remove and have IpClient logs dumped in NetworkStack dumpsys
+        public void dumpIpClientLogs(FileDescriptor fd, PrintWriter pw, String[] args) {
+            IpClient.this.dump(fd, pw, args);
+        }
+    }
+
     private void configureAndStartStateMachine() {
         addState(mStoppedState);
         addState(mStartedState);
@@ -828,7 +622,9 @@
         mShutdownLatch.countDown();
     }
 
-    // Shut down this IpClient instance altogether.
+    /**
+     * Shut down this IpClient instance altogether.
+     */
     public void shutdown() {
         stop();
         sendMessage(CMD_TERMINATE_AFTER_STOP);
@@ -849,7 +645,10 @@
         return new ProvisioningConfiguration.Builder();
     }
 
-    public void startProvisioning(ProvisioningConfiguration req) {
+    /**
+     * Start provisioning with the provided parameters.
+     */
+    public void startProvisioning(android.net.shared.ProvisioningConfiguration req) {
         if (!req.isValid()) {
             doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
             return;
@@ -863,7 +662,7 @@
         }
 
         mCallback.setNeighborDiscoveryOffload(true);
-        sendMessage(CMD_START, new ProvisioningConfiguration(req));
+        sendMessage(CMD_START, new android.net.shared.ProvisioningConfiguration(req));
     }
 
     // TODO: Delete this.
@@ -874,7 +673,7 @@
     }
 
     public void startProvisioning() {
-        startProvisioning(new ProvisioningConfiguration());
+        startProvisioning(new android.net.shared.ProvisioningConfiguration());
     }
 
     public void stop() {
@@ -930,7 +729,7 @@
 
         // Thread-unsafe access to mApfFilter but just used for debugging.
         final ApfFilter apfFilter = mApfFilter;
-        final ProvisioningConfiguration provisioningConfig = mConfiguration;
+        final android.net.shared.ProvisioningConfiguration provisioningConfig = mConfiguration;
         final ApfCapabilities apfCapabilities = (provisioningConfig != null)
                 ? provisioningConfig.mApfCapabilities : null;
 
@@ -1463,7 +1262,7 @@
                     break;
 
                 case CMD_START:
-                    mConfiguration = (ProvisioningConfiguration) msg.obj;
+                    mConfiguration = (android.net.shared.ProvisioningConfiguration) msg.obj;
                     transitionTo(mStartedState);
                     break;
 
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
new file mode 100644
index 0000000..0aec101
--- /dev/null
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -0,0 +1,92 @@
+/*
+ * 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.net.ip;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.os.ConditionVariable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+
+/**
+ * Utilities and wrappers to simplify communication with IpClient, which lives in the NetworkStack
+ * process.
+ *
+ * @hide
+ */
+public class IpClientUtil {
+    // TODO: remove once IpClient dumps are moved to NetworkStack and callers don't need this arg
+    public static final String DUMP_ARG = IpClient.DUMP_ARG;
+
+    /**
+     * Subclass of {@link IpClientCallbacks} allowing clients to block until provisioning is
+     * complete with {@link WaitForProvisioningCallbacks#waitForProvisioning()}.
+     */
+    public static class WaitForProvisioningCallbacks extends IpClientCallbacks {
+        private final ConditionVariable mCV = new ConditionVariable();
+        private LinkProperties mCallbackLinkProperties;
+
+        /**
+         * Block until either {@link #onProvisioningSuccess(LinkProperties)} or
+         * {@link #onProvisioningFailure(LinkProperties)} is called.
+         */
+        public LinkProperties waitForProvisioning() {
+            mCV.block();
+            return mCallbackLinkProperties;
+        }
+
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            mCallbackLinkProperties = newLp;
+            mCV.open();
+        }
+
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            mCallbackLinkProperties = null;
+            mCV.open();
+        }
+    }
+
+    /**
+     * Create a new IpClient.
+     *
+     * <p>This is a convenience method to allow clients to use {@link IpClientCallbacks} instead of
+     * {@link IIpClientCallbacks}.
+     */
+    public static void makeIpClient(Context context, String ifName, IpClientCallbacks callback) {
+        // TODO: request IpClient asynchronously from NetworkStack.
+        final IpClient ipClient = new IpClient(context, ifName, callback);
+        callback.onIpClientCreated(ipClient.makeConnector());
+    }
+
+    /**
+     * Dump logs for the specified IpClient.
+     * TODO: remove logging from this method once IpClient logs are dumped in NetworkStack dumpsys,
+     * then remove callers and delete.
+     */
+    public static void dumpIpClient(
+            IIpClient connector, FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!(connector instanceof IpClient.IpClientConnector)) {
+            pw.println("Invalid connector");
+            return;
+        }
+        ((IpClient.IpClientConnector) connector).dumpIpClientLogs(fd, pw, args);
+    }
+}
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
deleted file mode 100644
index 2eb36a2..0000000
--- a/services/net/java/android/net/ip/IpManager.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.ip;
-
-import android.content.Context;
-import android.net.INetd;
-import android.net.LinkProperties;
-import android.net.Network;
-import android.net.StaticIpConfiguration;
-import android.net.apf.ApfCapabilities;
-import android.net.util.NetdService;
-import android.os.INetworkManagementService;
-import android.os.ServiceManager;
-import android.net.apf.ApfCapabilities;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-
-/*
- * TODO: Delete this altogether in favor of its renamed successor: IpClient.
- *
- * @hide
- */
-public class IpManager extends IpClient {
-    public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration {
-        public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) {
-            super(ipcConfig);
-        }
-
-        public static class Builder extends IpClient.ProvisioningConfiguration.Builder {
-            @Override
-            public Builder withoutIPv4() {
-                super.withoutIPv4();
-                return this;
-            }
-            @Override
-            public Builder withoutIPv6() {
-                super.withoutIPv6();
-                return this;
-            }
-            @Override
-            public Builder withoutIpReachabilityMonitor() {
-                super.withoutIpReachabilityMonitor();
-                return this;
-            }
-            @Override
-            public Builder withPreDhcpAction() {
-                super.withPreDhcpAction();
-                return this;
-            }
-            @Override
-            public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
-                super.withPreDhcpAction(dhcpActionTimeoutMs);
-                return this;
-            }
-            // No Override; locally defined type.
-            public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
-                super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig);
-                return this;
-            }
-            @Override
-            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
-                super.withStaticConfiguration(staticConfig);
-                return this;
-            }
-            @Override
-            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
-                super.withApfCapabilities(apfCapabilities);
-                return this;
-            }
-            @Override
-            public Builder withProvisioningTimeoutMs(int timeoutMs) {
-                super.withProvisioningTimeoutMs(timeoutMs);
-                return this;
-            }
-            @Override
-            public Builder withNetwork(Network network) {
-                super.withNetwork(network);
-                return this;
-            }
-            @Override
-            public Builder withDisplayName(String displayName) {
-                super.withDisplayName(displayName);
-                return this;
-            }
-            @Override
-            public ProvisioningConfiguration build() {
-                return new ProvisioningConfiguration(super.build());
-            }
-        }
-    }
-
-    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
-        return new ProvisioningConfiguration.Builder();
-    }
-
-    public static class InitialConfiguration extends IpClient.InitialConfiguration {
-    }
-
-    public static class Callback extends IpClient.Callback {
-    }
-
-    public IpManager(Context context, String ifName, Callback callback) {
-        super(context, ifName, callback);
-    }
-
-    public void startProvisioning(ProvisioningConfiguration req) {
-        super.startProvisioning((IpClient.ProvisioningConfiguration) req);
-    }
-}
diff --git a/services/net/java/android/net/netlink/ConntrackMessage.java b/services/net/java/android/net/netlink/ConntrackMessage.java
index 605c46b..4ee6432 100644
--- a/services/net/java/android/net/netlink/ConntrackMessage.java
+++ b/services/net/java/android/net/netlink/ConntrackMessage.java
@@ -28,7 +28,6 @@
 
 import android.system.OsConstants;
 import android.util.Log;
-import libcore.io.SizeOf;
 
 import java.net.Inet4Address;
 import java.net.Inet6Address;
diff --git a/services/net/java/android/net/netlink/StructNfGenMsg.java b/services/net/java/android/net/netlink/StructNfGenMsg.java
index 99695e2..8155977 100644
--- a/services/net/java/android/net/netlink/StructNfGenMsg.java
+++ b/services/net/java/android/net/netlink/StructNfGenMsg.java
@@ -16,8 +16,6 @@
 
 package android.net.netlink;
 
-import libcore.io.SizeOf;
-
 import java.nio.ByteBuffer;
 
 
@@ -29,7 +27,7 @@
  * @hide
  */
 public class StructNfGenMsg {
-    public static final int STRUCT_SIZE = 2 + SizeOf.SHORT;
+    public static final int STRUCT_SIZE = 2 + Short.BYTES;
 
     public static final int NFNETLINK_V0 = 0;
 
diff --git a/services/net/java/android/net/netlink/StructNlAttr.java b/services/net/java/android/net/netlink/StructNlAttr.java
index 811bdbb..28a4e88 100644
--- a/services/net/java/android/net/netlink/StructNlAttr.java
+++ b/services/net/java/android/net/netlink/StructNlAttr.java
@@ -17,7 +17,6 @@
 package android.net.netlink;
 
 import android.net.netlink.NetlinkConstants;
-import libcore.io.SizeOf;
 
 import java.net.InetAddress;
 import java.net.UnknownHostException;
@@ -117,7 +116,7 @@
     public StructNlAttr(short type, short value, ByteOrder order) {
         this(order);
         nla_type = type;
-        setValue(new byte[SizeOf.SHORT]);
+        setValue(new byte[Short.BYTES]);
         getValueAsByteBuffer().putShort(value);
     }
 
@@ -128,7 +127,7 @@
     public StructNlAttr(short type, int value, ByteOrder order) {
         this(order);
         nla_type = type;
-        setValue(new byte[SizeOf.INT]);
+        setValue(new byte[Integer.BYTES]);
         getValueAsByteBuffer().putInt(value);
     }
 
@@ -164,7 +163,7 @@
 
     public int getValueAsInt(int defaultValue) {
         final ByteBuffer byteBuffer = getValueAsByteBuffer();
-        if (byteBuffer == null || byteBuffer.remaining() != SizeOf.INT) {
+        if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
             return defaultValue;
         }
         return getValueAsByteBuffer().getInt();
diff --git a/services/net/java/android/net/netlink/StructNlMsgErr.java b/services/net/java/android/net/netlink/StructNlMsgErr.java
index f095af4..6fcc6e6 100644
--- a/services/net/java/android/net/netlink/StructNlMsgErr.java
+++ b/services/net/java/android/net/netlink/StructNlMsgErr.java
@@ -18,7 +18,6 @@
 
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.StructNlMsgHdr;
-import libcore.io.SizeOf;
 
 import java.nio.ByteBuffer;
 
@@ -31,7 +30,7 @@
  * @hide
  */
 public class StructNlMsgErr {
-    public static final int STRUCT_SIZE = SizeOf.INT + StructNlMsgHdr.STRUCT_SIZE;
+    public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE;
 
     public static boolean hasAvailableSpace(ByteBuffer byteBuffer) {
         return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE;
diff --git a/services/net/java/android/net/shared/InitialConfiguration.java b/services/net/java/android/net/shared/InitialConfiguration.java
new file mode 100644
index 0000000..bc2373f
--- /dev/null
+++ b/services/net/java/android/net/shared/InitialConfiguration.java
@@ -0,0 +1,234 @@
+/*
+ * 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.net.shared;
+
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+import static android.text.TextUtils.join;
+
+import android.net.InitialConfigurationParcelable;
+import android.net.IpPrefix;
+import android.net.IpPrefixParcelable;
+import android.net.LinkAddress;
+import android.net.LinkAddressParcelable;
+import android.net.RouteInfo;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/** @hide */
+public class InitialConfiguration {
+    public final Set<LinkAddress> ipAddresses = new HashSet<>();
+    public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
+    public final Set<InetAddress> dnsServers = new HashSet<>();
+
+    private static final int RFC6177_MIN_PREFIX_LENGTH = 48;
+    private static final int RFC7421_PREFIX_LENGTH = 64;
+
+    /**
+     * Create a InitialConfiguration that is a copy of the specified configuration.
+     */
+    public static InitialConfiguration copy(InitialConfiguration config) {
+        if (config == null) {
+            return null;
+        }
+        InitialConfiguration configCopy = new InitialConfiguration();
+        configCopy.ipAddresses.addAll(config.ipAddresses);
+        configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
+        configCopy.dnsServers.addAll(config.dnsServers);
+        return configCopy;
+    }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s})",
+                join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
+                join(", ", dnsServers));
+    }
+
+    /**
+     * Tests whether the contents of this IpConfiguration represent a valid configuration.
+     */
+    public boolean isValid() {
+        if (ipAddresses.isEmpty()) {
+            return false;
+        }
+
+        // For every IP address, there must be at least one prefix containing that address.
+        for (LinkAddress addr : ipAddresses) {
+            if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
+                return false;
+            }
+        }
+        // For every dns server, there must be at least one prefix containing that address.
+        for (InetAddress addr : dnsServers) {
+            if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
+                return false;
+            }
+        }
+        // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
+        // (read: compliant with RFC4291#section2.5.4).
+        if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
+            return false;
+        }
+        // If directlyConnectedRoutes contains an IPv6 default route
+        // then ipAddresses MUST contain at least one non-ULA GUA.
+        if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
+                && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
+            return false;
+        }
+        // The prefix length of routes in directlyConnectedRoutes be within reasonable
+        // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
+        if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
+            return false;
+        }
+        // There no more than one IPv4 address
+        if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * @return true if the given list of addressess and routes satisfies provisioning for this
+     * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+     * because addresses and routes seen by Netlink will contain additional fields like flags,
+     * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+     * provisioning check always fails.
+     *
+     * If the given list of routes is null, only addresses are taken into considerations.
+     */
+    public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+        if (ipAddresses.isEmpty()) {
+            return false;
+        }
+
+        for (LinkAddress addr : ipAddresses) {
+            if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+                return false;
+            }
+        }
+
+        if (routes != null) {
+            for (IpPrefix prefix : directlyConnectedRoutes) {
+                if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+                    return false;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Convert this configuration to a {@link InitialConfigurationParcelable}.
+     */
+    public InitialConfigurationParcelable toStableParcelable() {
+        final InitialConfigurationParcelable p = new InitialConfigurationParcelable();
+        p.ipAddresses = toParcelableArray(ipAddresses,
+                LinkPropertiesParcelableUtil::toStableParcelable, LinkAddressParcelable.class);
+        p.directlyConnectedRoutes = toParcelableArray(directlyConnectedRoutes,
+                LinkPropertiesParcelableUtil::toStableParcelable, IpPrefixParcelable.class);
+        p.dnsServers = toParcelableArray(
+                dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class);
+        return p;
+    }
+
+    /**
+     * Create an instance of {@link InitialConfiguration} based on the contents of the specified
+     * {@link InitialConfigurationParcelable}.
+     */
+    public static InitialConfiguration fromStableParcelable(InitialConfigurationParcelable p) {
+        if (p == null) return null;
+        final InitialConfiguration config = new InitialConfiguration();
+        config.ipAddresses.addAll(fromParcelableArray(
+                p.ipAddresses, LinkPropertiesParcelableUtil::fromStableParcelable));
+        config.directlyConnectedRoutes.addAll(fromParcelableArray(
+                p.directlyConnectedRoutes, LinkPropertiesParcelableUtil::fromStableParcelable));
+        config.dnsServers.addAll(
+                fromParcelableArray(p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress));
+        return config;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof InitialConfiguration)) return false;
+        final InitialConfiguration other = (InitialConfiguration) obj;
+        return ipAddresses.equals(other.ipAddresses)
+                && directlyConnectedRoutes.equals(other.directlyConnectedRoutes)
+                && dnsServers.equals(other.dnsServers);
+    }
+
+    private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+        return !route.hasGateway() && prefix.equals(route.getDestination());
+    }
+
+    private static boolean isPrefixLengthCompliant(LinkAddress addr) {
+        return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+    }
+
+    private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
+        return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+    }
+
+    private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
+        return (RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
+                && (prefixLength <= RFC7421_PREFIX_LENGTH);
+    }
+
+    private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
+        return prefix.getAddress().equals(Inet6Address.ANY);
+    }
+
+    private static boolean isIPv6GUA(LinkAddress addr) {
+        return addr.isIPv6() && addr.isGlobalPreferred();
+    }
+
+    // TODO: extract out into CollectionUtils.
+
+    /**
+     * Indicate whether any element of the specified iterable verifies the specified predicate.
+     */
+    public static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+        for (T t : coll) {
+            if (fn.test(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Indicate whether all elements of the specified iterable verifies the specified predicate.
+     */
+    public static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+        return !any(coll, not(fn));
+    }
+
+    /**
+     * Create a predicate that returns the opposite value of the specified predicate.
+     */
+    public static <T> Predicate<T> not(Predicate<T> fn) {
+        return (t) -> !fn.test(t);
+    }
+}
diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
new file mode 100644
index 0000000..2c368c8
--- /dev/null
+++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
@@ -0,0 +1,135 @@
+/*
+ * 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.net.shared;
+
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+
+import android.annotation.Nullable;
+import android.net.ApfCapabilitiesParcelable;
+import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
+import android.net.InetAddresses;
+import android.net.StaticIpConfiguration;
+import android.net.StaticIpConfigurationParcelable;
+import android.net.apf.ApfCapabilities;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+/**
+ * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient
+ * configuration classes.
+ * @hide
+ */
+public final class IpConfigurationParcelableUtil {
+    /**
+     * Convert a StaticIpConfiguration to a StaticIpConfigurationParcelable.
+     */
+    public static StaticIpConfigurationParcelable toStableParcelable(
+            @Nullable StaticIpConfiguration config) {
+        if (config == null) return null;
+        final StaticIpConfigurationParcelable p = new StaticIpConfigurationParcelable();
+        p.ipAddress = LinkPropertiesParcelableUtil.toStableParcelable(config.ipAddress);
+        p.gateway = parcelAddress(config.gateway);
+        p.dnsServers = toParcelableArray(
+                config.dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class);
+        p.domains = config.domains;
+        return p;
+    }
+
+    /**
+     * Convert a StaticIpConfigurationParcelable to a StaticIpConfiguration.
+     */
+    public static StaticIpConfiguration fromStableParcelable(
+            @Nullable StaticIpConfigurationParcelable p) {
+        if (p == null) return null;
+        final StaticIpConfiguration config = new StaticIpConfiguration();
+        config.ipAddress = LinkPropertiesParcelableUtil.fromStableParcelable(p.ipAddress);
+        config.gateway = unparcelAddress(p.gateway);
+        config.dnsServers.addAll(fromParcelableArray(
+                p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress));
+        config.domains = p.domains;
+        return config;
+    }
+
+    /**
+     * Convert DhcpResults to a DhcpResultsParcelable.
+     */
+    public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) {
+        if (results == null) return null;
+        final DhcpResultsParcelable p = new DhcpResultsParcelable();
+        p.baseConfiguration = toStableParcelable((StaticIpConfiguration) results);
+        p.leaseDuration = results.leaseDuration;
+        p.mtu = results.mtu;
+        p.serverAddress = parcelAddress(results.serverAddress);
+        p.vendorInfo = results.vendorInfo;
+        return p;
+    }
+
+    /**
+     * Convert a DhcpResultsParcelable to DhcpResults.
+     */
+    public static DhcpResults fromStableParcelable(@Nullable DhcpResultsParcelable p) {
+        if (p == null) return null;
+        final DhcpResults results = new DhcpResults(fromStableParcelable(p.baseConfiguration));
+        results.leaseDuration = p.leaseDuration;
+        results.mtu = p.mtu;
+        results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress);
+        results.vendorInfo = p.vendorInfo;
+        return results;
+    }
+
+    /**
+     * Convert ApfCapabilities to ApfCapabilitiesParcelable.
+     */
+    public static ApfCapabilitiesParcelable toStableParcelable(@Nullable ApfCapabilities caps) {
+        if (caps == null) return null;
+        final ApfCapabilitiesParcelable p = new ApfCapabilitiesParcelable();
+        p.apfVersionSupported = caps.apfVersionSupported;
+        p.maximumApfProgramSize = caps.maximumApfProgramSize;
+        p.apfPacketFormat = caps.apfPacketFormat;
+        return p;
+    }
+
+    /**
+     * Convert ApfCapabilitiesParcelable toApfCapabilities.
+     */
+    public static ApfCapabilities fromStableParcelable(@Nullable ApfCapabilitiesParcelable p) {
+        if (p == null) return null;
+        return new ApfCapabilities(
+                p.apfVersionSupported, p.maximumApfProgramSize, p.apfPacketFormat);
+    }
+
+    /**
+     * Convert InetAddress to String.
+     * TODO: have an InetAddressParcelable
+     */
+    public static String parcelAddress(@Nullable InetAddress addr) {
+        if (addr == null) return null;
+        return addr.getHostAddress();
+    }
+
+    /**
+     * Convert String to InetAddress.
+     * TODO: have an InetAddressParcelable
+     */
+    public static InetAddress unparcelAddress(@Nullable String addr) {
+        if (addr == null) return null;
+        return InetAddresses.parseNumericAddress(addr);
+    }
+}
diff --git a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
index 5b77f54..d5213df 100644
--- a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
+++ b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java
@@ -16,11 +16,12 @@
 
 package android.net.shared;
 
+import static android.net.shared.IpConfigurationParcelableUtil.parcelAddress;
+import static android.net.shared.IpConfigurationParcelableUtil.unparcelAddress;
 import static android.net.shared.ParcelableUtil.fromParcelableArray;
 import static android.net.shared.ParcelableUtil.toParcelableArray;
 
 import android.annotation.Nullable;
-import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.IpPrefixParcelable;
 import android.net.LinkAddress;
@@ -33,7 +34,6 @@
 import android.net.RouteInfoParcelable;
 import android.net.Uri;
 
-import java.net.InetAddress;
 import java.util.Arrays;
 
 /**
@@ -81,7 +81,7 @@
             return null;
         }
         final IpPrefixParcelable parcel = new IpPrefixParcelable();
-        parcel.address = ipPrefix.getAddress().getHostAddress();
+        parcel.address = parcelAddress(ipPrefix.getAddress());
         parcel.prefixLength = ipPrefix.getPrefixLength();
         return parcel;
     }
@@ -93,7 +93,7 @@
         if (parcel == null) {
             return null;
         }
-        return new IpPrefix(InetAddresses.parseNumericAddress(parcel.address), parcel.prefixLength);
+        return new IpPrefix(unparcelAddress(parcel.address), parcel.prefixLength);
     }
 
     /**
@@ -105,7 +105,7 @@
         }
         final RouteInfoParcelable parcel = new RouteInfoParcelable();
         parcel.destination = toStableParcelable(routeInfo.getDestination());
-        parcel.gatewayAddr = routeInfo.getGateway().getHostAddress();
+        parcel.gatewayAddr = parcelAddress(routeInfo.getGateway());
         parcel.ifaceName = routeInfo.getInterface();
         parcel.type = routeInfo.getType();
         return parcel;
@@ -120,7 +120,7 @@
         }
         final IpPrefix destination = fromStableParcelable(parcel.destination);
         return new RouteInfo(
-                destination, InetAddresses.parseNumericAddress(parcel.gatewayAddr),
+                destination, unparcelAddress(parcel.gatewayAddr),
                 parcel.ifaceName, parcel.type);
     }
 
@@ -132,7 +132,7 @@
             return null;
         }
         final LinkAddressParcelable parcel = new LinkAddressParcelable();
-        parcel.address = la.getAddress().getHostAddress();
+        parcel.address = parcelAddress(la.getAddress());
         parcel.prefixLength = la.getPrefixLength();
         parcel.flags = la.getFlags();
         parcel.scope = la.getScope();
@@ -147,7 +147,7 @@
             return null;
         }
         return new LinkAddress(
-                InetAddresses.parseNumericAddress(parcel.address),
+                unparcelAddress(parcel.address),
                 parcel.prefixLength,
                 parcel.flags,
                 parcel.scope);
@@ -167,11 +167,11 @@
                 LinkPropertiesParcelableUtil::toStableParcelable,
                 LinkAddressParcelable.class);
         parcel.dnses = toParcelableArray(
-                lp.getDnsServers(), InetAddress::getHostAddress, String.class);
+                lp.getDnsServers(), IpConfigurationParcelableUtil::parcelAddress, String.class);
         parcel.pcscfs = toParcelableArray(
-                lp.getPcscfServers(), InetAddress::getHostAddress, String.class);
-        parcel.validatedPrivateDnses = toParcelableArray(
-                lp.getValidatedPrivateDnsServers(), InetAddress::getHostAddress, String.class);
+                lp.getPcscfServers(), IpConfigurationParcelableUtil::parcelAddress, String.class);
+        parcel.validatedPrivateDnses = toParcelableArray(lp.getValidatedPrivateDnsServers(),
+                IpConfigurationParcelableUtil::parcelAddress, String.class);
         parcel.usePrivateDns = lp.isPrivateDnsActive();
         parcel.privateDnsServerName = lp.getPrivateDnsServerName();
         parcel.domains = lp.getDomains();
@@ -199,11 +199,13 @@
         lp.setInterfaceName(parcel.ifaceName);
         lp.setLinkAddresses(fromParcelableArray(parcel.linkAddresses,
                 LinkPropertiesParcelableUtil::fromStableParcelable));
-        lp.setDnsServers(fromParcelableArray(parcel.dnses, InetAddresses::parseNumericAddress));
-        lp.setPcscfServers(fromParcelableArray(parcel.pcscfs, InetAddresses::parseNumericAddress));
+        lp.setDnsServers(fromParcelableArray(
+                parcel.dnses, IpConfigurationParcelableUtil::unparcelAddress));
+        lp.setPcscfServers(fromParcelableArray(
+                parcel.pcscfs, IpConfigurationParcelableUtil::unparcelAddress));
         lp.setValidatedPrivateDnsServers(
                 fromParcelableArray(parcel.validatedPrivateDnses,
-                InetAddresses::parseNumericAddress));
+                IpConfigurationParcelableUtil::unparcelAddress));
         lp.setUsePrivateDns(parcel.usePrivateDns);
         lp.setPrivateDnsServerName(parcel.privateDnsServerName);
         lp.setDomains(parcel.domains);
diff --git a/services/net/java/android/net/shared/NetworkParcelableUtil.java b/services/net/java/android/net/shared/NetworkParcelableUtil.java
new file mode 100644
index 0000000..d0b54b8
--- /dev/null
+++ b/services/net/java/android/net/shared/NetworkParcelableUtil.java
@@ -0,0 +1,50 @@
+/*
+ * 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.net.shared;
+
+import android.annotation.Nullable;
+import android.net.Network;
+import android.net.NetworkParcelable;
+
+/**
+ * Utility methods to convert to/from stable AIDL parcelables for network attribute classes.
+ * @hide
+ */
+public final class NetworkParcelableUtil {
+    /**
+     * Convert from a Network to a NetworkParcelable.
+     */
+    public static NetworkParcelable toStableParcelable(@Nullable Network network) {
+        if (network == null) {
+            return null;
+        }
+        final NetworkParcelable p = new NetworkParcelable();
+        p.networkHandle = network.getNetworkHandle();
+
+        return p;
+    }
+
+    /**
+     * Convert from a NetworkParcelable to a Network.
+     */
+    public static Network fromStableParcelable(@Nullable NetworkParcelable p) {
+        if (p == null) {
+            return null;
+        }
+        return Network.fromNetworkHandle(p.networkHandle);
+    }
+}
diff --git a/services/net/java/android/net/shared/ParcelableUtil.java b/services/net/java/android/net/shared/ParcelableUtil.java
index a18976c..3f40300 100644
--- a/services/net/java/android/net/shared/ParcelableUtil.java
+++ b/services/net/java/android/net/shared/ParcelableUtil.java
@@ -20,7 +20,7 @@
 
 import java.lang.reflect.Array;
 import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
 import java.util.function.Function;
 
 /**
@@ -36,7 +36,7 @@
      * converter function.
      */
     public static <ParcelableType, BaseType> ParcelableType[] toParcelableArray(
-            @NonNull List<BaseType> base,
+            @NonNull Collection<BaseType> base,
             @NonNull Function<BaseType, ParcelableType> conv,
             @NonNull Class<ParcelableType> parcelClass) {
         final ParcelableType[] out = (ParcelableType[]) Array.newInstance(parcelClass, base.size());
diff --git a/services/net/java/android/net/shared/PrivateDnsConfig.java b/services/net/java/android/net/shared/PrivateDnsConfig.java
index 41e0bad..c7dc530 100644
--- a/services/net/java/android/net/shared/PrivateDnsConfig.java
+++ b/services/net/java/android/net/shared/PrivateDnsConfig.java
@@ -16,7 +16,9 @@
 
 package android.net.shared;
 
-import android.net.InetAddresses;
+import static android.net.shared.ParcelableUtil.fromParcelableArray;
+import static android.net.shared.ParcelableUtil.toParcelableArray;
+
 import android.net.PrivateDnsConfigParcel;
 import android.text.TextUtils;
 
@@ -70,12 +72,8 @@
     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;
+        parcel.ips = toParcelableArray(
+                Arrays.asList(ips), IpConfigurationParcelableUtil::parcelAddress, String.class);
 
         return parcel;
     }
@@ -84,11 +82,9 @@
      * 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]);
-        }
-
+        InetAddress[] ips = new InetAddress[parcel.ips.length];
+        ips = fromParcelableArray(parcel.ips, IpConfigurationParcelableUtil::unparcelAddress)
+                .toArray(ips);
         return new PrivateDnsConfig(parcel.hostname, ips);
     }
 }
diff --git a/services/net/java/android/net/shared/ProvisioningConfiguration.java b/services/net/java/android/net/shared/ProvisioningConfiguration.java
new file mode 100644
index 0000000..f937065
--- /dev/null
+++ b/services/net/java/android/net/shared/ProvisioningConfiguration.java
@@ -0,0 +1,310 @@
+/*
+ * 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.net.shared;
+
+import android.annotation.Nullable;
+import android.net.INetd;
+import android.net.Network;
+import android.net.ProvisioningConfigurationParcelable;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+import android.net.ip.IIpClient;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * This class encapsulates parameters to be passed to
+ * IpClient#startProvisioning(). A defensive copy is made by IpClient
+ * and the values specified herein are in force until IpClient#stop()
+ * is called.
+ *
+ * Example use:
+ *
+ *     final ProvisioningConfiguration config =
+ *             new ProvisioningConfiguration.Builder()
+ *                     .withPreDhcpAction()
+ *                     .withProvisioningTimeoutMs(36 * 1000)
+ *                     .build();
+ *     mIpClient.startProvisioning(config.toStableParcelable());
+ *     ...
+ *     mIpClient.stop();
+ *
+ * The specified provisioning configuration will only be active until
+ * IIpClient#stop() is called. Future calls to IIpClient#startProvisioning()
+ * must specify the configuration again.
+ * @hide
+ */
+public class ProvisioningConfiguration {
+    // TODO: Delete this default timeout once those callers that care are
+    // fixed to pass in their preferred timeout.
+    //
+    // We pick 36 seconds so we can send DHCP requests at
+    //
+    //     t=0, t=2, t=6, t=14, t=30
+    //
+    // allowing for 10% jitter.
+    private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
+
+    /**
+     * Builder to create a {@link ProvisioningConfiguration}.
+     */
+    public static class Builder {
+        protected ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
+
+        /**
+         * Specify that the configuration should not enable IPv4. It is enabled by default.
+         */
+        public Builder withoutIPv4() {
+            mConfig.mEnableIPv4 = false;
+            return this;
+        }
+
+        /**
+         * Specify that the configuration should not enable IPv6. It is enabled by default.
+         */
+        public Builder withoutIPv6() {
+            mConfig.mEnableIPv6 = false;
+            return this;
+        }
+
+        /**
+         * Specify that the configuration should not use a MultinetworkPolicyTracker. It is used
+         * by default.
+         */
+        public Builder withoutMultinetworkPolicyTracker() {
+            mConfig.mUsingMultinetworkPolicyTracker = false;
+            return this;
+        }
+
+        /**
+         * Specify that the configuration should not use a IpReachabilityMonitor. It is used by
+         * default.
+         */
+        public Builder withoutIpReachabilityMonitor() {
+            mConfig.mUsingIpReachabilityMonitor = false;
+            return this;
+        }
+
+        /**
+         * Identical to {@link #withPreDhcpAction(int)}, using a default timeout.
+         * @see #withPreDhcpAction(int)
+         */
+        public Builder withPreDhcpAction() {
+            mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
+            return this;
+        }
+
+        /**
+         * Specify that {@link IpClientCallbacks#onPreDhcpAction()} should be called. Clients must
+         * call {@link IIpClient#completedPreDhcpAction()} when the callback called. This behavior
+         * is disabled by default.
+         * @param dhcpActionTimeoutMs Timeout for clients to call completedPreDhcpAction().
+         */
+        public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
+            mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
+            return this;
+        }
+
+        /**
+         * Specify the initial provisioning configuration.
+         */
+        public Builder withInitialConfiguration(InitialConfiguration initialConfig) {
+            mConfig.mInitialConfig = initialConfig;
+            return this;
+        }
+
+        /**
+         * Specify a static configuration for provisioning.
+         */
+        public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
+            mConfig.mStaticIpConfig = staticConfig;
+            return this;
+        }
+
+        /**
+         * Specify ApfCapabilities.
+         */
+        public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
+            mConfig.mApfCapabilities = apfCapabilities;
+            return this;
+        }
+
+        /**
+         * Specify the timeout to use for provisioning.
+         */
+        public Builder withProvisioningTimeoutMs(int timeoutMs) {
+            mConfig.mProvisioningTimeoutMs = timeoutMs;
+            return this;
+        }
+
+        /**
+         * Specify that IPv6 address generation should use a random MAC address.
+         */
+        public Builder withRandomMacAddress() {
+            mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64;
+            return this;
+        }
+
+        /**
+         * Specify that IPv6 address generation should use a stable MAC address.
+         */
+        public Builder withStableMacAddress() {
+            mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+            return this;
+        }
+
+        /**
+         * Specify the network to use for provisioning.
+         */
+        public Builder withNetwork(Network network) {
+            mConfig.mNetwork = network;
+            return this;
+        }
+
+        /**
+         * Specify the display name that the IpClient should use.
+         */
+        public Builder withDisplayName(String displayName) {
+            mConfig.mDisplayName = displayName;
+            return this;
+        }
+
+        /**
+         * Build the configuration using previously specified parameters.
+         */
+        public ProvisioningConfiguration build() {
+            return new ProvisioningConfiguration(mConfig);
+        }
+    }
+
+    public boolean mEnableIPv4 = true;
+    public boolean mEnableIPv6 = true;
+    public boolean mUsingMultinetworkPolicyTracker = true;
+    public boolean mUsingIpReachabilityMonitor = true;
+    public int mRequestedPreDhcpActionMs;
+    public InitialConfiguration mInitialConfig;
+    public StaticIpConfiguration mStaticIpConfig;
+    public ApfCapabilities mApfCapabilities;
+    public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
+    public int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY;
+    public Network mNetwork = null;
+    public String mDisplayName = null;
+
+    public ProvisioningConfiguration() {} // used by Builder
+
+    public ProvisioningConfiguration(ProvisioningConfiguration other) {
+        mEnableIPv4 = other.mEnableIPv4;
+        mEnableIPv6 = other.mEnableIPv6;
+        mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker;
+        mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
+        mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
+        mInitialConfig = InitialConfiguration.copy(other.mInitialConfig);
+        mStaticIpConfig = other.mStaticIpConfig == null
+                ? null
+                : new StaticIpConfiguration(other.mStaticIpConfig);
+        mApfCapabilities = other.mApfCapabilities;
+        mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
+        mIPv6AddrGenMode = other.mIPv6AddrGenMode;
+        mNetwork = other.mNetwork;
+        mDisplayName = other.mDisplayName;
+    }
+
+    /**
+     * Create a ProvisioningConfigurationParcelable from this ProvisioningConfiguration.
+     */
+    public ProvisioningConfigurationParcelable toStableParcelable() {
+        final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable();
+        p.enableIPv4 = mEnableIPv4;
+        p.enableIPv6 = mEnableIPv6;
+        p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker;
+        p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor;
+        p.requestedPreDhcpActionMs = mRequestedPreDhcpActionMs;
+        p.initialConfig = mInitialConfig == null ? null : mInitialConfig.toStableParcelable();
+        p.staticIpConfig = IpConfigurationParcelableUtil.toStableParcelable(mStaticIpConfig);
+        p.apfCapabilities = IpConfigurationParcelableUtil.toStableParcelable(mApfCapabilities);
+        p.provisioningTimeoutMs = mProvisioningTimeoutMs;
+        p.ipv6AddrGenMode = mIPv6AddrGenMode;
+        p.network = NetworkParcelableUtil.toStableParcelable(mNetwork);
+        p.displayName = mDisplayName;
+        return p;
+    }
+
+    /**
+     * Create a ProvisioningConfiguration from a ProvisioningConfigurationParcelable.
+     */
+    public static ProvisioningConfiguration fromStableParcelable(
+            @Nullable ProvisioningConfigurationParcelable p) {
+        if (p == null) return null;
+        final ProvisioningConfiguration config = new ProvisioningConfiguration();
+        config.mEnableIPv4 = p.enableIPv4;
+        config.mEnableIPv6 = p.enableIPv6;
+        config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker;
+        config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor;
+        config.mRequestedPreDhcpActionMs = p.requestedPreDhcpActionMs;
+        config.mInitialConfig = InitialConfiguration.fromStableParcelable(p.initialConfig);
+        config.mStaticIpConfig = IpConfigurationParcelableUtil.fromStableParcelable(
+                p.staticIpConfig);
+        config.mApfCapabilities = IpConfigurationParcelableUtil.fromStableParcelable(
+                p.apfCapabilities);
+        config.mProvisioningTimeoutMs = p.provisioningTimeoutMs;
+        config.mIPv6AddrGenMode = p.ipv6AddrGenMode;
+        config.mNetwork = NetworkParcelableUtil.fromStableParcelable(p.network);
+        config.mDisplayName = p.displayName;
+        return config;
+    }
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
+                .add("mEnableIPv4: " + mEnableIPv4)
+                .add("mEnableIPv6: " + mEnableIPv6)
+                .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker)
+                .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
+                .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
+                .add("mInitialConfig: " + mInitialConfig)
+                .add("mStaticIpConfig: " + mStaticIpConfig)
+                .add("mApfCapabilities: " + mApfCapabilities)
+                .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
+                .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode)
+                .add("mNetwork: " + mNetwork)
+                .add("mDisplayName: " + mDisplayName)
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ProvisioningConfiguration)) return false;
+        final ProvisioningConfiguration other = (ProvisioningConfiguration) obj;
+        return mEnableIPv4 == other.mEnableIPv4
+                && mEnableIPv6 == other.mEnableIPv6
+                && mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker
+                && mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor
+                && mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs
+                && Objects.equals(mInitialConfig, other.mInitialConfig)
+                && Objects.equals(mStaticIpConfig, other.mStaticIpConfig)
+                && Objects.equals(mApfCapabilities, other.mApfCapabilities)
+                && mProvisioningTimeoutMs == other.mProvisioningTimeoutMs
+                && mIPv6AddrGenMode == other.mIPv6AddrGenMode
+                && Objects.equals(mNetwork, other.mNetwork)
+                && Objects.equals(mDisplayName, other.mDisplayName);
+    }
+
+    public boolean isValid() {
+        return (mInitialConfig == null) || mInitialConfig.isValid();
+    }
+}
diff --git a/services/net/java/android/net/util/InterfaceParams.java b/services/net/java/android/net/util/InterfaceParams.java
index a4b2fbb..7b060da 100644
--- a/services/net/java/android/net/util/InterfaceParams.java
+++ b/services/net/java/android/net/util/InterfaceParams.java
@@ -16,9 +16,9 @@
 
 package android.net.util;
 
-import static android.net.MacAddress.ALL_ZEROS_ADDRESS;
 import static android.net.util.NetworkConstants.ETHER_MTU;
 import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
+
 import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.net.MacAddress;
@@ -67,7 +67,8 @@
         checkArgument((index > 0), "invalid interface index");
         this.name = name;
         this.index = index;
-        this.macAddr = (macAddr != null) ? macAddr : ALL_ZEROS_ADDRESS;
+        this.macAddr = (macAddr != null) ? macAddr : MacAddress.fromBytes(new byte[] {
+                0x02, 0x00, 0x00, 0x00, 0x00, 0x00 });
         this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
new file mode 100644
index 0000000..c52c8d7
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.wm;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.util.function.Supplier;
+
+@SmallTest
+@Presubmit
+public class LetterboxTest {
+
+    Letterbox mLetterbox;
+    SurfaceControlMocker mSurfaces;
+    SurfaceControl.Transaction mTransaction;
+
+    @Before
+    public void setUp() throws Exception {
+        mSurfaces = new SurfaceControlMocker();
+        mLetterbox = new Letterbox(mSurfaces);
+        mTransaction = mock(SurfaceControl.Transaction.class);
+    }
+
+    @Test
+    public void testOverlappingWith_usesGlobalCoordinates() {
+        mLetterbox.layout(new Rect(0, 0, 10, 50), new Rect(0, 2, 10, 45), new Point(1000, 2000));
+        assertTrue(mLetterbox.isOverlappingWith(new Rect(0, 0, 1, 1)));
+    }
+
+    @Test
+    public void testSurfaceOrigin_applied() {
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+        mLetterbox.applySurfaceChanges(mTransaction);
+        verify(mTransaction).setPosition(mSurfaces.top, -1000, -2000);
+    }
+
+    @Test
+    public void testSurfaceOrigin_changeCausesReapply() {
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000));
+        mLetterbox.applySurfaceChanges(mTransaction);
+        clearInvocations(mTransaction);
+        mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0));
+        assertTrue(mLetterbox.needsApplySurfaceChanges());
+        mLetterbox.applySurfaceChanges(mTransaction);
+        verify(mTransaction).setPosition(mSurfaces.top, 0, 0);
+    }
+
+    class SurfaceControlMocker implements Supplier<SurfaceControl.Builder> {
+        private SurfaceControl.Builder mLeftBuilder;
+        public SurfaceControl left;
+        private SurfaceControl.Builder mTopBuilder;
+        public SurfaceControl top;
+        private SurfaceControl.Builder mRightBuilder;
+        public SurfaceControl right;
+        private SurfaceControl.Builder mBottomBuilder;
+        public SurfaceControl bottom;
+
+        @Override
+        public SurfaceControl.Builder get() {
+            final SurfaceControl.Builder builder = mock(SurfaceControl.Builder.class,
+                    InvocationOnMock::getMock);
+            when(builder.setName(anyString())).then((i) -> {
+                if (((String) i.getArgument(0)).contains("left")) {
+                    mLeftBuilder = (SurfaceControl.Builder) i.getMock();
+                } else if (((String) i.getArgument(0)).contains("top")) {
+                    mTopBuilder = (SurfaceControl.Builder) i.getMock();
+                } else if (((String) i.getArgument(0)).contains("right")) {
+                    mRightBuilder = (SurfaceControl.Builder) i.getMock();
+                } else if (((String) i.getArgument(0)).contains("bottom")) {
+                    mBottomBuilder = (SurfaceControl.Builder) i.getMock();
+                }
+                return i.getMock();
+            });
+
+            doAnswer((i) -> {
+                final SurfaceControl control = mock(SurfaceControl.class);
+                if (i.getMock() == mLeftBuilder) {
+                    left = control;
+                } else if (i.getMock() == mTopBuilder) {
+                    top = control;
+                } else if (i.getMock() == mRightBuilder) {
+                    right = control;
+                } else if (i.getMock() == mBottomBuilder) {
+                    bottom = control;
+                }
+                return control;
+            }).when(builder).build();
+            return builder;
+        }
+    }
+}
diff --git a/tests/net/java/android/net/apf/ApfTest.java b/tests/net/java/android/net/apf/ApfTest.java
index 151b559..aaed659 100644
--- a/tests/net/java/android/net/apf/ApfTest.java
+++ b/tests/net/java/android/net/apf/ApfTest.java
@@ -16,10 +16,19 @@
 
 package android.net.apf;
 
-import static android.net.util.NetworkConstants.*;
-import static android.system.OsConstants.*;
+import static android.net.util.NetworkConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static android.net.util.NetworkConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.ARPHRD_ETHER;
+import static android.system.OsConstants.ETH_P_ARP;
+import static android.system.OsConstants.ETH_P_IP;
+import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_STREAM;
+
 import static com.android.internal.util.BitUtils.bytesToBEInt;
-import static com.android.internal.util.BitUtils.put;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -33,7 +42,7 @@
 import android.net.apf.ApfFilter.ApfConfiguration;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
-import android.net.ip.IpClient;
+import android.net.ip.IpClientCallbacks;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.RaEvent;
 import android.net.util.InterfaceParams;
@@ -47,8 +56,20 @@
 import android.system.Os;
 import android.text.format.DateUtils;
 import android.util.Log;
+
 import com.android.frameworks.tests.net.R;
 import com.android.internal.util.HexDump;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+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 java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
@@ -59,14 +80,6 @@
 import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.Random;
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-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;
 
 /**
  * Tests for APF program generator and interpreter.
@@ -902,7 +915,7 @@
             HexDump.toHexString(data, false), result);
     }
 
-    private class MockIpClientCallback extends IpClient.Callback {
+    private class MockIpClientCallback extends IpClientCallbacks {
         private final ConditionVariable mGotApfProgram = new ConditionVariable();
         private byte[] mLastApfProgram;
 
@@ -933,7 +946,7 @@
         private final long mFixedTimeMs = SystemClock.elapsedRealtime();
 
         public TestApfFilter(Context context, ApfConfiguration config,
-                IpClient.Callback ipClientCallback, IpConnectivityLog log) throws Exception {
+                IpClientCallbacks ipClientCallback, IpConnectivityLog log) throws Exception {
             super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, log);
         }
 
@@ -1062,7 +1075,7 @@
     private static final byte[] IPV4_ANY_HOST_ADDR       = {0, 0, 0, 0};
 
     // Helper to initialize a default apfFilter.
-    private ApfFilter setupApfFilter(IpClient.Callback ipClientCallback, ApfConfiguration config)
+    private ApfFilter setupApfFilter(IpClientCallbacks ipClientCallback, ApfConfiguration config)
             throws Exception {
         LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19);
         LinkProperties lp = new LinkProperties();
diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/tests/net/java/android/net/ip/IpClientTest.java
index cba3c65..a2dcfef 100644
--- a/tests/net/java/android/net/ip/IpClientTest.java
+++ b/tests/net/java/android/net/ip/IpClientTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyString;
 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.timeout;
@@ -40,9 +39,8 @@
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.RouteInfo;
-import android.net.ip.IpClient.Callback;
-import android.net.ip.IpClient.InitialConfiguration;
-import android.net.ip.IpClient.ProvisioningConfiguration;
+import android.net.shared.InitialConfiguration;
+import android.net.shared.ProvisioningConfiguration;
 import android.net.util.InterfaceParams;
 import android.os.INetworkManagementService;
 import android.provider.Settings;
@@ -50,8 +48,8 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.test.mock.MockContentResolver;
 
-import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.net.BaseNetworkObserver;
 
 import org.junit.Before;
@@ -63,8 +61,8 @@
 
 import java.net.InetAddress;
 import java.util.Arrays;
-import java.util.List;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -87,7 +85,7 @@
     @Mock private INetworkManagementService mNMService;
     @Mock private INetd mNetd;
     @Mock private Resources mResources;
-    @Mock private Callback mCb;
+    @Mock private IpClientCallbacks mCb;
     @Mock private AlarmManager mAlarm;
     @Mock private IpClient.Dependencies mDependecies;
     private MockContentResolver mContentResolver;
@@ -179,7 +177,7 @@
     public void testInterfaceNotFoundFailsImmediately() throws Exception {
         setTestInterfaceParams(null);
         final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies);
-        ipc.startProvisioning(new IpClient.ProvisioningConfiguration());
+        ipc.startProvisioning(new ProvisioningConfiguration());
         verify(mCb, times(1)).onProvisioningFailure(any());
         ipc.shutdown();
     }
diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
index a9f9758..1fc67a8 100644
--- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
+++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java
@@ -27,6 +27,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.reflect.Modifier;
 import java.net.Inet4Address;
 import java.net.InetAddress;
 import java.util.Arrays;
@@ -60,6 +61,12 @@
         builder.setMtu(null);
         in = builder.build();
         assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable())));
+
+        // Verify that this test does not miss any new field added later.
+        // If any field is added to NetworkAttributes it must be tested here for parceling
+        // roundtrip.
+        assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+                .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
     }
 
     @Test
diff --git a/tests/net/java/android/net/shared/InitialConfigurationTest.java b/tests/net/java/android/net/shared/InitialConfigurationTest.java
new file mode 100644
index 0000000..78792bd
--- /dev/null
+++ b/tests/net/java/android/net/shared/InitialConfigurationTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.net.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+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 java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link InitialConfiguration}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InitialConfigurationTest {
+    private InitialConfiguration mConfig;
+
+    @Before
+    public void setUp() {
+        mConfig = new InitialConfiguration();
+        mConfig.ipAddresses.addAll(Arrays.asList(
+                new LinkAddress(parseNumericAddress("192.168.45.45"), 16),
+                new LinkAddress(parseNumericAddress("2001:db8::45"), 33)));
+        mConfig.directlyConnectedRoutes.addAll(Arrays.asList(
+                new IpPrefix(parseNumericAddress("192.168.46.46"), 17),
+                new IpPrefix(parseNumericAddress("2001:db8::46"), 34)));
+        mConfig.dnsServers.addAll(Arrays.asList(
+                parseNumericAddress("192.168.47.47"),
+                parseNumericAddress("2001:db8::47")));
+        // Any added InitialConfiguration field must be included in equals() to be tested properly
+        assertFieldCountEquals(3, InitialConfiguration.class);
+    }
+
+    @Test
+    public void testParcelUnparcelInitialConfiguration() {
+        final InitialConfiguration unparceled =
+                InitialConfiguration.fromStableParcelable(mConfig.toStableParcelable());
+        assertEquals(mConfig, unparceled);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mConfig, InitialConfiguration.copy(mConfig));
+
+        assertNotEqualsAfterChange(c -> c.ipAddresses.add(
+                new LinkAddress(parseNumericAddress("192.168.47.47"), 24)));
+        assertNotEqualsAfterChange(c -> c.directlyConnectedRoutes.add(
+                new IpPrefix(parseNumericAddress("192.168.46.46"), 32)));
+        assertNotEqualsAfterChange(c -> c.dnsServers.add(parseNumericAddress("2001:db8::49")));
+        assertFieldCountEquals(3, InitialConfiguration.class);
+    }
+
+    private void assertNotEqualsAfterChange(Consumer<InitialConfiguration> mutator) {
+        final InitialConfiguration newConfig = InitialConfiguration.copy(mConfig);
+        mutator.accept(newConfig);
+        assertNotEquals(mConfig, newConfig);
+    }
+}
diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
new file mode 100644
index 0000000..14df392
--- /dev/null
+++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.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 android.net.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable;
+import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable;
+import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.DhcpResults;
+import android.net.LinkAddress;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+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 java.net.Inet4Address;
+
+/**
+ * Tests for {@link IpConfigurationParcelableUtil}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConfigurationParcelableUtilTest {
+    private StaticIpConfiguration mStaticIpConfiguration;
+    private DhcpResults mDhcpResults;
+
+    @Before
+    public void setUp() {
+        mStaticIpConfiguration = new StaticIpConfiguration();
+        mStaticIpConfiguration.ipAddress = new LinkAddress(parseNumericAddress("2001:db8::42"), 64);
+        mStaticIpConfiguration.gateway = parseNumericAddress("192.168.42.42");
+        mStaticIpConfiguration.dnsServers.add(parseNumericAddress("2001:db8::43"));
+        mStaticIpConfiguration.dnsServers.add(parseNumericAddress("192.168.43.43"));
+        mStaticIpConfiguration.domains = "example.com";
+        // Any added StaticIpConfiguration field must be included in equals() to be tested properly
+        assertFieldCountEquals(4, StaticIpConfiguration.class);
+
+        mDhcpResults = new DhcpResults(mStaticIpConfiguration);
+        mDhcpResults.serverAddress = (Inet4Address) parseNumericAddress("192.168.44.44");
+        mDhcpResults.vendorInfo = "TEST_VENDOR_INFO";
+        mDhcpResults.leaseDuration = 3600;
+        mDhcpResults.mtu = 1450;
+        // Any added DhcpResults field must be included in equals() to be tested properly
+        assertFieldCountEquals(4, DhcpResults.class);
+    }
+
+    @Test
+    public void testParcelUnparcelStaticConfiguration() {
+        doStaticConfigurationParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcelStaticConfiguration_NullIpAddress() {
+        mStaticIpConfiguration.ipAddress = null;
+        doStaticConfigurationParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcelStaticConfiguration_NullGateway() {
+        mStaticIpConfiguration.gateway = null;
+        doStaticConfigurationParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcelStaticConfiguration_NullDomains() {
+        mStaticIpConfiguration.domains = null;
+        doStaticConfigurationParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcelStaticConfiguration_EmptyDomains() {
+        mStaticIpConfiguration.domains = "";
+        doStaticConfigurationParcelUnparcelTest();
+    }
+
+    private void doStaticConfigurationParcelUnparcelTest() {
+        final StaticIpConfiguration unparceled =
+                fromStableParcelable(toStableParcelable(mStaticIpConfiguration));
+        assertEquals(mStaticIpConfiguration, unparceled);
+    }
+
+    @Test
+    public void testParcelUnparcelDhcpResults() {
+        doDhcpResultsParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcelDhcpResults_NullServerAddress() {
+        mDhcpResults.serverAddress = null;
+        doDhcpResultsParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcelDhcpResults_NullVendorInfo() {
+        mDhcpResults.vendorInfo = null;
+        doDhcpResultsParcelUnparcelTest();
+    }
+
+    private void doDhcpResultsParcelUnparcelTest() {
+        final DhcpResults unparceled = fromStableParcelable(toStableParcelable(mDhcpResults));
+        assertEquals(mDhcpResults, unparceled);
+    }
+
+    @Test
+    public void testParcelUnparcelApfCapabilities() {
+        final ApfCapabilities caps = new ApfCapabilities(123, 456, 789);
+        assertEquals(caps, fromStableParcelable(toStableParcelable(caps)));
+    }
+}
diff --git a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
index 4cabfc9..6f711c0 100644
--- a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
+++ b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java
@@ -18,6 +18,7 @@
 
 import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
 import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable;
+import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals;
 
 import static org.junit.Assert.assertEquals;
 
@@ -35,7 +36,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import java.util.Collections;
 
@@ -100,8 +100,7 @@
         // Verify that this test does not miss any new field added later.
         // If any added field is not included in LinkProperties#equals, assertLinkPropertiesEquals
         // must also be updated.
-        assertEquals(14, Arrays.stream(LinkProperties.class.getDeclaredFields())
-                .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
+        assertFieldCountEquals(14, LinkProperties.class);
 
         return lp;
     }
diff --git a/tests/net/java/android/net/shared/ParcelableTestUtil.java b/tests/net/java/android/net/shared/ParcelableTestUtil.java
new file mode 100644
index 0000000..088ea3c
--- /dev/null
+++ b/tests/net/java/android/net/shared/ParcelableTestUtil.java
@@ -0,0 +1,41 @@
+/*
+ * 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.net.shared;
+
+import static org.junit.Assert.assertEquals;
+
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+/**
+ * Utility classes to write tests for stable AIDL parceling/unparceling
+ */
+public final class ParcelableTestUtil {
+
+    /**
+     * Verifies that the number of nonstatic fields in a class equals a given count.
+     *
+     * <p>This assertion serves as a reminder to update test code around it if fields are added
+     * after the test is written.
+     * @param count Expected number of nonstatic fields in the class.
+     * @param clazz Class to test.
+     */
+    public static <T> void assertFieldCountEquals(int count, Class<T> clazz) {
+        assertEquals(count, Arrays.stream(clazz.getDeclaredFields())
+                .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
+    }
+}
diff --git a/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java
new file mode 100644
index 0000000..6ea47d2
--- /dev/null
+++ b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.net.shared;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals;
+import static android.net.shared.ProvisioningConfiguration.fromStableParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.net.LinkAddress;
+import android.net.Network;
+import android.net.StaticIpConfiguration;
+import android.net.apf.ApfCapabilities;
+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 java.util.function.Consumer;
+
+/**
+ * Tests for {@link ProvisioningConfiguration}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ProvisioningConfigurationTest {
+    private ProvisioningConfiguration mConfig;
+
+    @Before
+    public void setUp() {
+        mConfig = new ProvisioningConfiguration();
+        mConfig.mEnableIPv4 = true;
+        mConfig.mEnableIPv6 = true;
+        mConfig.mUsingMultinetworkPolicyTracker = true;
+        mConfig.mUsingIpReachabilityMonitor = true;
+        mConfig.mRequestedPreDhcpActionMs = 42;
+        mConfig.mInitialConfig = new InitialConfiguration();
+        mConfig.mInitialConfig.ipAddresses.add(
+                new LinkAddress(parseNumericAddress("192.168.42.42"), 24));
+        mConfig.mStaticIpConfig = new StaticIpConfiguration();
+        mConfig.mStaticIpConfig.ipAddress =
+                new LinkAddress(parseNumericAddress("2001:db8::42"), 90);
+        // Not testing other InitialConfig or StaticIpConfig members: they have their own unit tests
+        mConfig.mApfCapabilities = new ApfCapabilities(1, 2, 3);
+        mConfig.mProvisioningTimeoutMs = 4200;
+        mConfig.mIPv6AddrGenMode = 123;
+        mConfig.mNetwork = new Network(321);
+        mConfig.mDisplayName = "test_config";
+        // Any added field must be included in equals() to be tested properly
+        assertFieldCountEquals(12, ProvisioningConfiguration.class);
+    }
+
+    @Test
+    public void testParcelUnparcel() {
+        doParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcel_NullInitialConfiguration() {
+        mConfig.mInitialConfig = null;
+        doParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcel_NullStaticConfiguration() {
+        mConfig.mStaticIpConfig = null;
+        doParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcel_NullApfCapabilities() {
+        mConfig.mApfCapabilities = null;
+        doParcelUnparcelTest();
+    }
+
+    @Test
+    public void testParcelUnparcel_NullNetwork() {
+        mConfig.mNetwork = null;
+        doParcelUnparcelTest();
+    }
+
+    private void doParcelUnparcelTest() {
+        final ProvisioningConfiguration unparceled =
+                fromStableParcelable(mConfig.toStableParcelable());
+        assertEquals(mConfig, unparceled);
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(mConfig, new ProvisioningConfiguration(mConfig));
+
+        assertNotEqualsAfterChange(c -> c.mEnableIPv4 = false);
+        assertNotEqualsAfterChange(c -> c.mEnableIPv6 = false);
+        assertNotEqualsAfterChange(c -> c.mUsingMultinetworkPolicyTracker = false);
+        assertNotEqualsAfterChange(c -> c.mUsingIpReachabilityMonitor = false);
+        assertNotEqualsAfterChange(c -> c.mRequestedPreDhcpActionMs++);
+        assertNotEqualsAfterChange(c -> c.mInitialConfig.ipAddresses.add(
+                new LinkAddress(parseNumericAddress("192.168.47.47"), 16)));
+        assertNotEqualsAfterChange(c -> c.mInitialConfig = null);
+        assertNotEqualsAfterChange(c -> c.mStaticIpConfig.ipAddress =
+                new LinkAddress(parseNumericAddress("2001:db8::47"), 64));
+        assertNotEqualsAfterChange(c -> c.mStaticIpConfig = null);
+        assertNotEqualsAfterChange(c -> c.mApfCapabilities = new ApfCapabilities(4, 5, 6));
+        assertNotEqualsAfterChange(c -> c.mApfCapabilities = null);
+        assertNotEqualsAfterChange(c -> c.mProvisioningTimeoutMs++);
+        assertNotEqualsAfterChange(c -> c.mIPv6AddrGenMode++);
+        assertNotEqualsAfterChange(c -> c.mNetwork = new Network(123));
+        assertNotEqualsAfterChange(c -> c.mNetwork = null);
+        assertNotEqualsAfterChange(c -> c.mDisplayName = "other_test");
+        assertNotEqualsAfterChange(c -> c.mDisplayName = null);
+        assertFieldCountEquals(12, ProvisioningConfiguration.class);
+    }
+
+    private void assertNotEqualsAfterChange(Consumer<ProvisioningConfiguration> mutator) {
+        final ProvisioningConfiguration newConfig = new ProvisioningConfiguration(mConfig);
+        mutator.accept(newConfig);
+        assertNotEquals(mConfig, newConfig);
+    }
+}
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
index e63c3b0..94bcd28 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -16,13 +16,30 @@
 
 package com.android.server.net.ipmemorystore;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 
 import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnBlobRetrievedListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.Status;
+import android.net.ipmemorystore.StatusParcelable;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,41 +47,267 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
+import java.lang.reflect.Modifier;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 
-/** Unit tests for {@link IpMemoryStoreServiceTest}. */
+/** Unit tests for {@link IpMemoryStoreService}. */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class IpMemoryStoreServiceTest {
+    private static final String TEST_CLIENT_ID = "testClientId";
+    private static final String TEST_DATA_NAME = "testData";
+
     @Mock
-    Context mMockContext;
+    private Context mMockContext;
+    private File mDbFile;
+
+    private IpMemoryStoreService mService;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString());
+        final Context context = InstrumentationRegistry.getContext();
+        final File dir = context.getFilesDir();
+        mDbFile = new File(dir, "test.db");
+        doReturn(mDbFile).when(mMockContext).getDatabasePath(anyString());
+        mService = new IpMemoryStoreService(mMockContext);
+    }
+
+    @After
+    public void tearDown() {
+        mService.shutdown();
+        mDbFile.delete();
+    }
+
+    /** Helper method to make a vanilla IOnStatusListener */
+    private IOnStatusListener onStatus(Consumer<Status> functor) {
+        return new IOnStatusListener() {
+            @Override
+            public void onComplete(final StatusParcelable statusParcelable) throws RemoteException {
+                functor.accept(new Status(statusParcelable));
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+    }
+
+    /** Helper method to make an IOnBlobRetrievedListener */
+    private interface OnBlobRetrievedListener {
+        void onBlobRetrieved(Status status, String l2Key, String name, byte[] data);
+    }
+    private IOnBlobRetrievedListener onBlobRetrieved(final OnBlobRetrievedListener functor) {
+        return new IOnBlobRetrievedListener() {
+            @Override
+            public void onBlobRetrieved(final StatusParcelable statusParcelable,
+                    final String l2Key, final String name, final Blob blob) throws RemoteException {
+                functor.onBlobRetrieved(new Status(statusParcelable), l2Key, name,
+                        null == blob ? null : blob.data);
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+    }
+
+    /** Helper method to make an IOnNetworkAttributesRetrievedListener */
+    private interface OnNetworkAttributesRetrievedListener  {
+        void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attr);
+    }
+    private IOnNetworkAttributesRetrieved onNetworkAttributesRetrieved(
+            final OnNetworkAttributesRetrievedListener functor) {
+        return new IOnNetworkAttributesRetrieved() {
+            @Override
+            public void onL2KeyResponse(final StatusParcelable status, final String l2Key,
+                    final NetworkAttributesParcelable attributes)
+                    throws RemoteException {
+                functor.onNetworkAttributesRetrieved(new Status(status), l2Key,
+                        null == attributes ? null : new NetworkAttributes(attributes));
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+    }
+
+    // Helper method to factorize some boilerplate
+    private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        functor.accept(latch);
+        try {
+            latch.await(5000, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail(timeoutMessage);
+        }
     }
 
     @Test
     public void testNetworkAttributes() {
-        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
-        // TODO : implement this
+        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+        try {
+            na.setAssignedV4Address(
+                    (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4}));
+        } catch (UnknownHostException e) { /* Can't happen */ }
+        na.setGroupHint("hint1");
+        na.setMtu(219);
+        final String l2Key = UUID.randomUUID().toString();
+        NetworkAttributes attributes = na.build();
+        doLatched("Did not complete storing attributes", latch ->
+                mService.storeNetworkAttributes(l2Key, attributes.toParcelable(),
+                        onStatus(status -> {
+                            assertTrue("Store status not successful : " + status.resultCode,
+                                    status.isSuccess());
+                            latch.countDown();
+                        })));
+
+        doLatched("Did not complete retrieving attributes", latch ->
+                mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+                        (status, key, attr) -> {
+                            assertTrue("Retrieve network attributes not successful : "
+                                    + status.resultCode, status.isSuccess());
+                            assertEquals(l2Key, key);
+                            assertEquals(attributes, attr);
+                            latch.countDown();
+                        })));
+
+        final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder();
+        try {
+            na.setDnsAddresses(Arrays.asList(
+                    new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")}));
+        } catch (UnknownHostException e) { /* Still can't happen */ }
+        final NetworkAttributes attributes2 = na2.build();
+        doLatched("Did not complete storing attributes 2", latch ->
+                mService.storeNetworkAttributes(l2Key, attributes2.toParcelable(),
+                        onStatus(status -> latch.countDown())));
+
+        doLatched("Did not complete retrieving attributes 2", latch ->
+                mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+                        (status, key, attr) -> {
+                            assertTrue("Retrieve network attributes not successful : "
+                                    + status.resultCode, status.isSuccess());
+                            assertEquals(l2Key, key);
+                            assertEquals(attributes.assignedV4Address, attr.assignedV4Address);
+                            assertEquals(attributes.groupHint, attr.groupHint);
+                            assertEquals(attributes.mtu, attr.mtu);
+                            assertEquals(attributes2.dnsAddresses, attr.dnsAddresses);
+                            latch.countDown();
+                        })));
+
+        doLatched("Did not complete retrieving attributes 3", latch ->
+                mService.retrieveNetworkAttributes(l2Key + "nonexistent",
+                        onNetworkAttributesRetrieved(
+                                (status, key, attr) -> {
+                                    assertTrue("Retrieve network attributes not successful : "
+                                            + status.resultCode, status.isSuccess());
+                                    assertEquals(l2Key + "nonexistent", key);
+                                    assertNull("Retrieved data not stored", attr);
+                                    latch.countDown();
+                                }
+                        )));
+
+        // Verify that this test does not miss any new field added later.
+        // If any field is added to NetworkAttributes it must be tested here for storing
+        // and retrieving.
+        assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields())
+                .filter(f -> !Modifier.isStatic(f.getModifiers())).count());
+    }
+
+    @Test
+    public void testInvalidAttributes() {
+        doLatched("Did not complete storing bad attributes", latch ->
+                mService.storeNetworkAttributes("key", null, onStatus(status -> {
+                    assertFalse("Success storing on a null key",
+                            status.isSuccess());
+                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+                    latch.countDown();
+                })));
+
+        final NetworkAttributes na = new NetworkAttributes.Builder().setMtu(2).build();
+        doLatched("Did not complete storing bad attributes", latch ->
+                mService.storeNetworkAttributes(null, na.toParcelable(), onStatus(status -> {
+                    assertFalse("Success storing null attributes on a null key",
+                            status.isSuccess());
+                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+                    latch.countDown();
+                })));
+
+        doLatched("Did not complete storing bad attributes", latch ->
+                mService.storeNetworkAttributes(null, null, onStatus(status -> {
+                    assertFalse("Success storing null attributes on a null key",
+                            status.isSuccess());
+                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+                    latch.countDown();
+                })));
+
+        doLatched("Did not complete retrieving bad attributes", latch ->
+                mService.retrieveNetworkAttributes(null, onNetworkAttributesRetrieved(
+                        (status, key, attr) -> {
+                            assertFalse("Success retrieving attributes for a null key",
+                                    status.isSuccess());
+                            assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+                            assertNull(key);
+                            assertNull(attr);
+                        })));
     }
 
     @Test
     public void testPrivateData() {
-        final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext);
-        // TODO : implement this
+        final Blob b = new Blob();
+        b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
+        final String l2Key = UUID.randomUUID().toString();
+        doLatched("Did not complete storing private data", latch ->
+                mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                        onStatus(status -> {
+                            assertTrue("Store status not successful : " + status.resultCode,
+                                    status.isSuccess());
+                            latch.countDown();
+                        })));
+
+        doLatched("Did not complete retrieving private data", latch ->
+                mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
+                        (status, key, name, data) -> {
+                            assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                                    status.isSuccess());
+                            assertEquals(l2Key, key);
+                            assertEquals(name, TEST_DATA_NAME);
+                            Arrays.equals(b.data, data);
+                            latch.countDown();
+                        })));
+
+        // Most puzzling error message ever
+        doLatched("Did not complete retrieving nothing", latch ->
+                mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME + "2", onBlobRetrieved(
+                        (status, key, name, data) -> {
+                            assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                                    status.isSuccess());
+                            assertEquals(l2Key, key);
+                            assertEquals(name, TEST_DATA_NAME + "2");
+                            assertNull(data);
+                            latch.countDown();
+                        })));
     }
 
     @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/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 01728fa1..2a8f695 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -17,6 +17,7 @@
 Generate API lists for non-SDK API enforcement.
 """
 import argparse
+from collections import defaultdict
 import os
 import sys
 import re
@@ -27,16 +28,20 @@
 FLAG_BLACKLIST = "blacklist"
 FLAG_GREYLIST_MAX_O = "greylist-max-o"
 FLAG_GREYLIST_MAX_P = "greylist-max-p"
+FLAG_CORE_PLATFORM_API = "core-platform-api"
 
 # List of all known flags.
-FLAGS = [
+FLAGS_API_LIST = [
     FLAG_WHITELIST,
     FLAG_GREYLIST,
     FLAG_BLACKLIST,
     FLAG_GREYLIST_MAX_O,
     FLAG_GREYLIST_MAX_P,
 ]
-FLAGS_SET = set(FLAGS)
+ALL_FLAGS = FLAGS_API_LIST + [ FLAG_CORE_PLATFORM_API ]
+
+FLAGS_API_LIST_SET = set(FLAGS_API_LIST)
+ALL_FLAGS_SET = set(ALL_FLAGS)
 
 # Suffix used in command line args to express that only known and
 # otherwise unassigned entries should be assign the given flag.
@@ -62,7 +67,7 @@
 SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$')
 
 # Predicates to be used with filter_apis.
-IS_UNASSIGNED = lambda api, flags: not flags
+HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags)
 IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api)
 
 def get_args():
@@ -73,12 +78,10 @@
     """
     parser = argparse.ArgumentParser()
     parser.add_argument('--output', required=True)
-    parser.add_argument('--public', required=True, help='list of all public entries')
-    parser.add_argument('--private', required=True, help='list of all private entries')
     parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE',
         help='CSV files to be merged into output')
 
-    for flag in FLAGS:
+    for flag in ALL_FLAGS:
         ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX
         parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE',
             help='lists of entries with flag "' + flag + '"')
@@ -118,26 +121,9 @@
         f.writelines(lines)
 
 class FlagsDict:
-    def __init__(self, public_api, private_api):
-        # Bootstrap the entries dictionary.
-
-        # Check that the two sets do not overlap.
-        public_api_set = set(public_api)
-        private_api_set = set(private_api)
-        assert public_api_set.isdisjoint(private_api_set), (
-            "Lists of public and private API overlap. " +
-            "This suggests an issue with the `hiddenapi` build tool.")
-
-        # Compute the whole key set
-        self._dict_keyset = public_api_set.union(private_api_set)
-
-        # Create a dict that creates entries for both public and private API,
-        # and assigns public API to the whitelist.
-        self._dict = {}
-        for api in public_api:
-            self._dict[api] = set([ FLAG_WHITELIST ])
-        for api in private_api:
-            self._dict[api] = set()
+    def __init__(self):
+        self._dict_keyset = set()
+        self._dict = defaultdict(set)
 
     def _check_entries_set(self, keys_subset, source):
         assert isinstance(keys_subset, set)
@@ -150,12 +136,12 @@
 
     def _check_flags_set(self, flags_subset, source):
         assert isinstance(flags_subset, set)
-        assert flags_subset.issubset(FLAGS_SET), (
+        assert flags_subset.issubset(ALL_FLAGS_SET), (
             "Error processing: {}\n"
             "The following flags were not recognized: \n"
             "{}\n"
             "Please visit go/hiddenapi for more information.").format(
-                source, "\n".join(flags_subset - FLAGS_SET))
+                source, "\n".join(flags_subset - ALL_FLAGS_SET))
 
     def filter_apis(self, filter_fn):
         """Returns APIs which match a given predicate.
@@ -173,7 +159,7 @@
 
     def get_valid_subset_of_unassigned_apis(self, api_subset):
         """Sanitizes a key set input to only include keys which exist in the dictionary
-        and have not been assigned any flags.
+        and have not been assigned any API list flags.
 
         Args:
             entries_subset (set/list): Key set to be sanitized.
@@ -182,7 +168,7 @@
             Sanitized key set.
         """
         assert isinstance(api_subset, set)
-        return api_subset.intersection(self.filter_apis(IS_UNASSIGNED))
+        return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED))
 
     def generate_csv(self):
         """Constructs CSV entries from a dictionary.
@@ -203,14 +189,13 @@
             source (string): Origin of `csv_lines`. Will be printed in error messages.
 
         Throws:
-            AssertionError if parsed API signatures of flags are invalid.
+            AssertionError if parsed flags are invalid.
         """
         # Split CSV lines into arrays of values.
         csv_values = [ line.split(',') for line in csv_lines ]
 
-        # Check that all entries exist in the dict.
-        csv_keys = set([ csv[0] for csv in csv_values ])
-        self._check_entries_set(csv_keys, source)
+        # Update the full set of API signatures.
+        self._dict_keyset.update([ csv[0] for csv in csv_values ])
 
         # Check that all flags are known.
         csv_flags = set(reduce(lambda x, y: set(x).union(y), [ csv[1:] for csv in csv_values ], []))
@@ -224,7 +209,7 @@
         """Assigns a flag to given subset of entries.
 
         Args:
-            flag (string): One of FLAGS.
+            flag (string): One of ALL_FLAGS.
             apis (set): Subset of APIs to recieve the flag.
             source (string): Origin of `entries_subset`. Will be printed in error messages.
 
@@ -245,18 +230,23 @@
     # Parse arguments.
     args = vars(get_args())
 
-    flags = FlagsDict(read_lines(args["public"]), read_lines(args["private"]))
+    # Initialize API->flags dictionary.
+    flags = FlagsDict()
+
+    # Merge input CSV files into the dictionary.
+    # Do this first because CSV files produced by parsing API stubs will
+    # contain the full set of APIs. Subsequent additions from text files
+    # will be able to detect invalid entries, and/or filter all as-yet
+    # unassigned entries.
+    for filename in args["csv"]:
+        flags.parse_and_merge_csv(read_lines(filename), filename)
 
     # Combine inputs which do not require any particular order.
     # (1) Assign serialization API to whitelist.
     flags.assign_flag(FLAG_WHITELIST, flags.filter_apis(IS_SERIALIZATION))
 
-    # (2) Merge input CSV files into the dictionary.
-    for filename in args["csv"]:
-        flags.parse_and_merge_csv(read_lines(filename), filename)
-
-    # (3) Merge text files with a known flag into the dictionary.
-    for flag in FLAGS:
+    # (2) Merge text files with a known flag into the dictionary.
+    for flag in ALL_FLAGS:
         for filename in args[flag]:
             flags.assign_flag(flag, read_lines(filename), filename)
 
@@ -265,13 +255,13 @@
     # (a) the entry exists, and
     # (b) it has not been assigned any other flag.
     # Because of (b), this must run after all strict assignments have been performed.
-    for flag in FLAGS:
+    for flag in ALL_FLAGS:
         for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]:
             valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename))
             flags.assign_flag(flag, valid_entries, filename)
 
     # Assign all remaining entries to the blacklist.
-    flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(IS_UNASSIGNED))
+    flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED))
 
     # Write output.
     write_lines(args["output"], flags.generate_csv())