Merge "Clean up the keepalive slots when network disconnect"
diff --git a/Android.bp b/Android.bp
index 57f6513..53da6e2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -763,6 +763,7 @@
     srcs: [
         "core/java/android/annotation/IntDef.java",
         "core/java/android/annotation/UnsupportedAppUsage.java",
+        ":unsupportedappusage_annotation_files",
     ],
 }
 
diff --git a/api/current.txt b/api/current.txt
index 2417d8e..ea82aa1 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -11323,6 +11323,7 @@
     field public static final String FEATURE_HIFI_SENSORS = "android.hardware.sensor.hifi_sensors";
     field public static final String FEATURE_HOME_SCREEN = "android.software.home_screen";
     field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
+    field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
     field public static final String FEATURE_LEANBACK = "android.software.leanback";
     field public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
     field public static final String FEATURE_LIVE_TV = "android.software.live_tv";
@@ -20930,7 +20931,6 @@
     ctor public JapaneseCalendar(int, int, int, int);
     ctor public JapaneseCalendar(int, int, int);
     ctor public JapaneseCalendar(int, int, int, int, int, int);
-    field @Deprecated public static final int CURRENT_ERA;
     field public static final int HEISEI;
     field public static final int MEIJI;
     field public static final int SHOWA;
@@ -26786,6 +26786,7 @@
     method public int getVideoHeight();
     method public float getVideoPixelAspectRatio();
     method public int getVideoWidth();
+    method public boolean isEncrypted();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
     field public static final int TYPE_AUDIO = 0; // 0x0
@@ -26799,6 +26800,7 @@
     method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
     method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
     method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
+    method public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
     method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
     method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
     method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
diff --git a/api/removed.txt b/api/removed.txt
index 72202ad..f40b146 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -275,6 +275,14 @@
 
 }
 
+package android.icu.util {
+
+  public class JapaneseCalendar extends android.icu.util.GregorianCalendar {
+    field public static final int CURRENT_ERA;
+  }
+
+}
+
 package android.location {
 
   public class Location implements android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 3b4a868..ce379f5 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4609,17 +4609,6 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
     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 DEFAULT_SM_DP_PLUS = "default_sm_dp_plus";
diff --git a/api/test-current.txt b/api/test-current.txt
index cb9ff493..21d761f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1201,17 +1201,6 @@
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
     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 HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
     field public static final String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch";
     field public static final String LOW_POWER_MODE = "low_power";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1ac6ea8..f04f017 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -33,6 +33,7 @@
 import "frameworks/base/core/proto/android/server/connectivity/data_stall_event.proto";
 import "frameworks/base/core/proto/android/server/enums.proto";
 import "frameworks/base/core/proto/android/stats/connectivity/network_stack.proto";
+import "frameworks/base/core/proto/android/stats/dnsresolver/dns_resolver.proto";
 import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
 import "frameworks/base/core/proto/android/telecomm/enums.proto";
 import "frameworks/base/core/proto/android/telephony/enums.proto";
@@ -133,7 +134,7 @@
         PhoneStateChanged phone_state_changed = 95;
         LowMemReported low_mem_reported = 81;
         ThermalThrottlingStateChanged thermal_throttling = 86;
-        NetworkDnsEventReported network_dns_event_reported = 116;
+        NetworkDnsEventReported network_dns_event_reported = 116 [(log_from_module) = "resolv"];
         DataStallEvent data_stall_event = 121 [(log_from_module) = "network_stack"];
         BluetoothLinkLayerConnectionEvent bluetooth_link_layer_connection_event = 125;
         BluetoothAclConnectionStateChanged bluetooth_acl_connection_state_changed = 126;
@@ -2794,51 +2795,37 @@
 }
 
 /**
- * Logs the latency period(in microseconds) and the return code of
- * the DNS(Domain Name System) lookups.
- * These 4 methods(GETADDRINFO,GETHOSTBYNAME,GETHOSTBYADDR,RES_NSEND)
- * to get info(address or hostname) from DNS server(or DNS cache).
- * Logged from:
- *   /system/netd/server/DnsProxyListener.cpp
+ * Logs a DNS lookup operation initiated by the system resolver on behalf of an application
+ * invoking native APIs such as getaddrinfo() or Java APIs such as Network#getAllByName().
+ *
+ * The NetworkDnsEventReported message represents the entire lookup operation, which may
+ * result one or more queries to the recursive DNS resolvers. Those are individually logged
+ * in DnsQueryEvents to enable computing error rates and network latency and timeouts
+ * broken up by query type, transport, network interface, etc.
  */
 message NetworkDnsEventReported {
-    // The types of the DNS lookups, as defined in
-    //system/netd/server/binder/android/net/metrics/INetdEventListener.aidl
-    enum EventType {
-        EVENT_UNKNOWN = 0;
-        EVENT_GETADDRINFO = 1;
-        EVENT_GETHOSTBYNAME = 2;
-        EVENT_GETHOSTBYADDR = 3;
-        EVENT_RES_NSEND = 4;
-    }
-    optional EventType event_type = 1;
+    optional android.stats.dnsresolver.EventType event_type = 1;
 
-    // The return value of the DNS resolver for each DNS lookups.
-    //bionic/libc/include/netdb.h
-    //system/netd/resolv/include/netd_resolv/resolv.h
-    enum ReturnCode {
-        EAI_NO_ERROR = 0;
-        EAI_ADDRFAMILY = 1;
-        EAI_AGAIN = 2;
-        EAI_BADFLAGS = 3;
-        EAI_FAIL = 4;
-        EAI_FAMILY = 5;
-        EAI_MEMORY = 6;
-        EAI_NODATA = 7;
-        EAI_NONAME = 8;
-        EAI_SERVICE = 9;
-        EAI_SOCKTYPE = 10;
-        EAI_SYSTEM = 11;
-        EAI_BADHINTS = 12;
-        EAI_PROTOCOL = 13;
-        EAI_OVERFLOW = 14;
-        RESOLV_TIMEOUT = 255;
-        EAI_MAX = 256;
-    }
-    optional ReturnCode return_code = 2;
+    optional android.stats.dnsresolver.ReturnCode return_code = 2;
 
-    // The latency period(in microseconds) it took for this DNS lookup to complete.
+    // The latency in microseconds of the entire DNS lookup operation.
     optional int32 latency_micros = 3;
+
+    // Only valid for event_type = EVENT_GETADDRINFO.
+    optional int32 hints_ai_flags = 4;
+
+    // Flags passed to android_res_nsend() defined in multinetwork.h
+    // Only valid for event_type = EVENT_RESNSEND.
+    optional int32 res_nsend_flags = 5;
+
+    optional android.stats.dnsresolver.Transport network_type = 6;
+
+    // The DNS over TLS mode on a specific netId.
+    optional android.stats.dnsresolver.PrivateDnsModes private_dns_modes = 7;
+
+    // Additional pass-through fields opaque to statsd.
+    // The DNS resolver Mainline module can add new fields here without requiring an OS update.
+    optional android.stats.dnsresolver.DnsQueryEvents dns_query_events = 8 [(log_mode) = MODE_BYTES];
 }
 
 /**
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 70fa5fa..f6b7eef 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -555,7 +555,7 @@
                 new CachedServiceFetcher<RcsManager>() {
                     @Override
                     public RcsManager createService(ContextImpl ctx) {
-                        return new RcsManager();
+                        return new RcsManager(ctx.getOuterContext());
                     }
                 });
 
diff --git a/core/java/android/app/backup/OWNERS b/core/java/android/app/backup/OWNERS
index 1c9a43a..9c21e8f 100644
--- a/core/java/android/app/backup/OWNERS
+++ b/core/java/android/app/backup/OWNERS
@@ -1,7 +1,9 @@
-artikz@google.com
+alsutton@google.com
+anniemeng@google.com
 brufino@google.com
 bryanmawhinney@google.com
 ctate@google.com
 jorlow@google.com
-mkarpinski@google.com
+nathch@google.com
+rthakohov@google.com
 
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 9faa1d6..e7f2d18 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -22,23 +22,16 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.ParcelUuid;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
-
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 
 /**
@@ -209,101 +202,32 @@
     @UnsupportedAppUsage
     public static final int OPTIONAL_CODECS_PREF_ENABLED = 1;
 
-    private Context mContext;
-    private ServiceListener mServiceListener;
-    private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock();
-    @GuardedBy("mServiceLock")
-    private IBluetoothA2dp mService;
     private BluetoothAdapter mAdapter;
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        try {
-                            mServiceLock.writeLock().lock();
-                            if (mService != null) {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            }
-                        } catch (Exception re) {
-                            Log.e(TAG, "", re);
-                        } finally {
-                            mServiceLock.writeLock().unlock();
-                        }
-                    } else {
-                        try {
-                            mServiceLock.readLock().lock();
-                            if (mService == null) {
-                                if (VDBG) Log.d(TAG, "Binding service...");
-                                doBind();
-                            }
-                        } catch (Exception re) {
-                            Log.e(TAG, "", re);
-                        } finally {
-                            mServiceLock.readLock().unlock();
-                        }
-                    }
+    private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp",
+                    IBluetoothA2dp.class.getName()) {
+                @Override
+                public IBluetoothA2dp getServiceInterface(IBinder service) {
+                    return IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothA2dp proxy object for interacting with the local
      * Bluetooth A2DP service.
      */
-    /*package*/ BluetoothA2dp(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
+    /*package*/ BluetoothA2dp(Context context, ServiceListener listener) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothA2dp.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                UserHandle.CURRENT_OR_SELF)) {
-            Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     @UnsupportedAppUsage
     /*package*/ void close() {
-        mServiceListener = null;
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        try {
-            mServiceLock.writeLock().lock();
-            if (mService != null) {
-                mService = null;
-                mContext.unbindService(mConnection);
-            }
-        } catch (Exception re) {
-            Log.e(TAG, "", re);
-        } finally {
-            mServiceLock.writeLock().unlock();
-        }
+    private IBluetoothA2dp getService() {
+        return mProfileConnector.getService();
     }
 
     @Override
@@ -333,17 +257,15 @@
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled() && isValidDevice(device)) {
-                return mService.connect(device);
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.connect(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -376,17 +298,15 @@
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled() && isValidDevice(device)) {
-                return mService.disconnect(device);
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.disconnect(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -397,17 +317,15 @@
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.getConnectedDevices();
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.getConnectedDevices();
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<BluetoothDevice>();
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return new ArrayList<BluetoothDevice>();
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -418,17 +336,15 @@
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.getDevicesMatchingConnectionStates(states);
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.getDevicesMatchingConnectionStates(states);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<BluetoothDevice>();
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return new ArrayList<BluetoothDevice>();
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -439,18 +355,16 @@
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.STATE_DISCONNECTED;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return BluetoothProfile.STATE_DISCONNECTED;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -480,18 +394,16 @@
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
                     && ((device == null) || isValidDevice(device))) {
-                return mService.setActiveDevice(device);
+                return service.setActiveDevice(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -511,17 +423,15 @@
     public BluetoothDevice getActiveDevice() {
         if (VDBG) log("getActiveDevice()");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.getActiveDevice();
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.getActiveDevice();
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return null;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return null;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -543,22 +453,20 @@
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
                 if (priority != BluetoothProfile.PRIORITY_OFF
                         && priority != BluetoothProfile.PRIORITY_ON) {
                     return false;
                 }
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -578,18 +486,16 @@
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.PRIORITY_OFF;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return BluetoothProfile.PRIORITY_OFF;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -602,17 +508,15 @@
     public boolean isAvrcpAbsoluteVolumeSupported() {
         if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.isAvrcpAbsoluteVolumeSupported();
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.isAvrcpAbsoluteVolumeSupported();
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -625,15 +529,13 @@
     public void setAvrcpAbsoluteVolume(int volume) {
         if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                mService.setAvrcpAbsoluteVolume(volume);
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                service.setAvrcpAbsoluteVolume(volume);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -646,18 +548,16 @@
      */
     public boolean isA2dpPlaying(BluetoothDevice device) {
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return mService.isA2dpPlaying(device);
+                return service.isA2dpPlaying(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -694,19 +594,17 @@
     public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.getCodecStatus(device);
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.getCodecStatus(device);
             }
-            if (mService == null) {
+            if (service == null) {
                 Log.w(TAG, "Proxy not attached to service");
             }
             return null;
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to BT service in getCodecStatus()", e);
             return null;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -723,17 +621,15 @@
                                          BluetoothCodecConfig codecConfig) {
         if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                mService.setCodecConfigPreference(device, codecConfig);
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                service.setCodecConfigPreference(device, codecConfig);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return;
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e);
             return;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -772,21 +668,19 @@
      */
     private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
                 if (enable) {
-                    mService.enableOptionalCodecs(device);
+                    service.enableOptionalCodecs(device);
                 } else {
-                    mService.disableOptionalCodecs(device);
+                    service.disableOptionalCodecs(device);
                 }
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return;
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e);
             return;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -801,17 +695,15 @@
     @UnsupportedAppUsage
     public int supportsOptionalCodecs(BluetoothDevice device) {
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled() && isValidDevice(device)) {
-                return mService.supportsOptionalCodecs(device);
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.supportsOptionalCodecs(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to BT service in getSupportsOptionalCodecs()", e);
             return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -826,17 +718,15 @@
     @UnsupportedAppUsage
     public int getOptionalCodecsEnabled(BluetoothDevice device) {
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled() && isValidDevice(device)) {
-                return mService.getOptionalCodecsEnabled(device);
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.getOptionalCodecsEnabled(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return OPTIONAL_CODECS_PREF_UNKNOWN;
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to BT service in getSupportsOptionalCodecs()", e);
             return OPTIONAL_CODECS_PREF_UNKNOWN;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -858,18 +748,16 @@
                 Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
                 return;
             }
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                mService.setOptionalCodecsEnabled(device, value);
+                service.setOptionalCodecsEnabled(device, value);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -900,35 +788,6 @@
         }
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            try {
-                mServiceLock.writeLock().lock();
-                mService = IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service));
-            } finally {
-                mServiceLock.writeLock().unlock();
-            }
-
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.A2DP, BluetoothA2dp.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            try {
-                mServiceLock.writeLock().lock();
-                mService = null;
-            } finally {
-                mServiceLock.writeLock().unlock();
-            }
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
-            }
-        }
-    };
-
     private boolean isEnabled() {
         if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
         return false;
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index fda2f89..5a8055a 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -17,10 +17,7 @@
 package android.bluetooth;
 
 import android.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -124,93 +121,31 @@
     public static final String EXTRA_AUDIO_CONFIG =
             "android.bluetooth.a2dp-sink.profile.extra.AUDIO_CONFIG";
 
-    private Context mContext;
-    private ServiceListener mServiceListener;
-    private volatile IBluetoothA2dpSink mService;
     private BluetoothAdapter mAdapter;
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
+    private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK,
+                    "BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) {
+                @Override
+                public IBluetoothA2dpSink getServiceInterface(IBinder service) {
+                    return IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothA2dp proxy object for interacting with the local
      * Bluetooth A2DP service.
      */
-    /*package*/ BluetoothA2dpSink(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
+    /*package*/ BluetoothA2dpSink(Context context, ServiceListener listener) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothA2dpSink.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     /*package*/ void close() {
-        mServiceListener = null;
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
+    private IBluetoothA2dpSink getService() {
+        return mProfileConnector.getService();
     }
 
     @Override
@@ -241,7 +176,7 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.connect(device);
@@ -282,7 +217,7 @@
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.disconnect(device);
@@ -301,7 +236,7 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -320,7 +255,7 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -339,7 +274,7 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -365,7 +300,7 @@
      */
     public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
         if (VDBG) log("getAudioConfig(" + device + ")");
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getAudioConfig(device);
@@ -395,7 +330,7 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             if (priority != BluetoothProfile.PRIORITY_OFF
                     && priority != BluetoothProfile.PRIORITY_ON) {
@@ -427,7 +362,7 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getPriority(device);
@@ -448,7 +383,7 @@
      * @param device BluetoothDevice device
      */
     public boolean isA2dpPlaying(BluetoothDevice device) {
-        final IBluetoothA2dpSink service = mService;
+        final IBluetoothA2dpSink service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.isA2dpPlaying(device);
@@ -487,25 +422,6 @@
         }
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.A2DP_SINK,
-                        BluetoothA2dpSink.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP_SINK);
-            }
-        }
-    };
-
     private boolean isEnabled() {
         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index e7c8944..4e7e441 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -16,10 +16,7 @@
 
 package android.bluetooth;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -79,93 +76,32 @@
     public static final String EXTRA_PLAYER_SETTING =
             "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
 
-    private Context mContext;
-    private ServiceListener mServiceListener;
-    private volatile IBluetoothAvrcpController mService;
     private BluetoothAdapter mAdapter;
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
+    private final BluetoothProfileConnector<IBluetoothAvrcpController> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.AVRCP_CONTROLLER,
+                    "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) {
+                @Override
+                public IBluetoothAvrcpController getServiceInterface(IBinder service) {
+                    return IBluetoothAvrcpController.Stub.asInterface(
+                            Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothAvrcpController proxy object for interacting with the local
      * Bluetooth AVRCP service.
      */
-    /*package*/ BluetoothAvrcpController(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
+    /*package*/ BluetoothAvrcpController(Context context, ServiceListener listener) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothAvrcpController.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth AVRCP Controller Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     /*package*/ void close() {
-        mServiceListener = null;
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
+    private IBluetoothAvrcpController getService() {
+        return mProfileConnector.getService();
     }
 
     @Override
@@ -179,7 +115,8 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothAvrcpController service = mService;
+        final IBluetoothAvrcpController service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -198,7 +135,8 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothAvrcpController service = mService;
+        final IBluetoothAvrcpController service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -217,7 +155,8 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        final IBluetoothAvrcpController service = mService;
+        final IBluetoothAvrcpController service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -238,7 +177,8 @@
     public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getPlayerSettings");
         BluetoothAvrcpPlayerSettings settings = null;
-        final IBluetoothAvrcpController service = mService;
+        final IBluetoothAvrcpController service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 settings = service.getPlayerSettings(device);
@@ -256,7 +196,8 @@
      */
     public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
         if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
-        final IBluetoothAvrcpController service = mService;
+        final IBluetoothAvrcpController service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.setPlayerApplicationSetting(plAppSetting);
@@ -276,7 +217,8 @@
     public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
         Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = "
                 + keyState);
-        final IBluetoothAvrcpController service = mService;
+        final IBluetoothAvrcpController service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 service.sendGroupNavigationCmd(device, keyCode, keyState);
@@ -289,25 +231,6 @@
         if (service == null) Log.w(TAG, "Proxy not attached to service");
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothAvrcpController.Stub.asInterface(Binder.allowBlocking(service));
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER,
-                        BluetoothAvrcpController.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.AVRCP_CONTROLLER);
-            }
-        }
-    };
-
     private boolean isEnabled() {
         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 8d9d340..9862a63 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -337,19 +337,9 @@
                 public void onBluetoothStateChange(boolean up) {
                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
                     if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
                         doUnbind();
                     } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
+                        doBind();
                     }
                 }
             };
@@ -374,24 +364,32 @@
         doBind();
     }
 
-    boolean doBind() {
-        try {
-            return mAdapter.getBluetoothManager().bindBluetoothProfileService(
-                    BluetoothProfile.HEADSET, mConnection);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Unable to bind HeadsetService", e);
+    private boolean doBind() {
+        synchronized (mConnection) {
+            if (mService == null) {
+                if (VDBG) Log.d(TAG, "Binding service...");
+                try {
+                    return mAdapter.getBluetoothManager().bindBluetoothProfileService(
+                            BluetoothProfile.HEADSET, mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to bind HeadsetService", e);
+                }
+            }
         }
         return false;
     }
 
-    void doUnbind() {
+    private void doUnbind() {
         synchronized (mConnection) {
             if (mService != null) {
+                if (VDBG) Log.d(TAG, "Unbinding service...");
                 try {
                     mAdapter.getBluetoothManager().unbindBluetoothProfileService(
                             BluetoothProfile.HEADSET, mConnection);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Unable to unbind HeadsetService", e);
+                } finally {
+                    mService = null;
                 }
             }
         }
@@ -411,8 +409,8 @@
         if (mgr != null) {
             try {
                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
             }
         }
         mServiceListener = null;
@@ -1169,7 +1167,7 @@
         @Override
         public void onServiceDisconnected(ComponentName className) {
             if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mService = null;
+            doUnbind();
             mHandler.sendMessage(mHandler.obtainMessage(
                     MESSAGE_HEADSET_SERVICE_DISCONNECTED));
         }
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index ec18d42..05833b5 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -17,10 +17,7 @@
 package android.bluetooth;
 
 import android.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -366,73 +363,22 @@
     public static final int CALL_ACCEPT_HOLD = 1;
     public static final int CALL_ACCEPT_TERMINATE = 2;
 
-    private Context mContext;
-    private ServiceListener mServiceListener;
-    private volatile IBluetoothHeadsetClient mService;
     private BluetoothAdapter mAdapter;
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
+    private final BluetoothProfileConnector<IBluetoothHeadsetClient> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.HEADSET_CLIENT,
+                    "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) {
                 @Override
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    Intent intent = new Intent(
-                                            IBluetoothHeadsetClient.class.getName());
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
+                public IBluetoothHeadsetClient getServiceInterface(IBinder service) {
+                    return IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothHeadsetClient proxy object.
      */
-    /*package*/ BluetoothHeadsetClient(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
+    /*package*/ BluetoothHeadsetClient(Context context, ServiceListener listener) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothHeadsetClient.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth Headset Client Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     /**
@@ -443,27 +389,11 @@
      */
     /*package*/ void close() {
         if (VDBG) log("close()");
+        mProfileConnector.disconnect();
+    }
 
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
+    private IBluetoothHeadsetClient getService() {
+        return mProfileConnector.getService();
     }
 
     /**
@@ -480,7 +410,8 @@
     @UnsupportedAppUsage
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.connect(device);
@@ -503,7 +434,8 @@
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.disconnect(device);
@@ -524,7 +456,8 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -547,7 +480,8 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -569,7 +503,8 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getConnectionState(" + device + ")");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -589,7 +524,8 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             if (priority != BluetoothProfile.PRIORITY_OFF
                     && priority != BluetoothProfile.PRIORITY_ON) {
@@ -611,7 +547,8 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getPriority(device);
@@ -637,7 +574,8 @@
      */
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.startVoiceRecognition(device);
@@ -662,7 +600,8 @@
      */
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.stopVoiceRecognition(device);
@@ -682,7 +621,8 @@
      */
     public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
         if (DBG) log("getCurrentCalls()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getCurrentCalls(device);
@@ -702,7 +642,8 @@
      */
     public Bundle getCurrentAgEvents(BluetoothDevice device) {
         if (DBG) log("getCurrentCalls()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getCurrentAgEvents(device);
@@ -726,7 +667,8 @@
     @UnsupportedAppUsage
     public boolean acceptCall(BluetoothDevice device, int flag) {
         if (DBG) log("acceptCall()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.acceptCall(device, flag);
@@ -747,7 +689,8 @@
      */
     public boolean holdCall(BluetoothDevice device) {
         if (DBG) log("holdCall()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.holdCall(device);
@@ -773,7 +716,8 @@
     @UnsupportedAppUsage
     public boolean rejectCall(BluetoothDevice device) {
         if (DBG) log("rejectCall()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.rejectCall(device);
@@ -803,7 +747,8 @@
      */
     public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
         if (DBG) log("terminateCall()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.terminateCall(device, call);
@@ -831,7 +776,8 @@
      */
     public boolean enterPrivateMode(BluetoothDevice device, int index) {
         if (DBG) log("enterPrivateMode()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.enterPrivateMode(device, index);
@@ -858,7 +804,8 @@
      */
     public boolean explicitCallTransfer(BluetoothDevice device) {
         if (DBG) log("explicitCallTransfer()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.explicitCallTransfer(device);
@@ -881,7 +828,8 @@
      */
     public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
         if (DBG) log("dial()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.dial(device, number);
@@ -905,7 +853,8 @@
      */
     public boolean sendDTMF(BluetoothDevice device, byte code) {
         if (DBG) log("sendDTMF()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.sendDTMF(device, code);
@@ -931,7 +880,8 @@
      */
     public boolean getLastVoiceTagNumber(BluetoothDevice device) {
         if (DBG) log("getLastVoiceTagNumber()");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getLastVoiceTagNumber(device);
@@ -951,7 +901,8 @@
     @UnsupportedAppUsage
     public int getAudioState(BluetoothDevice device) {
         if (VDBG) log("getAudioState");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getAudioState(device);
@@ -974,7 +925,8 @@
      */
     public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
         if (VDBG) log("setAudioRouteAllowed");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 service.setAudioRouteAllowed(device, allowed);
@@ -996,7 +948,8 @@
      */
     public boolean getAudioRouteAllowed(BluetoothDevice device) {
         if (VDBG) log("getAudioRouteAllowed");
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getAudioRouteAllowed(device);
@@ -1020,7 +973,8 @@
      * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
      */
     public boolean connectAudio(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.connectAudio(device);
@@ -1044,7 +998,8 @@
      * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
      */
     public boolean disconnectAudio(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.disconnectAudio(device);
@@ -1065,7 +1020,8 @@
      * @return bundle of AG features; null if no service or AG not connected
      */
     public Bundle getCurrentAgFeatures(BluetoothDevice device) {
-        final IBluetoothHeadsetClient service = mService;
+        final IBluetoothHeadsetClient service =
+                getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getCurrentAgFeatures(device);
@@ -1079,29 +1035,6 @@
         return null;
     }
 
-
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service));
-
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET_CLIENT,
-                        BluetoothHeadsetClient.this);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET_CLIENT);
-            }
-        }
-    };
-
     private boolean isEnabled() {
         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index 58ff2e1..60fb6fb 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -22,20 +22,14 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
-import com.android.internal.annotations.GuardedBy;
-
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
  * This class provides the public APIs to control the Hearing Aid profile.
@@ -128,97 +122,31 @@
      */
     public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID;
 
-    private Context mContext;
-    private ServiceListener mServiceListener;
-    private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock();
-    @GuardedBy("mServiceLock")
-    private IBluetoothHearingAid mService;
     private BluetoothAdapter mAdapter;
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        try {
-                            mServiceLock.writeLock().lock();
-                            mService = null;
-                            mContext.unbindService(mConnection);
-                        } catch (Exception re) {
-                            Log.e(TAG, "", re);
-                        } finally {
-                            mServiceLock.writeLock().unlock();
-                        }
-                    } else {
-                        try {
-                            mServiceLock.readLock().lock();
-                            if (mService == null) {
-                                if (VDBG) Log.d(TAG, "Binding service...");
-                                doBind();
-                            }
-                        } catch (Exception re) {
-                            Log.e(TAG, "", re);
-                        } finally {
-                            mServiceLock.readLock().unlock();
-                        }
-                    }
+    private final BluetoothProfileConnector<IBluetoothHearingAid> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.HEARING_AID,
+                    "BluetoothHearingAid", IBluetoothHearingAid.class.getName()) {
+                @Override
+                public IBluetoothHearingAid getServiceInterface(IBinder service) {
+                    return IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothHearingAid proxy object for interacting with the local
      * Bluetooth Hearing Aid service.
      */
-    /*package*/ BluetoothHearingAid(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
+    /*package*/ BluetoothHearingAid(Context context, ServiceListener listener) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        doBind();
-    }
-
-    void doBind() {
-        Intent intent = new Intent(IBluetoothHearingAid.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                android.os.Process.myUserHandle())) {
-            Log.e(TAG, "Could not bind to Bluetooth Hearing Aid Service with " + intent);
-            return;
-        }
+        mProfileConnector.connect(context, listener);
     }
 
     /*package*/ void close() {
-        mServiceListener = null;
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        try {
-            mServiceLock.writeLock().lock();
-            if (mService != null) {
-                mService = null;
-                mContext.unbindService(mConnection);
-            }
-        } catch (Exception re) {
-            Log.e(TAG, "", re);
-        } finally {
-            mServiceLock.writeLock().unlock();
-        }
+    private IBluetoothHearingAid getService() {
+        return mProfileConnector.getService();
     }
 
     /**
@@ -240,18 +168,16 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled() && isValidDevice(device)) {
-                return mService.connect(device);
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.connect(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -282,18 +208,16 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled() && isValidDevice(device)) {
-                return mService.disconnect(device);
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.disconnect(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -303,18 +227,16 @@
     @Override
     public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.getConnectedDevices();
+            if (service != null && isEnabled()) {
+                return service.getConnectedDevices();
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<BluetoothDevice>();
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return new ArrayList<BluetoothDevice>();
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -325,18 +247,16 @@
     public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
     @NonNull int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.getDevicesMatchingConnectionStates(states);
+            if (service != null && isEnabled()) {
+                return service.getDevicesMatchingConnectionStates(states);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<BluetoothDevice>();
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return new ArrayList<BluetoothDevice>();
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -347,19 +267,17 @@
     public @BluetoothProfile.BtProfileState int getConnectionState(
     @NonNull BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return mService.getConnectionState(device);
+                return service.getConnectionState(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.STATE_DISCONNECTED;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return BluetoothProfile.STATE_DISCONNECTED;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -387,20 +305,18 @@
      */
     public boolean setActiveDevice(@Nullable BluetoothDevice device) {
         if (DBG) log("setActiveDevice(" + device + ")");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            if (service != null && isEnabled()
                     && ((device == null) || isValidDevice(device))) {
-                mService.setActiveDevice(device);
+                service.setActiveDevice(device);
                 return true;
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -418,18 +334,16 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     public List<BluetoothDevice> getActiveDevices() {
         if (VDBG) log("getActiveDevices()");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.getActiveDevices();
+            if (service != null && isEnabled()) {
+                return service.getActiveDevices();
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return new ArrayList<>();
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return new ArrayList<>();
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -450,23 +364,21 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
                 if (priority != BluetoothProfile.PRIORITY_OFF
                         && priority != BluetoothProfile.PRIORITY_ON) {
                     return false;
                 }
-                return mService.setPriority(device, priority);
+                return service.setPriority(device, priority);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return false;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return false;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -484,19 +396,17 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return mService.getPriority(device);
+                return service.getPriority(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return BluetoothProfile.PRIORITY_OFF;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return BluetoothProfile.PRIORITY_OFF;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -535,18 +445,16 @@
         if (VDBG) {
             log("getVolume()");
         }
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()) {
-                return mService.getVolume();
+            if (service != null && isEnabled()) {
+                return service.getVolume();
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return 0;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return 0;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -566,21 +474,18 @@
     public void adjustVolume(int direction) {
         if (DBG) log("adjustVolume(" + direction + ")");
 
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-
-            if (mService == null) {
+            if (service == null) {
                 Log.w(TAG, "Proxy not attached to service");
                 return;
             }
 
             if (!isEnabled()) return;
 
-            mService.adjustVolume(direction);
+            service.adjustVolume(direction);
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -593,20 +498,18 @@
     public void setVolume(int volume) {
         if (DBG) Log.d(TAG, "setVolume(" + volume + ")");
 
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService == null) {
+            if (service == null) {
                 Log.w(TAG, "Proxy not attached to service");
                 return;
             }
 
             if (!isEnabled()) return;
 
-            mService.setVolume(volume);
+            service.setVolume(volume);
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -622,21 +525,19 @@
         if (VDBG) {
             log("getCustomerId(" + device + ")");
         }
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService == null) {
+            if (service == null) {
                 Log.w(TAG, "Proxy not attached to service");
                 return HI_SYNC_ID_INVALID;
             }
 
             if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID;
 
-            return mService.getHiSyncId(device);
+            return service.getHiSyncId(device);
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return HI_SYNC_ID_INVALID;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -652,19 +553,17 @@
         if (VDBG) {
             log("getDeviceSide(" + device + ")");
         }
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return mService.getDeviceSide(device);
+                return service.getDeviceSide(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return SIDE_LEFT;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return SIDE_LEFT;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
@@ -680,52 +579,20 @@
         if (VDBG) {
             log("getDeviceMode(" + device + ")");
         }
+        final IBluetoothHearingAid service = getService();
         try {
-            mServiceLock.readLock().lock();
-            if (mService != null && isEnabled()
+            if (service != null && isEnabled()
                     && isValidDevice(device)) {
-                return mService.getDeviceMode(device);
+                return service.getDeviceMode(device);
             }
-            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
             return MODE_MONAURAL;
         } catch (RemoteException e) {
             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
             return MODE_MONAURAL;
-        } finally {
-            mServiceLock.readLock().unlock();
         }
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            try {
-                mServiceLock.writeLock().lock();
-                mService = IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service));
-            } finally {
-                mServiceLock.writeLock().unlock();
-            }
-
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.HEARING_AID,
-                                                    BluetoothHearingAid.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            try {
-                mServiceLock.writeLock().lock();
-                mService = null;
-            } finally {
-                mServiceLock.writeLock().unlock();
-            }
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.HEARING_AID);
-            }
-        }
-    };
-
     private boolean isEnabled() {
         if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
         return false;
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index 3bc8544..e9b0be2 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -18,10 +18,8 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -327,11 +325,6 @@
         }
     }
 
-    private Context mContext;
-    private ServiceListener mServiceListener;
-    private volatile IBluetoothHidDevice mService;
-    private BluetoothAdapter mAdapter;
-
     private static class CallbackWrapper extends IBluetoothHidDeviceCallback.Stub {
 
         private final Executor mExecutor;
@@ -385,114 +378,33 @@
         }
     }
 
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-
-                public void onBluetoothStateChange(boolean up) {
-                    Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    synchronized (mConnection) {
-                        if (up) {
-                            try {
-                                if (mService == null) {
-                                    Log.d(TAG, "Binding HID Device service...");
-                                    doBind();
-                                }
-                            } catch (IllegalStateException e) {
-                                Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev "
-                                        + "service: ", e);
-                            } catch (SecurityException e) {
-                                Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev "
-                                        + "service: ", e);
-                            }
-                        } else {
-                            Log.d(TAG, "Unbinding service...");
-                            doUnbind();
-                        }
-                    }
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothHidDevice> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.HID_DEVICE,
+                    "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) {
+                @Override
+                public IBluetoothHidDevice getServiceInterface(IBinder service) {
+                    return IBluetoothHidDevice.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
-
-    private final ServiceConnection mConnection =
-            new ServiceConnection() {
-                public void onServiceConnected(ComponentName className, IBinder service) {
-                    Log.d(TAG, "onServiceConnected()");
-                    mService = IBluetoothHidDevice.Stub.asInterface(service);
-                    if (mServiceListener != null) {
-                        mServiceListener.onServiceConnected(
-                                BluetoothProfile.HID_DEVICE, BluetoothHidDevice.this);
-                    }
-                }
-
-                public void onServiceDisconnected(ComponentName className) {
-                    Log.d(TAG, "onServiceDisconnected()");
-                    mService = null;
-                    if (mServiceListener != null) {
-                        mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
-                    }
-                }
-            };
+    };
 
     BluetoothHidDevice(Context context, ServiceListener listener) {
-        mContext = context;
-        mServiceListener = listener;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothHidDevice.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
-            return false;
-        }
-        Log.d(TAG, "Bound to HID Device Service");
-        return true;
-    }
-
-    void doUnbind() {
-        if (mService != null) {
-            mService = null;
-            try {
-                mContext.unbindService(mConnection);
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Unable to unbind HidDevService", e);
-            }
-        }
+        mProfileConnector.connect(context, listener);
     }
 
     void close() {
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                e.printStackTrace();
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        synchronized (mConnection) {
-            doUnbind();
-        }
-        mServiceListener = null;
+    private IBluetoothHidDevice getService() {
+        return mProfileConnector.getService();
     }
 
     /** {@inheritDoc} */
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 return service.getConnectedDevices();
@@ -509,7 +421,7 @@
     /** {@inheritDoc} */
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -526,7 +438,7 @@
     /** {@inheritDoc} */
     @Override
     public int getConnectionState(BluetoothDevice device) {
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 return service.getConnectionState(device);
@@ -583,7 +495,7 @@
             throw new IllegalArgumentException("callback parameter cannot be null");
         }
 
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 CallbackWrapper cbw = new CallbackWrapper(executor, callback);
@@ -611,7 +523,7 @@
     public boolean unregisterApp() {
         boolean result = false;
 
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 result = service.unregisterApp();
@@ -636,7 +548,7 @@
     public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
         boolean result = false;
 
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 result = service.sendReport(device, id, data);
@@ -662,7 +574,7 @@
     public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
         boolean result = false;
 
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 result = service.replyReport(device, type, id, data);
@@ -686,7 +598,7 @@
     public boolean reportError(BluetoothDevice device, byte error) {
         boolean result = false;
 
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 result = service.reportError(device, error);
@@ -707,7 +619,7 @@
      * {@hide}
      */
     public String getUserAppName() {
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
 
         if (service != null) {
             try {
@@ -733,7 +645,7 @@
     public boolean connect(BluetoothDevice device) {
         boolean result = false;
 
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 result = service.connect(device);
@@ -757,7 +669,7 @@
     public boolean disconnect(BluetoothDevice device) {
         boolean result = false;
 
-        final IBluetoothHidDevice service = mService;
+        final IBluetoothHidDevice service = getService();
         if (service != null) {
             try {
                 result = service.disconnect(device);
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index 289e769..4afb382 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -18,10 +18,7 @@
 
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -219,97 +216,32 @@
     public static final String EXTRA_IDLE_TIME =
             "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME";
 
-    private Context mContext;
-    private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
-    private volatile IBluetoothHidHost mService;
-
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                if (mService != null) {
-                                    mService = null;
-                                    mContext.unbindService(mConnection);
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
+    private final BluetoothProfileConnector<IBluetoothHidHost> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.HID_HOST,
+                    "BluetoothHidHost", IBluetoothHidHost.class.getName()) {
+                @Override
+                public IBluetoothHidHost getServiceInterface(IBinder service) {
+                    return IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothHidHost proxy object for interacting with the local
      * Bluetooth Service which handles the InputDevice profile
      */
-    /*package*/ BluetoothHidHost(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
+    /*package*/ BluetoothHidHost(Context context, ServiceListener listener) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothHidHost.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     /*package*/ void close() {
         if (VDBG) log("close()");
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
+    private IBluetoothHidHost getService() {
+        return mProfileConnector.getService();
     }
 
     /**
@@ -333,7 +265,7 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.connect(device);
@@ -373,7 +305,7 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.disconnect(device);
@@ -392,7 +324,7 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -411,7 +343,7 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -430,7 +362,7 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -460,7 +392,7 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             if (priority != BluetoothProfile.PRIORITY_OFF
                     && priority != BluetoothProfile.PRIORITY_ON) {
@@ -492,7 +424,7 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getPriority(device);
@@ -505,26 +437,6 @@
         return BluetoothProfile.PRIORITY_OFF;
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
-
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.HID_HOST,
-                        BluetoothHidHost.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.HID_HOST);
-            }
-        }
-    };
-
     private boolean isEnabled() {
         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
     }
@@ -544,7 +456,7 @@
      */
     public boolean virtualUnplug(BluetoothDevice device) {
         if (DBG) log("virtualUnplug(" + device + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.virtualUnplug(device);
@@ -570,7 +482,7 @@
      */
     public boolean getProtocolMode(BluetoothDevice device) {
         if (VDBG) log("getProtocolMode(" + device + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getProtocolMode(device);
@@ -594,7 +506,7 @@
      */
     public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
         if (DBG) log("setProtocolMode(" + device + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.setProtocolMode(device, protocolMode);
@@ -625,7 +537,7 @@
             log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
                     + "bufferSize=" + bufferSize);
         }
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getReport(device, reportType, reportId, bufferSize);
@@ -651,7 +563,7 @@
      */
     public boolean setReport(BluetoothDevice device, byte reportType, String report) {
         if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.setReport(device, reportType, report);
@@ -676,7 +588,7 @@
      */
     public boolean sendData(BluetoothDevice device, String report) {
         if (DBG) log("sendData(" + device + "), report=" + report);
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.sendData(device, report);
@@ -700,7 +612,7 @@
      */
     public boolean getIdleTime(BluetoothDevice device) {
         if (DBG) log("getIdletime(" + device + ")");
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getIdleTime(device);
@@ -725,7 +637,7 @@
      */
     public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
         if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
-        final IBluetoothHidHost service = mService;
+        final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.setIdleTime(device, idleTime);
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 98c23c6..dd2f150 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -17,10 +17,7 @@
 package android.bluetooth;
 
 import android.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -44,11 +41,6 @@
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
 
-    private volatile IBluetoothMap mService;
-    private final Context mContext;
-    private ServiceListener mServiceListener;
-    private BluetoothAdapter mAdapter;
-
     /** There was an error trying to obtain the state */
     public static final int STATE_ERROR = -1;
 
@@ -57,64 +49,23 @@
     /** Connection canceled before completion. */
     public static final int RESULT_CANCELED = 2;
 
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothMap> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.MAP,
+                    "BluetoothMap", IBluetoothMap.class.getName()) {
+                @Override
+                public IBluetoothMap getServiceInterface(IBinder service) {
+                    return IBluetoothMap.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothMap proxy object.
      */
-    /*package*/ BluetoothMap(Context context, ServiceListener l) {
+    /*package*/ BluetoothMap(Context context, ServiceListener listener) {
         if (DBG) Log.d(TAG, "Create BluetoothMap proxy object");
-        mContext = context;
-        mServiceListener = l;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothMap.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth MAP Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     protected void finalize() throws Throwable {
@@ -132,26 +83,11 @@
      * are ok.
      */
     public synchronized void close() {
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
+    private IBluetoothMap getService() {
+        return mProfileConnector.getService();
     }
 
     /**
@@ -162,7 +98,7 @@
      */
     public int getState() {
         if (VDBG) log("getState()");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null) {
             try {
                 return service.getState();
@@ -184,7 +120,7 @@
      */
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null) {
             try {
                 return service.getClient();
@@ -205,7 +141,7 @@
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null) {
             try {
                 return service.isConnected(device);
@@ -237,7 +173,7 @@
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.disconnect(device);
@@ -278,7 +214,7 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -298,7 +234,7 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -318,7 +254,7 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -344,7 +280,7 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             if (priority != BluetoothProfile.PRIORITY_OFF
                     && priority != BluetoothProfile.PRIORITY_ON) {
@@ -373,7 +309,7 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothMap service = mService;
+        final IBluetoothMap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getPriority(device);
@@ -386,24 +322,6 @@
         return PRIORITY_OFF;
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) log("Proxy object connected");
-            mService = IBluetoothMap.Stub.asInterface(Binder.allowBlocking(service));
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.MAP, BluetoothMap.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) log("Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.MAP);
-            }
-        }
-    };
-
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 559a59b..ec0180c5 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -18,11 +18,9 @@
 
 import android.annotation.UnsupportedAppUsage;
 import android.app.PendingIntent;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
@@ -60,11 +58,6 @@
     public static final String EXTRA_SENDER_CONTACT_NAME =
             "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
 
-    private volatile IBluetoothMapClient mService;
-    private final Context mContext;
-    private ServiceListener mServiceListener;
-    private BluetoothAdapter mAdapter;
-
     /** There was an error trying to obtain the state */
     public static final int STATE_ERROR = -1;
 
@@ -75,64 +68,23 @@
 
     private static final int UPLOADING_FEATURE_BITMASK = 0x08;
 
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT,
+                    "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
+                @Override
+                public IBluetoothMapClient getServiceInterface(IBinder service) {
+                    return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothMapClient proxy object.
      */
-    /*package*/ BluetoothMapClient(Context context, ServiceListener l) {
+    /*package*/ BluetoothMapClient(Context context, ServiceListener listener) {
         if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
-        mContext = context;
-        mServiceListener = l;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothMapClient.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth MAP MCE Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     protected void finalize() throws Throwable {
@@ -150,26 +102,11 @@
      * are ok.
      */
     public void close() {
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
+    private IBluetoothMapClient getService() {
+        return mProfileConnector.getService();
     }
 
     /**
@@ -179,7 +116,7 @@
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null) {
             try {
                 return service.isConnected(device);
@@ -199,7 +136,7 @@
      */
     public boolean connect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null) {
             try {
                 return service.connect(device);
@@ -221,7 +158,7 @@
      */
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "disconnect(" + device + ")");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.disconnect(device);
@@ -241,7 +178,7 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) Log.d(TAG, "getConnectedDevices()");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -262,7 +199,7 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -283,7 +220,7 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -307,7 +244,7 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             if (priority != BluetoothProfile.PRIORITY_OFF
                     && priority != BluetoothProfile.PRIORITY_ON) {
@@ -336,7 +273,7 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getPriority(device);
@@ -365,7 +302,7 @@
     public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
             PendingIntent sentIntent, PendingIntent deliveredIntent) {
         if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
@@ -385,7 +322,7 @@
      */
     public boolean getUnreadMessages(BluetoothDevice device) {
         if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
-        final IBluetoothMapClient service = mService;
+        final IBluetoothMapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getUnreadMessages(device);
@@ -405,34 +342,16 @@
      *         MapSupportedFeatures field is set. False is returned otherwise.
      */
     public boolean isUploadingSupported(BluetoothDevice device) {
+        final IBluetoothMapClient service = getService();
         try {
-            return (mService != null && isEnabled() && isValidDevice(device))
-                && ((mService.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
+            return (service != null && isEnabled() && isValidDevice(device))
+                && ((service.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
         } catch (RemoteException e) {
             Log.e(TAG, e.getMessage());
         }
         return false;
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "Proxy object connected");
-            mService = IBluetoothMapClient.Stub.asInterface(service);
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.MAP_CLIENT,
-                        BluetoothMapClient.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.MAP_CLIENT);
-            }
-        }
-    };
-
     private boolean isEnabled() {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 58be732..fb78789 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -19,10 +19,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -121,108 +118,42 @@
      */
     public static final int PAN_OPERATION_SUCCESS = 1004;
 
-    private Context mContext;
-    private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
-    private volatile IBluetoothPan mPanService;
+    private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.PAN,
+                    "BluetoothPan", IBluetoothPan.class.getName()) {
+                @Override
+                public IBluetoothPan getServiceInterface(IBinder service) {
+                    return IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
 
     /**
      * Create a BluetoothPan proxy object for interacting with the local
      * Bluetooth Service which handles the Pan profile
      */
     @UnsupportedAppUsage
-    /*package*/ BluetoothPan(Context context, ServiceListener l) {
-        mContext = context;
-        mServiceListener = l;
+    /*package*/ BluetoothPan(Context context, ServiceListener listener) {
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        try {
-            mAdapter.getBluetoothManager().registerStateChangeCallback(mStateChangeCallback);
-        } catch (RemoteException re) {
-            Log.w(TAG, "Unable to register BluetoothStateChangeCallback", re);
-        }
-        if (VDBG) Log.d(TAG, "BluetoothPan() call bindService");
-        doBind();
-    }
-
-    @UnsupportedAppUsage
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothPan.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth Pan Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     @UnsupportedAppUsage
     /*package*/ void close() {
         if (VDBG) log("close()");
-
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mStateChangeCallback);
-            } catch (RemoteException re) {
-                Log.w(TAG, "Unable to unregister BluetoothStateChangeCallback", re);
-            }
-        }
-
-        synchronized (mConnection) {
-            if (mPanService != null) {
-                try {
-                    mPanService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
+        mProfileConnector.disconnect();
     }
 
+    private IBluetoothPan getService() {
+        return mProfileConnector.getService();
+    }
+
+
     protected void finalize() {
         close();
     }
 
-    private final IBluetoothStateChangeCallback mStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-
-                @Override
-                public void onBluetoothStateChange(boolean on) {
-                    // Handle enable request to bind again.
-                    Log.d(TAG, "onBluetoothStateChange on: " + on);
-                    if (on) {
-                        try {
-                            if (mPanService == null) {
-                                if (VDBG) Log.d(TAG, "onBluetoothStateChange calling doBind()");
-                                doBind();
-                            }
-
-                        } catch (IllegalStateException e) {
-                            Log.e(TAG, "onBluetoothStateChange: could not bind to PAN service: ",
-                                    e);
-
-                        } catch (SecurityException e) {
-                            Log.e(TAG, "onBluetoothStateChange: could not bind to PAN service: ",
-                                    e);
-                        }
-                    } else {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mPanService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
-                }
-            };
-
     /**
      * Initiate connection to a profile of the remote bluetooth device.
      *
@@ -243,7 +174,7 @@
     @UnsupportedAppUsage
     public boolean connect(BluetoothDevice device) {
         if (DBG) log("connect(" + device + ")");
-        final IBluetoothPan service = mPanService;
+        final IBluetoothPan service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.connect(device);
@@ -284,7 +215,7 @@
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothPan service = mPanService;
+        final IBluetoothPan service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.disconnect(device);
@@ -303,7 +234,7 @@
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
-        final IBluetoothPan service = mPanService;
+        final IBluetoothPan service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -322,7 +253,7 @@
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (VDBG) log("getDevicesMatchingStates()");
-        final IBluetoothPan service = mPanService;
+        final IBluetoothPan service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -341,7 +272,7 @@
     @Override
     public int getConnectionState(BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
-        final IBluetoothPan service = mPanService;
+        final IBluetoothPan service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -357,7 +288,7 @@
     @UnsupportedAppUsage
     public void setBluetoothTethering(boolean value) {
         if (DBG) log("setBluetoothTethering(" + value + ")");
-        final IBluetoothPan service = mPanService;
+        final IBluetoothPan service = getService();
         if (service != null && isEnabled()) {
             try {
                 service.setBluetoothTethering(value);
@@ -370,7 +301,7 @@
     @UnsupportedAppUsage
     public boolean isTetheringOn() {
         if (VDBG) log("isTetheringOn()");
-        final IBluetoothPan service = mPanService;
+        final IBluetoothPan service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.isTetheringOn();
@@ -381,25 +312,6 @@
         return false;
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) Log.d(TAG, "BluetoothPAN Proxy object connected");
-            mPanService = IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.PAN,
-                        BluetoothPan.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) Log.d(TAG, "BluetoothPAN Proxy object disconnected");
-            mPanService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.PAN);
-            }
-        }
-    };
-
     @UnsupportedAppUsage
     private boolean isEnabled() {
         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index b303c34..d94c657 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -24,6 +24,7 @@
 import android.content.ServiceConnection;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -117,28 +118,9 @@
                 public void onBluetoothStateChange(boolean up) {
                     log("onBluetoothStateChange: up=" + up);
                     if (!up) {
-                        log("Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                if (mService != null) {
-                                    mService = null;
-                                    mContext.unbindService(mConnection);
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
+                        doUnbind();
                     } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    log("Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
+                        doBind();
                     }
                 }
             };
@@ -154,25 +136,51 @@
         if (mgr != null) {
             try {
                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
             }
         }
         doBind();
     }
 
     boolean doBind() {
-        Intent intent = new Intent(IBluetoothPbap.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
-            return false;
+        synchronized (mConnection) {
+            try {
+                if (mService == null) {
+                    log("Binding service...");
+                    Intent intent = new Intent(IBluetoothPbap.class.getName());
+                    ComponentName comp = intent.resolveSystemService(
+                            mContext.getPackageManager(), 0);
+                    intent.setComponent(comp);
+                    if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                            UserHandle.CURRENT_OR_SELF)) {
+                        Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
+                        return false;
+                    }
+                }
+            } catch (SecurityException se) {
+                Log.e(TAG, "", se);
+                return false;
+            }
         }
         return true;
     }
 
+    private void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                log("Unbinding service...");
+                try {
+                    mContext.unbindService(mConnection);
+                } catch (IllegalArgumentException ie) {
+                    Log.e(TAG, "", ie);
+                } finally {
+                    mService = null;
+                }
+            }
+        }
+    }
+
     protected void finalize() throws Throwable {
         try {
             close();
@@ -192,21 +200,11 @@
         if (mgr != null) {
             try {
                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
             }
         }
-
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
+        doUnbind();
         mServiceListener = null;
     }
 
@@ -312,7 +310,7 @@
 
         public void onServiceDisconnected(ComponentName className) {
             log("Proxy object disconnected");
-            mService = null;
+            doUnbind();
             if (mServiceListener != null) {
                 mServiceListener.onServiceDisconnected();
             }
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index 1446adc..d70e1e7 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -16,10 +16,7 @@
 
 package android.bluetooth;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -42,11 +39,6 @@
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED";
 
-    private volatile IBluetoothPbapClient mService;
-    private final Context mContext;
-    private ServiceListener mServiceListener;
-    private BluetoothAdapter mAdapter;
-
     /** There was an error trying to obtain the state */
     public static final int STATE_ERROR = -1;
 
@@ -55,72 +47,25 @@
     /** Connection canceled before completion. */
     public static final int RESULT_CANCELED = 2;
 
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) {
-                        Log.d(TAG, "onBluetoothStateChange: PBAP CLIENT up=" + up);
-                    }
-                    if (!up) {
-                        if (VDBG) {
-                            Log.d(TAG, "Unbinding service...");
-                        }
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) {
-                                        Log.d(TAG, "Binding service...");
-                                    }
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothPbapClient> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.PBAP_CLIENT,
+                    "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) {
+                @Override
+                public IBluetoothPbapClient getServiceInterface(IBinder service) {
+                    return IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothPbapClient proxy object.
      */
-    BluetoothPbapClient(Context context, ServiceListener l) {
+    BluetoothPbapClient(Context context, ServiceListener listener) {
         if (DBG) {
             Log.d(TAG, "Create BluetoothPbapClient proxy object");
         }
-        mContext = context;
-        mServiceListener = l;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-        doBind();
-    }
-
-    private boolean doBind() {
-        Intent intent = new Intent(IBluetoothPbapClient.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth PBAP Client Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     protected void finalize() throws Throwable {
@@ -138,26 +83,11 @@
      * are ok.
      */
     public synchronized void close() {
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
+    private IBluetoothPbapClient getService() {
+        return mProfileConnector.getService();
     }
 
     /**
@@ -173,7 +103,7 @@
         if (DBG) {
             log("connect(" + device + ") for PBAP Client.");
         }
-        final IBluetoothPbapClient service = mService;
+        final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.connect(device);
@@ -198,7 +128,7 @@
         if (DBG) {
             log("disconnect(" + device + ")" + new Exception());
         }
-        final IBluetoothPbapClient service = mService;
+        final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 service.disconnect(device);
@@ -225,7 +155,7 @@
         if (DBG) {
             log("getConnectedDevices()");
         }
-        final IBluetoothPbapClient service = mService;
+        final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -250,7 +180,7 @@
         if (DBG) {
             log("getDevicesMatchingStates()");
         }
-        final IBluetoothPbapClient service = mService;
+        final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -275,7 +205,7 @@
         if (DBG) {
             log("getConnectionState(" + device + ")");
         }
-        final IBluetoothPbapClient service = mService;
+        final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -290,29 +220,6 @@
         return BluetoothProfile.STATE_DISCONNECTED;
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) {
-                log("Proxy object connected");
-            }
-            mService = IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service));
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.PBAP_CLIENT,
-                        BluetoothPbapClient.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) {
-                log("Proxy object disconnected");
-            }
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP_CLIENT);
-            }
-        }
-    };
-
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
@@ -345,7 +252,7 @@
         if (DBG) {
             log("setPriority(" + device + ", " + priority + ")");
         }
-        final IBluetoothPbapClient service = mService;
+        final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             if (priority != BluetoothProfile.PRIORITY_OFF
                     && priority != BluetoothProfile.PRIORITY_ON) {
@@ -378,7 +285,7 @@
         if (VDBG) {
             log("getPriority(" + device + ")");
         }
-        final IBluetoothPbapClient service = mService;
+        final IBluetoothPbapClient service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getPriority(device);
diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java
new file mode 100644
index 0000000..d9987249
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothProfileConnector.java
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+package android.bluetooth;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+/**
+ * Connector for Bluetooth profile proxies to bind manager service and
+ * profile services
+ * @param <T> The Bluetooth profile interface for this connection.
+ * @hide
+ */
+public abstract class BluetoothProfileConnector<T> {
+    private int mProfileId;
+    private BluetoothProfile.ServiceListener mServiceListener;
+    private BluetoothProfile mProfileProxy;
+    private Context mContext;
+    private String mProfileName;
+    private String mServiceName;
+    private volatile T mService;
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+        public void onBluetoothStateChange(boolean up) {
+            if (up) {
+                doBind();
+            } else {
+                doUnbind();
+            }
+        }
+    };
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            logDebug("Proxy object connected");
+            mService = getServiceInterface(service);
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(mProfileId, mProfileProxy);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            logDebug("Proxy object disconnected");
+            doUnbind();
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.A2DP);
+            }
+        }
+    };
+
+    BluetoothProfileConnector(BluetoothProfile profile, int profileId, String profileName,
+            String serviceName) {
+        mProfileId = profileId;
+        mProfileProxy = profile;
+        mProfileName = profileName;
+        mServiceName = serviceName;
+    }
+
+    private boolean doBind() {
+        synchronized (mConnection) {
+            if (mService == null) {
+                logDebug("Binding service...");
+                try {
+                    Intent intent = new Intent(mServiceName);
+                    ComponentName comp = intent.resolveSystemService(
+                            mContext.getPackageManager(), 0);
+                    intent.setComponent(comp);
+                    if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                            UserHandle.CURRENT_OR_SELF)) {
+                        logError("Could not bind to Bluetooth Service with " + intent);
+                        return false;
+                    }
+                } catch (SecurityException se) {
+                    logError("Failed to bind service. " + se);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                logDebug("Unbinding service...");
+                try {
+                    mContext.unbindService(mConnection);
+                } catch (IllegalArgumentException ie) {
+                    logError("Unable to unbind service: " + ie);
+                } finally {
+                    mService = null;
+                }
+            }
+        }
+    }
+
+    void connect(Context context, BluetoothProfile.ServiceListener listener) {
+        mContext = context;
+        mServiceListener = listener;
+        IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                logError("Failed to register state change callback. " + re);
+            }
+        }
+        doBind();
+    }
+
+    void disconnect() {
+        mServiceListener = null;
+        IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                logError("Failed to unregister state change callback" + re);
+            }
+        }
+        doUnbind();
+    }
+
+    T getService() {
+        return mService;
+    }
+
+    /**
+     * This abstract function is used to implement method to get the
+     * connected Bluetooth service interface.
+     * @param service the connected binder service.
+     * @return T the binder interface of {@code service}.
+     * @hide
+     */
+    public abstract T getServiceInterface(IBinder service);
+
+    private void logDebug(String log) {
+        Log.d(mProfileName, log);
+    }
+
+    private void logError(String log) {
+        Log.e(mProfileName, log);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index 1b73206..e0610c8 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -17,10 +17,7 @@
 package android.bluetooth;
 
 import android.annotation.UnsupportedAppUsage;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -69,11 +66,6 @@
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
 
-    private volatile IBluetoothSap mService;
-    private final Context mContext;
-    private ServiceListener mServiceListener;
-    private BluetoothAdapter mAdapter;
-
     /**
      * There was an error trying to obtain the state.
      *
@@ -95,64 +87,23 @@
      */
     public static final int RESULT_CANCELED = 2;
 
-    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
-            new IBluetoothStateChangeCallback.Stub() {
-                public void onBluetoothStateChange(boolean up) {
-                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
-                    if (!up) {
-                        if (VDBG) Log.d(TAG, "Unbinding service...");
-                        synchronized (mConnection) {
-                            try {
-                                mService = null;
-                                mContext.unbindService(mConnection);
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    } else {
-                        synchronized (mConnection) {
-                            try {
-                                if (mService == null) {
-                                    if (VDBG) Log.d(TAG, "Binding service...");
-                                    doBind();
-                                }
-                            } catch (Exception re) {
-                                Log.e(TAG, "", re);
-                            }
-                        }
-                    }
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothSap> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.SAP,
+                    "BluetoothSap", IBluetoothSap.class.getName()) {
+                @Override
+                public IBluetoothSap getServiceInterface(IBinder service) {
+                    return IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service));
                 }
-            };
+    };
 
     /**
      * Create a BluetoothSap proxy object.
      */
-    /*package*/ BluetoothSap(Context context, ServiceListener l) {
+    /*package*/ BluetoothSap(Context context, ServiceListener listener) {
         if (DBG) Log.d(TAG, "Create BluetoothSap proxy object");
-        mContext = context;
-        mServiceListener = l;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-        }
-        doBind();
-    }
-
-    boolean doBind() {
-        Intent intent = new Intent(IBluetoothSap.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                mContext.getUser())) {
-            Log.e(TAG, "Could not bind to Bluetooth SAP Service with " + intent);
-            return false;
-        }
-        return true;
+        mProfileConnector.connect(context, listener);
     }
 
     protected void finalize() throws Throwable {
@@ -172,26 +123,11 @@
      * @hide
      */
     public synchronized void close() {
-        IBluetoothManager mgr = mAdapter.getBluetoothManager();
-        if (mgr != null) {
-            try {
-                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
-            } catch (Exception e) {
-                Log.e(TAG, "", e);
-            }
-        }
+        mProfileConnector.disconnect();
+    }
 
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG, "", re);
-                }
-            }
-        }
-        mServiceListener = null;
+    private IBluetoothSap getService() {
+        return mProfileConnector.getService();
     }
 
     /**
@@ -203,7 +139,7 @@
      */
     public int getState() {
         if (VDBG) log("getState()");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null) {
             try {
                 return service.getState();
@@ -226,7 +162,7 @@
      */
     public BluetoothDevice getClient() {
         if (VDBG) log("getClient()");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null) {
             try {
                 return service.getClient();
@@ -249,7 +185,7 @@
      */
     public boolean isConnected(BluetoothDevice device) {
         if (VDBG) log("isConnected(" + device + ")");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null) {
             try {
                 return service.isConnected(device);
@@ -284,7 +220,7 @@
     @UnsupportedAppUsage
     public boolean disconnect(BluetoothDevice device) {
         if (DBG) log("disconnect(" + device + ")");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.disconnect(device);
@@ -305,7 +241,7 @@
      */
     public List<BluetoothDevice> getConnectedDevices() {
         if (DBG) log("getConnectedDevices()");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getConnectedDevices();
@@ -326,7 +262,7 @@
      */
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         if (DBG) log("getDevicesMatchingStates()");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null && isEnabled()) {
             try {
                 return service.getDevicesMatchingConnectionStates(states);
@@ -347,7 +283,7 @@
      */
     public int getConnectionState(BluetoothDevice device) {
         if (DBG) log("getConnectionState(" + device + ")");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getConnectionState(device);
@@ -372,7 +308,7 @@
      */
     public boolean setPriority(BluetoothDevice device, int priority) {
         if (DBG) log("setPriority(" + device + ", " + priority + ")");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             if (priority != BluetoothProfile.PRIORITY_OFF
                     && priority != BluetoothProfile.PRIORITY_ON) {
@@ -398,7 +334,7 @@
      */
     public int getPriority(BluetoothDevice device) {
         if (VDBG) log("getPriority(" + device + ")");
-        final IBluetoothSap service = mService;
+        final IBluetoothSap service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
             try {
                 return service.getPriority(device);
@@ -411,24 +347,6 @@
         return PRIORITY_OFF;
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) log("Proxy object connected");
-            mService = IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service));
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.SAP, BluetoothSap.this);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) log("Proxy object disconnected");
-            mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.SAP);
-            }
-        }
-    };
-
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 72981a7..4f7f07b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2719,6 +2719,16 @@
             = "android.content.pm.CLEAN_EXTERNAL_STORAGE";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the requisite kernel support for multinetworking-capable IPsec tunnels.
+     *
+     * <p>This feature implies that the device supports XFRM Interfaces (CONFIG_XFRM_INTERFACE), or
+     * VTIs with kernel patches allowing updates of output/set mark via UPDSA.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
+
+    /**
      * Extra field name for the URI to a verification file. Passed to a package
      * verifier.
      *
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index d710d57..ca43d40 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -80,7 +80,6 @@
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Base64;
-import android.util.ByteStringUtils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.PackageUtils;
@@ -99,6 +98,7 @@
 
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
+import libcore.util.HexEncoding;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -5999,7 +5999,8 @@
             }
 
             // first see if the hash represents a single-signer in our signing history
-            byte[] sha256Bytes = ByteStringUtils.fromHexToByteArray(sha256String);
+            byte[] sha256Bytes = sha256String == null
+                    ? null : HexEncoding.decode(sha256String, false /* allowSingleChar */);
             if (hasSha256Certificate(sha256Bytes, flags)) {
                 return true;
             }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 0e10de8..a69ca99 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3449,6 +3449,10 @@
             final NetworkCallback callback;
             synchronized (sCallbacks) {
                 callback = sCallbacks.get(request);
+                if (message.what == CALLBACK_UNAVAIL) {
+                    sCallbacks.remove(request);
+                    callback.networkRequest = ALREADY_UNREGISTERED;
+                }
             }
             if (DBG) {
                 Log.d(TAG, getCallbackName(message.what) + " for network " + network);
@@ -3995,8 +3999,10 @@
         synchronized (sCallbacks) {
             Preconditions.checkArgument(networkCallback.networkRequest != null,
                     "NetworkCallback was not registered");
-            Preconditions.checkArgument(networkCallback.networkRequest != ALREADY_UNREGISTERED,
-                    "NetworkCallback was already unregistered");
+            if (networkCallback.networkRequest == ALREADY_UNREGISTERED) {
+                Log.d(TAG, "NetworkCallback was already unregistered");
+                return;
+            }
             for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
                 if (e.getValue() == networkCallback) {
                     reqs.add(e.getKey());
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index a33f3fc..65bc75b 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -64,6 +64,8 @@
     @UnsupportedAppUsage
     public int mtu;
 
+    public String serverHostName;
+
     public DhcpResults() {
         super();
     }
@@ -97,6 +99,7 @@
             vendorInfo = source.vendorInfo;
             leaseDuration = source.leaseDuration;
             mtu = source.mtu;
+            serverHostName = source.serverHostName;
         }
     }
 
@@ -129,6 +132,7 @@
         vendorInfo = null;
         leaseDuration = 0;
         mtu = 0;
+        serverHostName = null;
     }
 
     @Override
@@ -139,6 +143,7 @@
         str.append(" Vendor info ").append(vendorInfo);
         str.append(" lease ").append(leaseDuration).append(" seconds");
         if (mtu != 0) str.append(" MTU ").append(mtu);
+        str.append(" Servername ").append(serverHostName);
 
         return str.toString();
     }
@@ -154,6 +159,7 @@
         return toStaticIpConfiguration().equals(target.toStaticIpConfiguration())
                 && Objects.equals(serverAddress, target.serverAddress)
                 && Objects.equals(vendorInfo, target.vendorInfo)
+                && Objects.equals(serverHostName, target.serverHostName)
                 && leaseDuration == target.leaseDuration
                 && mtu == target.mtu;
     }
@@ -179,6 +185,7 @@
         dest.writeInt(mtu);
         InetAddressUtils.parcelInetAddress(dest, serverAddress, flags);
         dest.writeString(vendorInfo);
+        dest.writeString(serverHostName);
     }
 
     @Override
@@ -193,6 +200,7 @@
         dhcpResults.mtu = in.readInt();
         dhcpResults.serverAddress = (Inet4Address) InetAddressUtils.unparcelInetAddress(in);
         dhcpResults.vendorInfo = in.readString();
+        dhcpResults.serverHostName = in.readString();
         return dhcpResults;
     }
 
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index d4c3edc..889e9bc 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -941,7 +941,8 @@
             throw new IllegalArgumentException(sse);
         } else if (sse.errorCode == OsConstants.EAGAIN) {
             throw new IllegalStateException(sse);
-        } else if (sse.errorCode == OsConstants.EOPNOTSUPP) {
+        } else if (sse.errorCode == OsConstants.EOPNOTSUPP
+                || sse.errorCode == OsConstants.EPROTONOSUPPORT) {
             throw new UnsupportedOperationException(sse);
         }
     }
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index 30d3b84..cd8ce8d 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -176,7 +176,7 @@
         out.writeInt(filteredRas);
         out.writeInt(currentRas);
         out.writeInt(programLength);
-        out.writeInt(flags);
+        out.writeInt(this.flags);
     }
 
     /** @hide */
@@ -192,6 +192,18 @@
                 programLength, actualLifetime, lifetimeString, namesOf(flags));
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(ApfProgramEvent.class))) return false;
+        final ApfProgramEvent other = (ApfProgramEvent) obj;
+        return lifetime == other.lifetime
+                && actualLifetime == other.actualLifetime
+                && filteredRas == other.filteredRas
+                && currentRas == other.currentRas
+                && programLength == other.programLength
+                && flags == other.flags;
+    }
+
     /** @hide */
     public static final Parcelable.Creator<ApfProgramEvent> CREATOR
             = new Parcelable.Creator<ApfProgramEvent>() {
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index ddc788d..2e78469 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -275,6 +275,22 @@
                 .toString();
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(ApfStats.class))) return false;
+        final ApfStats other = (ApfStats) obj;
+        return durationMs == other.durationMs
+                && receivedRas == other.receivedRas
+                && matchingRas == other.matchingRas
+                && droppedRas == other.droppedRas
+                && zeroLifetimeRas == other.zeroLifetimeRas
+                && parseErrors == other.parseErrors
+                && programUpdates == other.programUpdates
+                && programUpdatesAll == other.programUpdatesAll
+                && programUpdatesAllowingMulticast == other.programUpdatesAllowingMulticast
+                && maxProgramSize == other.maxProgramSize;
+    }
+
     /** @hide */
     public static final Parcelable.Creator<ApfStats> CREATOR = new Parcelable.Creator<ApfStats>() {
         public ApfStats createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 93063cb..fa6bff3 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -22,6 +22,7 @@
 import android.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 /**
  * An event recorded when a DhcpClient state machine transitions to a new state.
@@ -101,6 +102,14 @@
         return String.format("DhcpClientEvent(%s, %dms)", msg, durationMs);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(DhcpClientEvent.class))) return false;
+        final DhcpClientEvent other = (DhcpClientEvent) obj;
+        return TextUtils.equals(msg, other.msg)
+                && durationMs == other.durationMs;
+    }
+
     /** @hide */
     public static final Parcelable.Creator<DhcpClientEvent> CREATOR
         = new Parcelable.Creator<DhcpClientEvent>() {
diff --git a/core/java/android/net/metrics/IpManagerEvent.java b/core/java/android/net/metrics/IpManagerEvent.java
index 013e353..77908e6 100644
--- a/core/java/android/net/metrics/IpManagerEvent.java
+++ b/core/java/android/net/metrics/IpManagerEvent.java
@@ -101,6 +101,14 @@
                 Decoder.constants.get(eventType), durationMs);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(IpManagerEvent.class))) return false;
+        final IpManagerEvent other = (IpManagerEvent) obj;
+        return eventType == other.eventType
+                && durationMs == other.durationMs;
+    }
+
     final static class Decoder {
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{IpManagerEvent.class},
diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java
index c736297..f9ee39b 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -93,6 +93,13 @@
         return String.format("IpReachabilityEvent(%s:%02x)", eventName, lo);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(IpReachabilityEvent.class))) return false;
+        final IpReachabilityEvent other = (IpReachabilityEvent) obj;
+        return eventType == other.eventType;
+    }
+
     final static class Decoder {
         static final SparseArray<String> constants =
                 MessageUtils.findMessageNames(new Class[]{IpReachabilityEvent.class},
diff --git a/core/java/android/net/metrics/NetworkEvent.java b/core/java/android/net/metrics/NetworkEvent.java
index 5128115..ec0f82a 100644
--- a/core/java/android/net/metrics/NetworkEvent.java
+++ b/core/java/android/net/metrics/NetworkEvent.java
@@ -121,6 +121,14 @@
                 Decoder.constants.get(eventType), durationMs);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(NetworkEvent.class))) return false;
+        final NetworkEvent other = (NetworkEvent) obj;
+        return eventType == other.eventType
+                && durationMs == other.durationMs;
+    }
+
     final static class Decoder {
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{NetworkEvent.class}, new String[]{"NETWORK_"});
diff --git a/core/java/android/net/metrics/RaEvent.java b/core/java/android/net/metrics/RaEvent.java
index d16a104..6ccca7d 100644
--- a/core/java/android/net/metrics/RaEvent.java
+++ b/core/java/android/net/metrics/RaEvent.java
@@ -97,6 +97,18 @@
                 .toString();
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(RaEvent.class))) return false;
+        final RaEvent other = (RaEvent) obj;
+        return routerLifetime == other.routerLifetime
+                && prefixValidLifetime == other.prefixValidLifetime
+                && prefixPreferredLifetime == other.prefixPreferredLifetime
+                && routeInfoLifetime == other.routeInfoLifetime
+                && rdnssLifetime == other.rdnssLifetime
+                && dnsslLifetime == other.dnsslLifetime;
+    }
+
     /** @hide */
     public static final Parcelable.Creator<RaEvent> CREATOR = new Parcelable.Creator<RaEvent>() {
         public RaEvent createFromParcel(Parcel in) {
diff --git a/core/java/android/net/metrics/ValidationProbeEvent.java b/core/java/android/net/metrics/ValidationProbeEvent.java
index ebca7e6..6784420 100644
--- a/core/java/android/net/metrics/ValidationProbeEvent.java
+++ b/core/java/android/net/metrics/ValidationProbeEvent.java
@@ -170,6 +170,15 @@
                 getProbeName(probeType), returnCode, getValidationStage(probeType), durationMs);
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj.getClass().equals(ValidationProbeEvent.class))) return false;
+        final ValidationProbeEvent other = (ValidationProbeEvent) obj;
+        return durationMs == other.durationMs
+                && probeType == other.probeType
+                && returnCode == other.returnCode;
+    }
+
     final static class Decoder {
         static final SparseArray<String> constants = MessageUtils.findMessageNames(
                 new Class[]{ValidationProbeEvent.class},
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index e366fe0..32735aa 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -32,7 +32,7 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
-import java.io.IOException;
+import java.io.FileNotFoundException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.Executor;
@@ -146,7 +146,6 @@
             @NonNull BugreportParams params,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull BugreportCallback callback) {
-        File tmpScreenshotFile = null;
         try {
             Preconditions.checkNotNull(bugreportFd);
             Preconditions.checkNotNull(params);
@@ -155,13 +154,10 @@
 
             if (screenshotFd == null) {
                 // Binder needs a valid File Descriptor to be passed
-                tmpScreenshotFile = File.createTempFile("tmp", ".png");
-                screenshotFd = ParcelFileDescriptor.open(tmpScreenshotFile,
+                screenshotFd = ParcelFileDescriptor.open(new File("/dev/null"),
                         ParcelFileDescriptor.MODE_READ_ONLY);
             }
-            DumpstateListener dsListener = new DumpstateListener(executor,
-                    callback, tmpScreenshotFile);
-
+            DumpstateListener dsListener = new DumpstateListener(executor, callback);
             // Note: mBinder can get callingUid from the binder transaction.
             mBinder.startBugreport(-1 /* callingUid */,
                     mContext.getOpPackageName(),
@@ -169,13 +165,9 @@
                     screenshotFd.getFileDescriptor(),
                     params.getMode(), dsListener);
         } catch (RemoteException e) {
-            deleteFile(tmpScreenshotFile);
             throw e.rethrowFromSystemServer();
-        } catch (IOException e) {
-            // Need to delete the file if it was created but failed while trying to get fd
-            deleteFile(tmpScreenshotFile);
-            Log.e(TAG, "Not able to create/open temporary screenshot file ", e);
-            callback.onError(BugreportCallback.BUGREPORT_ERROR_RUNTIME);
+        } catch (FileNotFoundException e) {
+            Log.wtf(TAG, "Not able to find /dev/null file: ", e);
         } finally {
             // We can close the file descriptors here because binder would have duped them.
             IoUtils.closeQuietly(bugreportFd);
@@ -197,26 +189,13 @@
         }
     }
 
-    private void deleteFile(@Nullable File tmpScreenshotFile) {
-        try {
-            if (tmpScreenshotFile != null && tmpScreenshotFile.exists()) {
-                tmpScreenshotFile.delete();
-            }
-        } catch (SecurityException e) {
-            Log.e(TAG, "Not able to delete temporary screenshot file ", e);
-        }
-    }
-
     private final class DumpstateListener extends IDumpstateListener.Stub {
         private final Executor mExecutor;
         private final BugreportCallback mCallback;
-        private final File mTmpScreenshotFile;
 
-        DumpstateListener(Executor executor, BugreportCallback callback,
-                @Nullable File tmpScreenshotFile) {
+        DumpstateListener(Executor executor, BugreportCallback callback) {
             mExecutor = executor;
             mCallback = callback;
-            mTmpScreenshotFile = tmpScreenshotFile;
         }
 
         @Override
@@ -240,7 +219,6 @@
                 });
             } finally {
                 Binder.restoreCallingIdentity(identity);
-                deleteFile(mTmpScreenshotFile);
             }
         }
 
@@ -253,7 +231,6 @@
                 });
             } finally {
                 Binder.restoreCallingIdentity(identity);
-                deleteFile(mTmpScreenshotFile);
             }
         }
 
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 03e8c15..7f60b9c 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -363,18 +363,6 @@
     boolean isNetworkActive();
 
     /**
-     * Setup a new physical network.
-     * @param permission PERMISSION_NONE if no permissions required to access this network.
-     *                   PERMISSION_NETWORK or PERMISSION_SYSTEM to set respective permission.
-     */
-    void createPhysicalNetwork(int netId, int permission);
-
-    /**
-     * Setup a new VPN.
-     */
-    void createVirtualNetwork(int netId, boolean secure);
-
-    /**
      * Add an interface to a network.
      */
     void addInterfaceToNetwork(String iface, int netId);
@@ -396,9 +384,6 @@
      */
     void setNetworkPermission(int netId, int permission);
 
-    void setPermission(String permission, in int[] uids);
-    void clearPermission(in int[] uids);
-
     /**
      * Allow UID to call protect().
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f98641d..e94d3a7 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10336,8 +10336,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
 
         /**
@@ -10346,8 +10344,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
 
         /**
@@ -10356,8 +10352,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
 
         /**
@@ -10367,8 +10361,6 @@
          * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
 
         /**
@@ -10397,8 +10389,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
 
         /**
@@ -10407,8 +10397,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
 
         /**
@@ -10417,8 +10405,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
 
         /**
@@ -10427,8 +10413,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final String CAPTIVE_PORTAL_OTHER_FALLBACK_URLS =
                 "captive_portal_other_fallback_urls";
 
@@ -10438,8 +10422,6 @@
          * by "@@,@@".
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final String CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS =
                 "captive_portal_fallback_probe_specs";
 
@@ -10450,8 +10432,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
 
         /**
@@ -10460,8 +10440,6 @@
          *
          * @hide
          */
-        @SystemApi
-        @TestApi
         public static final String CAPTIVE_PORTAL_USER_AGENT = "captive_portal_user_agent";
 
         /**
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 3861695..fc23c54 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -19,7 +19,7 @@
 import android.annotation.Nullable;
 import android.text.TextUtils;
 
-import libcore.net.MimeUtils;
+import libcore.net.MimeMap;
 
 import java.util.regex.Pattern;
 
@@ -79,7 +79,7 @@
      * @return {@code true} if there is a mimeType entry in the map.
      */
     public boolean hasMimeType(String mimeType) {
-        return MimeUtils.hasMimeType(mimeType);
+        return MimeMap.getDefault().hasMimeType(mimeType);
     }
 
     /**
@@ -89,12 +89,12 @@
      */
     @Nullable
     public String getMimeTypeFromExtension(String extension) {
-        return MimeUtils.guessMimeTypeFromExtension(extension);
+        return MimeMap.getDefault().guessMimeTypeFromExtension(extension);
     }
 
     // Static method called by jni.
     private static String mimeTypeFromExtension(String extension) {
-        return MimeUtils.guessMimeTypeFromExtension(extension);
+        return MimeMap.getDefault().guessMimeTypeFromExtension(extension);
     }
 
     /**
@@ -103,7 +103,7 @@
      * @return {@code true} if there is an extension entry in the map.
      */
     public boolean hasExtension(String extension) {
-        return MimeUtils.hasExtension(extension);
+        return MimeMap.getDefault().hasExtension(extension);
     }
 
     /**
@@ -115,7 +115,7 @@
      */
     @Nullable
     public String getExtensionFromMimeType(String mimeType) {
-        return MimeUtils.guessExtensionFromMimeType(mimeType);
+        return MimeMap.getDefault().guessExtensionFromMimeType(mimeType);
     }
 
     /**
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 28c59db..c5fc9b3 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -49,8 +49,8 @@
 namespace android {
 
 constexpr int MAXPACKETSIZE = 8 * 1024;
-// FrameworkListener limits the size of commands to 1024 bytes. TODO: fix this.
-constexpr int MAXCMDSIZE = 1024;
+// FrameworkListener limits the size of commands to 4096 bytes.
+constexpr int MAXCMDSIZE = 4096;
 
 static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
     ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 9c60e6b..129b8af 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -188,7 +188,7 @@
         if (env->IsInstanceOf(excep, gErrorClass)) {
             /* It's an error */
             LOG(ERROR) << "Forcefully exiting";
-            exit(1);
+            _exit(1);
         } else {
             LOG(ERROR) << "Uncaught exception!";
         }
diff --git a/core/jni/android_util_jar_StrictJarFile.cpp b/core/jni/android_util_jar_StrictJarFile.cpp
index 182a621..a26707e 100644
--- a/core/jni/android_util_jar_StrictJarFile.cpp
+++ b/core/jni/android_util_jar_StrictJarFile.cpp
@@ -146,7 +146,7 @@
 
   ZipEntry data;
   const int32_t error = FindEntry(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
-                                  ZipString(entryNameChars.c_str()), &data);
+                                  entryNameChars.c_str(), &data);
   if (error) {
     return NULL;
   }
diff --git a/core/proto/android/stats/dnsresolver/Android.bp b/core/proto/android/stats/dnsresolver/Android.bp
new file mode 100644
index 0000000..0b5aa86
--- /dev/null
+++ b/core/proto/android/stats/dnsresolver/Android.bp
@@ -0,0 +1,25 @@
+// 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.
+
+java_library_static {
+    name: "dnsresolverprotosnano",
+    proto: {
+        type: "nano",
+    },
+    srcs: [
+        "dns_resolver.proto",
+    ],
+    sdk_version: "system_current",
+    no_framework_libs: true,
+}
diff --git a/core/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto
new file mode 100644
index 0000000..af6fea0
--- /dev/null
+++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto
@@ -0,0 +1,214 @@
+/*

+ * 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.

+ */

+syntax = "proto2";

+package android.stats.dnsresolver;

+

+enum EventType {

+    EVENT_UNKNOWN = 0;

+    EVENT_GETADDRINFO = 1;

+    EVENT_GETHOSTBYNAME = 2;

+    EVENT_GETHOSTBYADDR = 3;

+    EVENT_RES_NSEND = 4;

+}

+

+// The return value of the DNS resolver for each DNS lookups.

+// bionic/libc/include/netdb.h

+// system/netd/resolv/include/netd_resolv/resolv.h

+enum ReturnCode {

+    RC_EAI_NO_ERROR = 0;

+    RC_EAI_ADDRFAMILY = 1;

+    RC_EAI_AGAIN = 2;

+    RC_EAI_BADFLAGS = 3;

+    RC_EAI_FAIL = 4;

+    RC_EAI_FAMILY = 5;

+    RC_EAI_MEMORY = 6;

+    RC_EAI_NODATA = 7;

+    RC_EAI_NONAME = 8;

+    RC_EAI_SERVICE = 9;

+    RC_EAI_SOCKTYPE = 10;

+    RC_EAI_SYSTEM = 11;

+    RC_EAI_BADHINTS = 12;

+    RC_EAI_PROTOCOL = 13;

+    RC_EAI_OVERFLOW = 14;

+    RC_RESOLV_TIMEOUT = 255;

+    RC_EAI_MAX = 256;

+}

+

+enum NsRcode {

+    NS_R_NO_ERROR = 0;  // No error occurred.

+    NS_R_FORMERR = 1;   // Format error.

+    NS_R_SERVFAIL = 2;  // Server failure.

+    NS_R_NXDOMAIN = 3;  // Name error.

+    NS_R_NOTIMPL = 4;   // Unimplemented.

+    NS_R_REFUSED = 5;   // Operation refused.

+    // these are for BIND_UPDATE

+    NS_R_YXDOMAIN = 6;  // Name exists

+    NS_R_YXRRSET = 7;   // RRset exists

+    NS_R_NXRRSET = 8;   // RRset does not exist

+    NS_R_NOTAUTH = 9;   // Not authoritative for zone

+    NS_R_NOTZONE = 10;  // Zone of record different from zone section

+    NS_R_MAX = 11;

+    // The following are EDNS extended rcodes

+    NS_R_BADVERS = 16;

+    // The following are TSIG errors

+    // NS_R_BADSIG  = 16,

+    NS_R_BADKEY = 17;

+    NS_R_BADTIME = 18;

+}

+

+// Currently defined type values for resources and queries.

+enum NsType {

+    NS_T_INVALID = 0;      // Cookie.

+    NS_T_A = 1;            // Host address.

+    NS_T_NS = 2;           // Authoritative server.

+    NS_T_MD = 3;           // Mail destination.

+    NS_T_MF = 4;           // Mail forwarder.

+    NS_T_CNAME = 5;        // Canonical name.

+    NS_T_SOA = 6;          // Start of authority zone.

+    NS_T_MB = 7;           // Mailbox domain name.

+    NS_T_MG = 8;           // Mail group member.

+    NS_T_MR = 9;           // Mail rename name.

+    NS_T_NULL = 10;        // Null resource record.

+    NS_T_WKS = 11;         // Well known service.

+    NS_T_PTR = 12;         // Domain name pointer.

+    NS_T_HINFO = 13;       // Host information.

+    NS_T_MINFO = 14;       // Mailbox information.

+    NS_T_MX = 15;          // Mail routing information.

+    NS_T_TXT = 16;         // Text strings.

+    NS_T_RP = 17;          // Responsible person.

+    NS_T_AFSDB = 18;       // AFS cell database.

+    NS_T_X25 = 19;         // X_25 calling address.

+    NS_T_ISDN = 20;        // ISDN calling address.

+    NS_T_RT = 21;          // Router.

+    NS_T_NSAP = 22;        // NSAP address.

+    NS_T_NSAP_PTR = 23;    // Reverse NSAP lookup (deprecated).

+    NS_T_SIG = 24;         // Security signature.

+    NS_T_KEY = 25;         // Security key.

+    NS_T_PX = 26;          // X.400 mail mapping.

+    NS_T_GPOS = 27;        // Geographical position (withdrawn).

+    NS_T_AAAA = 28;        // IPv6 Address.

+    NS_T_LOC = 29;         // Location Information.

+    NS_T_NXT = 30;         // Next domain (security).

+    NS_T_EID = 31;         // Endpoint identifier.

+    NS_T_NIMLOC = 32;      // Nimrod Locator.

+    NS_T_SRV = 33;         // Server Selection.

+    NS_T_ATMA = 34;        // ATM Address

+    NS_T_NAPTR = 35;       // Naming Authority PoinTeR

+    NS_T_KX = 36;          // Key Exchange

+    NS_T_CERT = 37;        // Certification record

+    NS_T_A6 = 38;          // IPv6 address (experimental)

+    NS_T_DNAME = 39;       // Non-terminal DNAME

+    NS_T_SINK = 40;        // Kitchen sink (experimentatl)

+    NS_T_OPT = 41;         // EDNS0 option (meta-RR)

+    NS_T_APL = 42;         // Address prefix list (RFC 3123)

+    NS_T_DS = 43;          // Delegation Signer

+    NS_T_SSHFP = 44;       // SSH Fingerprint

+    NS_T_IPSECKEY = 45;    // IPSEC Key

+    NS_T_RRSIG = 46;       // RRset Signature

+    NS_T_NSEC = 47;        // Negative security

+    NS_T_DNSKEY = 48;      // DNS Key

+    NS_T_DHCID = 49;       // Dynamic host configuratin identifier

+    NS_T_NSEC3 = 50;       // Negative security type 3

+    NS_T_NSEC3PARAM = 51;  // Negative security type 3 parameters

+    NS_T_HIP = 55;         // Host Identity Protocol

+    NS_T_SPF = 99;         // Sender Policy Framework

+    NS_T_TKEY = 249;       // Transaction key

+    NS_T_TSIG = 250;       // Transaction signature.

+    NS_T_IXFR = 251;       // Incremental zone transfer.

+    NS_T_AXFR = 252;       // Transfer zone of authority.

+    NS_T_MAILB = 253;      // Transfer mailbox records.

+    NS_T_MAILA = 254;      // Transfer mail agent records.

+    NS_T_ANY = 255;        // Wildcard match.

+    NS_T_ZXFR = 256;       // BIND-specific, nonstandard.

+    NS_T_DLV = 32769;      // DNSSEC look-aside validatation.

+    NS_T_MAX = 65536;

+}

+

+enum IpVersion {

+    IV_UNKNOWN = 0;

+    IV_IPV4 = 1;

+    IV_IPV6 = 2;

+}

+

+enum TransportType {

+    TT_UNKNOWN = 0;

+    TT_UDP = 1;

+    TT_TCP = 2;

+    TT_DOT = 3;

+}

+

+enum PrivateDnsModes {

+    PDM_UNKNOWN = 0;

+    PDM_OFF = 1;

+    PDM_OPPORTUNISTIC = 2;

+    PDM_STRICT = 3;

+}

+

+enum Transport {

+    // Indicates this network uses a Cellular transport.

+    TRANSPORT_DEFAULT = 0;  // TRANSPORT_CELLULAR

+    // Indicates this network uses a Wi-Fi transport.

+    TRANSPORT_WIFI = 1;

+    // Indicates this network uses a Bluetooth transport.

+    TRANSPORT_BLUETOOTH = 2;

+    // Indicates this network uses an Ethernet transport.

+    TRANSPORT_ETHERNET = 3;

+    // Indicates this network uses a VPN transport.

+    TRANSPORT_VPN = 4;

+    // Indicates this network uses a Wi-Fi Aware transport.

+    TRANSPORT_WIFI_AWARE = 5;

+    // Indicates this network uses a LoWPAN transport.

+    TRANSPORT_LOWPAN = 6;

+}

+

+enum CacheStatus{

+    // the cache can't handle that kind of queries.

+    // or the answer buffer is too small.

+    CS_UNSUPPORTED = 0;

+    // the cache doesn't know about this query.

+    CS_NOTFOUND = 1;

+    // the cache found the answer.

+    CS_FOUND = 2;

+    // Don't do anything on cache.

+    CS_SKIP = 3;

+}

+

+message DnsQueryEvent {

+    optional android.stats.dnsresolver.NsRcode rcode = 1;

+

+    optional android.stats.dnsresolver.NsType type = 2;

+

+    optional android.stats.dnsresolver.CacheStatus cache_hit = 3;

+

+    optional android.stats.dnsresolver.IpVersion ip_version = 4;

+

+    optional android.stats.dnsresolver.TransportType transport = 5;

+

+    // Number of DNS query retry times

+    optional int32 retry_times = 6;

+

+    // Ordinal number of name server.

+    optional int32 dns_server_count = 7;

+

+    // Used only by TCP and DOT. True for new connections.

+    optional bool connected = 8;

+

+    optional int32 latency_micros = 9;

+}

+

+message DnsQueryEvents {

+    repeated DnsQueryEvent dns_query_event = 1;

+}

diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f1e9d65..be49250 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3395,7 +3395,7 @@
     <item name="config_inCallNotificationVolume" format="float" type="dimen">.10</item>
 
     <!-- URI for in call notification sound -->
-    <string translatable="false" name="config_inCallNotificationSound">/system/media/audio/ui/InCallNotification.ogg</string>
+    <string translatable="false" name="config_inCallNotificationSound">/product/media/audio/ui/InCallNotification.ogg</string>
 
     <!-- The OEM specified sensor type for the lift trigger to launch the camera app. -->
     <integer name="config_cameraLiftTriggerSensorType">-1</integer>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 7163769..a92c500 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -70,7 +70,7 @@
     <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188|43030" standard="244444" />
 
     <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf -->
-    <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765" />
+    <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111|30118" free="98765|30075" />
 
     <!-- Chile: 4-5 digits (not confirmed), known premium codes listed -->
     <shortcode country="cl" pattern="\\d{4,5}" free="9963|9240" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 875f2c0..c17f076 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -33,6 +33,11 @@
         <permission name="android.permission.CRYPT_KEEPER"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.captiveportallogin">
+        <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+        <permission name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.cellbroadcastreceiver">
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
         <permission name="android.permission.MANAGE_USERS"/>
@@ -204,6 +209,7 @@
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/>
         <permission name="android.permission.MANAGE_USB"/>
+        <permission name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"/>
         <permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD"/>
         <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
         <permission name="android.permission.READ_PRECISE_PHONE_STATE"/>
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 66a5477..47b7d64 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -120,9 +120,8 @@
   std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path));
 
   // Find the resource table.
-  ::ZipString entry_name(kResourcesArsc.c_str());
   ::ZipEntry entry;
-  result = ::FindEntry(loaded_apk->zip_handle_.get(), entry_name, &entry);
+  result = ::FindEntry(loaded_apk->zip_handle_.get(), kResourcesArsc, &entry);
   if (result != 0) {
     // There is no resources.arsc, so create an empty LoadedArsc and return.
     loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
@@ -160,9 +159,8 @@
 std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
   CHECK(zip_handle_ != nullptr);
 
-  ::ZipString name(path.c_str());
   ::ZipEntry entry;
-  int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
+  int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
   if (result != 0) {
     return {};
   }
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 6e2ca60c..44614c1 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -98,7 +98,7 @@
 
     data->name = ZipString(entryName);
 
-    const int32_t error = FindEntry(mHandle, data->name, &(data->entry));
+    const int32_t error = FindEntry(mHandle, entryName, &(data->entry));
     if (error) {
         delete data;
         return NULL;
diff --git a/libs/androidfw/tests/TestHelpers.cpp b/libs/androidfw/tests/TestHelpers.cpp
index 9e320a2..a81bb6f 100644
--- a/libs/androidfw/tests/TestHelpers.cpp
+++ b/libs/androidfw/tests/TestHelpers.cpp
@@ -34,9 +34,8 @@
                               << "': " << ::ErrorCodeString(result);
   }
 
-  ::ZipString name(file.c_str());
   ::ZipEntry entry;
-  result = ::FindEntry(handle, name, &entry);
+  result = ::FindEntry(handle, file.c_str(), &entry);
   if (result != 0) {
     ::CloseArchive(handle);
     return AssertionFailure() << "Could not find file '" << file << "' in zip '" << zip_path
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 5f5a92e..adfae5b 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -81,7 +81,7 @@
     // mean and stddev which doesn't make sense for our usage
     std::vector<BenchmarkReporter::Run> reports;
     BenchmarkReporter::Run report;
-    report.run_name = info.name;
+    report.run_name.function_name = info.name;
     report.iterations = static_cast<int64_t>(opts.count);
     report.real_accumulated_time = durationInS;
     report.cpu_accumulated_time = durationInS;
@@ -94,8 +94,8 @@
     // in that test case than percentiles.
     if (!opts.renderOffscreen) {
         for (auto& ri : REPORTS) {
-            reports[0].run_name = info.name;
-            reports[0].run_name += ri.suffix;
+            reports[0].run_name.function_name = info.name;
+            reports[0].run_name.function_name += ri.suffix;
             durationInS = proxy->frameTimePercentile(ri.percentile) / 1000.0;
             reports[0].real_accumulated_time = durationInS;
             reports[0].cpu_accumulated_time = durationInS;
diff --git a/libs/usb/tests/AccessoryChat/Android.bp b/libs/usb/tests/AccessoryChat/Android.bp
index 4af6274..63a670c 100644
--- a/libs/usb/tests/AccessoryChat/Android.bp
+++ b/libs/usb/tests/AccessoryChat/Android.bp
@@ -1 +1,25 @@
 subdirs = ["accessorychat"]
+//
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+    name: "AccessoryChat",
+
+    srcs: ["**/*.java"],
+
+    platform_apis: true,
+
+}
diff --git a/libs/usb/tests/AccessoryChat/Android.mk b/libs/usb/tests/AccessoryChat/Android.mk
deleted file mode 100644
index cfe2da1..0000000
--- a/libs/usb/tests/AccessoryChat/Android.mk
+++ /dev/null
@@ -1,28 +0,0 @@
-#
-# Copyright (C) 2011 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.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_PACKAGE_NAME := AccessoryChat
-
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-include $(BUILD_PACKAGE)
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index c9c881c..2bc9a2b 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -58,6 +58,7 @@
     private final String mId;
     private final String mLanguage;
     private final CharSequence mDescription;
+    private final boolean mEncrypted;
     private final int mAudioChannelCount;
     private final int mAudioSampleRate;
     private final int mVideoWidth;
@@ -69,13 +70,14 @@
     private final Bundle mExtra;
 
     private TvTrackInfo(int type, String id, String language, CharSequence description,
-            int audioChannelCount, int audioSampleRate, int videoWidth, int videoHeight,
-            float videoFrameRate, float videoPixelAspectRatio, byte videoActiveFormatDescription,
-            Bundle extra) {
+            boolean encrypted, int audioChannelCount, int audioSampleRate, int videoWidth,
+            int videoHeight, float videoFrameRate, float videoPixelAspectRatio,
+            byte videoActiveFormatDescription, Bundle extra) {
         mType = type;
         mId = id;
         mLanguage = language;
         mDescription = description;
+        mEncrypted = encrypted;
         mAudioChannelCount = audioChannelCount;
         mAudioSampleRate = audioSampleRate;
         mVideoWidth = videoWidth;
@@ -91,6 +93,7 @@
         mId = in.readString();
         mLanguage = in.readString();
         mDescription = in.readString();
+        mEncrypted = in.readInt() != 0;
         mAudioChannelCount = in.readInt();
         mAudioSampleRate = in.readInt();
         mVideoWidth = in.readInt();
@@ -133,6 +136,18 @@
     }
 
     /**
+     * Returns {@code true} if the track is encrypted, {@code false} otherwise. If the encryption
+     * status is unknown or could not be determined, the corresponding value will be {@code false}.
+     *
+     * <p>For example: ISO/IEC 13818-1 defines a CA descriptor that can be used to determine the
+     * encryption status of some broadcast streams.
+     */
+
+    public boolean isEncrypted() {
+        return mEncrypted;
+    }
+
+    /**
      * Returns the audio channel count. Valid only for {@link #TYPE_AUDIO} tracks.
      *
      * @throws IllegalStateException if not called on an audio track
@@ -248,6 +263,7 @@
         dest.writeString(mId);
         dest.writeString(mLanguage);
         dest.writeString(mDescription != null ? mDescription.toString() : null);
+        dest.writeInt(mEncrypted ? 1 : 0);
         dest.writeInt(mAudioChannelCount);
         dest.writeInt(mAudioSampleRate);
         dest.writeInt(mVideoWidth);
@@ -273,6 +289,7 @@
                 && mType == obj.mType
                 && TextUtils.equals(mLanguage, obj.mLanguage)
                 && TextUtils.equals(mDescription, obj.mDescription)
+                && mEncrypted == obj.mEncrypted
                 && Objects.equals(mExtra, obj.mExtra)
                 && (mType == TYPE_AUDIO
                         ? mAudioChannelCount == obj.mAudioChannelCount
@@ -310,6 +327,7 @@
         private final int mType;
         private String mLanguage;
         private CharSequence mDescription;
+        private boolean mEncrypted;
         private int mAudioChannelCount;
         private int mAudioSampleRate;
         private int mVideoWidth;
@@ -361,6 +379,19 @@
         }
 
         /**
+         * Sets the encryption status of the track.
+         *
+         * <p>For example: ISO/IEC 13818-1 defines a CA descriptor that can be used to determine the
+         * encryption status of some broadcast streams.
+         *
+         * @param encrypted The encryption status of the track.
+         */
+        public Builder setEncrypted(boolean encrypted) {
+            mEncrypted = encrypted;
+            return this;
+        }
+
+        /**
          * Sets the audio channel count. Valid only for {@link #TYPE_AUDIO} tracks.
          *
          * @param audioChannelCount The audio channel count.
@@ -490,9 +521,9 @@
          * @return The new {@link TvTrackInfo} instance
          */
         public TvTrackInfo build() {
-            return new TvTrackInfo(mType, mId, mLanguage, mDescription, mAudioChannelCount,
-                    mAudioSampleRate, mVideoWidth, mVideoHeight, mVideoFrameRate,
-                    mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
+            return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
+                    mAudioChannelCount, mAudioSampleRate, mVideoWidth, mVideoHeight,
+                    mVideoFrameRate, mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
         }
     }
 }
diff --git a/nfc-extras/tests/Android.bp b/nfc-extras/tests/Android.bp
new file mode 100644
index 0000000..fc52006
--- /dev/null
+++ b/nfc-extras/tests/Android.bp
@@ -0,0 +1,33 @@
+// Copyright 2011, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "NfcExtrasTests",
+
+    // We only want this apk build for tests.
+
+    libs: [
+        "android.test.runner.stubs",
+        "com.android.nfc_extras.stubs",
+        "android.test.base.stubs",
+    ],
+
+    static_libs: ["junit"],
+
+    // Include all test java files.
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "current",
+
+}
diff --git a/nfc-extras/tests/Android.mk b/nfc-extras/tests/Android.mk
deleted file mode 100644
index 8bba3ba..0000000
--- a/nfc-extras/tests/Android.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright 2011, 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.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.runner.stubs \
-    com.android.nfc_extras.stubs \
-    android.test.base.stubs
-
-LOCAL_STATIC_JAVA_LIBRARIES := junit
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := NfcExtrasTests
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_PACKAGE)
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index 44e0a65..0a03425 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -26,10 +26,12 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
     <uses-permission android:name="android.permission.NETWORK_BYPASS_PRIVATE_DNS" />
     <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" />
 
     <application android:label="@string/app_name"
+                 android:icon="@drawable/app_icon"
                  android:usesCleartextTraffic="true"
                  android:supportsRtl="true" >
         <activity
diff --git a/packages/CaptivePortalLogin/res/drawable/app_icon.xml b/packages/CaptivePortalLogin/res/drawable/app_icon.xml
new file mode 100644
index 0000000..456ca83
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/drawable/app_icon.xml
@@ -0,0 +1,26 @@
+<!--
+    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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background>
+        <color android:color="@*android:color/accent_device_default_light" />
+    </background>
+    <foreground>
+        <inset
+            android:drawable="@drawable/maybe_wifi"
+            android:inset="25%">
+        </inset>
+    </foreground>
+</adaptive-icon>
diff --git a/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml b/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml
new file mode 100644
index 0000000..207aade
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="26.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="26.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#4DFFFFFF"
+        android:pathData="M19.1,14l-3.4,0l0,-1.5c0,-1.8 0.8,-2.8 1.5,-3.4C18.1,8.3 19.200001,8 20.6,8c1.2,0 2.3,0.3 3.1,0.8l1.9,-2.3C25.1,6.1 20.299999,2.1 13,2.1S0.9,6.1 0.4,6.5L13,22l0,0l0,0l0,0l0,0l6.5,-8.1L19.1,14z"/>
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M19.5,17.799999c0,-0.8 0.1,-1.3 0.2,-1.6c0.2,-0.3 0.5,-0.7 1.1,-1.2c0.4,-0.4 0.7,-0.8 1,-1.1s0.4,-0.8 0.4,-1.2c0,-0.5 -0.1,-0.9 -0.4,-1.2c-0.3,-0.3 -0.7,-0.4 -1.2,-0.4c-0.4,0 -0.8,0.1 -1.1,0.3c-0.3,0.2 -0.4,0.6 -0.4,1.1l-1.9,0c0,-1 0.3,-1.7 1,-2.2c0.6,-0.5 1.5,-0.8 2.5,-0.8c1.1,0 2,0.3 2.6,0.8c0.6,0.5 0.9,1.3 0.9,2.3c0,0.7 -0.2,1.3 -0.6,1.8c-0.4,0.6 -0.9,1.1 -1.5,1.6c-0.3,0.3 -0.5,0.5 -0.6,0.7c-0.1,0.2 -0.1,0.6 -0.1,1L19.5,17.700001zM21.4,21l-1.9,0l0,-1.8l1.9,0L21.4,21z"/>
+</vector>
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index e0bb862..62de2ba 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -56,15 +56,24 @@
     srcs: [
         "jni/network_stack_utils_jni.cpp"
     ],
-
+    sdk_version: "current",
     shared_libs: [
         "liblog",
-        "libcutils",
-        "libnativehelper",
+        "libnativehelper_compat_libc++",
     ],
-    static_libs: [
-        "libpcap",
-    ],
+
+    // We cannot use plain "libc++" here to link libc++ dynamically because it results in:
+    //   java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++_shared.so" not found
+    // even if "libc++" is added into jni_libs below. Adding "libc++_shared" into jni_libs doesn't
+    // build because soong complains of:
+    //   module NetworkStack missing dependencies: libc++_shared
+    //
+    // So, link libc++ statically. This means that we also need to ensure that all the C++ libraries
+    // we depend on do not dynamically link libc++. This is currently the case, because liblog is
+    // C-only and libnativehelper_compat_libc also uses stl: "c++_static".
+    //
+    // TODO: find a better solution for this in R.
+    stl: "c++_static",
     cflags: [
         "-Wall",
         "-Werror",
@@ -79,7 +88,10 @@
     static_libs: [
         "NetworkStackBase",
     ],
-    jni_libs: ["libnetworkstackutilsjni"],
+    jni_libs: [
+        "libnativehelper_compat_libc++",
+        "libnetworkstackutilsjni",
+    ],
     // Resources already included in NetworkStackBase
     resource_dirs: [],
     jarjar_rules: "jarjar-rules-shared.txt",
diff --git a/packages/NetworkStack/jni/network_stack_utils_jni.cpp b/packages/NetworkStack/jni/network_stack_utils_jni.cpp
index 5544eaa..f2ba575 100644
--- a/packages/NetworkStack/jni/network_stack_utils_jni.cpp
+++ b/packages/NetworkStack/jni/network_stack_utils_jni.cpp
@@ -31,7 +31,7 @@
 #include <string>
 
 #include <nativehelper/JNIHelp.h>
-#include <utils/Log.h>
+#include <android/log.h>
 
 namespace android {
 constexpr const char NETWORKSTACKUTILS_PKG_NAME[] = "android/net/util/NetworkStackUtils";
@@ -249,7 +249,7 @@
 extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
     JNIEnv *env;
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
-        ALOGE("ERROR: GetEnv failed");
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
         return JNI_ERR;
     }
 
@@ -261,4 +261,4 @@
     return JNI_VERSION_1_6;
 
 }
-}; // namespace android
\ No newline at end of file
+}; // namespace android
diff --git a/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java b/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java
index 475f826..41715b2 100644
--- a/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java
+++ b/packages/NetworkStack/src/android/net/NetworkStackIpMemoryStore.java
@@ -19,6 +19,9 @@
 import android.annotation.NonNull;
 import android.content.Context;
 
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+
 /**
  * service used to communicate with the ip memory store service in network stack,
  * which is running in the same module.
@@ -35,8 +38,7 @@
     }
 
     @Override
-    @NonNull
-    protected IIpMemoryStore getService() {
-        return mService;
+    protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException {
+        cb.accept(mService);
     }
 }
diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
index 663e2f1..359c859 100644
--- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -39,6 +39,7 @@
 import android.content.IntentFilter;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NattKeepalivePacketDataParcelable;
 import android.net.TcpKeepalivePacketDataParcelable;
 import android.net.apf.ApfGenerator.IllegalInstructionException;
 import android.net.apf.ApfGenerator.Register;
@@ -1691,13 +1692,13 @@
     }
 
     /**
-     * Add keepalive ack packet filter.
+     * Add TCP keepalive ack packet filter.
      * This will add a filter to drop acks to the keepalive packet passed as an argument.
      *
      * @param slot The index used to access the filter.
      * @param sentKeepalivePacket The attributes of the sent keepalive packet.
      */
-    public synchronized void addKeepalivePacketFilter(final int slot,
+    public synchronized void addTcpKeepalivePacketFilter(final int slot,
             final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
         log("Adding keepalive ack(" + slot + ")");
         if (null != mKeepaliveAcks.get(slot)) {
@@ -1711,6 +1712,18 @@
     }
 
     /**
+     * Add NATT keepalive packet filter.
+     * This will add a filter to drop NATT keepalive packet which is passed as an argument.
+     *
+     * @param slot The index used to access the filter.
+     * @param sentKeepalivePacket The attributes of the sent keepalive packet.
+     */
+    public synchronized void addNattKeepalivePacketFilter(final int slot,
+            final NattKeepalivePacketDataParcelable sentKeepalivePacket) {
+        Log.e(TAG, "APF add NATT keepalive filter is not implemented");
+    }
+
+    /**
      * Remove keepalive packet filter.
      *
      * @param slot The index used to access the filter.
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
index d7ff98b1..a15d423 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpPacket.java
@@ -195,6 +195,18 @@
     public static final String VENDOR_INFO_ANDROID_METERED = "ANDROID_METERED";
 
     /**
+     * DHCP Optional Type: Option overload option
+     */
+    protected static final byte DHCP_OPTION_OVERLOAD = 52;
+
+    /**
+     * Possible values of the option overload option.
+     */
+    private static final byte OPTION_OVERLOAD_FILE = 1;
+    private static final byte OPTION_OVERLOAD_SNAME = 2;
+    private static final byte OPTION_OVERLOAD_BOTH = 3;
+
+    /**
      * DHCP Optional Type: DHCP Requested IP Address
      */
     protected static final byte DHCP_REQUESTED_IP = 50;
@@ -309,6 +321,11 @@
     protected final byte[] mClientMac;
 
     /**
+     * The server host name from server.
+     */
+    protected String mServerHostName;
+
+    /**
      * Asks the packet object to create a ByteBuffer serialization of
      * the packet for transmission.
      */
@@ -848,6 +865,8 @@
         Inet4Address ipDst = null;
         Inet4Address bcAddr = null;
         Inet4Address requestedIp = null;
+        String serverHostName;
+        byte optionOverload = 0;
 
         // The following are all unsigned integers. Internally we store them as signed integers of
         // the same length because that way we're guaranteed that they can't be out of the range of
@@ -989,9 +1008,9 @@
         packet.get(clientMac);
 
         // skip over address padding (16 octets allocated)
-        packet.position(packet.position() + (16 - addrLen)
-                        + 64    // skip server host name (64 chars)
-                        + 128); // skip boot file name (128 chars)
+        packet.position(packet.position() + (16 - addrLen));
+        serverHostName = readAsciiString(packet, 64, false);
+        packet.position(packet.position() + 128);
 
         // Ensure this is a DHCP packet with a magic cookie, and not BOOTP. http://b/31850211
         if (packet.remaining() < 4) {
@@ -1102,6 +1121,11 @@
                             // Embedded nulls are safe as this does not get passed to netd.
                             vendorInfo = readAsciiString(packet, optionLen, true);
                             break;
+                        case DHCP_OPTION_OVERLOAD:
+                            expectedLen = 1;
+                            optionOverload = packet.get();
+                            optionOverload &= OPTION_OVERLOAD_BOTH;
+                            break;
                         default:
                             // ignore any other parameters
                             for (int i = 0; i < optionLen; i++) {
@@ -1192,6 +1216,11 @@
         newPacket.mT2 = T2;
         newPacket.mVendorId = vendorId;
         newPacket.mVendorInfo = vendorInfo;
+        if ((optionOverload & OPTION_OVERLOAD_SNAME) == 0) {
+            newPacket.mServerHostName = serverHostName;
+        } else {
+            newPacket.mServerHostName = "";
+        }
         return newPacket;
     }
 
@@ -1251,6 +1280,7 @@
         results.vendorInfo = mVendorInfo;
         results.leaseDuration = (mLeaseTime != null) ? mLeaseTime : INFINITE_LEASE;
         results.mtu = (mMtu != null && MIN_MTU <= mMtu && mMtu <= MAX_MTU) ? mMtu : 0;
+        results.serverHostName = mServerHostName;
 
         return results;
     }
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
index 96e09fa..dc74c04 100644
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -29,6 +29,7 @@
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NattKeepalivePacketDataParcelable;
 import android.net.NetworkStackIpMemoryStore;
 import android.net.ProvisioningConfigurationParcelable;
 import android.net.ProxyInfo;
@@ -371,6 +372,10 @@
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
 
+    /* This must match the definition in KeepaliveTracker.KeepaliveInfo */
+    private static final int TYPE_NATT = 1;
+    private static final int TYPE_TCP = 2;
+
     /**
      * Reading the snapshot is an asynchronous operation initiated by invoking
      * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
@@ -553,6 +558,11 @@
             IpClient.this.addKeepalivePacketFilter(slot, pkt);
         }
         @Override
+        public void addNattKeepalivePacketFilter(int slot, NattKeepalivePacketDataParcelable pkt) {
+            checkNetworkStackCallingPermission();
+            IpClient.this.addNattKeepalivePacketFilter(slot, pkt);
+        }
+        @Override
         public void removeKeepalivePacketFilter(int slot) {
             checkNetworkStackCallingPermission();
             IpClient.this.removeKeepalivePacketFilter(slot);
@@ -691,11 +701,20 @@
     }
 
     /**
-     * Called by WifiStateMachine to add keepalive packet filter before setting up
+     * Called by WifiStateMachine to add TCP keepalive packet filter before setting up
      * keepalive offload.
      */
     public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) {
-        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt);
+        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_TCP, pkt);
+    }
+
+    /**
+     *  Called by WifiStateMachine to add NATT keepalive packet filter before setting up
+     *  keepalive offload.
+     */
+    public void addNattKeepalivePacketFilter(int slot,
+            @NonNull NattKeepalivePacketDataParcelable pkt) {
+        sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_NATT, pkt);
     }
 
     /**
@@ -1607,9 +1626,16 @@
 
                 case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: {
                     final int slot = msg.arg1;
+                    final int type = msg.arg2;
+
                     if (mApfFilter != null) {
-                        mApfFilter.addKeepalivePacketFilter(slot,
-                                (TcpKeepalivePacketDataParcelable) msg.obj);
+                        if (type == TYPE_NATT) {
+                            mApfFilter.addNattKeepalivePacketFilter(slot,
+                                    (NattKeepalivePacketDataParcelable) msg.obj);
+                        } else {
+                            mApfFilter.addTcpKeepalivePacketFilter(slot,
+                                    (TcpKeepalivePacketDataParcelable) msg.obj);
+                        }
                     }
                     break;
                 }
diff --git a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
index fb03c54..abfed3e 100644
--- a/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
+++ b/packages/NetworkStack/src/android/net/util/NetworkStackUtils.java
@@ -62,6 +62,49 @@
      */
     public static final String CAPTIVE_PORTAL_USE_HTTPS = "captive_portal_use_https";
 
+    /**
+     * The URL used for HTTPS captive portal detection upon a new connection.
+     * A 204 response code from the server is used for validation.
+     */
+    public static final String CAPTIVE_PORTAL_HTTPS_URL = "captive_portal_https_url";
+
+    /**
+     * The URL used for HTTP captive portal detection upon a new connection.
+     * A 204 response code from the server is used for validation.
+     */
+    public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
+
+    /**
+     * The URL used for fallback HTTP captive portal detection when previous HTTP
+     * and HTTPS captive portal detection attemps did not return a conclusive answer.
+     */
+    public static final String CAPTIVE_PORTAL_FALLBACK_URL = "captive_portal_fallback_url";
+
+    /**
+     * What to do when connecting a network that presents a captive portal.
+     * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
+     *
+     * The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
+     */
+    public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
+
+    /**
+     * Don't attempt to detect captive portals.
+     */
+    public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0;
+
+    /**
+     * When detecting a captive portal, display a notification that
+     * prompts the user to sign in.
+     */
+    public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1;
+
+    /**
+     * When detecting a captive portal, immediately disconnect from the
+     * network and do not reconnect to that network in the future.
+     */
+    public static final int CAPTIVE_PORTAL_MODE_AVOID = 2;
+
     static {
         System.loadLibrary("networkstackutilsjni");
     }
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 50eb5d4..d6355bc 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -44,6 +44,12 @@
 import static android.net.util.DataStallUtils.DEFAULT_DATA_STALL_VALID_DNS_TIME_THRESHOLD_MS;
 import static android.net.util.DataStallUtils.DEFAULT_DNS_LOG_SIZE;
 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS;
+import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_URL;
+import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTPS_URL;
+import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_HTTP_URL;
+import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE;
+import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_IGNORE;
+import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT;
 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS;
 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT;
 import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS;
@@ -144,9 +150,6 @@
 
     private static final int SOCKET_TIMEOUT_MS = 10000;
     private static final int PROBE_TIMEOUT_MS  = 3000;
-    // Enough for 3 DNS queries 5 seconds apart.
-    // TODO: get this from resources and DeviceConfig instead.
-    private static final int DNS_TIMEOUT_MS = 12500;
 
     enum EvaluationResult {
         VALIDATED(true),
@@ -276,8 +279,8 @@
 
     private final Context mContext;
     private final INetworkMonitorCallbacks mCallback;
+    private final Network mCleartextDnsNetwork;
     private final Network mNetwork;
-    private final Network mNonPrivateDnsBypassNetwork;
     private final TelephonyManager mTelephonyManager;
     private final WifiManager mWifiManager;
     private final ConnectivityManager mCm;
@@ -367,8 +370,8 @@
         mCallback = cb;
         mDependencies = deps;
         mDetectionStatsUtils = detectionStatsUtils;
-        mNonPrivateDnsBypassNetwork = network;
-        mNetwork = deps.getPrivateDnsBypassNetwork(network);
+        mNetwork = network;
+        mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network);
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -493,7 +496,7 @@
 
     @Override
     protected void log(String s) {
-        if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s);
+        if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s);
     }
 
     private void validationLog(int probeType, Object url, String msg) {
@@ -766,7 +769,7 @@
                 case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
                     final Bundle appExtras = new Bundle();
                     // OneAddressPerFamilyNetwork is not parcelable across processes.
-                    final Network network = new Network(mNetwork);
+                    final Network network = new Network(mCleartextDnsNetwork);
                     appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network);
                     final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
                     appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
@@ -878,7 +881,7 @@
         CustomIntentReceiver(String action, int token, int what) {
             mToken = token;
             mWhat = what;
-            mAction = action + "_" + mNetwork.getNetworkHandle() + "_" + token;
+            mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token;
             mContext.registerReceiver(this, new IntentFilter(mAction));
         }
         public PendingIntent getPendingIntent() {
@@ -991,7 +994,8 @@
         private void resolveStrictModeHostname() {
             try {
                 // Do a blocking DNS resolution using the network-assigned nameservers.
-                final InetAddress[] ips = mNetwork.getAllByName(mPrivateDnsProviderHostname);
+                final InetAddress[] ips = mCleartextDnsNetwork.getAllByName(
+                        mPrivateDnsProviderHostname);
                 mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
                 validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig);
             } catch (UnknownHostException uhe) {
@@ -1030,7 +1034,7 @@
                     + oneTimeHostnameSuffix;
             final Stopwatch watch = new Stopwatch().start();
             try {
-                final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host);
+                final InetAddress[] ips = mNetwork.getAllByName(host);
                 final long time = watch.stop();
                 final String strIps = Arrays.toString(ips);
                 final boolean success = (ips != null && ips.length > 0);
@@ -1178,10 +1182,10 @@
     }
 
     private boolean getIsCaptivePortalCheckEnabled() {
-        String symbol = Settings.Global.CAPTIVE_PORTAL_MODE;
-        int defaultValue = Settings.Global.CAPTIVE_PORTAL_MODE_PROMPT;
+        String symbol = CAPTIVE_PORTAL_MODE;
+        int defaultValue = CAPTIVE_PORTAL_MODE_PROMPT;
         int mode = mDependencies.getSetting(mContext, symbol, defaultValue);
-        return mode != Settings.Global.CAPTIVE_PORTAL_MODE_IGNORE;
+        return mode != CAPTIVE_PORTAL_MODE_IGNORE;
     }
 
     private boolean getUseHttpsValidation() {
@@ -1191,8 +1195,7 @@
 
     private String getCaptivePortalServerHttpsUrl() {
         return getSettingFromResource(mContext, R.string.config_captive_portal_https_url,
-                R.string.default_captive_portal_https_url,
-                Settings.Global.CAPTIVE_PORTAL_HTTPS_URL);
+                R.string.default_captive_portal_https_url, CAPTIVE_PORTAL_HTTPS_URL);
     }
 
     private int getDnsProbeTimeout() {
@@ -1231,8 +1234,7 @@
      */
     public String getCaptivePortalServerHttpUrl() {
         return getSettingFromResource(mContext, R.string.config_captive_portal_http_url,
-                R.string.default_captive_portal_http_url,
-                Settings.Global.CAPTIVE_PORTAL_HTTP_URL);
+                R.string.default_captive_portal_http_url, CAPTIVE_PORTAL_HTTP_URL);
     }
 
     private int getConsecutiveDnsTimeoutThreshold() {
@@ -1261,8 +1263,8 @@
 
     private URL[] makeCaptivePortalFallbackUrls() {
         try {
-            final String firstUrl = mDependencies.getSetting(mContext,
-                    Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, null);
+            final String firstUrl = mDependencies.getSetting(mContext, CAPTIVE_PORTAL_FALLBACK_URL,
+                    null);
 
             final URL[] settingProviderUrls;
             if (!TextUtils.isEmpty(firstUrl)) {
@@ -1505,7 +1507,7 @@
 
         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
                 TrafficStatsConstants.TAG_SYSTEM_PROBE);
-        mDependencies.getDnsResolver().query(mNetwork, host, DnsResolver.FLAG_EMPTY,
+        mDependencies.getDnsResolver().query(mCleartextDnsNetwork, host, DnsResolver.FLAG_EMPTY,
                 r -> r.run() /* executor */, null /* cancellationSignal */, callback);
         TrafficStats.setThreadStatsTag(oldTag);
 
@@ -1564,7 +1566,7 @@
         final int oldTag = TrafficStats.getAndSetThreadStatsTag(
                 TrafficStatsConstants.TAG_SYSTEM_PROBE);
         try {
-            urlConnection = (HttpURLConnection) mNetwork.openConnection(url);
+            urlConnection = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url);
             urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
             urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
             urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
@@ -1813,7 +1815,7 @@
 
     private void logNetworkEvent(int evtype) {
         int[] transports = mNetworkCapabilities.getTransportTypes();
-        mMetricsLog.log(mNetwork, transports, new NetworkEvent(evtype));
+        mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype));
     }
 
     private int networkEventType(ValidationStage s, EvaluationResult r) {
@@ -1835,7 +1837,7 @@
     private void maybeLogEvaluationResult(int evtype) {
         if (mEvaluationTimer.isRunning()) {
             int[] transports = mNetworkCapabilities.getTransportTypes();
-            mMetricsLog.log(mNetwork, transports,
+            mMetricsLog.log(mCleartextDnsNetwork, transports,
                     new NetworkEvent(evtype, mEvaluationTimer.stop()));
             mEvaluationTimer.reset();
         }
@@ -1849,7 +1851,7 @@
                 .setReturnCode(probeResult)
                 .setDurationMs(durationMs)
                 .build();
-        mMetricsLog.log(mNetwork, transports, ev);
+        mMetricsLog.log(mCleartextDnsNetwork, transports, ev);
     }
 
     @VisibleForTesting
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
index fe3c1e8..039f6bf 100644
--- a/packages/NetworkStack/tests/Android.bp
+++ b/packages/NetworkStack/tests/Android.bp
@@ -56,6 +56,7 @@
         "liblog",
         "liblzma",
         "libnativehelper",
+        "libnativehelper_compat_libc++",
         "libnetworkstacktestsjni",
         "libnetworkstackutilsjni",
         "libpackagelistparser",
@@ -99,5 +100,4 @@
         "libapf",
         "libpcap",
     ],
-
 }
diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
index a0e508f..93ab3be 100644
--- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
+++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
@@ -1553,7 +1553,7 @@
         parcel.seq = seqNum;
         parcel.ack = ackNum;
 
-        apfFilter.addKeepalivePacketFilter(slot1, parcel);
+        apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
         program = cb.getApfProgram();
 
         // Verify IPv4 keepalive ack packet is dropped
@@ -1592,7 +1592,7 @@
             ipv6Parcel.seq = seqNum;
             ipv6Parcel.ack = ackNum;
 
-            apfFilter.addKeepalivePacketFilter(slot1, ipv6Parcel);
+            apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel);
             program = cb.getApfProgram();
 
             // Verify IPv6 keepalive ack packet is dropped
@@ -1614,8 +1614,8 @@
             apfFilter.removeKeepalivePacketFilter(slot1);
 
             // Verify multiple filters
-            apfFilter.addKeepalivePacketFilter(slot1, parcel);
-            apfFilter.addKeepalivePacketFilter(slot2, ipv6Parcel);
+            apfFilter.addTcpKeepalivePacketFilter(slot1, parcel);
+            apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel);
             program = cb.getApfProgram();
 
             // Verify IPv4 keepalive ack packet is dropped
diff --git a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
index 4d98403..a30d3e4 100644
--- a/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
+++ b/packages/NetworkStack/tests/src/android/net/dhcp/DhcpPacketTest.java
@@ -302,8 +302,9 @@
     }
 
     private void assertDhcpResults(String ipAddress, String gateway, String dnsServersString,
-            String domains, String serverAddress, String vendorInfo, int leaseDuration,
-            boolean hasMeteredHint, int mtu, DhcpResults dhcpResults) throws Exception {
+            String domains, String serverAddress, String serverHostName, String vendorInfo,
+            int leaseDuration, boolean hasMeteredHint, int mtu, DhcpResults dhcpResults)
+                    throws Exception {
         assertEquals(new LinkAddress(ipAddress), dhcpResults.ipAddress);
         assertEquals(v4Address(gateway), dhcpResults.gateway);
 
@@ -316,6 +317,7 @@
 
         assertEquals(domains, dhcpResults.domains);
         assertEquals(v4Address(serverAddress), dhcpResults.serverAddress);
+        assertEquals(serverHostName, dhcpResults.serverHostName);
         assertEquals(vendorInfo, dhcpResults.vendorInfo);
         assertEquals(leaseDuration, dhcpResults.leaseDuration);
         assertEquals(hasMeteredHint, dhcpResults.hasMeteredHint());
@@ -327,6 +329,7 @@
         // TODO: Turn all of these into golden files. This will probably require using
         // androidx.test.InstrumentationRegistry for obtaining a Context object
         // to read such golden files, along with an appropriate Android.mk.
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
             "451001480000000080118849c0a89003c0a89ff7" +
@@ -347,16 +350,18 @@
             // Options
             "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
             "3a0400000e103b040000189cff00000000000000000000"));
+        // CHECKSTYLE:ON Generated code
 
         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
         assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
         DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4",
-                null, "192.168.144.3", null, 7200, false, 0, dhcpResults);
+                null, "192.168.144.3", "", null, 7200, false, 0, dhcpResults);
     }
 
     @Test
     public void testOffer2() throws Exception {
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
             "450001518d0600004011144dc0a82b01c0a82bf7" +
@@ -366,9 +371,9 @@
             "02010600dfc23d1f0002000000000000c0a82bf7c0a82b0100000000" +
             // MAC address.
             "30766ff2a90c00000000000000000000" +
-            // Server name.
-            "0000000000000000000000000000000000000000000000000000000000000000" +
-            "0000000000000000000000000000000000000000000000000000000000000000" +
+            // Server name ("dhcp.android.com" plus invalid "AAAA" after null terminator).
+            "646863702e616e64726f69642e636f6d00000000000000000000000000000000" +
+            "0000000000004141414100000000000000000000000000000000000000000000" +
             // File.
             "0000000000000000000000000000000000000000000000000000000000000000" +
             "0000000000000000000000000000000000000000000000000000000000000000" +
@@ -377,13 +382,15 @@
             // Options
             "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
             "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
+        // CHECKSTYLE:ON Generated code
 
         assertEquals(337, packet.limit());
         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
         assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
         DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertDhcpResults("192.168.43.247/24", "192.168.43.1", "192.168.43.1",
-                null, "192.168.43.1", "ANDROID_METERED", 3600, true, 0, dhcpResults);
+                null, "192.168.43.1", "dhcp.android.com", "ANDROID_METERED", 3600, true, 0,
+                dhcpResults);
         assertTrue(dhcpResults.hasMeteredHint());
     }
 
@@ -588,11 +595,12 @@
         assertTrue(offerPacket instanceof DhcpOfferPacket);  // Implicitly checks it's non-null.
         DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertDhcpResults("192.168.159.247/20", "192.168.159.254", "8.8.8.8,8.8.4.4",
-                null, "192.168.144.3", null, 7200, false, expectedMtu, dhcpResults);
+                null, "192.168.144.3", "", null, 7200, false, expectedMtu, dhcpResults);
     }
 
     @Test
     public void testMtu() throws Exception {
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
             "451001480000000080118849c0a89003c0a89ff7" +
@@ -613,6 +621,7 @@
             // Options
             "638253633501023604c0a89003330400001c200104fffff0000304c0a89ffe06080808080808080404" +
             "3a0400000e103b040000189cff00000000"));
+        // CHECKSTYLE:ON Generated code
 
         checkMtu(packet, 0, null);
         checkMtu(packet, 0, mtuBytes(1501));
@@ -629,6 +638,7 @@
 
     @Test
     public void testBadHwaddrLength() throws Exception {
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
             "450001518d0600004011144dc0a82b01c0a82bf7" +
@@ -649,6 +659,7 @@
             // Options
             "638253633501023604c0a82b01330400000e103a04000007083b0400000c4e0104ffffff00" +
             "1c04c0a82bff0304c0a82b010604c0a82b012b0f414e44524f49445f4d455445524544ff"));
+        // CHECKSTYLE:ON Generated code
         String expectedClientMac = "30766FF2A90C";
 
         final int hwAddrLenOffset = 20 + 8 + 2;
@@ -705,6 +716,7 @@
         //    store any information in the overloaded fields).
         //
         // For now, we just check that it parses correctly.
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
             "b4cef6000000e80462236e300800" +
@@ -727,16 +739,18 @@
             // Options
             "638253633501023604010101010104ffff000033040000a8c03401030304ac1101010604ac110101" +
             "0000000000000000000000000000000000000000000000ff000000"));
+        // CHECKSTYLE:ON Generated code
 
         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
         assertTrue(offerPacket instanceof DhcpOfferPacket);
         DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertDhcpResults("172.17.152.118/16", "172.17.1.1", "172.17.1.1",
-                null, "1.1.1.1", null, 43200, false, 0, dhcpResults);
+                null, "1.1.1.1", "", null, 43200, false, 0, dhcpResults);
     }
 
     @Test
     public void testBug2111() throws Exception {
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // IP header.
             "4500014c00000000ff119beac3eaf3880a3f5d04" +
@@ -757,16 +771,18 @@
             // Options.
             "638253633501023604c00002fe33040000bfc60104fffff00003040a3f50010608c0000201c0000202" +
             "0f0f646f6d61696e3132332e636f2e756b0000000000ff00000000"));
+        // CHECKSTYLE:ON Generated code
 
         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L3);
         assertTrue(offerPacket instanceof DhcpOfferPacket);
         DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertDhcpResults("10.63.93.4/20", "10.63.80.1", "192.0.2.1,192.0.2.2",
-                "domain123.co.uk", "192.0.2.254", null, 49094, false, 0, dhcpResults);
+                "domain123.co.uk", "192.0.2.254", "", null, 49094, false, 0, dhcpResults);
     }
 
     @Test
     public void testBug2136() throws Exception {
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
             "bcf5ac000000d0c7890000000800" +
@@ -789,17 +805,19 @@
             // Options.
             "6382536335010236040a20ff80330400001c200104fffff00003040a20900106089458413494584135" +
             "0f0b6c616e63732e61632e756b000000000000000000ff00000000"));
+        // CHECKSTYLE:ON Generated code
 
         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
         assertTrue(offerPacket instanceof DhcpOfferPacket);
         assertEquals("BCF5AC000000", HexDump.toHexString(offerPacket.getClientMac()));
         DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertDhcpResults("10.32.158.205/20", "10.32.144.1", "148.88.65.52,148.88.65.53",
-                "lancs.ac.uk", "10.32.255.128", null, 7200, false, 0, dhcpResults);
+                "lancs.ac.uk", "10.32.255.128", "", null, 7200, false, 0, dhcpResults);
     }
 
     @Test
     public void testUdpServerAnySourcePort() throws Exception {
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
             "9cd917000000001c2e0000000800" +
@@ -823,6 +841,7 @@
             // Options.
             "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
             "d18180060f0777766d2e6564751c040a0fffffff000000"));
+        // CHECKSTYLE:ON Generated code
 
         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
         assertTrue(offerPacket instanceof DhcpOfferPacket);
@@ -830,11 +849,12 @@
         DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertDhcpResults("10.15.122.242/16", "10.15.200.23",
                 "209.129.128.3,209.129.148.3,209.129.128.6",
-                "wvm.edu", "10.1.105.252", null, 86400, false, 0, dhcpResults);
+                "wvm.edu", "10.1.105.252", "", null, 86400, false, 0, dhcpResults);
     }
 
     @Test
     public void testUdpInvalidDstPort() throws Exception {
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
             "9cd917000000001c2e0000000800" +
@@ -858,6 +878,7 @@
             // Options.
             "6382536335010236040a0169fc3304000151800104ffff000003040a0fc817060cd1818003d1819403" +
             "d18180060f0777766d2e6564751c040a0fffffff000000"));
+        // CHECKSTYLE:ON Generated code
 
         try {
             DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
@@ -867,6 +888,7 @@
 
     @Test
     public void testMultipleRouters() throws Exception {
+        // CHECKSTYLE:OFF Generated code
         final ByteBuffer packet = ByteBuffer.wrap(HexDump.hexStringToByteArray(
             // Ethernet header.
             "fc3d93000000" + "081735000000" + "0800" +
@@ -889,13 +911,14 @@
             // Options.
             "638253633501023604c0abbd023304000070803a04000038403b04000062700104ffffff00" +
             "0308c0a8bd01ffffff0006080808080808080404ff000000000000"));
+        // CHECKSTYLE:ON Generated code
 
         DhcpPacket offerPacket = DhcpPacket.decodeFullPacket(packet, ENCAP_L2);
         assertTrue(offerPacket instanceof DhcpOfferPacket);
         assertEquals("FC3D93000000", HexDump.toHexString(offerPacket.getClientMac()));
         DhcpResults dhcpResults = offerPacket.toDhcpResults();
         assertDhcpResults("192.168.189.49/24", "192.168.189.1", "8.8.8.8,8.8.4.4",
-                null, "192.171.189.2", null, 28800, false, 0, dhcpResults);
+                null, "192.171.189.2", "", null, 28800, false, 0, dhcpResults);
     }
 
     @Test
diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
index 0dc1cbf..6f5c27e 100644
--- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -42,10 +42,10 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -66,6 +66,7 @@
 import android.net.NetworkInfo;
 import android.net.captiveportal.CaptivePortalProbeResult;
 import android.net.metrics.IpConnectivityLog;
+import android.net.shared.PrivateDnsConfig;
 import android.net.util.SharedLog;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
@@ -73,6 +74,7 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.provider.Settings;
@@ -131,7 +133,8 @@
     private @Mock Random mRandom;
     private @Mock NetworkMonitor.Dependencies mDependencies;
     private @Mock INetworkMonitorCallbacks mCallbacks;
-    private @Spy Network mNetwork = new Network(TEST_NETID);
+    private @Spy Network mCleartextDnsNetwork = new Network(TEST_NETID);
+    private @Mock Network mNetwork;
     private @Mock DataStallStatsUtils mDataStallStatsUtils;
     private @Mock WifiInfo mWifiInfo;
     private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor;
@@ -166,35 +169,97 @@
     private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
 
-    private void setDnsAnswers(String[] answers) throws UnknownHostException {
-        if (answers == null) {
-            doThrow(new UnknownHostException()).when(mNetwork).getAllByName(any());
-            doNothing().when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
-            return;
+    /**
+     * Fakes DNS responses.
+     *
+     * Allows test methods to configure the IP addresses that will be resolved by
+     * Network#getAllByName and by DnsResolver#query.
+     */
+    class FakeDns {
+        private final ArrayMap<String, List<InetAddress>> mAnswers = new ArrayMap<>();
+        private boolean mNonBypassPrivateDnsWorking = true;
+
+        /** Whether DNS queries on mNonBypassPrivateDnsWorking should succeed. */
+        private void setNonBypassPrivateDnsWorking(boolean working) {
+            mNonBypassPrivateDnsWorking = working;
         }
 
-        List<InetAddress> answerList = new ArrayList<>();
-        for (String answer : answers) {
-            answerList.add(InetAddresses.parseNumericAddress(answer));
+        /** Clears all DNS entries. */
+        private synchronized void clearAll() {
+            mAnswers.clear();
         }
-        InetAddress[] answerArray = answerList.toArray(new InetAddress[0]);
 
-        doReturn(answerArray).when(mNetwork).getAllByName(any());
+        /** Returns the answer for a given name on the given mock network. */
+        private synchronized List<InetAddress> getAnswer(Object mock, String hostname) {
+            if (mock == mNetwork && !mNonBypassPrivateDnsWorking) {
+                return null;
+            }
+            if (mAnswers.containsKey(hostname)) {
+                return mAnswers.get(hostname);
+            }
+            return mAnswers.get("*");
+        }
 
-        doAnswer((invocation) -> {
-            Executor executor = (Executor) invocation.getArgument(3);
-            DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
-            new Handler(Looper.getMainLooper()).post(() -> {
-                executor.execute(() -> callback.onAnswer(answerList, 0));
-            });
-            return null;
-        }).when(mDnsResolver).query(eq(mNetwork), any(), anyInt(), any(), any(), any());
+        /** Sets the answer for a given name. */
+        private synchronized void setAnswer(String hostname, String[] answer)
+                throws UnknownHostException {
+            if (answer == null) {
+                mAnswers.remove(hostname);
+            } else {
+                List<InetAddress> answerList = new ArrayList<>();
+                for (String addr : answer) {
+                    answerList.add(InetAddresses.parseNumericAddress(addr));
+                }
+                mAnswers.put(hostname, answerList);
+            }
+        }
+
+        /** Simulates a getAllByName call for the specified name on the specified mock network. */
+        private InetAddress[] getAllByName(Object mock, String hostname)
+                throws UnknownHostException {
+            List<InetAddress> answer = getAnswer(mock, hostname);
+            if (answer == null || answer.size() == 0) {
+                throw new UnknownHostException(hostname);
+            }
+            return answer.toArray(new InetAddress[0]);
+        }
+
+        /** Starts mocking DNS queries. */
+        private void startMocking() throws UnknownHostException {
+            // Queries on mCleartextDnsNetwork using getAllByName.
+            doAnswer(invocation -> {
+                return getAllByName(invocation.getMock(), invocation.getArgument(0));
+            }).when(mCleartextDnsNetwork).getAllByName(any());
+
+            // Queries on mNetwork using getAllByName.
+            doAnswer(invocation -> {
+                return getAllByName(invocation.getMock(), invocation.getArgument(0));
+            }).when(mNetwork).getAllByName(any());
+
+            // Queries on mCleartextDnsNetwork using DnsResolver#query.
+            doAnswer(invocation -> {
+                String hostname = (String) invocation.getArgument(1);
+                Executor executor = (Executor) invocation.getArgument(3);
+                DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
+
+                List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
+                if (answer != null && answer.size() > 0) {
+                    new Handler(Looper.getMainLooper()).post(() -> {
+                        executor.execute(() -> callback.onAnswer(answer, 0));
+                    });
+                }
+                // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
+                return null;
+            }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
+        }
     }
 
+    private FakeDns mFakeDns;
+
     @Before
     public void setUp() throws IOException {
         MockitoAnnotations.initMocks(this);
-        when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork);
+        when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mCleartextDnsNetwork);
         when(mDependencies.getDnsResolver()).thenReturn(mDnsResolver);
         when(mDependencies.getRandom()).thenReturn(mRandom);
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
@@ -206,7 +271,7 @@
         when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any()))
                 .thenReturn(TEST_HTTPS_URL);
 
-        doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
+        doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
 
         when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
         when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
@@ -222,6 +287,9 @@
         setFallbackSpecs(null); // Test with no fallback spec by default
         when(mRandom.nextInt()).thenReturn(0);
 
+        when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
+                .thenReturn(500);
+
         doAnswer((invocation) -> {
             URL url = invocation.getArgument(0);
             switch(url.toString()) {
@@ -237,11 +305,13 @@
                     fail("URL not mocked: " + url.toString());
                     return null;
             }
-        }).when(mNetwork).openConnection(any());
+        }).when(mCleartextDnsNetwork).openConnection(any());
         when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
         when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
 
-        setDnsAnswers(new String[]{"2001:db8::1", "192.0.2.2"});
+        mFakeDns = new FakeDns();
+        mFakeDns.startMocking();
+        mFakeDns.setAnswer("*", new String[]{"2001:db8::1", "192.0.2.2"});
 
         when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> {
             mRegisteredReceivers.add(invocation.getArgument(0));
@@ -264,6 +334,7 @@
 
     @After
     public void tearDown() {
+        mFakeDns.clearAll();
         assertTrue(mCreatedNetworkMonitors.size() > 0);
         // Make a local copy of mCreatedNetworkMonitors because during the iteration below,
         // WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
@@ -284,8 +355,8 @@
         private final ConditionVariable mQuitCv = new ConditionVariable(false);
 
         WrappedNetworkMonitor() {
-                super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mDependencies,
-                        mDataStallStatsUtils);
+            super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger,
+                    mDependencies, mDataStallStatsUtils);
         }
 
         @Override
@@ -314,23 +385,22 @@
         }
     }
 
-    private WrappedNetworkMonitor makeMonitor() {
+    private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
         final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
         nm.start();
+        setNetworkCapabilities(nm, nc);
         waitForIdle(nm.getHandler());
         mCreatedNetworkMonitors.add(nm);
         return nm;
     }
 
     private WrappedNetworkMonitor makeMeteredNetworkMonitor() {
-        final WrappedNetworkMonitor nm = makeMonitor();
-        setNetworkCapabilities(nm, METERED_CAPABILITIES);
+        final WrappedNetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
         return nm;
     }
 
     private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() {
-        final WrappedNetworkMonitor nm = makeMonitor();
-        setNetworkCapabilities(nm, NOT_METERED_CAPABILITIES);
+        final WrappedNetworkMonitor nm = makeMonitor(NOT_METERED_CAPABILITIES);
         return nm;
     }
 
@@ -595,7 +665,7 @@
     @Test
     public void testNoInternetCapabilityValidated() throws Exception {
         runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID);
-        verify(mNetwork, never()).openConnection(any());
+        verify(mCleartextDnsNetwork, never()).openConnection(any());
     }
 
     @Test
@@ -603,7 +673,7 @@
         setSslException(mHttpsConnection);
         setPortal302(mHttpConnection);
 
-        final NetworkMonitor nm = makeMonitor();
+        final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
         nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES);
 
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
@@ -638,6 +708,63 @@
     }
 
     @Test
+    public void testPrivateDnsSuccess() throws Exception {
+        setStatus(mHttpsConnection, 204);
+        setStatus(mHttpConnection, 204);
+        mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::53"});
+
+        WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
+        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+        wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+    }
+
+    @Test
+    public void testPrivateDnsResolutionRetryUpdate() throws Exception {
+        // Set a private DNS hostname that doesn't resolve and expect validation to fail.
+        mFakeDns.setAnswer("dns.google", new String[0]);
+        setStatus(mHttpsConnection, 204);
+        setStatus(mHttpConnection, 204);
+
+        WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
+        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+        wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+
+        // Fix DNS and retry, expect validation to succeed.
+        reset(mCallbacks);
+        mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
+
+        wnm.forceReevaluation(Process.myUid());
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+
+        // Change configuration to an invalid DNS name, expect validation to fail.
+        reset(mCallbacks);
+        mFakeDns.setAnswer("dns.bad", new String[0]);
+        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+
+        // Change configuration back to working again, but make private DNS not work.
+        // Expect validation to fail.
+        reset(mCallbacks);
+        mFakeDns.setNonBypassPrivateDnsWorking(false);
+        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+
+        // Make private DNS work again. Expect validation to succeed.
+        reset(mCallbacks);
+        mFakeDns.setNonBypassPrivateDnsWorking(true);
+        wnm.forceReevaluation(Process.myUid());
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+    }
+
+    @Test
     public void testDataStall_StallSuspectedAndSendMetrics() throws IOException {
         WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
         wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
@@ -728,25 +855,27 @@
         WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
         final int shortTimeoutMs = 200;
 
+        // Clear the wildcard DNS response created in setUp.
+        mFakeDns.setAnswer("*", null);
+
         String[] expected = new String[]{"2001:db8::"};
-        setDnsAnswers(expected);
+        mFakeDns.setAnswer("www.google.com", expected);
         InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
         assertIpAddressArrayEquals(expected, actual);
 
         expected = new String[]{"2001:db8::", "192.0.2.1"};
-        setDnsAnswers(expected);
-        actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
+        mFakeDns.setAnswer("www.googleapis.com", expected);
+        actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs);
         assertIpAddressArrayEquals(expected, actual);
 
-        expected = new String[0];
-        setDnsAnswers(expected);
+        mFakeDns.setAnswer("www.google.com", new String[0]);
         try {
             wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
             fail("No DNS results, expected UnknownHostException");
         } catch (UnknownHostException e) {
         }
 
-        setDnsAnswers(null);
+        mFakeDns.setAnswer("www.google.com", null);
         try {
             wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
             fail("DNS query timed out, expected UnknownHostException");
@@ -841,7 +970,7 @@
     }
 
     private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
-        final NetworkMonitor monitor = makeMonitor();
+        final NetworkMonitor monitor = makeMonitor(nc);
         monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
         try {
             verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index e1a602b..9536bae 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -68,19 +68,19 @@
 
     <!-- user interface sound effects -->
     <integer name="def_power_sounds_enabled">1</integer>
-    <string name="def_low_battery_sound" translatable="false">/system/media/audio/ui/LowBattery.ogg</string>
+    <string name="def_low_battery_sound" translatable="false">/product/media/audio/ui/LowBattery.ogg</string>
     <integer name="def_dock_sounds_enabled">0</integer>
     <integer name="def_dock_sounds_enabled_when_accessibility">0</integer>
-    <string name="def_desk_dock_sound" translatable="false">/system/media/audio/ui/Dock.ogg</string>
-    <string name="def_desk_undock_sound" translatable="false">/system/media/audio/ui/Undock.ogg</string>
-    <string name="def_car_dock_sound" translatable="false">/system/media/audio/ui/Dock.ogg</string>
-    <string name="def_car_undock_sound" translatable="false">/system/media/audio/ui/Undock.ogg</string>
+    <string name="def_desk_dock_sound" translatable="false">/product/media/audio/ui/Dock.ogg</string>
+    <string name="def_desk_undock_sound" translatable="false">/product/media/audio/ui/Undock.ogg</string>
+    <string name="def_car_dock_sound" translatable="false">/product/media/audio/ui/Dock.ogg</string>
+    <string name="def_car_undock_sound" translatable="false">/product/media/audio/ui/Undock.ogg</string>
     <integer name="def_lockscreen_sounds_enabled">1</integer>
-    <string name="def_lock_sound" translatable="false">/system/media/audio/ui/Lock.ogg</string>
-    <string name="def_unlock_sound" translatable="false">/system/media/audio/ui/Unlock.ogg</string>
-    <string name="def_trusted_sound" translatable="false">/system/media/audio/ui/Trusted.ogg</string>
-    <string name="def_wireless_charging_started_sound" translatable="false">/system/media/audio/ui/WirelessChargingStarted.ogg</string>
-    <string name="def_charging_started_sound" translatable="false">/system/media/audio/ui/ChargingStarted.ogg</string>
+    <string name="def_lock_sound" translatable="false">/product/media/audio/ui/Lock.ogg</string>
+    <string name="def_unlock_sound" translatable="false">/product/media/audio/ui/Unlock.ogg</string>
+    <string name="def_trusted_sound" translatable="false">/product/media/audio/ui/Trusted.ogg</string>
+    <string name="def_wireless_charging_started_sound" translatable="false">/product/media/audio/ui/WirelessChargingStarted.ogg</string>
+    <string name="def_charging_started_sound" translatable="false">/product/media/audio/ui/ChargingStarted.ogg</string>
 
     <!-- sound trigger detection service default values -->
     <integer name="def_max_sound_trigger_detection_service_ops_per_day" translatable="false">1000</integer>
diff --git a/packages/Shell/Android.bp b/packages/Shell/Android.bp
new file mode 100644
index 0000000..aaaf044
--- /dev/null
+++ b/packages/Shell/Android.bp
@@ -0,0 +1,14 @@
+android_app {
+    name: "Shell",
+    srcs: ["src/**/*.java",":dumpstate_aidl"],
+    aidl: {
+        include_dirs: ["frameworks/native/cmds/dumpstate/binder"],
+    },
+    static_libs: ["androidx.legacy_legacy-support-v4"],
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+    jacoco: {
+        include_filter: ["com.android.shell.*"],
+    },
+}
diff --git a/packages/Shell/Android.mk b/packages/Shell/Android.mk
deleted file mode 100644
index b738d77..0000000
--- a/packages/Shell/Android.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SRC_FILES += \
-        ../../../native/cmds/dumpstate/binder/android/os/IDumpstate.aidl \
-        ../../../native/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl \
-        ../../../native/cmds/dumpstate/binder/android/os/IDumpstateToken.aidl
-
-LOCAL_AIDL_INCLUDES = frameworks/native/cmds/dumpstate/binder
-
-LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4
-LOCAL_USE_AAPT2 := true
-
-LOCAL_PACKAGE_NAME := Shell
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_CERTIFICATE := platform
-LOCAL_PRIVILEGED_MODULE := true
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.shell.*
-
-include $(BUILD_PACKAGE)
-
-include $(LOCAL_PATH)/tests/Android.mk
diff --git a/packages/Shell/tests/Android.bp b/packages/Shell/tests/Android.bp
new file mode 100644
index 0000000..8536c4f
--- /dev/null
+++ b/packages/Shell/tests/Android.bp
@@ -0,0 +1,19 @@
+android_test {
+    name: "ShellTests",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+        "ub-uiautomator",
+        "junit",
+    ],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    instrumentation_for: "Shell",
+    certificate: "platform",
+}
diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk
deleted file mode 100644
index 44ff338..0000000
--- a/packages/Shell/tests/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.rules \
-    mockito-target-minus-junit4 \
-    ub-uiautomator \
-    junit \
-
-LOCAL_PACKAGE_NAME := ShellTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_INSTRUMENTATION_FOR := Shell
-
-LOCAL_CERTIFICATE := platform
-
-include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml b/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml
new file mode 100644
index 0000000..fe1bb26
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml
@@ -0,0 +1,31 @@
+<!--
+     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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M1.22,8.49l0.43-4.96h4.33v1.17H2.67L2.44,7.41c0.41-0.29,0.85-0.43,1.33-0.43c0.77,0,1.38,0.3,1.83,0.9 s0.66,1.41,0.66,2.43c0,1.03-0.24,1.84-0.72,2.43s-1.14,0.88-1.98,0.88c-0.75,0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07,0.57,0.23,1,0.49,1.29c0.26,0.29,0.59,0.43,1.01,0.43c0.47,0,0.84-0.2,1.1-0.61c0.26-0.41,0.4-0.96,0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.96,8.11,3.47,8.11c-0.4,0-0.72,0.1-0.96,0.31L2.19,8.75L1.22,8.49z" />
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M14.14,12.24l-0.22,0.27c-0.63,0.73-1.55,1.1-2.76,1.1c-1.08,0-1.92-0.36-2.53-1.07c-0.61-0.71-0.93-1.72-0.94-3.02V7.56 c0-1.39,0.28-2.44,0.84-3.13c0.56-0.7,1.39-1.04,2.51-1.04c0.95,0,1.69,0.26,2.23,0.79c0.54,0.53,0.83,1.28,0.89,2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45c-0.29-0.35-0.74-0.52-1.34-0.52c-0.72,0-1.24,0.23-1.57,0.7C9.14,5.63,8.96,6.37,8.95,7.4v2.03 c0,1,0.19,1.77,0.57,2.31c0.38,0.54,0.93,0.8,1.65,0.8c0.67,0,1.19-0.16,1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z" />
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M20.96,8.88h-3.52v3.53h4.1v1.07h-5.35V3.52h5.28V4.6h-4.03V7.8h3.52V8.88z" />
+
+</vector>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4437f49..ccf345b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -390,6 +390,9 @@
     <!-- Content description of the data connection type LTE+. [CHAR LIMIT=NONE] -->
     <string name="data_connection_lte_plus">LTE+</string>
 
+    <!-- Content description of the data connection type 5Ge. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5ge" translate="false">5Ge</string>
+
     <!-- Content description of the data connection type 5G. [CHAR LIMIT=NONE] -->
     <string name="data_connection_5g" translate="false">5G</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 2f7e3b18..b7c20aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -46,6 +46,8 @@
 import java.io.PrintWriter;
 import java.util.BitSet;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 
 public class MobileSignalController extends SignalController<
@@ -72,6 +74,8 @@
     private SignalStrength mSignalStrength;
     private MobileIconGroup mDefaultIcons;
     private Config mConfig;
+    // Some specific carriers have 5GE network which is special LTE CA network.
+    private static final int NETWORK_TYPE_LTE_CA_5GE = TelephonyManager.MAX_NETWORK_TYPE + 1;
 
     // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
     // need listener lists anymore.
@@ -235,6 +239,8 @@
                         TelephonyIcons.LTE_PLUS);
             }
         }
+        mNetworkToIconLookup.put(NETWORK_TYPE_LTE_CA_5GE,
+                TelephonyIcons.LTE_CA_5G_E);
         mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC);
     }
 
@@ -400,6 +406,26 @@
         }
     }
 
+    private boolean isCarrierSpecificDataIcon() {
+        if (mConfig.patternOfCarrierSpecificDataIcon == null
+                || mConfig.patternOfCarrierSpecificDataIcon.length() == 0) {
+            return false;
+        }
+
+        Pattern stringPattern = Pattern.compile(mConfig.patternOfCarrierSpecificDataIcon);
+        String[] operatorNames = new String[]{mServiceState.getOperatorAlphaLongRaw(),
+                mServiceState.getOperatorAlphaShortRaw()};
+        for (String opName : operatorNames) {
+            if (!TextUtils.isEmpty(opName)) {
+                Matcher matcher = stringPattern.matcher(opName);
+                if (matcher.find()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Updates the network's name based on incoming spn and plmn.
      */
@@ -566,12 +592,8 @@
                         + " dataState=" + state.getDataRegState());
             }
             mServiceState = state;
-            if (state != null) {
-                mDataNetType = state.getDataNetworkType();
-                if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE && mServiceState != null &&
-                        mServiceState.isUsingCarrierAggregation()) {
-                    mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
-                }
+            if (mServiceState != null) {
+                updateDataNetType(mServiceState.getDataNetworkType());
             }
             updateTelephony();
         }
@@ -583,14 +605,21 @@
                         + " type=" + networkType);
             }
             mDataState = state;
-            mDataNetType = networkType;
-            if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE && mServiceState != null &&
-                    mServiceState.isUsingCarrierAggregation()) {
-                mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
-            }
+            updateDataNetType(networkType);
             updateTelephony();
         }
 
+        private void updateDataNetType(int networkType) {
+            mDataNetType = networkType;
+            if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE) {
+                if (isCarrierSpecificDataIcon()) {
+                    mDataNetType = NETWORK_TYPE_LTE_CA_5GE;
+                } else if (mServiceState != null && mServiceState.isUsingCarrierAggregation()) {
+                    mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
+                }
+            }
+        }
+
         @Override
         public void onDataActivity(int direction) {
             if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index b4f0fec..5c6634c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -1084,6 +1084,7 @@
         boolean hspaDataDistinguishable;
         boolean inflateSignalStrengths = false;
         boolean alwaysShowDataRatIcon = false;
+        public String patternOfCarrierSpecificDataIcon = "";
 
         /**
          * Mapping from NR 5G status string to an integer. The NR 5G status string should match
@@ -1122,6 +1123,8 @@
                         CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL);
                 config.hideLtePlus = b.getBoolean(
                         CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL);
+                config.patternOfCarrierSpecificDataIcon = b.getString(
+                        CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
                 String nr5GIconConfiguration =
                         b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
                 if (!TextUtils.isEmpty(nr5GIconConfiguration)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 7347f66..2c4b1f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -35,6 +35,7 @@
     static final int ICON_3G = R.drawable.ic_3g_mobiledata;
     static final int ICON_4G = R.drawable.ic_4g_mobiledata;
     static final int ICON_4G_PLUS = R.drawable.ic_4g_plus_mobiledata;
+    static final int ICON_5G_E = R.drawable.ic_5g_e_mobiledata;
     static final int ICON_1X = R.drawable.ic_1x_mobiledata;
     static final int ICON_5G = R.drawable.ic_5g_mobiledata;
     static final int ICON_5G_PLUS = R.drawable.ic_5g_plus_mobiledata;
@@ -204,6 +205,19 @@
             TelephonyIcons.ICON_LTE_PLUS,
             true);
 
+    static final MobileIconGroup LTE_CA_5G_E = new MobileIconGroup(
+            "5Ge",
+            null,
+            null,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+            0, 0,
+            0,
+            0,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+            R.string.data_connection_5ge,
+            TelephonyIcons.ICON_5G_E,
+            true);
+
     static final MobileIconGroup NR_5G = new MobileIconGroup(
             "5G",
             null,
@@ -260,6 +274,7 @@
         ICON_NAME_TO_ICON.put("h+", H_PLUS);
         ICON_NAME_TO_ICON.put("4g", FOUR_G);
         ICON_NAME_TO_ICON.put("4g+", FOUR_G_PLUS);
+        ICON_NAME_TO_ICON.put("5ge", LTE_CA_5G_E);
         ICON_NAME_TO_ICON.put("lte", LTE);
         ICON_NAME_TO_ICON.put("lte+", LTE_PLUS);
         ICON_NAME_TO_ICON.put("5g", NR_5G);
diff --git a/services/backup/OWNERS b/services/backup/OWNERS
index 1c9a43a..9c21e8f 100644
--- a/services/backup/OWNERS
+++ b/services/backup/OWNERS
@@ -1,7 +1,9 @@
-artikz@google.com
+alsutton@google.com
+anniemeng@google.com
 brufino@google.com
 bryanmawhinney@google.com
 ctate@google.com
 jorlow@google.com
-mkarpinski@google.com
+nathch@google.com
+rthakohov@google.com
 
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index b60dd0f..223eb55 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1094,11 +1094,21 @@
     public void unbindBluetoothProfileService(int bluetoothProfile,
             IBluetoothProfileServiceConnection proxy) {
         synchronized (mProfileServices) {
-            ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
+            Integer profile = new Integer(bluetoothProfile);
+            ProfileServiceConnections psc = mProfileServices.get(profile);
             if (psc == null) {
                 return;
             }
             psc.removeProxy(proxy);
+            if (psc.isEmpty()) {
+                // All prxoies are disconnected, unbind with the service.
+                try {
+                    mContext.unbindService(psc);
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e);
+                }
+                mProfileServices.remove(profile);
+            }
         }
     }
 
@@ -1255,6 +1265,10 @@
             mProxies.kill();
         }
 
+        private boolean isEmpty() {
+            return mProxies.getRegisteredCallbackCount() == 0;
+        }
+
         @Override
         public void onServiceConnected(ComponentName className, IBinder service) {
             // remove timeout message
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 90c86c7..63fd2fd 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2861,7 +2861,7 @@
         try {
             nai.networkMonitor().notifyPrivateDnsChanged(cfg.toParcel());
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            e.rethrowAsRuntimeException();
         }
 
         // With Private DNS bypass support, we can proceed to update the
@@ -3031,7 +3031,7 @@
         try {
             nai.networkMonitor().notifyNetworkDisconnected();
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            e.rethrowAsRuntimeException();
         }
         mNetworkAgentInfos.remove(nai.messenger);
         nai.clatd.update();
@@ -3070,11 +3070,7 @@
             // fallback network the default or requested a new network from the
             // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
             // long time.
-            try {
-                mNetd.networkDestroy(nai.network.netId);
-            } catch (RemoteException | ServiceSpecificException e) {
-                loge("Exception destroying network: " + e);
-            }
+            destroyNativeNetwork(nai);
             mDnsManager.removeNetwork(nai.network);
         }
         synchronized (mNetworkForNetId) {
@@ -3082,6 +3078,35 @@
         }
     }
 
+    private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
+        try {
+            // This should never fail.  Specifying an already in use NetID will cause failure.
+            if (networkAgent.isVPN()) {
+                mNetd.networkCreateVpn(networkAgent.network.netId,
+                        (networkAgent.networkMisc == null
+                                || !networkAgent.networkMisc.allowBypass));
+            } else {
+                mNetd.networkCreatePhysical(networkAgent.network.netId,
+                        getNetworkPermission(networkAgent.networkCapabilities));
+            }
+            mDnsResolver.createNetworkCache(networkAgent.network.netId);
+            return true;
+        } catch (RemoteException | ServiceSpecificException e) {
+            loge("Error creating network " + networkAgent.network.netId + ": "
+                    + e.getMessage());
+            return false;
+        }
+    }
+
+    private void destroyNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
+        try {
+            mNetd.networkDestroy(networkAgent.network.netId);
+            mDnsResolver.destroyNetworkCache(networkAgent.network.netId);
+        } catch (RemoteException | ServiceSpecificException e) {
+            loge("Exception destroying network: " + e);
+        }
+    }
+
     // If this method proves to be too slow then we can maintain a separate
     // pendingIntent => NetworkRequestInfo map.
     // This method assumes that every non-null PendingIntent maps to exactly 1 NetworkRequestInfo.
@@ -3420,7 +3445,7 @@
             try {
                 nai.networkMonitor().setAcceptPartialConnectivity();
             } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+                e.rethrowAsRuntimeException();
             }
         }
     }
@@ -3456,7 +3481,7 @@
             try {
                 nai.networkMonitor().launchCaptivePortalApp();
             } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+                e.rethrowAsRuntimeException();
             }
         });
     }
@@ -4084,7 +4109,7 @@
         try {
             nai.networkMonitor().forceReevaluation(uid);
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            e.rethrowAsRuntimeException();
         }
     }
 
@@ -5464,7 +5489,7 @@
         try {
             networkMonitor.start();
         } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
+            e.rethrowAsRuntimeException();
         }
         nai.asyncChannel.connect(mContext, mTrackerHandler, nai.messenger);
         NetworkInfo networkInfo = nai.networkInfo;
@@ -5521,7 +5546,7 @@
             try {
                 networkAgent.networkMonitor().notifyLinkPropertiesChanged(newLp);
             } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+                e.rethrowAsRuntimeException();
             }
             if (networkAgent.everConnected) {
                 notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
@@ -6482,21 +6507,7 @@
             // A network that has just connected has zero requests and is thus a foreground network.
             networkAgent.networkCapabilities.addCapability(NET_CAPABILITY_FOREGROUND);
 
-            try {
-                // This should never fail.  Specifying an already in use NetID will cause failure.
-                if (networkAgent.isVPN()) {
-                    mNMS.createVirtualNetwork(networkAgent.network.netId,
-                            (networkAgent.networkMisc == null ||
-                                !networkAgent.networkMisc.allowBypass));
-                } else {
-                    mNMS.createPhysicalNetwork(networkAgent.network.netId,
-                            getNetworkPermission(networkAgent.networkCapabilities));
-                }
-            } catch (Exception e) {
-                loge("Error creating network " + networkAgent.network.netId + ": "
-                        + e.getMessage());
-                return;
-            }
+            if (!createNativeNetwork(networkAgent)) return;
             networkAgent.created = true;
         }
 
@@ -6527,7 +6538,7 @@
                 networkAgent.networkMonitor().notifyNetworkConnected(
                         networkAgent.linkProperties, networkAgent.networkCapabilities);
             } catch (RemoteException e) {
-                e.rethrowFromSystemServer();
+                e.rethrowAsRuntimeException();
             }
             scheduleUnvalidatedPrompt(networkAgent);
 
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 887de74..09fa006 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -861,10 +861,13 @@
             } catch (IllegalArgumentException e) {  // restat throws this on error
                 throw new IOException("Can't restat: " + mDropBoxDir);
             }
-            int available = mStatFs.getAvailableBlocks();
-            int nonreserved = available - mStatFs.getBlockCount() * reservePercent / 100;
+            long available = mStatFs.getAvailableBlocksLong();
+            long nonreserved = available - mStatFs.getBlockCountLong() * reservePercent / 100;
+            long maxAvailableLong = nonreserved * quotaPercent / 100;
+            int maxAvailable = Math.toIntExact(Math.max(0,
+                    Math.min(maxAvailableLong, Integer.MAX_VALUE)));
             int maximum = quotaKb * 1024 / mBlockSize;
-            mCachedQuotaBlocks = Math.min(maximum, Math.max(0, nonreserved * quotaPercent / 100));
+            mCachedQuotaBlocks = Math.min(maximum, maxAvailable);
             mCachedQuotaUptimeMillis = uptimeMillis;
         }
 
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 1ff50b2..6dbe3ad 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -2045,28 +2045,6 @@
     }
 
     @Override
-    public void createPhysicalNetwork(int netId, int permission) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        try {
-            mNetdService.networkCreatePhysical(netId, permission);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void createVirtualNetwork(int netId, boolean secure) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        try {
-            mNetdService.networkCreateVpn(netId, secure);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
     public void addInterfaceToNetwork(String iface, int netId) {
         modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, netId, iface);
     }
@@ -2143,38 +2121,6 @@
         }
     }
 
-    private int parsePermission(String permission) {
-        if (permission.equals("NETWORK")) {
-            return INetd.PERMISSION_NETWORK;
-        }
-        if (permission.equals("SYSTEM")) {
-            return INetd.PERMISSION_SYSTEM;
-        }
-        return INetd.PERMISSION_NONE;
-    }
-
-    @Override
-    public void setPermission(String permission, int[] uids) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        try {
-            mNetdService.networkSetPermissionForUser(parsePermission(permission), uids);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    @Override
-    public void clearPermission(int[] uids) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
-        try {
-            mNetdService.networkClearPermissionForUser(uids);
-        } catch (RemoteException | ServiceSpecificException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
     @Override
     public void allowProtect(int uid) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index e33392d..2321afb 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -263,12 +263,6 @@
     }
 
     public void removeNetwork(Network network) {
-        try {
-            mDnsResolver.clearResolverConfiguration(network.netId);
-        } catch (RemoteException | ServiceSpecificException e) {
-            Slog.e(TAG, "Error clearing DNS configuration: " + e);
-            return;
-        }
         mPrivateDnsMap.remove(network.netId);
         mPrivateDnsValidationMap.remove(network.netId);
     }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 0271d3b..1275302 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1141,7 +1141,11 @@
             }
         } catch (RuntimeException e) {
             IoUtils.closeQuietly(tun);
-            agentDisconnect();
+            // If this is not seamless handover, disconnect partially-established network when error
+            // occurs.
+            if (oldNetworkAgent != mNetworkAgent) {
+                agentDisconnect();
+            }
             // restore old state
             mConfig = oldConfig;
             mConnection = oldConnection;
diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index 38eb0bc..8186343 100644
--- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -16,7 +16,6 @@
 
 package com.android.server.connectivity.tethering;
 
-import android.net.ConnectivityManager;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -164,11 +163,6 @@
     }
 
     private LinkProperties getInterfaceIPv6LinkProperties(IpServer ipServer) {
-        if (ipServer.interfaceType() == ConnectivityManager.TETHERING_BLUETOOTH) {
-            // TODO: Figure out IPv6 support on PAN interfaces.
-            return null;
-        }
-
         final Downstream ds = findDownstream(ipServer);
         if (ds == null) return null;
 
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 8dda485..0ab1a3e 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -1,3 +1,5 @@
+set noparent
+
 ogunwale@google.com
 jjaggi@google.com
 racarr@google.com
@@ -5,3 +7,6 @@
 vishnun@google.com
 akulian@google.com
 roosa@google.com
+erosky@google.com
+riddlehsu@google.com
+louischang@google.com
diff --git a/services/net/Android.bp b/services/net/Android.bp
index d72f1cf..ab11fe4 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -16,7 +16,10 @@
         },
     },
     api_dir: "aidl/ipmemorystore",
-    versions: ["1"],
+    versions: [
+        "1",
+        "2",
+    ],
 }
 
 aidl_interface {
@@ -30,6 +33,7 @@
         "java/android/net/INetworkStackConnector.aidl",
         "java/android/net/INetworkStackStatusCallback.aidl",
         "java/android/net/InitialConfigurationParcelable.aidl",
+        "java/android/net/NattKeepalivePacketDataParcelable.aidl",
         "java/android/net/PrivateDnsConfigParcel.aidl",
         "java/android/net/ProvisioningConfigurationParcelable.aidl",
         "java/android/net/TcpKeepalivePacketDataParcelable.aidl",
@@ -49,7 +53,11 @@
     },
     api_dir: "aidl/networkstack",
     imports: ["ipmemorystore-aidl-interfaces"],
-    versions: ["1"],
+    versions: [
+        "1",
+        "2",
+        "3",
+    ],
 }
 
 java_library_static {
diff --git a/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl b/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl
new file mode 100644
index 0000000..a8cbab2
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStore.aidl
@@ -0,0 +1,9 @@
+package android.net;
+interface IIpMemoryStore {
+  oneway void storeNetworkAttributes(String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void storeBlob(String l2Key, String clientId, String name, in android.net.ipmemorystore.Blob data, android.net.ipmemorystore.IOnStatusListener listener);
+  oneway void findL2Key(in android.net.ipmemorystore.NetworkAttributesParcelable attributes, android.net.ipmemorystore.IOnL2KeyResponseListener listener);
+  oneway void isSameNetwork(String l2Key1, String l2Key2, android.net.ipmemorystore.IOnSameL3NetworkResponseListener listener);
+  oneway void retrieveNetworkAttributes(String l2Key, android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener listener);
+  oneway void retrieveBlob(String l2Key, String clientId, String name, android.net.ipmemorystore.IOnBlobRetrievedListener listener);
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl b/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl
new file mode 100644
index 0000000..cf02c26
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/IIpMemoryStoreCallbacks.aidl
@@ -0,0 +1,4 @@
+package android.net;
+interface IIpMemoryStoreCallbacks {
+  oneway void onIpMemoryStoreFetched(in android.net.IIpMemoryStore ipMemoryStore);
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl
new file mode 100644
index 0000000..291dbef
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/Blob.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+parcelable Blob {
+  byte[] data;
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
new file mode 100644
index 0000000..52f40d4
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnBlobRetrievedListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnBlobRetrievedListener {
+  oneway void onBlobRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in String name, in android.net.ipmemorystore.Blob data);
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
new file mode 100644
index 0000000..7853514
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnL2KeyResponseListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnL2KeyResponseListener {
+  oneway void onL2KeyResponse(in android.net.ipmemorystore.StatusParcelable status, in String l2Key);
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
new file mode 100644
index 0000000..3dd2ae6
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnNetworkAttributesRetrievedListener {
+  oneway void onNetworkAttributesRetrieved(in android.net.ipmemorystore.StatusParcelable status, in String l2Key, in android.net.ipmemorystore.NetworkAttributesParcelable attributes);
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
new file mode 100644
index 0000000..46d4ecb
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnSameL3NetworkResponseListener {
+  oneway void onSameL3NetworkResponse(in android.net.ipmemorystore.StatusParcelable status, in android.net.ipmemorystore.SameL3NetworkResponseParcelable response);
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl
new file mode 100644
index 0000000..54e654b
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/IOnStatusListener.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+interface IOnStatusListener {
+  oneway void onComplete(in android.net.ipmemorystore.StatusParcelable status);
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
new file mode 100644
index 0000000..9531ea3
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/NetworkAttributesParcelable.aidl
@@ -0,0 +1,8 @@
+package android.net.ipmemorystore;
+parcelable NetworkAttributesParcelable {
+  byte[] assignedV4Address;
+  long assignedV4AddressExpiry;
+  String groupHint;
+  android.net.ipmemorystore.Blob[] dnsAddresses;
+  int mtu;
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
new file mode 100644
index 0000000..414272b
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/SameL3NetworkResponseParcelable.aidl
@@ -0,0 +1,6 @@
+package android.net.ipmemorystore;
+parcelable SameL3NetworkResponseParcelable {
+  String l2Key1;
+  String l2Key2;
+  float confidence;
+}
diff --git a/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl
new file mode 100644
index 0000000..92c6779
--- /dev/null
+++ b/services/net/aidl/ipmemorystore/2/android/net/ipmemorystore/StatusParcelable.aidl
@@ -0,0 +1,4 @@
+package android.net.ipmemorystore;
+parcelable StatusParcelable {
+  int resultCode;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl b/services/net/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31891de
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,9 @@
+package android.net;
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  String serverHostName;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/INetworkMonitor.aidl b/services/net/aidl/networkstack/2/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..029968b
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/INetworkMonitor.aidl
@@ -0,0 +1,24 @@
+package android.net;
+interface INetworkMonitor {
+  oneway void start();
+  oneway void launchCaptivePortalApp();
+  oneway void notifyCaptivePortalAppFinished(int response);
+  oneway void setAcceptPartialConnectivity();
+  oneway void forceReevaluation(int uid);
+  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
+  oneway void notifyDnsResponse(int returnCode);
+  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkDisconnected();
+  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
+  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
+  const int NETWORK_TEST_RESULT_VALID = 0;
+  const int NETWORK_TEST_RESULT_INVALID = 1;
+  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
+  const int NETWORK_VALIDATION_RESULT_VALID = 1;
+  const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
+  const int NETWORK_VALIDATION_PROBE_DNS = 4;
+  const int NETWORK_VALIDATION_PROBE_HTTP = 8;
+  const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
+  const int NETWORK_VALIDATION_PROBE_FALLBACK = 32;
+  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 64;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/INetworkMonitorCallbacks.aidl b/services/net/aidl/networkstack/2/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..ee9871d
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,8 @@
+package android.net;
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
+  oneway void showProvisioningNotification(String action, String packageName);
+  oneway void hideProvisioningNotification();
+}
diff --git a/services/net/aidl/networkstack/2/android/net/INetworkStackConnector.aidl b/services/net/aidl/networkstack/2/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..7da11e4
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,7 @@
+package android.net;
+interface INetworkStackConnector {
+  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
+  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
+  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
+  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+}
diff --git a/services/net/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl b/services/net/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..f6ca6f7
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,4 @@
+package android.net;
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/services/net/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl b/services/net/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..c80a787
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..65de883
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl b/services/net/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..2de790b
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,5 @@
+package android.net;
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl b/services/net/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..3a6c304
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,15 @@
+package android.net;
+parcelable ProvisioningConfigurationParcelable {
+  boolean enableIPv4;
+  boolean enableIPv6;
+  boolean usingMultinetworkPolicyTracker;
+  boolean usingIpReachabilityMonitor;
+  int requestedPreDhcpActionMs;
+  android.net.InitialConfigurationParcelable initialConfig;
+  android.net.StaticIpConfiguration staticIpConfig;
+  android.net.apf.ApfCapabilities apfCapabilities;
+  int provisioningTimeoutMs;
+  int ipv6AddrGenMode;
+  android.net.Network network;
+  String displayName;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..e121c06
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,13 @@
+package android.net;
+parcelable TcpKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+  int seq;
+  int ack;
+  int rcvWnd;
+  int rcvWndScale;
+  int tos;
+  int ttl;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl b/services/net/aidl/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..67193ae
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,11 @@
+package android.net.dhcp;
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl b/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..9143158
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,10 @@
+package android.net.dhcp;
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb);
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
+  oneway void stop(in android.net.INetworkStackStatusCallback cb);
+  const int STATUS_UNKNOWN = 0;
+  const int STATUS_SUCCESS = 1;
+  const int STATUS_INVALID_ARGUMENT = 2;
+  const int STATUS_UNKNOWN_ERROR = 3;
+}
diff --git a/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl b/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..dcc4489
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,4 @@
+package android.net.dhcp;
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/services/net/aidl/networkstack/2/android/net/ip/IIpClient.aidl b/services/net/aidl/networkstack/2/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..77d5917
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/ip/IIpClient.aidl
@@ -0,0 +1,15 @@
+package android.net.ip;
+interface IIpClient {
+  oneway void completedPreDhcpAction();
+  oneway void confirmConfiguration();
+  oneway void readPacketFilterComplete(in byte[] data);
+  oneway void shutdown();
+  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
+  oneway void stop();
+  oneway void setTcpBufferSizes(in String tcpBufferSizes);
+  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
+  oneway void setMulticastFilter(boolean enabled);
+  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
+  oneway void removeKeepalivePacketFilter(int slot);
+  oneway void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
+}
diff --git a/services/net/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl b/services/net/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..d6bc808
--- /dev/null
+++ b/services/net/aidl/networkstack/2/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,16 @@
+package android.net.ip;
+interface IIpClientCallbacks {
+  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
+  oneway void onPreDhcpAction();
+  oneway void onPostDhcpAction();
+  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
+  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
+  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
+  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
+  oneway void onReachabilityLost(in String logMsg);
+  oneway void onQuit();
+  oneway void installPacketFilter(in byte[] filter);
+  oneway void startReadPacketFilter();
+  oneway void setFallbackMulticastFilter(boolean enabled);
+  oneway void setNeighborDiscoveryOffload(boolean enable);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl b/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
new file mode 100644
index 0000000..31891de
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/DhcpResultsParcelable.aidl
@@ -0,0 +1,9 @@
+package android.net;
+parcelable DhcpResultsParcelable {
+  android.net.StaticIpConfiguration baseConfiguration;
+  int leaseDuration;
+  int mtu;
+  String serverAddress;
+  String vendorInfo;
+  String serverHostName;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl b/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
new file mode 100644
index 0000000..029968b
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/INetworkMonitor.aidl
@@ -0,0 +1,24 @@
+package android.net;
+interface INetworkMonitor {
+  oneway void start();
+  oneway void launchCaptivePortalApp();
+  oneway void notifyCaptivePortalAppFinished(int response);
+  oneway void setAcceptPartialConnectivity();
+  oneway void forceReevaluation(int uid);
+  oneway void notifyPrivateDnsChanged(in android.net.PrivateDnsConfigParcel config);
+  oneway void notifyDnsResponse(int returnCode);
+  oneway void notifyNetworkConnected(in android.net.LinkProperties lp, in android.net.NetworkCapabilities nc);
+  oneway void notifyNetworkDisconnected();
+  oneway void notifyLinkPropertiesChanged(in android.net.LinkProperties lp);
+  oneway void notifyNetworkCapabilitiesChanged(in android.net.NetworkCapabilities nc);
+  const int NETWORK_TEST_RESULT_VALID = 0;
+  const int NETWORK_TEST_RESULT_INVALID = 1;
+  const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
+  const int NETWORK_VALIDATION_RESULT_VALID = 1;
+  const int NETWORK_VALIDATION_RESULT_PARTIAL = 2;
+  const int NETWORK_VALIDATION_PROBE_DNS = 4;
+  const int NETWORK_VALIDATION_PROBE_HTTP = 8;
+  const int NETWORK_VALIDATION_PROBE_HTTPS = 16;
+  const int NETWORK_VALIDATION_PROBE_FALLBACK = 32;
+  const int NETWORK_VALIDATION_PROBE_PRIVDNS = 64;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
new file mode 100644
index 0000000..ee9871d
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/INetworkMonitorCallbacks.aidl
@@ -0,0 +1,8 @@
+package android.net;
+interface INetworkMonitorCallbacks {
+  oneway void onNetworkMonitorCreated(in android.net.INetworkMonitor networkMonitor);
+  oneway void notifyNetworkTested(int testResult, @nullable String redirectUrl);
+  oneway void notifyPrivateDnsConfigResolved(in android.net.PrivateDnsConfigParcel config);
+  oneway void showProvisioningNotification(String action, String packageName);
+  oneway void hideProvisioningNotification();
+}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl b/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
new file mode 100644
index 0000000..7da11e4
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/INetworkStackConnector.aidl
@@ -0,0 +1,7 @@
+package android.net;
+interface INetworkStackConnector {
+  oneway void makeDhcpServer(in String ifName, in android.net.dhcp.DhcpServingParamsParcel params, in android.net.dhcp.IDhcpServerCallbacks cb);
+  oneway void makeNetworkMonitor(in android.net.Network network, String name, in android.net.INetworkMonitorCallbacks cb);
+  oneway void makeIpClient(in String ifName, in android.net.ip.IIpClientCallbacks callbacks);
+  oneway void fetchIpMemoryStore(in android.net.IIpMemoryStoreCallbacks cb);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl b/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
new file mode 100644
index 0000000..f6ca6f7
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/INetworkStackStatusCallback.aidl
@@ -0,0 +1,4 @@
+package android.net;
+interface INetworkStackStatusCallback {
+  oneway void onStatusAvailable(int statusCode);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl b/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
new file mode 100644
index 0000000..c80a787
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/InitialConfigurationParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable InitialConfigurationParcelable {
+  android.net.LinkAddress[] ipAddresses;
+  android.net.IpPrefix[] directlyConnectedRoutes;
+  String[] dnsServers;
+  String gateway;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..65de883
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,7 @@
+package android.net;
+parcelable NattKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl b/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
new file mode 100644
index 0000000..2de790b
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/PrivateDnsConfigParcel.aidl
@@ -0,0 +1,5 @@
+package android.net;
+parcelable PrivateDnsConfigParcel {
+  String hostname;
+  String[] ips;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl b/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
new file mode 100644
index 0000000..3a6c304
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/ProvisioningConfigurationParcelable.aidl
@@ -0,0 +1,15 @@
+package android.net;
+parcelable ProvisioningConfigurationParcelable {
+  boolean enableIPv4;
+  boolean enableIPv6;
+  boolean usingMultinetworkPolicyTracker;
+  boolean usingIpReachabilityMonitor;
+  int requestedPreDhcpActionMs;
+  android.net.InitialConfigurationParcelable initialConfig;
+  android.net.StaticIpConfiguration staticIpConfig;
+  android.net.apf.ApfCapabilities apfCapabilities;
+  int provisioningTimeoutMs;
+  int ipv6AddrGenMode;
+  android.net.Network network;
+  String displayName;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl b/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
new file mode 100644
index 0000000..e121c06
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -0,0 +1,13 @@
+package android.net;
+parcelable TcpKeepalivePacketDataParcelable {
+  byte[] srcAddress;
+  int srcPort;
+  byte[] dstAddress;
+  int dstPort;
+  int seq;
+  int ack;
+  int rcvWnd;
+  int rcvWndScale;
+  int tos;
+  int ttl;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
new file mode 100644
index 0000000..67193ae
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/DhcpServingParamsParcel.aidl
@@ -0,0 +1,11 @@
+package android.net.dhcp;
+parcelable DhcpServingParamsParcel {
+  int serverAddr;
+  int serverAddrPrefixLength;
+  int[] defaultRouters;
+  int[] dnsServers;
+  int[] excludedAddrs;
+  long dhcpLeaseTimeSecs;
+  int linkMtu;
+  boolean metered;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
new file mode 100644
index 0000000..9143158
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServer.aidl
@@ -0,0 +1,10 @@
+package android.net.dhcp;
+interface IDhcpServer {
+  oneway void start(in android.net.INetworkStackStatusCallback cb);
+  oneway void updateParams(in android.net.dhcp.DhcpServingParamsParcel params, in android.net.INetworkStackStatusCallback cb);
+  oneway void stop(in android.net.INetworkStackStatusCallback cb);
+  const int STATUS_UNKNOWN = 0;
+  const int STATUS_SUCCESS = 1;
+  const int STATUS_INVALID_ARGUMENT = 2;
+  const int STATUS_UNKNOWN_ERROR = 3;
+}
diff --git a/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
new file mode 100644
index 0000000..dcc4489
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/dhcp/IDhcpServerCallbacks.aidl
@@ -0,0 +1,4 @@
+package android.net.dhcp;
+interface IDhcpServerCallbacks {
+  oneway void onDhcpServerCreated(int statusCode, in android.net.dhcp.IDhcpServer server);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl b/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
new file mode 100644
index 0000000..176a5ce
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/ip/IIpClient.aidl
@@ -0,0 +1,16 @@
+package android.net.ip;
+interface IIpClient {
+  oneway void completedPreDhcpAction();
+  oneway void confirmConfiguration();
+  oneway void readPacketFilterComplete(in byte[] data);
+  oneway void shutdown();
+  oneway void startProvisioning(in android.net.ProvisioningConfigurationParcelable req);
+  oneway void stop();
+  oneway void setTcpBufferSizes(in String tcpBufferSizes);
+  oneway void setHttpProxy(in android.net.ProxyInfo proxyInfo);
+  oneway void setMulticastFilter(boolean enabled);
+  oneway void addKeepalivePacketFilter(int slot, in android.net.TcpKeepalivePacketDataParcelable pkt);
+  oneway void removeKeepalivePacketFilter(int slot);
+  oneway void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
+  oneway void addNattKeepalivePacketFilter(int slot, in android.net.NattKeepalivePacketDataParcelable pkt);
+}
diff --git a/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl b/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
new file mode 100644
index 0000000..d6bc808
--- /dev/null
+++ b/services/net/aidl/networkstack/3/android/net/ip/IIpClientCallbacks.aidl
@@ -0,0 +1,16 @@
+package android.net.ip;
+interface IIpClientCallbacks {
+  oneway void onIpClientCreated(in android.net.ip.IIpClient ipClient);
+  oneway void onPreDhcpAction();
+  oneway void onPostDhcpAction();
+  oneway void onNewDhcpResults(in android.net.DhcpResultsParcelable dhcpResults);
+  oneway void onProvisioningSuccess(in android.net.LinkProperties newLp);
+  oneway void onProvisioningFailure(in android.net.LinkProperties newLp);
+  oneway void onLinkPropertiesChange(in android.net.LinkProperties newLp);
+  oneway void onReachabilityLost(in String logMsg);
+  oneway void onQuit();
+  oneway void installPacketFilter(in byte[] filter);
+  oneway void startReadPacketFilter();
+  oneway void setFallbackMulticastFilter(boolean enabled);
+  oneway void setNeighborDiscoveryOffload(boolean enable);
+}
diff --git a/services/net/java/android/net/DhcpResultsParcelable.aidl b/services/net/java/android/net/DhcpResultsParcelable.aidl
index 978638b..c98d9c2 100644
--- a/services/net/java/android/net/DhcpResultsParcelable.aidl
+++ b/services/net/java/android/net/DhcpResultsParcelable.aidl
@@ -24,4 +24,5 @@
     int mtu;
     String serverAddress;
     String vendorInfo;
-}
\ No newline at end of file
+    String serverHostName;
+}
diff --git a/services/net/java/android/net/INetworkMonitor.aidl b/services/net/java/android/net/INetworkMonitor.aidl
index b32ef12..3fc81a3 100644
--- a/services/net/java/android/net/INetworkMonitor.aidl
+++ b/services/net/java/android/net/INetworkMonitor.aidl
@@ -40,6 +40,20 @@
     // for https probe.
     const int NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY = 2;
 
+    // Network validation flags indicate probe result and types. If no NETWORK_VALIDATION_RESULT_*
+    // are set, then it's equal to NETWORK_TEST_RESULT_INVALID. If NETWORK_VALIDATION_RESULT_VALID
+    // is set, then the network validates and equal to NETWORK_TEST_RESULT_VALID. If
+    // NETWORK_VALIDATION_RESULT_PARTIAL is set, then the network has partial connectivity which
+    // is equal to NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY. NETWORK_VALIDATION_PROBE_* is set
+    // when the specific probe result of the network is resolved.
+    const int NETWORK_VALIDATION_RESULT_VALID = 0x01;
+    const int NETWORK_VALIDATION_RESULT_PARTIAL = 0x02;
+    const int NETWORK_VALIDATION_PROBE_DNS = 0x04;
+    const int NETWORK_VALIDATION_PROBE_HTTP = 0x08;
+    const int NETWORK_VALIDATION_PROBE_HTTPS = 0x10;
+    const int NETWORK_VALIDATION_PROBE_FALLBACK = 0x20;
+    const int NETWORK_VALIDATION_PROBE_PRIVDNS = 0x40;
+
     void start();
     void launchCaptivePortalApp();
     void notifyCaptivePortalAppFinished(int response);
diff --git a/services/net/java/android/net/IpMemoryStore.java b/services/net/java/android/net/IpMemoryStore.java
index 4a115e6..6f91e00 100644
--- a/services/net/java/android/net/IpMemoryStore.java
+++ b/services/net/java/android/net/IpMemoryStore.java
@@ -18,11 +18,14 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 
 /**
  * Manager class used to communicate with the ip memory store service in the network stack,
@@ -30,15 +33,18 @@
  * @hide
 */
 public class IpMemoryStore extends IpMemoryStoreClient {
-    private final CompletableFuture<IIpMemoryStore> mService;
+    private static final String TAG = IpMemoryStore.class.getSimpleName();
+    @NonNull private final CompletableFuture<IIpMemoryStore> mService;
+    @NonNull private final AtomicReference<CompletableFuture<IIpMemoryStore>> mTailNode;
 
     public IpMemoryStore(@NonNull final Context context) {
         super(context);
         mService = new CompletableFuture<>();
+        mTailNode = new AtomicReference<CompletableFuture<IIpMemoryStore>>(mService);
         getNetworkStackClient().fetchIpMemoryStore(
                 new IIpMemoryStoreCallbacks.Stub() {
                     @Override
-                    public void onIpMemoryStoreFetched(final IIpMemoryStore memoryStore) {
+                    public void onIpMemoryStoreFetched(@NonNull final IIpMemoryStore memoryStore) {
                         mService.complete(memoryStore);
                     }
 
@@ -49,9 +55,28 @@
                 });
     }
 
+    /*
+     *  If the IpMemoryStore is ready, this function will run the request synchronously.
+     *  Otherwise, it will enqueue the requests for execution immediately after the
+     *  service becomes ready. The requests are guaranteed to be executed in the order
+     *  they are sumbitted.
+     */
     @Override
-    protected IIpMemoryStore getService() throws InterruptedException, ExecutionException {
-        return mService.get();
+    protected void runWhenServiceReady(Consumer<IIpMemoryStore> cb) throws ExecutionException {
+        mTailNode.getAndUpdate(future -> future.handle((store, exception) -> {
+            if (exception != null) {
+                // this should never happens since we also catch the exception below
+                Log.wtf(TAG, "Error fetching IpMemoryStore", exception);
+                return store;
+            }
+
+            try {
+                cb.accept(store);
+            } catch (Exception e) {
+                Log.wtf(TAG, "Exception occured: " + e.getMessage());
+            }
+            return store;
+        }));
     }
 
     @VisibleForTesting
diff --git a/services/net/java/android/net/IpMemoryStoreClient.java b/services/net/java/android/net/IpMemoryStoreClient.java
index 379c017..3d56202 100644
--- a/services/net/java/android/net/IpMemoryStoreClient.java
+++ b/services/net/java/android/net/IpMemoryStoreClient.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 
 import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
 
 /**
  * service used to communicate with the ip memory store service in network stack,
@@ -46,8 +47,25 @@
         mContext = context;
     }
 
-    @NonNull
-    protected abstract IIpMemoryStore getService() throws InterruptedException, ExecutionException;
+    protected abstract void runWhenServiceReady(Consumer<IIpMemoryStore> cb)
+            throws ExecutionException;
+
+    @FunctionalInterface
+    private interface ThrowingRunnable {
+        void run() throws RemoteException;
+    }
+
+    private void ignoringRemoteException(ThrowingRunnable r) {
+        ignoringRemoteException("Failed to execute remote procedure call", r);
+    }
+
+    private void ignoringRemoteException(String message, ThrowingRunnable r) {
+        try {
+            r.run();
+        } catch (RemoteException e) {
+            Log.e(TAG, message, e);
+        }
+    }
 
     /**
      * Store network attributes for a given L2 key.
@@ -69,14 +87,12 @@
             @NonNull final NetworkAttributes attributes,
             @Nullable final OnStatusListener listener) {
         try {
-            try {
-                getService().storeNetworkAttributes(l2Key, attributes.toParcelable(),
-                        OnStatusListener.toAIDL(listener));
-            } catch (InterruptedException | ExecutionException m) {
-                listener.onComplete(new Status(Status.ERROR_UNKNOWN));
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error storing network attributes", e);
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.storeNetworkAttributes(l2Key, attributes.toParcelable(),
+                            OnStatusListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error storing network attributes",
+                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
         }
     }
 
@@ -95,14 +111,12 @@
             @NonNull final String name, @NonNull final Blob data,
             @Nullable final OnStatusListener listener) {
         try {
-            try {
-                getService().storeBlob(l2Key, clientId, name, data,
-                        OnStatusListener.toAIDL(listener));
-            } catch (InterruptedException | ExecutionException m) {
-                listener.onComplete(new Status(Status.ERROR_UNKNOWN));
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error storing blob", e);
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.storeBlob(l2Key, clientId, name, data,
+                            OnStatusListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error storing blob",
+                    () -> listener.onComplete(new Status(Status.ERROR_UNKNOWN)));
         }
     }
 
@@ -123,14 +137,12 @@
     public void findL2Key(@NonNull final NetworkAttributes attributes,
             @NonNull final OnL2KeyResponseListener listener) {
         try {
-            try {
-                getService().findL2Key(attributes.toParcelable(),
-                        OnL2KeyResponseListener.toAIDL(listener));
-            } catch (InterruptedException | ExecutionException m) {
-                listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error finding L2 Key", e);
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.findL2Key(attributes.toParcelable(),
+                            OnL2KeyResponseListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error finding L2 Key",
+                    () -> listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null));
         }
     }
 
@@ -146,14 +158,12 @@
     public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
             @NonNull final OnSameL3NetworkResponseListener listener) {
         try {
-            try {
-                getService().isSameNetwork(l2Key1, l2Key2,
-                        OnSameL3NetworkResponseListener.toAIDL(listener));
-            } catch (InterruptedException | ExecutionException m) {
-                listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error checking for network sameness", e);
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.isSameNetwork(l2Key1, l2Key2,
+                            OnSameL3NetworkResponseListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error checking for network sameness",
+                    () -> listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null));
         }
     }
 
@@ -169,14 +179,13 @@
     public void retrieveNetworkAttributes(@NonNull final String l2Key,
             @NonNull final OnNetworkAttributesRetrievedListener listener) {
         try {
-            try {
-                getService().retrieveNetworkAttributes(l2Key,
-                        OnNetworkAttributesRetrievedListener.toAIDL(listener));
-            } catch (InterruptedException | ExecutionException m) {
-                listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN), null, null);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error retrieving network attributes", e);
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.retrieveNetworkAttributes(l2Key,
+                            OnNetworkAttributesRetrievedListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error retrieving network attributes",
+                    () -> listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN),
+                            null, null));
         }
     }
 
@@ -194,14 +203,13 @@
     public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
             @NonNull final String name, @NonNull final OnBlobRetrievedListener listener) {
         try {
-            try {
-                getService().retrieveBlob(l2Key, clientId, name,
-                        OnBlobRetrievedListener.toAIDL(listener));
-            } catch (InterruptedException | ExecutionException m) {
-                listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN), null, null, null);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error retrieving blob", e);
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.retrieveBlob(l2Key, clientId, name,
+                            OnBlobRetrievedListener.toAIDL(listener))));
+        } catch (ExecutionException m) {
+            ignoringRemoteException("Error retrieving blob",
+                    () -> listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN),
+                            null, null, null));
         }
     }
 }
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/services/net/java/android/net/NattKeepalivePacketData.java
similarity index 84%
rename from core/java/android/net/NattKeepalivePacketData.java
rename to services/net/java/android/net/NattKeepalivePacketData.java
index bdb246f..27ed11e 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/services/net/java/android/net/NattKeepalivePacketData.java
@@ -19,8 +19,10 @@
 import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
 import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
 
+import android.annotation.NonNull;
 import android.net.SocketKeepalive.InvalidPacketException;
 import android.net.util.IpUtils;
+import android.os.Parcelable;
 import android.system.OsConstants;
 
 import java.net.Inet4Address;
@@ -29,8 +31,7 @@
 import java.nio.ByteOrder;
 
 /** @hide */
-public final class NattKeepalivePacketData extends KeepalivePacketData {
-
+public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
     // This should only be constructed via static factory methods, such as
     // nattKeepalivePacket
     private NattKeepalivePacketData(InetAddress srcAddress, int srcPort,
@@ -77,4 +78,18 @@
 
         return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
     }
+
+     /**
+     * Convert this NattKeepalivePacketData to a NattKeepalivePacketDataParcelable.
+     */
+    @NonNull
+    public NattKeepalivePacketDataParcelable toStableParcelable() {
+        final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable();
+
+        parcel.srcAddress = srcAddress.getAddress();
+        parcel.srcPort = srcPort;
+        parcel.dstAddress = dstAddress.getAddress();
+        parcel.dstPort = dstPort;
+        return parcel;
+    }
 }
diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl b/services/net/java/android/net/NattKeepalivePacketDataParcelable.aidl
similarity index 66%
copy from telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl
copy to services/net/java/android/net/NattKeepalivePacketDataParcelable.aidl
index a73ba50..6f006d4 100644
--- a/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl
+++ b/services/net/java/android/net/NattKeepalivePacketDataParcelable.aidl
@@ -1,12 +1,11 @@
 /*
- *
- * Copyright 2019, The Android Open Source Project
+ * 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
+ *      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,
@@ -15,6 +14,12 @@
  * limitations under the License.
  */
 
-package android.telephony.ims;
+package android.net;
 
-parcelable RcsMessageQueryResult;
+parcelable NattKeepalivePacketDataParcelable {
+    byte[] srcAddress;
+    int srcPort;
+    byte[] dstAddress;
+    int dstPort;
+}
+
diff --git a/services/net/java/android/net/ip/IIpClient.aidl b/services/net/java/android/net/ip/IIpClient.aidl
index 1e77264..9989c52 100644
--- a/services/net/java/android/net/ip/IIpClient.aidl
+++ b/services/net/java/android/net/ip/IIpClient.aidl
@@ -17,6 +17,7 @@
 
 import android.net.ProxyInfo;
 import android.net.ProvisioningConfigurationParcelable;
+import android.net.NattKeepalivePacketDataParcelable;
 import android.net.TcpKeepalivePacketDataParcelable;
 
 /** @hide */
@@ -33,4 +34,5 @@
     void addKeepalivePacketFilter(int slot, in TcpKeepalivePacketDataParcelable pkt);
     void removeKeepalivePacketFilter(int slot);
     void setL2KeyAndGroupHint(in String l2Key, in String groupHint);
+    void addNattKeepalivePacketFilter(int slot, in NattKeepalivePacketDataParcelable pkt);
 }
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
new file mode 100644
index 0000000..f8d7e84
--- /dev/null
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -0,0 +1,273 @@
+/*
+ * 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.annotation.NonNull;
+import android.net.NattKeepalivePacketData;
+import android.net.ProxyInfo;
+import android.net.TcpKeepalivePacketData;
+import android.net.shared.ProvisioningConfiguration;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for IpClient.
+ *
+ * Wraps IIpClient calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on IIpClient are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+public class IpClientManager {
+    @NonNull private final IIpClient mIpClient;
+    @NonNull private final String mTag;
+
+    public IpClientManager(@NonNull IIpClient ipClient, @NonNull String tag) {
+        mIpClient = ipClient;
+        mTag = tag;
+    }
+
+    public IpClientManager(@NonNull IIpClient ipClient) {
+        this(ipClient, IpClientManager.class.getSimpleName());
+    }
+
+    private void log(String s, Throwable e) {
+        Log.e(mTag, s, e);
+    }
+
+    /**
+     * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
+     * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
+     * proceed.
+     */
+    public boolean completedPreDhcpAction() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.completedPreDhcpAction();
+            return true;
+        } catch (RemoteException e) {
+            log("Error completing PreDhcpAction", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Confirm the provisioning configuration.
+     */
+    public boolean confirmConfiguration() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.confirmConfiguration();
+            return true;
+        } catch (RemoteException e) {
+            log("Error confirming IpClient configuration", e);
+            return false;
+        }
+    }
+
+    /**
+     * Indicate that packet filter read is complete.
+     */
+    public boolean readPacketFilterComplete(byte[] data) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.readPacketFilterComplete(data);
+            return true;
+        } catch (RemoteException e) {
+            log("Error notifying IpClient of packet filter read", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Shut down this IpClient instance altogether.
+     */
+    public boolean shutdown() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.shutdown();
+            return true;
+        } catch (RemoteException e) {
+            log("Error shutting down IpClient", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Start provisioning with the provided parameters.
+     */
+    public boolean startProvisioning(ProvisioningConfiguration prov) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.startProvisioning(prov.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error starting IpClient provisioning", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Stop this IpClient.
+     *
+     * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+     */
+    public boolean stop() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.stop();
+            return true;
+        } catch (RemoteException e) {
+            log("Error stopping IpClient", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the TCP buffer sizes to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public boolean setTcpBufferSizes(String tcpBufferSizes) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setTcpBufferSizes(tcpBufferSizes);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting IpClient TCP buffer sizes", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the HTTP Proxy configuration to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public boolean setHttpProxy(ProxyInfo proxyInfo) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setHttpProxy(proxyInfo);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting IpClient proxy", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
+     * if not, Callback.setFallbackMulticastFilter() is called.
+     */
+    public boolean setMulticastFilter(boolean enabled) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setMulticastFilter(enabled);
+            return true;
+        } catch (RemoteException e) {
+            log("Error setting multicast filter", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Add a TCP keepalive packet filter before setting up keepalive offload.
+     */
+    public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketData pkt) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.addKeepalivePacketFilter(slot, pkt.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error adding Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Add a NAT-T keepalive packet filter before setting up keepalive offload.
+     */
+    public boolean addKeepalivePacketFilter(int slot, NattKeepalivePacketData pkt) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.addNattKeepalivePacketFilter(slot, pkt.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error adding Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Remove a keepalive packet filter after stopping keepalive offload.
+     */
+    public boolean removeKeepalivePacketFilter(int slot) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.removeKeepalivePacketFilter(slot);
+            return true;
+        } catch (RemoteException e) {
+            log("Error removing Keepalive Packet Filter ", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the L2 key and group hint for storing info into the memory store.
+     */
+    public boolean setL2KeyAndGroupHint(String l2Key, String groupHint) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.setL2KeyAndGroupHint(l2Key, groupHint);
+            return true;
+        } catch (RemoteException e) {
+            log("Failed setL2KeyAndGroupHint", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
index 44d8e0c..172dc24 100644
--- a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
+++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java
@@ -41,6 +41,7 @@
         p.mtu = results.mtu;
         p.serverAddress = parcelAddress(results.serverAddress);
         p.vendorInfo = results.vendorInfo;
+        p.serverHostName = results.serverHostName;
         return p;
     }
 
@@ -54,6 +55,7 @@
         results.mtu = p.mtu;
         results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress);
         results.vendorInfo = p.vendorInfo;
+        results.serverHostName = p.serverHostName;
         return results;
     }
 
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 23f5572..c22ca90 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -51,7 +51,7 @@
         "liblzma",
         "libnativehelper",
         "libui",
-        "libunwind",
+        "libunwindstack",
         "libutils",
         "netd_aidl_interface-cpp",
     ],
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index e615428..6a3469c5 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -177,11 +177,15 @@
              * Audio Class Specific
              */
             case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE:
-                descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
+                if (mDeviceDescriptor.getDevClass() == UsbDescriptor.CLASSID_AUDIO) {
+                    descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
+                }
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT:
-                descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
+                if (mDeviceDescriptor.getDevClass() == UsbDescriptor.CLASSID_AUDIO) {
+                    descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
+                }
                 break;
 
             default:
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index e25c17d..0f63853 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -17,6 +17,7 @@
 package android.telecom;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.os.Build;
@@ -174,4 +175,21 @@
                 in.readString(),
                 UserHandle.CREATOR.createFromParcel(in));
     }
+
+    /**
+     * Determines if two {@link PhoneAccountHandle}s are from the same package.
+     *
+     * @param a Phone account handle to check for same {@link ConnectionService} package.
+     * @param b Other phone account handle to check for same {@link ConnectionService} package.
+     * @return {@code true} if the two {@link PhoneAccountHandle}s passed in belong to the same
+     * {@link ConnectionService} / package, {@code false} otherwise.  Note: {@code null} phone
+     * account handles are considered equivalent to other {@code null} phone account handles.
+     * @hide
+     */
+    public static boolean areFromSamePackage(@Nullable PhoneAccountHandle a,
+            @Nullable PhoneAccountHandle b) {
+        String aPackageName = a != null ? a.getComponentName().getPackageName() : null;
+        String bPackageName = b != null ? b.getComponentName().getPackageName() : null;
+        return Objects.equals(aPackageName, bPackageName);
+    }
 }
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index ecb0daf..e727a6e 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -4259,6 +4259,22 @@
          * @hide
          */
         public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
+
+        /**
+         * The current registered raw data network operator name in long alphanumeric format.
+         * <p>
+         * This is the same as {@link ServiceState#getOperatorAlphaLongRaw()}.
+         * @hide
+         */
+        public static final String OPERATOR_ALPHA_LONG_RAW = "operator_alpha_long_raw";
+
+        /**
+         * The current registered raw data network operator name in short alphanumeric format.
+         * <p>
+         * This is the same as {@link ServiceState#getOperatorAlphaShortRaw()}.
+         * @hide
+         */
+        public static final String OPERATOR_ALPHA_SHORT_RAW = "operator_alpha_short_raw";
     }
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 349ef55..c449dbf 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1202,7 +1202,7 @@
      * Override the SPN Display Condition 2 integer bits (lsb). B2, B1 is the last two bits of the
      * spn display condition coding.
      *
-     * The default value -1 mean this field is not config.
+     * The default value -1 mean this field is not set.
      *
      * B1 = 0: display of registered PLMN name not required when registered PLMN is either HPLMN
      * or a PLMN in the service provider PLMN list (see EF_SPDI).
@@ -1241,7 +1241,7 @@
 
     /**
      * Override the PNN - a string array of comma-separated alpha long and short names:
-     * "alpha_long1, alpha_short1".
+     * "alpha_long1,alpha_short1".
      *
      * Reference: 3GPP TS 31.102 v15.2.0 Section 4.2.58 EF_PNN.
      * @hide
@@ -1259,6 +1259,8 @@
 
     /**
      * Allow ERI rules to select a carrier name display string when using 3gpp2 access technologies.
+     * If this bit is not set, the carrier name display string will be selected from the carrier
+     * display name resolver which doesn't apply the ERI rules.
      *
      * @hide
      */
@@ -1386,6 +1388,24 @@
             "hide_lte_plus_data_icon_bool";
 
     /**
+     * The string is used to filter redundant string from PLMN Network Name that's supplied by
+     * specific carrier.
+     *
+     * @hide
+     */
+    public static final String KEY_OPERATOR_NAME_FILTER_PATTERN_STRING =
+            "operator_name_filter_pattern_string";
+
+    /**
+     * The string is used to compare with operator name. If it matches the pattern then show
+     * specific data icon.
+     *
+     * @hide
+     */
+    public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING =
+            "show_carrier_data_icon_pattern_string";
+
+    /**
      * Boolean to decide whether to show precise call failed cause to user
      * @hide
      */
@@ -3027,6 +3047,8 @@
         sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, false);
+        sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, "");
+        sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
         sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_TDSCDMA_BOOL, false);
@@ -3137,8 +3159,7 @@
      * May throw an {@link IllegalArgumentException} if {@code overrideValues} contains invalid
      * values for the specified config keys.
      *
-     * NOTE: This API is meant for testing purposes only and may only be accessed from the shell UID
-     * during instrumentation testing.
+     * NOTE: This API is meant for testing purposes only.
      *
      * @param subscriptionId The subscription ID for which the override should be done.
      * @param overrideValues Key-value pairs of the values that are to be overridden. If set to
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index a83d8f0..cbe5e71 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -49,10 +49,10 @@
 
     // long alpha Operator Name String or Enhanced Operator Name String
     /** @hide */
-    protected final String mAlphaLong;
+    protected String mAlphaLong;
     // short alpha Operator Name String or Enhanced Operator Name String
     /** @hide */
-    protected final String mAlphaShort;
+    protected String mAlphaShort;
 
     /** @hide */
     protected CellIdentity(String tag, int type, String mcc, String mnc, String alphal,
@@ -145,6 +145,13 @@
     }
 
     /**
+     * @hide
+     */
+    public void setOperatorAlphaLong(String alphaLong) {
+        mAlphaLong = alphaLong;
+    }
+
+    /**
      * @return The short alpha tag associated with the current scan result (may be the operator
      * name string or extended operator name string).  May be null if unknown.
      */
@@ -154,6 +161,13 @@
     }
 
     /**
+     * @hide
+     */
+    public void setOperatorAlphaShort(String alphaShort) {
+        mAlphaShort = alphaShort;
+    }
+
+    /**
      * @return a CellLocation object for this CellIdentity
      * @hide
      */
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 86cdce1..0ad1abe 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -339,6 +339,9 @@
 
     private List<NetworkRegistrationInfo> mNetworkRegistrationInfos = new ArrayList<>();
 
+    private String mOperatorAlphaLongRaw;
+    private String mOperatorAlphaShortRaw;
+
     /**
      * get String description of roaming type
      * @hide
@@ -420,6 +423,8 @@
         mNetworkRegistrationInfos = s.mNetworkRegistrationInfos == null ? null :
                 s.getNetworkRegistrationInfoList();
         mNrFrequencyRange = s.mNrFrequencyRange;
+        mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
+        mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
     }
 
     /**
@@ -453,6 +458,8 @@
         mChannelNumber = in.readInt();
         mCellBandwidths = in.createIntArray();
         mNrFrequencyRange = in.readInt();
+        mOperatorAlphaLongRaw = in.readString();
+        mOperatorAlphaShortRaw = in.readString();
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -478,6 +485,8 @@
         out.writeInt(mChannelNumber);
         out.writeIntArray(mCellBandwidths);
         out.writeInt(mNrFrequencyRange);
+        out.writeString(mOperatorAlphaLongRaw);
+        out.writeString(mOperatorAlphaShortRaw);
     }
 
     public int describeContents() {
@@ -836,7 +845,9 @@
                 mIsEmergencyOnly,
                 mLteEarfcnRsrpBoost,
                 mNetworkRegistrationInfos,
-                mNrFrequencyRange);
+                mNrFrequencyRange,
+                mOperatorAlphaLongRaw,
+                mOperatorAlphaShortRaw);
     }
 
     @Override
@@ -862,6 +873,8 @@
                 && equalsHandlesNulls(mCdmaDefaultRoamingIndicator,
                         s.mCdmaDefaultRoamingIndicator)
                 && mIsEmergencyOnly == s.mIsEmergencyOnly
+                && equalsHandlesNulls(mOperatorAlphaLongRaw, s.mOperatorAlphaLongRaw)
+                && equalsHandlesNulls(mOperatorAlphaShortRaw, s.mOperatorAlphaShortRaw)
                 && (mNetworkRegistrationInfos == null
                 ? s.mNetworkRegistrationInfos == null : s.mNetworkRegistrationInfos != null
                 && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos))
@@ -1019,6 +1032,8 @@
             .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
             .append(", mNetworkRegistrationInfos=").append(mNetworkRegistrationInfos)
             .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
+            .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
+            .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
             .append("}").toString();
     }
 
@@ -1056,6 +1071,8 @@
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
                 .build());
+        mOperatorAlphaLongRaw = null;
+        mOperatorAlphaShortRaw = null;
     }
 
     public void setStateOutOfService() {
@@ -1297,6 +1314,8 @@
         m.putInt("ChannelNumber", mChannelNumber);
         m.putIntArray("CellBandwidths", mCellBandwidths);
         m.putInt("mNrFrequencyRange", mNrFrequencyRange);
+        m.putString("operator-alpha-long-raw", mOperatorAlphaLongRaw);
+        m.putString("operator-alpha-short-raw", mOperatorAlphaShortRaw);
     }
 
     /** @hide */
@@ -1910,4 +1929,36 @@
 
         return state;
     }
+
+    /**
+     * @hide
+     */
+    public void setOperatorAlphaLongRaw(String operatorAlphaLong) {
+        mOperatorAlphaLongRaw = operatorAlphaLong;
+    }
+
+    /**
+     * The current registered raw data network operator name in long alphanumeric format.
+     *
+     * @hide
+     */
+    public String getOperatorAlphaLongRaw() {
+        return mOperatorAlphaLongRaw;
+    }
+
+    /**
+     * @hide
+     */
+    public void setOperatorAlphaShortRaw(String operatorAlphaShort) {
+        mOperatorAlphaShortRaw = operatorAlphaShort;
+    }
+
+    /**
+     * The current registered raw data network operator name in short alphanumeric format.
+     *
+     * @hide
+     */
+    public String getOperatorAlphaShortRaw() {
+        return mOperatorAlphaShortRaw;
+    }
 }
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index def7fbe..be7bb67 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -327,12 +327,12 @@
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
-                true /* persistMessage*/);
+                true /* persistMessage*/, ActivityThread.currentPackageName());
     }
 
     private void sendTextMessageInternal(String destinationAddress, String scAddress,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean persistMessage) {
+            boolean persistMessage, String packageName) {
         if (TextUtils.isEmpty(destinationAddress)) {
             throw new IllegalArgumentException("Invalid destinationAddress");
         }
@@ -345,9 +345,8 @@
             // If the subscription is invalid or default, we will use the default phone to send the
             // SMS and possibly fail later in the SMS sending process.
             ISms iSms = getISmsServiceOrThrow();
-            iSms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
-                    destinationAddress,
-                    scAddress, text, sentIntent, deliveryIntent,
+            iSms.sendTextForSubscriber(getSubscriptionId(), packageName,
+                    destinationAddress, scAddress, text, sentIntent, deliveryIntent,
                     persistMessage);
         } catch (RemoteException ex) {
             // ignore it
@@ -379,7 +378,7 @@
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
         sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
-                false /* persistMessage */);
+                false /* persistMessage */, ActivityThread.currentPackageName());
     }
 
     /**
@@ -620,13 +619,30 @@
             String destinationAddress, String scAddress, ArrayList<String> parts,
             ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
         sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
-                deliveryIntents, true /* persistMessage*/);
+                deliveryIntents, true /* persistMessage*/, ActivityThread.currentPackageName());
+    }
+
+    /**
+     * @hide
+     * Similar method as #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList)
+     * With an additional argument
+     * @param packageName serves as the default package name if ActivityThread.currentpackageName is
+     *                    null.
+     */
+    public void sendMultipartTextMessageExternal(
+            String destinationAddress, String scAddress, ArrayList<String> parts,
+            ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
+            String packageName) {
+        sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
+                deliveryIntents, true /* persistMessage*/,
+                ActivityThread.currentPackageName() == null
+                        ? packageName : ActivityThread.currentPackageName());
     }
 
     private void sendMultipartTextMessageInternal(
             String destinationAddress, String scAddress, List<String> parts,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
-            boolean persistMessage) {
+            boolean persistMessage, String packageName) {
         if (TextUtils.isEmpty(destinationAddress)) {
             throw new IllegalArgumentException("Invalid destinationAddress");
         }
@@ -638,8 +654,7 @@
             try {
                 ISms iSms = getISmsServiceOrThrow();
                 iSms.sendMultipartTextForSubscriber(getSubscriptionId(),
-                        ActivityThread.currentPackageName(),
-                        destinationAddress, scAddress, parts,
+                        packageName, destinationAddress, scAddress, parts,
                         sentIntents, deliveryIntents, persistMessage);
             } catch (RemoteException ex) {
                 // ignore it
@@ -653,8 +668,8 @@
             if (deliveryIntents != null && deliveryIntents.size() > 0) {
                 deliveryIntent = deliveryIntents.get(0);
             }
-            sendTextMessage(destinationAddress, scAddress, parts.get(0),
-                    sentIntent, deliveryIntent);
+            sendTextMessageInternal(destinationAddress, scAddress, parts.get(0),
+                    sentIntent, deliveryIntent, true, packageName);
         }
     }
 
@@ -675,7 +690,7 @@
             String destinationAddress, String scAddress, List<String> parts,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
         sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
-                deliveryIntents, false /* persistMessage*/);
+                deliveryIntents, false /* persistMessage*/, ActivityThread.currentPackageName());
     }
 
     /**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 58c05aa..43acfdd 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2065,7 +2065,6 @@
         } else {
             logd("putPhoneIdAndSubIdExtra: no valid subs");
             intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
-            intent.putExtra(PhoneConstants.SLOT_KEY, phoneId);
         }
     }
 
@@ -2076,9 +2075,6 @@
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
         intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId);
         intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
-        //FIXME this is using phoneId and slotIndex interchangeably
-        //Eventually, this should be removed as it is not the slot id
-        intent.putExtra(PhoneConstants.SLOT_KEY, phoneId);
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index add7d38..510e7c9 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3281,7 +3281,7 @@
     }
 
     /**
-     * Gets information about currently inserted UICCs and enabled eUICCs.
+     * Gets information about currently inserted UICCs and eUICCs.
      * <p>
      * Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      * <p>
diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java
index d95a499..2246ccc 100644
--- a/telephony/java/android/telephony/UiccCardInfo.java
+++ b/telephony/java/android/telephony/UiccCardInfo.java
@@ -101,7 +101,7 @@
 
     /**
      * Get the embedded ID (EID) of the eUICC. If the UiccCardInfo is not an eUICC
-     * (see {@link #isEuicc()}), returns null.
+     * (see {@link #isEuicc()}), or the EID is not available, returns null.
      * <p>
      * Note that this field may be omitted if the caller does not have the correct permissions
      * (see {@link TelephonyManager#getUiccCardsInfo()}).
@@ -115,7 +115,7 @@
     }
 
     /**
-     * Get the ICCID of the UICC.
+     * Get the ICCID of the UICC. If the ICCID is not availble, returns null.
      * <p>
      * Note that this field may be omitted if the caller does not have the correct permissions
      * (see {@link TelephonyManager#getUiccCardsInfo()}).
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 93a7da0..2bc6775 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -140,6 +140,14 @@
         return mIsEuicc;
     }
 
+    /**
+     * Returns the ICCID of the card in the slot, or the EID of an active eUICC.
+     * <p>
+     * If the UICC slot is for an active eUICC, returns the EID.
+     * If the UICC slot is for an inactive eUICC, returns the ICCID of the enabled profile, or the
+     * root profile if all other profiles are disabled.
+     * If the UICC slot is not an eUICC, returns the ICCID.
+     */
     public String getCardId() {
         return mCardId;
     }
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 2d6402d..b144ff76 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -558,6 +558,24 @@
         } else if (this.getDisplayPriorityScore()
                 < emergencyNumber.getDisplayPriorityScore()) {
             return 1;
+        } else if (this.getNumber().compareTo(emergencyNumber.getNumber()) != 0) {
+            return this.getNumber().compareTo(emergencyNumber.getNumber());
+        } else if (this.getCountryIso().compareTo(emergencyNumber.getCountryIso()) != 0) {
+            return this.getCountryIso().compareTo(emergencyNumber.getCountryIso());
+        } else if (this.getMnc().compareTo(emergencyNumber.getMnc()) != 0) {
+            return this.getMnc().compareTo(emergencyNumber.getMnc());
+        } else if (this.getEmergencyServiceCategoryBitmask()
+                != emergencyNumber.getEmergencyServiceCategoryBitmask()) {
+            return this.getEmergencyServiceCategoryBitmask()
+                    > emergencyNumber.getEmergencyServiceCategoryBitmask() ? -1 : 1;
+        } else if (this.getEmergencyUrns().toString().compareTo(
+                emergencyNumber.getEmergencyUrns().toString()) != 0) {
+            return this.getEmergencyUrns().toString().compareTo(
+                    emergencyNumber.getEmergencyUrns().toString());
+        } else if (this.getEmergencyCallRouting()
+                != emergencyNumber.getEmergencyCallRouting()) {
+            return this.getEmergencyCallRouting()
+                    > emergencyNumber.getEmergencyCallRouting() ? -1 : 1;
         } else {
             return 0;
         }
@@ -579,13 +597,9 @@
         if (emergencyNumberList == null) {
             return;
         }
-        Set<EmergencyNumber> mergedEmergencyNumber = new HashSet<>();
+        Set<Integer> duplicatedEmergencyNumberPosition = new HashSet<>();
         for (int i = 0; i < emergencyNumberList.size(); i++) {
-            // Skip the check because it was merged.
-            if (mergedEmergencyNumber.contains(emergencyNumberList.get(i))) {
-                continue;
-            }
-            for (int j = i + 1; j < emergencyNumberList.size(); j++) {
+            for (int j = 0; j < i; j++) {
                 if (areSameEmergencyNumbers(
                         emergencyNumberList.get(i), emergencyNumberList.get(j))) {
                     Rlog.e(LOG_TAG, "Found unexpected duplicate numbers: "
@@ -594,14 +608,15 @@
                     emergencyNumberList.set(i, mergeSameEmergencyNumbers(
                             emergencyNumberList.get(i), emergencyNumberList.get(j)));
                     // Mark the emergency number has been merged
-                    mergedEmergencyNumber.add(emergencyNumberList.get(j));
+                    duplicatedEmergencyNumberPosition.add(j);
                 }
             }
         }
-        // Remove the marked emergency number in the orignal list
-        for (int i = 0; i < emergencyNumberList.size(); i++) {
-            if (mergedEmergencyNumber.contains(emergencyNumberList.get(i))) {
-                emergencyNumberList.remove(i--);
+
+        // Remove the marked emergency number in the original list
+        for (int i = emergencyNumberList.size() - 1; i >= 0; i--) {
+            if (duplicatedEmergencyNumberPosition.contains(i)) {
+                emergencyNumberList.remove(i);
             }
         }
         Collections.sort(emergencyNumberList);
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index f1a5778..ee287d5 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -321,6 +321,18 @@
             "android.telephony.euicc.extra.FROM_SUBSCRIPTION_ID";
 
     /**
+     * Key for an extra set on privileged actions {@link #ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED}
+     * providing the physical slot ID of the target slot.
+     *
+     * <p>Expected type of the extra data: int
+     *
+     * @hide
+     */
+    // TODO: Make this a @SystemApi.
+    public static final String EXTRA_PHYSICAL_SLOT_ID =
+            "android.telephony.euicc.extra.PHYSICAL_SLOT_ID";
+
+    /**
      * Optional meta-data attribute for a carrier app providing an icon to use to represent the
      * carrier. If not provided, the app's launcher icon will be used as a fallback.
      */
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 568c11b..fd58f7e 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -545,7 +545,8 @@
                 + ", emergencyUrns=" + mEmergencyUrns
                 + ", emergencyCallRouting=" + mEmergencyCallRouting
                 + ", emergencyCallTesting=" + mEmergencyCallTesting
-                + ", hasKnownUserIntentEmergency=" + mHasKnownUserIntentEmergency + " }";
+                + ", hasKnownUserIntentEmergency=" + mHasKnownUserIntentEmergency
+                + ", mRestrictCause=" + mRestrictCause + " }";
     }
 
     @Override
@@ -565,6 +566,7 @@
         out.writeInt(mEmergencyCallRouting);
         out.writeBoolean(mEmergencyCallTesting);
         out.writeBoolean(mHasKnownUserIntentEmergency);
+        out.writeInt(mRestrictCause);
     }
 
     private void readFromParcel(Parcel in) {
@@ -577,6 +579,7 @@
         mEmergencyCallRouting = in.readInt();
         mEmergencyCallTesting = in.readBoolean();
         mHasKnownUserIntentEmergency = in.readBoolean();
+        mRestrictCause = in.readInt();
     }
 
     public static final Creator<ImsCallProfile> CREATOR = new Creator<ImsCallProfile>() {
diff --git a/telephony/java/android/telephony/ims/Rcs1To1Thread.java b/telephony/java/android/telephony/ims/Rcs1To1Thread.java
index 0bb1b43..e90548a 100644
--- a/telephony/java/android/telephony/ims/Rcs1To1Thread.java
+++ b/telephony/java/android/telephony/ims/Rcs1To1Thread.java
@@ -33,8 +33,8 @@
      *
      * @hide
      */
-    public Rcs1To1Thread(int threadId) {
-        super(threadId);
+    public Rcs1To1Thread(RcsControllerCall rcsControllerCall, int threadId) {
+        super(rcsControllerCall, threadId);
         mThreadId = threadId;
     }
 
@@ -56,7 +56,9 @@
      */
     @WorkerThread
     public long getFallbackThreadId() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.get1To1ThreadFallbackThreadId(mThreadId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.get1To1ThreadFallbackThreadId(mThreadId,
+                        callingPackage));
     }
 
     /**
@@ -69,8 +71,9 @@
      */
     @WorkerThread
     public void setFallbackThreadId(long fallbackThreadId) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.set1To1ThreadFallbackThreadId(mThreadId, fallbackThreadId));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.set1To1ThreadFallbackThreadId(mThreadId,
+                        fallbackThreadId, callingPackage));
     }
 
     /**
@@ -81,6 +84,9 @@
     @WorkerThread
     public RcsParticipant getRecipient() throws RcsMessageStoreException {
         return new RcsParticipant(
-                RcsControllerCall.call(iRcs -> iRcs.get1To1ThreadOtherParticipantId(mThreadId)));
+                mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.get1To1ThreadOtherParticipantId(mThreadId,
+                                callingPackage)));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java
index 5512c4c..a2d68ad 100644
--- a/telephony/java/android/telephony/ims/RcsControllerCall.java
+++ b/telephony/java/android/telephony/ims/RcsControllerCall.java
@@ -27,38 +27,38 @@
  * @hide - not meant for public use
  */
 class RcsControllerCall {
-    static <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException {
+    private final Context mContext;
+
+    RcsControllerCall(Context context) {
+        mContext = context;
+    }
+
+    <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException {
         IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE));
         if (iRcs == null) {
             throw new RcsMessageStoreException("Could not connect to RCS storage service");
         }
 
         try {
-            return serviceCall.methodOnIRcs(iRcs);
+            return serviceCall.methodOnIRcs(iRcs, mContext.getOpPackageName());
         } catch (RemoteException exception) {
             throw new RcsMessageStoreException(exception.getMessage());
         }
     }
 
-    static void callWithNoReturn(RcsServiceCallWithNoReturn serviceCall)
+    void callWithNoReturn(RcsServiceCallWithNoReturn serviceCall)
             throws RcsMessageStoreException {
-        IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE));
-        if (iRcs == null) {
-            throw new RcsMessageStoreException("Could not connect to RCS storage service");
-        }
-
-        try {
-            serviceCall.methodOnIRcs(iRcs);
-        } catch (RemoteException exception) {
-            throw new RcsMessageStoreException(exception.getMessage());
-        }
+        call((iRcs, callingPackage) -> {
+            serviceCall.methodOnIRcs(iRcs, callingPackage);
+            return null;
+        });
     }
 
     interface RcsServiceCall<R> {
-        R methodOnIRcs(IRcs iRcs) throws RemoteException;
+        R methodOnIRcs(IRcs iRcs, String callingPackage) throws RemoteException;
     }
 
     interface RcsServiceCallWithNoReturn {
-        void methodOnIRcs(IRcs iRcs) throws RemoteException;
+        void methodOnIRcs(IRcs iRcs, String callingPackage) throws RemoteException;
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsEvent.java b/telephony/java/android/telephony/ims/RcsEvent.java
index 994b27a..9dd0720 100644
--- a/telephony/java/android/telephony/ims/RcsEvent.java
+++ b/telephony/java/android/telephony/ims/RcsEvent.java
@@ -40,5 +40,5 @@
      *
      * @hide
      */
-    abstract void persist() throws RcsMessageStoreException;
+    abstract void persist(RcsControllerCall rcsControllerCall) throws RcsMessageStoreException;
 }
diff --git a/telephony/java/android/telephony/ims/RcsEventDescriptor.java b/telephony/java/android/telephony/ims/RcsEventDescriptor.java
index 8e3f6cd..b44adea 100644
--- a/telephony/java/android/telephony/ims/RcsEventDescriptor.java
+++ b/telephony/java/android/telephony/ims/RcsEventDescriptor.java
@@ -38,7 +38,7 @@
      * descriptor.
      */
     @VisibleForTesting(visibility = PROTECTED)
-    public abstract RcsEvent createRcsEvent();
+    public abstract RcsEvent createRcsEvent(RcsControllerCall rcsControllerCall);
 
     RcsEventDescriptor(Parcel in) {
         mTimestamp = in.readLong();
diff --git a/telephony/java/android/telephony/ims/RcsEventQueryResultDescriptor.java b/telephony/java/android/telephony/ims/RcsEventQueryResultDescriptor.java
index e30745b7..b972d55 100644
--- a/telephony/java/android/telephony/ims/RcsEventQueryResultDescriptor.java
+++ b/telephony/java/android/telephony/ims/RcsEventQueryResultDescriptor.java
@@ -39,9 +39,9 @@
         mEvents = events;
     }
 
-    protected RcsEventQueryResult getRcsEventQueryResult() {
+    protected RcsEventQueryResult getRcsEventQueryResult(RcsControllerCall rcsControllerCall) {
         List<RcsEvent> rcsEvents = mEvents.stream()
-                .map(RcsEventDescriptor::createRcsEvent)
+                .map(rcsEvent -> rcsEvent.createRcsEvent(rcsControllerCall))
                 .collect(Collectors.toList());
 
         return new RcsEventQueryResult(mContinuationToken, rcsEvents);
diff --git a/telephony/java/android/telephony/ims/RcsFileTransferPart.java b/telephony/java/android/telephony/ims/RcsFileTransferPart.java
index 3816cd4..ef66a76 100644
--- a/telephony/java/android/telephony/ims/RcsFileTransferPart.java
+++ b/telephony/java/android/telephony/ims/RcsFileTransferPart.java
@@ -103,12 +103,15 @@
     public @interface RcsFileTransferStatus {
     }
 
+    private final RcsControllerCall mRcsControllerCall;
+
     private int mId;
 
     /**
      * @hide
      */
-    RcsFileTransferPart(int id) {
+    RcsFileTransferPart(RcsControllerCall rcsControllerCall, int id) {
+        mRcsControllerCall = rcsControllerCall;
         mId = id;
     }
 
@@ -134,7 +137,9 @@
      */
     @WorkerThread
     public void setFileTransferSessionId(String sessionId) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferSessionId(mId, sessionId));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferSessionId(mId, sessionId,
+                        callingPackage));
     }
 
     /**
@@ -143,7 +148,8 @@
      */
     @WorkerThread
     public String getFileTransferSessionId() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferSessionId(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferSessionId(mId, callingPackage));
     }
 
     /**
@@ -155,7 +161,9 @@
      */
     @WorkerThread
     public void setContentUri(Uri contentUri) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferContentUri(mId, contentUri));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferContentUri(mId, contentUri,
+                        callingPackage));
     }
 
     /**
@@ -165,7 +173,8 @@
     @Nullable
     @WorkerThread
     public Uri getContentUri() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferContentUri(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferContentUri(mId, callingPackage));
     }
 
     /**
@@ -177,8 +186,9 @@
      */
     @WorkerThread
     public void setContentMimeType(String contentMimeType) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setFileTransferContentType(mId, contentMimeType));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferContentType(mId, contentMimeType,
+                        callingPackage));
     }
 
     /**
@@ -188,7 +198,8 @@
     @WorkerThread
     @Nullable
     public String getContentMimeType() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferContentType(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferContentType(mId, callingPackage));
     }
 
     /**
@@ -199,8 +210,9 @@
      */
     @WorkerThread
     public void setFileSize(long contentLength) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setFileTransferFileSize(mId, contentLength));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferFileSize(mId, contentLength,
+                        callingPackage));
     }
 
     /**
@@ -209,7 +221,8 @@
      */
     @WorkerThread
     public long getFileSize() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferFileSize(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferFileSize(mId, callingPackage));
     }
 
     /**
@@ -222,8 +235,9 @@
      */
     @WorkerThread
     public void setTransferOffset(long transferOffset) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setFileTransferTransferOffset(mId, transferOffset));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferTransferOffset(mId, transferOffset,
+                        callingPackage));
     }
 
     /**
@@ -232,7 +246,8 @@
      */
     @WorkerThread
     public long getTransferOffset() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferTransferOffset(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferTransferOffset(mId, callingPackage));
     }
 
     /**
@@ -244,7 +259,8 @@
     @WorkerThread
     public void setFileTransferStatus(@RcsFileTransferStatus int status)
             throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferStatus(mId, status));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferStatus(mId, status, callingPackage));
     }
 
     /**
@@ -253,7 +269,8 @@
      */
     @WorkerThread
     public @RcsFileTransferStatus int getFileTransferStatus() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferStatus(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferStatus(mId, callingPackage));
     }
 
     /**
@@ -262,7 +279,8 @@
      */
     @WorkerThread
     public int getWidth() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferWidth(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferWidth(mId, callingPackage));
     }
 
     /**
@@ -273,7 +291,8 @@
      */
     @WorkerThread
     public void setWidth(int width) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferWidth(mId, width));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferWidth(mId, width, callingPackage));
     }
 
     /**
@@ -282,7 +301,8 @@
      */
     @WorkerThread
     public int getHeight() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferHeight(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferHeight(mId, callingPackage));
     }
 
     /**
@@ -293,7 +313,8 @@
      */
     @WorkerThread
     public void setHeight(int height) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferHeight(mId, height));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferHeight(mId, height, callingPackage));
     }
 
     /**
@@ -302,7 +323,8 @@
      */
     @WorkerThread
     public long getLength() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferLength(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferLength(mId, callingPackage));
     }
 
     /**
@@ -313,7 +335,8 @@
      */
     @WorkerThread
     public void setLength(long length) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferLength(mId, length));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferLength(mId, length, callingPackage));
     }
 
     /**
@@ -323,7 +346,8 @@
      */
     @WorkerThread
     public Uri getPreviewUri() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferPreviewUri(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferPreviewUri(mId, callingPackage));
     }
 
     /**
@@ -334,7 +358,9 @@
      */
     @WorkerThread
     public void setPreviewUri(Uri previewUri) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferPreviewUri(mId, previewUri));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferPreviewUri(mId, previewUri,
+                        callingPackage));
     }
 
     /**
@@ -343,7 +369,8 @@
      */
     @WorkerThread
     public String getPreviewMimeType() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getFileTransferPreviewType(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransferPreviewType(mId, callingPackage));
     }
 
     /**
@@ -354,7 +381,8 @@
      */
     @WorkerThread
     public void setPreviewMimeType(String previewMimeType) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setFileTransferPreviewType(mId, previewMimeType));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setFileTransferPreviewType(mId, previewMimeType,
+                        callingPackage));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.java b/telephony/java/android/telephony/ims/RcsGroupThread.java
index baec19a..30abcb4 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThread.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThread.java
@@ -38,8 +38,8 @@
      *
      * @hide
      */
-    public RcsGroupThread(int threadId) {
-        super(threadId);
+    public RcsGroupThread(RcsControllerCall rcsControllerCall, int threadId) {
+        super(rcsControllerCall, threadId);
     }
 
     /**
@@ -58,7 +58,8 @@
     @Nullable
     @WorkerThread
     public String getGroupName() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadName(mThreadId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getGroupThreadName(mThreadId, callingPackage));
     }
 
     /**
@@ -69,7 +70,9 @@
      */
     @WorkerThread
     public void setGroupName(String groupName) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setGroupThreadName(mThreadId, groupName));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setGroupThreadName(mThreadId, groupName,
+                        callingPackage));
     }
 
     /**
@@ -79,7 +82,8 @@
      */
     @Nullable
     public Uri getGroupIcon() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadIcon(mThreadId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getGroupThreadIcon(mThreadId, callingPackage));
     }
 
     /**
@@ -90,7 +94,9 @@
      */
     @WorkerThread
     public void setGroupIcon(@Nullable Uri groupIcon) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setGroupThreadIcon(mThreadId, groupIcon));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setGroupThreadIcon(mThreadId, groupIcon,
+                        callingPackage));
     }
 
     /**
@@ -100,8 +106,11 @@
     @Nullable
     @WorkerThread
     public RcsParticipant getOwner() throws RcsMessageStoreException {
-        return new RcsParticipant(RcsControllerCall.call(
-                iRcs -> iRcs.getGroupThreadOwner(mThreadId)));
+        return new RcsParticipant(
+                mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getGroupThreadOwner(mThreadId,
+                                callingPackage)));
     }
 
     /**
@@ -114,8 +123,9 @@
      */
     @WorkerThread
     public void setOwner(@Nullable RcsParticipant participant) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setGroupThreadOwner(mThreadId, participant.getId()));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setGroupThreadOwner(mThreadId, participant.getId(),
+                        callingPackage));
     }
 
     /**
@@ -133,8 +143,9 @@
             return;
         }
 
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.addParticipantToGroupThread(mThreadId, participant.getId()));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.addParticipantToGroupThread(mThreadId,
+                        participant.getId(), callingPackage));
     }
 
     /**
@@ -150,8 +161,9 @@
             return;
         }
 
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.removeParticipantFromGroupThread(mThreadId, participant.getId()));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.removeParticipantFromGroupThread(mThreadId,
+                        participant.getId(), callingPackage));
     }
 
     /**
@@ -170,8 +182,10 @@
                 new RcsParticipantQueryParams.Builder().setThread(this).build();
 
         RcsParticipantQueryResult queryResult = new RcsParticipantQueryResult(
-                RcsControllerCall.call(
-                        iRcs -> iRcs.getParticipants(queryParameters)));
+                mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getParticipants(queryParameters,
+                                callingPackage)));
 
         List<RcsParticipant> participantList = queryResult.getParticipants();
         Set<RcsParticipant> participantSet = new LinkedHashSet<>(participantList);
@@ -187,7 +201,9 @@
     @Nullable
     @WorkerThread
     public Uri getConferenceUri() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadConferenceUri(mThreadId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getGroupThreadConferenceUri(mThreadId,
+                        callingPackage));
     }
 
     /**
@@ -200,7 +216,8 @@
     @Nullable
     @WorkerThread
     public void setConferenceUri(Uri conferenceUri) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setGroupThreadConferenceUri(mThreadId, conferenceUri));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setGroupThreadConferenceUri(mThreadId, conferenceUri,
+                        callingPackage));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java
index 4a6b963..f4beef7f 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java
@@ -23,14 +23,14 @@
  * @hide
  */
 public abstract class RcsGroupThreadEvent extends RcsEvent {
-    private final int mRcsGroupThreadId;
-    private final int mOriginatingParticipantId;
+    private final RcsGroupThread mRcsGroupThread;
+    private final RcsParticipant mOriginatingParticipant;
 
-    RcsGroupThreadEvent(long timestamp, int rcsGroupThreadId,
-            int originatingParticipantId) {
+    RcsGroupThreadEvent(long timestamp, RcsGroupThread rcsGroupThread,
+            RcsParticipant originatingParticipant) {
         super(timestamp);
-        mRcsGroupThreadId = rcsGroupThreadId;
-        mOriginatingParticipantId = originatingParticipantId;
+        mRcsGroupThread = rcsGroupThread;
+        mOriginatingParticipant = originatingParticipant;
     }
 
     /**
@@ -38,7 +38,7 @@
      */
     @NonNull
     public RcsGroupThread getRcsGroupThread() {
-        return new RcsGroupThread(mRcsGroupThreadId);
+        return mRcsGroupThread;
     }
 
     /**
@@ -46,6 +46,6 @@
      */
     @NonNull
     public RcsParticipant getOriginatingParticipant() {
-        return new RcsParticipant(mOriginatingParticipantId);
+        return mOriginatingParticipant;
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java
index 3c6c74f..23e39ff 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java
@@ -40,9 +40,10 @@
      * @param newIcon {@link Uri} to the new icon of this {@link RcsGroupThread}
      * @see RcsMessageStore#persistRcsEvent(RcsEvent)
      */
-    public RcsGroupThreadIconChangedEvent(long timestamp, @NonNull RcsGroupThread rcsGroupThread,
-            @NonNull RcsParticipant originatingParticipant, @Nullable Uri newIcon) {
-        super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId());
+    public RcsGroupThreadIconChangedEvent(long timestamp,
+            @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant,
+            @Nullable Uri newIcon) {
+        super(timestamp, rcsGroupThread, originatingParticipant);
         mNewIcon = newIcon;
     }
 
@@ -61,10 +62,10 @@
      * @hide - not meant for public use.
      */
     @Override
-    public void persist() throws RcsMessageStoreException {
+    void persist(RcsControllerCall rcsControllerCall) throws RcsMessageStoreException {
         // TODO ensure failure throws
-        RcsControllerCall.call(iRcs -> iRcs.createGroupThreadIconChangedEvent(
+        rcsControllerCall.call((iRcs, callingPackage) -> iRcs.createGroupThreadIconChangedEvent(
                 getTimestamp(), getRcsGroupThread().getThreadId(),
-                getOriginatingParticipant().getId(), mNewIcon));
+                getOriginatingParticipant().getId(), mNewIcon, callingPackage));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEventDescriptor.java b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEventDescriptor.java
index bcadc80..9350e40 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEventDescriptor.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEventDescriptor.java
@@ -38,9 +38,10 @@
 
     @Override
     @VisibleForTesting(visibility = PROTECTED)
-    public RcsGroupThreadIconChangedEvent createRcsEvent() {
-        return new RcsGroupThreadIconChangedEvent(mTimestamp, new RcsGroupThread(mRcsGroupThreadId),
-                new RcsParticipant(mOriginatingParticipantId), mNewIcon);
+    public RcsGroupThreadIconChangedEvent createRcsEvent(RcsControllerCall rcsControllerCall) {
+        return new RcsGroupThreadIconChangedEvent(mTimestamp,
+                new RcsGroupThread(rcsControllerCall, mRcsGroupThreadId),
+                new RcsParticipant(rcsControllerCall, mOriginatingParticipantId), mNewIcon);
     }
 
     public static final @NonNull Creator<RcsGroupThreadIconChangedEventDescriptor> CREATOR =
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java
index 5403253..a6a0867 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java
@@ -41,7 +41,7 @@
      */
     public RcsGroupThreadNameChangedEvent(long timestamp, @NonNull RcsGroupThread rcsGroupThread,
             @NonNull RcsParticipant originatingParticipant, @Nullable String newName) {
-        super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId());
+        super(timestamp, rcsGroupThread, originatingParticipant);
         mNewName = newName;
     }
 
@@ -60,9 +60,9 @@
      * @hide - not meant for public use.
      */
     @Override
-    public void persist() throws RcsMessageStoreException {
-        RcsControllerCall.call(iRcs -> iRcs.createGroupThreadNameChangedEvent(
+    void persist(RcsControllerCall rcsControllerCall) throws RcsMessageStoreException {
+        rcsControllerCall.call((iRcs, callingPackage) -> iRcs.createGroupThreadNameChangedEvent(
                 getTimestamp(), getRcsGroupThread().getThreadId(),
-                getOriginatingParticipant().getId(), mNewName));
+                getOriginatingParticipant().getId(), mNewName, callingPackage));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEventDescriptor.java b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEventDescriptor.java
index 597fa0a..f9ccdd5 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEventDescriptor.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEventDescriptor.java
@@ -37,11 +37,11 @@
 
     @Override
     @VisibleForTesting(visibility = PROTECTED)
-    public RcsGroupThreadNameChangedEvent createRcsEvent() {
+    public RcsGroupThreadNameChangedEvent createRcsEvent(RcsControllerCall rcsControllerCall) {
         return new RcsGroupThreadNameChangedEvent(
                 mTimestamp,
-                new RcsGroupThread(mRcsGroupThreadId),
-                new RcsParticipant(mOriginatingParticipantId),
+                new RcsGroupThread(rcsControllerCall, mRcsGroupThreadId),
+                new RcsParticipant(rcsControllerCall, mOriginatingParticipantId),
                 mNewName);
     }
 
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java
index 48be479..694c7de 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java
@@ -30,19 +30,20 @@
      * Creates a new {@link RcsGroupThreadParticipantJoinedEvent}. This event is not persisted into
      * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called.
      *
-     * @param timestamp The timestamp of when this event happened, in milliseconds passed after
-     *                  midnight, January 1st, 1970 UTC
-     * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on
+     * @param timestamp              The timestamp of when this event happened, in milliseconds
+     *                               passed after
+     *                               midnight, January 1st, 1970 UTC
+     * @param rcsGroupThread         The {@link RcsGroupThread} that this event happened on
      * @param originatingParticipant The {@link RcsParticipant} that added or invited the new
      *                               {@link RcsParticipant} into the {@link RcsGroupThread}
-     * @param joinedParticipant The new {@link RcsParticipant} that joined the
-     *                          {@link RcsGroupThread}
+     * @param joinedParticipant      The new {@link RcsParticipant} that joined the
+     *                               {@link RcsGroupThread}
      * @see RcsMessageStore#persistRcsEvent(RcsEvent)
      */
     public RcsGroupThreadParticipantJoinedEvent(long timestamp,
             @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant,
             @NonNull RcsParticipant joinedParticipant) {
-        super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId());
+        super(timestamp, rcsGroupThread, originatingParticipant);
         mJoinedParticipantId = joinedParticipant;
     }
 
@@ -59,10 +60,11 @@
      * @hide - not meant for public use.
      */
     @Override
-    public void persist() throws RcsMessageStoreException {
-        RcsControllerCall.call(
-                iRcs -> iRcs.createGroupThreadParticipantJoinedEvent(getTimestamp(),
+    void persist(RcsControllerCall rcsControllerCall) throws RcsMessageStoreException {
+        rcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.createGroupThreadParticipantJoinedEvent(
+                        getTimestamp(),
                         getRcsGroupThread().getThreadId(), getOriginatingParticipant().getId(),
-                        getJoinedParticipant().getId()));
+                        getJoinedParticipant().getId(), callingPackage));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEventDescriptor.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEventDescriptor.java
index abea10a..4a6803e 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEventDescriptor.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEventDescriptor.java
@@ -36,12 +36,13 @@
 
     @Override
     @VisibleForTesting(visibility = PROTECTED)
-    public RcsGroupThreadParticipantJoinedEvent createRcsEvent() {
+    public RcsGroupThreadParticipantJoinedEvent createRcsEvent(
+            RcsControllerCall rcsControllerCall) {
         return new RcsGroupThreadParticipantJoinedEvent(
                 mTimestamp,
-                new RcsGroupThread(mRcsGroupThreadId),
-                new RcsParticipant(mOriginatingParticipantId),
-                new RcsParticipant(mJoinedParticipantId));
+                new RcsGroupThread(rcsControllerCall, mRcsGroupThreadId),
+                new RcsParticipant(rcsControllerCall, mOriginatingParticipantId),
+                new RcsParticipant(rcsControllerCall, mJoinedParticipantId));
     }
 
     public static final @NonNull Creator<RcsGroupThreadParticipantJoinedEventDescriptor> CREATOR =
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java
index b724a3f..fec4354 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java
@@ -44,7 +44,7 @@
     public RcsGroupThreadParticipantLeftEvent(long timestamp,
             @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant,
             @NonNull RcsParticipant leavingParticipant) {
-        super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId());
+        super(timestamp, rcsGroupThread, originatingParticipant);
         mLeavingParticipant = leavingParticipant;
     }
 
@@ -58,10 +58,10 @@
     }
 
     @Override
-    public void persist() throws RcsMessageStoreException {
-        RcsControllerCall.call(
-                iRcs -> iRcs.createGroupThreadParticipantLeftEvent(getTimestamp(),
+    void persist(RcsControllerCall rcsControllerCall) throws RcsMessageStoreException {
+        rcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.createGroupThreadParticipantLeftEvent(getTimestamp(),
                         getRcsGroupThread().getThreadId(), getOriginatingParticipant().getId(),
-                        getLeavingParticipant().getId()));
+                        getLeavingParticipant().getId(), callingPackage));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEventDescriptor.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEventDescriptor.java
index f287db1..9b1085c 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEventDescriptor.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEventDescriptor.java
@@ -37,12 +37,12 @@
 
     @Override
     @VisibleForTesting(visibility = PROTECTED)
-    public RcsGroupThreadParticipantLeftEvent createRcsEvent() {
+    public RcsGroupThreadParticipantLeftEvent createRcsEvent(RcsControllerCall rcsControllerCall) {
         return new RcsGroupThreadParticipantLeftEvent(
                 mTimestamp,
-                new RcsGroupThread(mRcsGroupThreadId),
-                new RcsParticipant(mOriginatingParticipantId),
-                new RcsParticipant(mLeavingParticipantId));
+                new RcsGroupThread(rcsControllerCall, mRcsGroupThreadId),
+                new RcsParticipant(rcsControllerCall, mOriginatingParticipantId),
+                new RcsParticipant(rcsControllerCall, mLeavingParticipantId));
     }
 
     @NonNull
diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessage.java b/telephony/java/android/telephony/ims/RcsIncomingMessage.java
index 06e2a41..2810a49 100644
--- a/telephony/java/android/telephony/ims/RcsIncomingMessage.java
+++ b/telephony/java/android/telephony/ims/RcsIncomingMessage.java
@@ -26,8 +26,8 @@
     /**
      * @hide
      */
-    RcsIncomingMessage(int id) {
-        super(id);
+    RcsIncomingMessage(RcsControllerCall rcsControllerCall, int id) {
+        super(rcsControllerCall, id);
     }
 
     /**
@@ -39,8 +39,9 @@
      */
     @WorkerThread
     public void setArrivalTimestamp(long arrivalTimestamp) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setMessageArrivalTimestamp(mId, true, arrivalTimestamp));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setMessageArrivalTimestamp(mId, true,
+                        arrivalTimestamp, callingPackage));
     }
 
     /**
@@ -50,7 +51,9 @@
      */
     @WorkerThread
     public long getArrivalTimestamp() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getMessageArrivalTimestamp(mId, true));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getMessageArrivalTimestamp(mId, true,
+                        callingPackage));
     }
 
     /**
@@ -62,8 +65,9 @@
      */
     @WorkerThread
     public void setSeenTimestamp(long notifiedTimestamp) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setMessageSeenTimestamp(mId, true, notifiedTimestamp));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setMessageSeenTimestamp(mId, true, notifiedTimestamp,
+                        callingPackage));
     }
 
     /**
@@ -73,7 +77,8 @@
      */
     @WorkerThread
     public long getSeenTimestamp() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getMessageSeenTimestamp(mId, true));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getMessageSeenTimestamp(mId, true, callingPackage));
     }
 
     /**
@@ -83,7 +88,9 @@
     @WorkerThread
     public RcsParticipant getSenderParticipant() throws RcsMessageStoreException {
         return new RcsParticipant(
-                RcsControllerCall.call(iRcs -> iRcs.getSenderParticipant(mId)));
+                mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getSenderParticipant(mId, callingPackage)));
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java
index 63dc1ac..0d6ca3c 100644
--- a/telephony/java/android/telephony/ims/RcsManager.java
+++ b/telephony/java/android/telephony/ims/RcsManager.java
@@ -25,20 +25,19 @@
  */
 @SystemService(Context.TELEPHONY_RCS_SERVICE)
 public class RcsManager {
+    private final RcsMessageStore mRcsMessageStore;
 
     /**
      * @hide
      */
-    public RcsManager() {
-        // empty constructor
+    public RcsManager(Context context) {
+        mRcsMessageStore = new RcsMessageStore(context);
     }
 
-    private static final RcsMessageStore sRcsMessageStoreInstance = new RcsMessageStore();
-
     /**
      * Returns an instance of {@link RcsMessageStore}
      */
     public RcsMessageStore getRcsMessageStore() {
-        return sRcsMessageStoreInstance;
+        return mRcsMessageStore;
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsMessage.java b/telephony/java/android/telephony/ims/RcsMessage.java
index b0d0d5a..4601bfd 100644
--- a/telephony/java/android/telephony/ims/RcsMessage.java
+++ b/telephony/java/android/telephony/ims/RcsMessage.java
@@ -86,6 +86,11 @@
     /**
      * @hide
      */
+    protected final RcsControllerCall mRcsControllerCall;
+
+    /**
+     * @hide
+     */
     protected final int mId;
 
     @IntDef({
@@ -95,7 +100,8 @@
     public @interface RcsMessageStatus {
     }
 
-    RcsMessage(int id) {
+    RcsMessage(RcsControllerCall rcsControllerCall, int id) {
+        mRcsControllerCall = rcsControllerCall;
         mId = id;
     }
 
@@ -115,7 +121,8 @@
      * @see android.telephony.SubscriptionInfo#getSubscriptionId
      */
     public int getSubscriptionId() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getMessageSubId(mId, isIncoming()));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getMessageSubId(mId, isIncoming(), callingPackage));
     }
 
     /**
@@ -128,7 +135,9 @@
      */
     @WorkerThread
     public void setSubscriptionId(int subId) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setMessageSubId(mId, isIncoming(), subId));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setMessageSubId(mId, isIncoming(), subId,
+                        callingPackage));
     }
 
     /**
@@ -139,8 +148,9 @@
      */
     @WorkerThread
     public void setStatus(@RcsMessageStatus int rcsMessageStatus) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setMessageStatus(mId, isIncoming(), rcsMessageStatus));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setMessageStatus(mId, isIncoming(), rcsMessageStatus,
+                        callingPackage));
     }
 
     /**
@@ -150,7 +160,8 @@
      */
     @WorkerThread
     public @RcsMessageStatus int getStatus() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getMessageStatus(mId, isIncoming()));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getMessageStatus(mId, isIncoming(), callingPackage));
     }
 
     /**
@@ -163,8 +174,9 @@
      */
     @WorkerThread
     public void setOriginationTimestamp(long timestamp) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setMessageOriginationTimestamp(mId, isIncoming(), timestamp));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setMessageOriginationTimestamp(mId, isIncoming(),
+                        timestamp, callingPackage));
     }
 
     /**
@@ -175,8 +187,9 @@
      */
     @WorkerThread
     public long getOriginationTimestamp() throws RcsMessageStoreException {
-        return RcsControllerCall.call(
-                iRcs -> iRcs.getMessageOriginationTimestamp(mId, isIncoming()));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getMessageOriginationTimestamp(mId, isIncoming(),
+                        callingPackage));
     }
 
     /**
@@ -189,8 +202,9 @@
      */
     @WorkerThread
     public void setRcsMessageId(String rcsMessageGlobalId) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setGlobalMessageIdForMessage(mId, isIncoming(), rcsMessageGlobalId));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setGlobalMessageIdForMessage(mId, isIncoming(),
+                        rcsMessageGlobalId, callingPackage));
     }
 
     /**
@@ -200,7 +214,9 @@
      */
     @WorkerThread
     public String getRcsMessageId() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getGlobalMessageIdForMessage(mId, isIncoming()));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getGlobalMessageIdForMessage(mId, isIncoming(),
+                        callingPackage));
     }
 
     /**
@@ -209,7 +225,9 @@
      */
     @WorkerThread
     public String getText() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getTextForMessage(mId, isIncoming()));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getTextForMessage(mId, isIncoming(),
+                        callingPackage));
     }
 
     /**
@@ -220,18 +238,21 @@
      */
     @WorkerThread
     public void setText(String text) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setTextForMessage(mId, isIncoming(), text));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setTextForMessage(mId, isIncoming(), text,
+                        callingPackage));
     }
 
     /**
      * @return Returns the associated latitude for this message, or
      * {@link RcsMessage#LOCATION_NOT_SET} if it does not contain a location.
-     *
      * @throws RcsMessageStoreException if the value could not be read from the storage
      */
     @WorkerThread
     public double getLatitude() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getLatitudeForMessage(mId, isIncoming()));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getLatitudeForMessage(mId, isIncoming(),
+                        callingPackage));
     }
 
     /**
@@ -242,19 +263,21 @@
      */
     @WorkerThread
     public void setLatitude(double latitude) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setLatitudeForMessage(mId, isIncoming(), latitude));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setLatitudeForMessage(mId, isIncoming(), latitude,
+                        callingPackage));
     }
 
     /**
      * @return Returns the associated longitude for this message, or
      * {@link RcsMessage#LOCATION_NOT_SET} if it does not contain a location.
-     *
      * @throws RcsMessageStoreException if the value could not be read from the storage
      */
     @WorkerThread
     public double getLongitude() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getLongitudeForMessage(mId, isIncoming()));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getLongitudeForMessage(mId, isIncoming(),
+                        callingPackage));
     }
 
     /**
@@ -265,8 +288,9 @@
      */
     @WorkerThread
     public void setLongitude(double longitude) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.setLongitudeForMessage(mId, isIncoming(), longitude));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setLongitudeForMessage(mId, isIncoming(), longitude,
+                        callingPackage));
     }
 
     /**
@@ -282,8 +306,9 @@
     public RcsFileTransferPart insertFileTransfer(
             RcsFileTransferCreationParams fileTransferCreationParameters)
             throws RcsMessageStoreException {
-        return new RcsFileTransferPart(RcsControllerCall.call(
-                iRcs -> iRcs.storeFileTransfer(mId, isIncoming(), fileTransferCreationParameters)));
+        return new RcsFileTransferPart(mRcsControllerCall, mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.storeFileTransfer(mId, isIncoming(),
+                        fileTransferCreationParameters, callingPackage)));
     }
 
     /**
@@ -296,11 +321,12 @@
     public Set<RcsFileTransferPart> getFileTransferParts() throws RcsMessageStoreException {
         Set<RcsFileTransferPart> fileTransferParts = new HashSet<>();
 
-        int[] fileTransferIds = RcsControllerCall.call(
-                iRcs -> iRcs.getFileTransfersAttachedToMessage(mId, isIncoming()));
+        int[] fileTransferIds = mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getFileTransfersAttachedToMessage(mId, isIncoming(),
+                        callingPackage));
 
         for (int fileTransfer : fileTransferIds) {
-            fileTransferParts.add(new RcsFileTransferPart(fileTransfer));
+            fileTransferParts.add(new RcsFileTransferPart(mRcsControllerCall, fileTransfer));
         }
 
         return Collections.unmodifiableSet(fileTransferParts);
@@ -319,8 +345,9 @@
             return;
         }
 
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.deleteFileTransfer(fileTransferPart.getId()));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.deleteFileTransfer(fileTransferPart.getId(),
+                        callingPackage));
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryResult.java b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java
index 5df929b..36bb78a 100644
--- a/telephony/java/android/telephony/ims/RcsMessageQueryResult.java
+++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java
@@ -20,13 +20,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
 
-import com.android.ims.RcsTypeIdPair;
-
-import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * The result of a {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParams)}
@@ -35,23 +31,14 @@
  *
  * @hide
  */
-public final class RcsMessageQueryResult implements Parcelable {
-    // The token to continue the query to get the next batch of results
-    private RcsQueryContinuationToken mContinuationToken;
-    // The message type and message ID pairs for all the messages in this query result
-    private List<RcsTypeIdPair> mMessageTypeIdPairs;
+public final class RcsMessageQueryResult {
+    private final RcsControllerCall mRcsControllerCall;
+    private final RcsMessageQueryResultParcelable mRcsMessageQueryResultParcelable;
 
-    /**
-     * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
-     * to create query results
-     *
-     * @hide
-     */
-    public RcsMessageQueryResult(
-            RcsQueryContinuationToken continuationToken,
-            List<RcsTypeIdPair> messageTypeIdPairs) {
-        mContinuationToken = continuationToken;
-        mMessageTypeIdPairs = messageTypeIdPairs;
+    RcsMessageQueryResult(RcsControllerCall rcsControllerCall,
+            RcsMessageQueryResultParcelable rcsMessageQueryResultParcelable) {
+        mRcsControllerCall = rcsControllerCall;
+        mRcsMessageQueryResultParcelable = rcsMessageQueryResultParcelable;
     }
 
     /**
@@ -61,7 +48,7 @@
      */
     @Nullable
     public RcsQueryContinuationToken getContinuationToken() {
-        return mContinuationToken;
+        return mRcsMessageQueryResultParcelable.mContinuationToken;
     }
 
     /**
@@ -71,45 +58,10 @@
      */
     @NonNull
     public List<RcsMessage> getMessages() {
-        List<RcsMessage> messages = new ArrayList<>();
-        for (RcsTypeIdPair typeIdPair : mMessageTypeIdPairs) {
-            if (typeIdPair.getType() == MESSAGE_TYPE_INCOMING) {
-                messages.add(new RcsIncomingMessage(typeIdPair.getId()));
-            } else {
-                messages.add(new RcsOutgoingMessage(typeIdPair.getId()));
-            }
-        }
-
-        return messages;
-    }
-
-    private RcsMessageQueryResult(Parcel in) {
-        mContinuationToken = in.readParcelable(
-                RcsQueryContinuationToken.class.getClassLoader());
-        in.readTypedList(mMessageTypeIdPairs, RcsTypeIdPair.CREATOR);
-    }
-
-    public static final @android.annotation.NonNull Creator<RcsMessageQueryResult> CREATOR =
-            new Creator<RcsMessageQueryResult>() {
-                @Override
-                public RcsMessageQueryResult createFromParcel(Parcel in) {
-                    return new RcsMessageQueryResult(in);
-                }
-
-                @Override
-                public RcsMessageQueryResult[] newArray(int size) {
-                    return new RcsMessageQueryResult[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelable(mContinuationToken, flags);
-        dest.writeTypedList(mMessageTypeIdPairs);
+        return mRcsMessageQueryResultParcelable.mMessageTypeIdPairs.stream()
+                .map(typeIdPair -> typeIdPair.getType() == MESSAGE_TYPE_INCOMING
+                        ? new RcsIncomingMessage(mRcsControllerCall, typeIdPair.getId())
+                        : new RcsOutgoingMessage(mRcsControllerCall, typeIdPair.getId()))
+                .collect(Collectors.toList());
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryResultParcelable.aidl
similarity index 93%
rename from telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl
rename to telephony/java/android/telephony/ims/RcsMessageQueryResultParcelable.aidl
index a73ba50..86928bf 100644
--- a/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl
+++ b/telephony/java/android/telephony/ims/RcsMessageQueryResultParcelable.aidl
@@ -17,4 +17,4 @@
 
 package android.telephony.ims;
 
-parcelable RcsMessageQueryResult;
+parcelable RcsMessageQueryResultParcelable;
diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryResultParcelable.java b/telephony/java/android/telephony/ims/RcsMessageQueryResultParcelable.java
new file mode 100644
index 0000000..4972f9b
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsMessageQueryResultParcelable.java
@@ -0,0 +1,74 @@
+/*
+ * 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.telephony.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.ims.RcsTypeIdPair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide - used only for internal communication with the ircs service
+ */
+public class RcsMessageQueryResultParcelable implements Parcelable {
+    // The token to continue the query to get the next batch of results
+    final RcsQueryContinuationToken mContinuationToken;
+    // The message type and message ID pairs for all the messages in this query result
+    final List<RcsTypeIdPair> mMessageTypeIdPairs;
+
+    public RcsMessageQueryResultParcelable(
+            RcsQueryContinuationToken continuationToken,
+            List<RcsTypeIdPair> messageTypeIdPairs) {
+        mContinuationToken = continuationToken;
+        mMessageTypeIdPairs = messageTypeIdPairs;
+    }
+
+    private RcsMessageQueryResultParcelable(Parcel in) {
+        mContinuationToken = in.readParcelable(
+                RcsQueryContinuationToken.class.getClassLoader());
+
+        mMessageTypeIdPairs = new ArrayList<>();
+        in.readTypedList(mMessageTypeIdPairs, RcsTypeIdPair.CREATOR);
+    }
+
+    public static final Creator<RcsMessageQueryResultParcelable> CREATOR =
+            new Creator<RcsMessageQueryResultParcelable>() {
+                @Override
+                public RcsMessageQueryResultParcelable createFromParcel(Parcel in) {
+                    return new RcsMessageQueryResultParcelable(in);
+                }
+
+                @Override
+                public RcsMessageQueryResultParcelable[] newArray(int size) {
+                    return new RcsMessageQueryResultParcelable[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mContinuationToken, flags);
+        dest.writeTypedList(mMessageTypeIdPairs);
+    }
+}
diff --git a/telephony/java/android/telephony/ims/RcsMessageStore.java b/telephony/java/android/telephony/ims/RcsMessageStore.java
index 6fcb62b..d112798 100644
--- a/telephony/java/android/telephony/ims/RcsMessageStore.java
+++ b/telephony/java/android/telephony/ims/RcsMessageStore.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
+import android.content.Context;
 import android.net.Uri;
 
 import java.util.List;
@@ -30,6 +31,12 @@
  * @hide
  */
 public class RcsMessageStore {
+    RcsControllerCall mRcsControllerCall;
+
+    RcsMessageStore(Context context) {
+        mRcsControllerCall = new RcsControllerCall(context);
+    }
+
     /**
      * Returns the first chunk of existing {@link RcsThread}s in the common storage.
      *
@@ -41,8 +48,10 @@
     @NonNull
     public RcsThreadQueryResult getRcsThreads(@Nullable RcsThreadQueryParams queryParameters)
             throws RcsMessageStoreException {
-        return new RcsThreadQueryResult(
-                RcsControllerCall.call(iRcs -> iRcs.getRcsThreads(queryParameters)));
+        return new RcsThreadQueryResult(mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getRcsThreads(queryParameters,
+                                callingPackage)));
     }
 
     /**
@@ -56,8 +65,10 @@
     @NonNull
     public RcsThreadQueryResult getRcsThreads(@NonNull RcsQueryContinuationToken continuationToken)
             throws RcsMessageStoreException {
-        return new RcsThreadQueryResult(
-                RcsControllerCall.call(iRcs -> iRcs.getRcsThreadsWithToken(continuationToken)));
+        return new RcsThreadQueryResult(mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getRcsThreadsWithToken(continuationToken,
+                                callingPackage)));
     }
 
     /**
@@ -72,8 +83,10 @@
     public RcsParticipantQueryResult getRcsParticipants(
             @Nullable RcsParticipantQueryParams queryParameters)
             throws RcsMessageStoreException {
-        return new RcsParticipantQueryResult(
-                RcsControllerCall.call(iRcs -> iRcs.getParticipants(queryParameters)));
+        return new RcsParticipantQueryResult(mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getParticipants(queryParameters,
+                                callingPackage)));
     }
 
     /**
@@ -89,22 +102,26 @@
     public RcsParticipantQueryResult getRcsParticipants(
             @NonNull RcsQueryContinuationToken continuationToken)
             throws RcsMessageStoreException {
-        return new RcsParticipantQueryResult(
-                RcsControllerCall.call(iRcs -> iRcs.getParticipantsWithToken(continuationToken)));
+        return new RcsParticipantQueryResult(mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getParticipantsWithToken(continuationToken,
+                                callingPackage)));
     }
 
     /**
      * Returns the first chunk of existing {@link RcsMessage}s in the common storage.
      *
-     * @param queryParameters Parameters to specify to return a subset of all RcsMessages.
-     *                        Passing a value of null will return all messages.
+     * @param queryParams Parameters to specify to return a subset of all RcsMessages.
+     *                    Passing a value of null will return all messages.
      * @throws RcsMessageStoreException if the query could not be completed on the storage
      */
     @WorkerThread
     @NonNull
     public RcsMessageQueryResult getRcsMessages(
-            @Nullable RcsMessageQueryParams queryParameters) throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getMessages(queryParameters));
+            @Nullable RcsMessageQueryParams queryParams) throws RcsMessageStoreException {
+        return new RcsMessageQueryResult(mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getMessages(queryParams, callingPackage)));
     }
 
     /**
@@ -118,7 +135,10 @@
     @NonNull
     public RcsMessageQueryResult getRcsMessages(
             @NonNull RcsQueryContinuationToken continuationToken) throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getMessagesWithToken(continuationToken));
+        return new RcsMessageQueryResult(mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getMessagesWithToken(continuationToken,
+                                callingPackage)));
     }
 
     /**
@@ -132,8 +152,9 @@
     @NonNull
     public RcsEventQueryResult getRcsEvents(
             @Nullable RcsEventQueryParams queryParams) throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getEvents(queryParams))
-                .getRcsEventQueryResult();
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getEvents(queryParams, callingPackage))
+                .getRcsEventQueryResult(mRcsControllerCall);
     }
 
     /**
@@ -147,14 +168,16 @@
     @NonNull
     public RcsEventQueryResult getRcsEvents(
             @NonNull RcsQueryContinuationToken continuationToken) throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getEventsWithToken(continuationToken))
-                .getRcsEventQueryResult();
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getEventsWithToken(continuationToken,
+                        callingPackage))
+                .getRcsEventQueryResult(mRcsControllerCall);
     }
 
     /**
      * Persists an {@link RcsEvent} to common storage.
      *
-     * @param persistableEvent The {@link RcsEvent} to persist into storage.
+     * @param rcsEvent The {@link RcsEvent} to persist into storage.
      * @throws RcsMessageStoreException if the query could not be completed on the storage
      * @see RcsGroupThreadNameChangedEvent
      * @see RcsGroupThreadIconChangedEvent
@@ -164,8 +187,8 @@
      */
     @WorkerThread
     @NonNull
-    public void persistRcsEvent(RcsEvent persistableEvent) throws RcsMessageStoreException {
-        persistableEvent.persist();
+    public void persistRcsEvent(RcsEvent rcsEvent) throws RcsMessageStoreException {
+        rcsEvent.persist(mRcsControllerCall);
     }
 
     /**
@@ -180,7 +203,10 @@
     public Rcs1To1Thread createRcs1To1Thread(@NonNull RcsParticipant recipient)
             throws RcsMessageStoreException {
         return new Rcs1To1Thread(
-                RcsControllerCall.call(iRcs -> iRcs.createRcs1To1Thread(recipient.getId())));
+                mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.createRcs1To1Thread(recipient.getId(),
+                                callingPackage)));
     }
 
     /**
@@ -202,8 +228,12 @@
         }
 
         int[] finalRecipientIds = recipientIds;
-        return new RcsGroupThread(RcsControllerCall.call(
-                iRcs -> iRcs.createGroupThread(finalRecipientIds, groupName, groupIcon)));
+
+        int threadId = mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.createGroupThread(finalRecipientIds, groupName,
+                        groupIcon, callingPackage));
+
+        return new RcsGroupThread(mRcsControllerCall, threadId);
     }
 
     /**
@@ -218,8 +248,9 @@
             return;
         }
 
-        boolean isDeleteSucceeded = RcsControllerCall.call(
-                iRcs -> iRcs.deleteThread(thread.getThreadId(), thread.getThreadType()));
+        boolean isDeleteSucceeded = mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.deleteThread(thread.getThreadId(),
+                        thread.getThreadType(), callingPackage));
 
         if (!isDeleteSucceeded) {
             throw new RcsMessageStoreException("Could not delete RcsThread");
@@ -237,7 +268,8 @@
     @NonNull
     public RcsParticipant createRcsParticipant(String canonicalAddress, @Nullable String alias)
             throws RcsMessageStoreException {
-        return new RcsParticipant(
-                RcsControllerCall.call(iRcs -> iRcs.createRcsParticipant(canonicalAddress, alias)));
+        return new RcsParticipant(mRcsControllerCall, mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.createRcsParticipant(canonicalAddress, alias,
+                        callingPackage)));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java
index 1b4bfe5..7080ec6 100644
--- a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java
+++ b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java
@@ -27,8 +27,8 @@
  * @hide
  */
 public class RcsOutgoingMessage extends RcsMessage {
-    RcsOutgoingMessage(int id) {
-        super(id);
+    RcsOutgoingMessage(RcsControllerCall rcsControllerCall, int id) {
+        super(rcsControllerCall, id);
     }
 
     /**
@@ -45,12 +45,13 @@
         int[] deliveryParticipants;
         List<RcsOutgoingMessageDelivery> messageDeliveries = new ArrayList<>();
 
-        deliveryParticipants = RcsControllerCall.call(
-                iRcs -> iRcs.getMessageRecipients(mId));
+        deliveryParticipants = mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getMessageRecipients(mId, callingPackage));
 
         if (deliveryParticipants != null) {
             for (Integer deliveryParticipant : deliveryParticipants) {
-                messageDeliveries.add(new RcsOutgoingMessageDelivery(deliveryParticipant, mId));
+                messageDeliveries.add(new RcsOutgoingMessageDelivery(
+                        mRcsControllerCall, deliveryParticipant, mId));
             }
         }
 
diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java
index 2db49c6..df4a3e4 100644
--- a/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java
+++ b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java
@@ -25,6 +25,7 @@
  * @hide
  */
 public class RcsOutgoingMessageDelivery {
+    private final RcsControllerCall mRcsControllerCall;
     // The participant that this delivery is intended for
     private final int mRecipientId;
     // The message this delivery is associated with
@@ -35,7 +36,9 @@
      *
      * @hide
      */
-    RcsOutgoingMessageDelivery(int recipientId, int messageId) {
+    RcsOutgoingMessageDelivery(
+            RcsControllerCall rcsControllerCall, int recipientId, int messageId) {
+        mRcsControllerCall = rcsControllerCall;
         mRecipientId = recipientId;
         mRcsOutgoingMessageId = messageId;
     }
@@ -49,8 +52,9 @@
      */
     @WorkerThread
     public void setDeliveredTimestamp(long deliveredTimestamp) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliveryDeliveredTimestamp(
-                mRcsOutgoingMessageId, mRecipientId, deliveredTimestamp));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setOutgoingDeliveryDeliveredTimestamp(
+                        mRcsOutgoingMessageId, mRecipientId, deliveredTimestamp, callingPackage));
     }
 
     /**
@@ -61,8 +65,9 @@
      */
     @WorkerThread
     public long getDeliveredTimestamp() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getOutgoingDeliveryDeliveredTimestamp(
-                mRcsOutgoingMessageId, mRecipientId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getOutgoingDeliveryDeliveredTimestamp(
+                        mRcsOutgoingMessageId, mRecipientId, callingPackage));
     }
 
     /**
@@ -74,8 +79,9 @@
      */
     @WorkerThread
     public void setSeenTimestamp(long seenTimestamp) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliverySeenTimestamp(
-                mRcsOutgoingMessageId, mRecipientId, seenTimestamp));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setOutgoingDeliverySeenTimestamp(
+                        mRcsOutgoingMessageId, mRecipientId, seenTimestamp, callingPackage));
     }
 
     /**
@@ -86,8 +92,9 @@
      */
     @WorkerThread
     public long getSeenTimestamp() throws RcsMessageStoreException {
-        return RcsControllerCall.call(
-                iRcs -> iRcs.getOutgoingDeliverySeenTimestamp(mRcsOutgoingMessageId, mRecipientId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getOutgoingDeliverySeenTimestamp(
+                        mRcsOutgoingMessageId, mRecipientId, callingPackage));
     }
 
     /**
@@ -99,8 +106,9 @@
      */
     @WorkerThread
     public void setStatus(@RcsMessage.RcsMessageStatus int status) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliveryStatus(
-                mRcsOutgoingMessageId, mRecipientId, status));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setOutgoingDeliveryStatus(
+                        mRcsOutgoingMessageId, mRecipientId, status, callingPackage));
     }
 
     /**
@@ -109,8 +117,9 @@
      */
     @WorkerThread
     public @RcsMessage.RcsMessageStatus int getStatus() throws RcsMessageStoreException {
-        return RcsControllerCall.call(
-                iRcs -> iRcs.getOutgoingDeliveryStatus(mRcsOutgoingMessageId, mRecipientId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getOutgoingDeliveryStatus(mRcsOutgoingMessageId,
+                        mRecipientId, callingPackage));
     }
 
     /**
@@ -118,7 +127,7 @@
      */
     @NonNull
     public RcsParticipant getRecipient() {
-        return new RcsParticipant(mRecipientId);
+        return new RcsParticipant(mRcsControllerCall, mRecipientId);
     }
 
     /**
@@ -126,6 +135,6 @@
      */
     @NonNull
     public RcsOutgoingMessage getMessage() {
-        return new RcsOutgoingMessage(mRcsOutgoingMessageId);
+        return new RcsOutgoingMessage(mRcsControllerCall, mRcsOutgoingMessageId);
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsParticipant.java b/telephony/java/android/telephony/ims/RcsParticipant.java
index bcf134a..8512e96 100644
--- a/telephony/java/android/telephony/ims/RcsParticipant.java
+++ b/telephony/java/android/telephony/ims/RcsParticipant.java
@@ -24,8 +24,9 @@
  * @hide
  */
 public class RcsParticipant {
+    private final RcsControllerCall mRcsControllerCall;
     // The row ID of this participant in the database
-    private int mId;
+    private final int mId;
 
     /**
      * Constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
@@ -33,7 +34,8 @@
      *
      * @hide
      */
-    public RcsParticipant(int id) {
+    public RcsParticipant(RcsControllerCall rcsControllerCall, int id) {
+        mRcsControllerCall = rcsControllerCall;
         mId = id;
     }
 
@@ -45,7 +47,9 @@
     @Nullable
     @WorkerThread
     public String getCanonicalAddress() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantCanonicalAddress(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getRcsParticipantCanonicalAddress(mId,
+                        callingPackage));
     }
 
     /**
@@ -57,7 +61,8 @@
     @Nullable
     @WorkerThread
     public String getAlias() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantAlias(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getRcsParticipantAlias(mId, callingPackage));
     }
 
     /**
@@ -70,7 +75,8 @@
      */
     @WorkerThread
     public void setAlias(String alias) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setRcsParticipantAlias(mId, alias));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setRcsParticipantAlias(mId, alias, callingPackage));
     }
 
     /**
@@ -82,7 +88,8 @@
     @Nullable
     @WorkerThread
     public String getContactId() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantContactId(mId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getRcsParticipantContactId(mId, callingPackage));
     }
 
     /**
@@ -95,7 +102,9 @@
      */
     @WorkerThread
     public void setContactId(String contactId) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setRcsParticipantContactId(mId, contactId));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.setRcsParticipantContactId(mId, contactId,
+                        callingPackage));
     }
 
     @Override
diff --git a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java
index 61801f3..865bc05 100644
--- a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java
+++ b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java
@@ -69,8 +69,8 @@
      * @hide - not meant for public use.
      */
     @Override
-    public void persist() throws RcsMessageStoreException {
-        RcsControllerCall.call(iRcs -> iRcs.createParticipantAliasChangedEvent(
-                getTimestamp(), getParticipant().getId(), getNewAlias()));
+    void persist(RcsControllerCall rcsControllerCall) throws RcsMessageStoreException {
+        rcsControllerCall.call((iRcs, callingPackage) -> iRcs.createParticipantAliasChangedEvent(
+                getTimestamp(), getParticipant().getId(), getNewAlias(), callingPackage));
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEventDescriptor.java b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEventDescriptor.java
index b29896c..43b918c 100644
--- a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEventDescriptor.java
+++ b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEventDescriptor.java
@@ -41,9 +41,9 @@
 
     @Override
     @VisibleForTesting(visibility = PROTECTED)
-    public RcsParticipantAliasChangedEvent createRcsEvent() {
+    public RcsParticipantAliasChangedEvent createRcsEvent(RcsControllerCall rcsControllerCall) {
         return new RcsParticipantAliasChangedEvent(
-                mTimestamp, new RcsParticipant(mParticipantId), mNewAlias);
+                mTimestamp, new RcsParticipant(rcsControllerCall, mParticipantId), mNewAlias);
     }
 
     public static final @NonNull Creator<RcsParticipantAliasChangedEventDescriptor> CREATOR =
diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java
index 731c94e..0721dfd 100644
--- a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java
+++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java
@@ -30,10 +30,13 @@
  * @hide
  */
 public final class RcsParticipantQueryResult {
+    private final RcsControllerCall mRcsControllerCall;
     private final RcsParticipantQueryResultParcelable mRcsParticipantQueryResultParcelable;
 
     RcsParticipantQueryResult(
+            RcsControllerCall rcsControllerCall,
             RcsParticipantQueryResultParcelable rcsParticipantQueryResultParcelable) {
+        mRcsControllerCall = rcsControllerCall;
         mRcsParticipantQueryResultParcelable = rcsParticipantQueryResultParcelable;
     }
 
@@ -55,7 +58,7 @@
     @NonNull
     public List<RcsParticipant> getParticipants() {
         return mRcsParticipantQueryResultParcelable.mParticipantIds.stream()
-                .map(RcsParticipant::new)
+                .map(participantId -> new RcsParticipant(mRcsControllerCall, participantId))
                 .collect(Collectors.toList());
     }
 }
diff --git a/telephony/java/android/telephony/ims/RcsThread.java b/telephony/java/android/telephony/ims/RcsThread.java
index cf1dc76..efb2cca 100644
--- a/telephony/java/android/telephony/ims/RcsThread.java
+++ b/telephony/java/android/telephony/ims/RcsThread.java
@@ -33,6 +33,7 @@
 public abstract class RcsThread {
     /**
      * The rcs_participant_thread_id that represents this thread in the database
+     *
      * @hide
      */
     protected int mThreadId;
@@ -40,8 +41,14 @@
     /**
      * @hide
      */
-    protected RcsThread(int threadId) {
+    protected final RcsControllerCall mRcsControllerCall;
+
+    /**
+     * @hide
+     */
+    protected RcsThread(RcsControllerCall rcsControllerCall, int threadId) {
         mThreadId = threadId;
+        mRcsControllerCall = rcsControllerCall;
     }
 
     /**
@@ -51,7 +58,8 @@
     @WorkerThread
     @NonNull
     public RcsMessageSnippet getSnippet() throws RcsMessageStoreException {
-        return RcsControllerCall.call(iRcs -> iRcs.getMessageSnippet(mThreadId));
+        return mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.getMessageSnippet(mThreadId, callingPackage));
     }
 
     /**
@@ -64,8 +72,10 @@
     public RcsIncomingMessage addIncomingMessage(
             @NonNull RcsIncomingMessageCreationParams rcsIncomingMessageCreationParams)
             throws RcsMessageStoreException {
-        return new RcsIncomingMessage(RcsControllerCall.call(iRcs -> iRcs.addIncomingMessage(
-                mThreadId, rcsIncomingMessageCreationParams)));
+        int messageId = mRcsControllerCall.call(
+                (iRcs, callingPackage) -> iRcs.addIncomingMessage(mThreadId,
+                        rcsIncomingMessageCreationParams, callingPackage));
+        return new RcsIncomingMessage(mRcsControllerCall, messageId);
     }
 
     /**
@@ -78,10 +88,10 @@
     public RcsOutgoingMessage addOutgoingMessage(
             @NonNull RcsOutgoingMessageCreationParams rcsOutgoingMessageCreationParams)
             throws RcsMessageStoreException {
-        int messageId = RcsControllerCall.call(iRcs -> iRcs.addOutgoingMessage(
-                mThreadId, rcsOutgoingMessageCreationParams));
+        int messageId = mRcsControllerCall.call((iRcs, callingPackage) -> iRcs.addOutgoingMessage(
+                mThreadId, rcsOutgoingMessageCreationParams, callingPackage));
 
-        return new RcsOutgoingMessage(messageId);
+        return new RcsOutgoingMessage(mRcsControllerCall, messageId);
     }
 
     /**
@@ -92,9 +102,10 @@
      */
     @WorkerThread
     public void deleteMessage(@NonNull RcsMessage rcsMessage) throws RcsMessageStoreException {
-        RcsControllerCall.callWithNoReturn(
-                iRcs -> iRcs.deleteMessage(rcsMessage.getId(), rcsMessage.isIncoming(), mThreadId,
-                        isGroup()));
+        mRcsControllerCall.callWithNoReturn(
+                (iRcs, callingPackage) -> iRcs.deleteMessage(rcsMessage.getId(),
+                        rcsMessage.isIncoming(), mThreadId,
+                        isGroup(), callingPackage));
     }
 
     /**
@@ -108,9 +119,11 @@
     @WorkerThread
     @NonNull
     public RcsMessageQueryResult getMessages() throws RcsMessageStoreException {
-        RcsMessageQueryParams queryParameters =
+        RcsMessageQueryParams queryParams =
                 new RcsMessageQueryParams.Builder().setThread(this).build();
-        return RcsControllerCall.call(iRcs -> iRcs.getMessages(queryParameters));
+        return new RcsMessageQueryResult(mRcsControllerCall,
+                mRcsControllerCall.call(
+                        (iRcs, callingPackage) -> iRcs.getMessages(queryParams, callingPackage)));
     }
 
     /**
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java
index c77bdb3..3de25de 100644
--- a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java
@@ -33,9 +33,12 @@
  * @hide
  */
 public final class RcsThreadQueryResult {
+    private final RcsControllerCall mRcsControllerCall;
     private final RcsThreadQueryResultParcelable mRcsThreadQueryResultParcelable;
 
-    RcsThreadQueryResult(RcsThreadQueryResultParcelable rcsThreadQueryResultParcelable) {
+    RcsThreadQueryResult(RcsControllerCall rcsControllerCall,
+            RcsThreadQueryResultParcelable rcsThreadQueryResultParcelable) {
+        mRcsControllerCall = rcsControllerCall;
         mRcsThreadQueryResultParcelable = rcsThreadQueryResultParcelable;
     }
 
@@ -58,8 +61,8 @@
     public List<RcsThread> getThreads() {
         return mRcsThreadQueryResultParcelable.mRcsThreadIds.stream()
                 .map(typeIdPair -> typeIdPair.getType() == THREAD_TYPE_1_TO_1
-                        ? new Rcs1To1Thread(typeIdPair.getId())
-                        : new RcsGroupThread(typeIdPair.getId()))
+                        ? new Rcs1To1Thread(mRcsControllerCall, typeIdPair.getId())
+                        : new RcsGroupThread(mRcsControllerCall, typeIdPair.getId()))
                 .collect(Collectors.toList());
     }
 }
diff --git a/telephony/java/android/telephony/ims/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
index 50dc587..9ee15da 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
@@ -23,7 +23,7 @@
 import android.telephony.ims.RcsIncomingMessageCreationParams;
 import android.telephony.ims.RcsMessageSnippet;
 import android.telephony.ims.RcsMessageQueryParams;
-import android.telephony.ims.RcsMessageQueryResult;
+import android.telephony.ims.RcsMessageQueryResultParcelable;
 import android.telephony.ims.RcsOutgoingMessageCreationParams;
 import android.telephony.ims.RcsParticipantQueryParams;
 import android.telephony.ims.RcsParticipantQueryResultParcelable;
@@ -39,34 +39,34 @@
     /////////////////////////
     // RcsMessageStore APIs
     /////////////////////////
-    RcsThreadQueryResultParcelable getRcsThreads(in RcsThreadQueryParams queryParams);
+    RcsThreadQueryResultParcelable getRcsThreads(in RcsThreadQueryParams queryParams, String callingPackage);
 
     RcsThreadQueryResultParcelable getRcsThreadsWithToken(
-        in RcsQueryContinuationToken continuationToken);
+        in RcsQueryContinuationToken continuationToken, String callingPackage);
 
-    RcsParticipantQueryResultParcelable getParticipants(in RcsParticipantQueryParams queryParams);
+    RcsParticipantQueryResultParcelable getParticipants(in RcsParticipantQueryParams queryParams, String callingPackage);
 
     RcsParticipantQueryResultParcelable getParticipantsWithToken(
-        in RcsQueryContinuationToken continuationToken);
+        in RcsQueryContinuationToken continuationToken, String callingPackage);
 
-    RcsMessageQueryResult getMessages(in RcsMessageQueryParams queryParams);
+    RcsMessageQueryResultParcelable getMessages(in RcsMessageQueryParams queryParams, String callingPackage);
 
-    RcsMessageQueryResult getMessagesWithToken(
-        in RcsQueryContinuationToken continuationToken);
+    RcsMessageQueryResultParcelable getMessagesWithToken(
+        in RcsQueryContinuationToken continuationToken, String callingPackage);
 
-    RcsEventQueryResultDescriptor getEvents(in RcsEventQueryParams queryParams);
+    RcsEventQueryResultDescriptor getEvents(in RcsEventQueryParams queryParams, String callingPackage);
 
     RcsEventQueryResultDescriptor getEventsWithToken(
-        in RcsQueryContinuationToken continuationToken);
+        in RcsQueryContinuationToken continuationToken, String callingPackage);
 
     // returns true if the thread was successfully deleted
-    boolean deleteThread(int threadId, int threadType);
+    boolean deleteThread(int threadId, int threadType, String callingPackage);
 
     // Creates an Rcs1To1Thread and returns its row ID
-    int createRcs1To1Thread(int participantId);
+    int createRcs1To1Thread(int participantId, String callingPackage);
 
     // Creates an RcsGroupThread and returns its row ID
-    int createGroupThread(in int[] participantIds, String groupName, in Uri groupIcon);
+    int createGroupThread(in int[] participantIds, String groupName, in Uri groupIcon, String callingPackage);
 
     /////////////////////////
     // RcsThread APIs
@@ -74,128 +74,128 @@
 
     // Creates a new RcsIncomingMessage on the given thread and returns its row ID
     int addIncomingMessage(int rcsThreadId,
-            in RcsIncomingMessageCreationParams rcsIncomingMessageCreationParams);
+            in RcsIncomingMessageCreationParams rcsIncomingMessageCreationParams, String callingPackage);
 
     // Creates a new RcsOutgoingMessage on the given thread and returns its row ID
     int addOutgoingMessage(int rcsThreadId,
-            in RcsOutgoingMessageCreationParams rcsOutgoingMessageCreationParams);
+            in RcsOutgoingMessageCreationParams rcsOutgoingMessageCreationParams, String callingPackage);
 
     // TODO: modify RcsProvider URI's to allow deleting a message without specifying its thread
-    void deleteMessage(int rcsMessageId, boolean isIncoming, int rcsThreadId, boolean isGroup);
+    void deleteMessage(int rcsMessageId, boolean isIncoming, int rcsThreadId, boolean isGroup, String callingPackage);
 
-    RcsMessageSnippet getMessageSnippet(int rcsThreadId);
+    RcsMessageSnippet getMessageSnippet(int rcsThreadId, String callingPackage);
 
     /////////////////////////
     // Rcs1To1Thread APIs
     /////////////////////////
-    void set1To1ThreadFallbackThreadId(int rcsThreadId, long fallbackId);
+    void set1To1ThreadFallbackThreadId(int rcsThreadId, long fallbackId, String callingPackage);
 
-    long get1To1ThreadFallbackThreadId(int rcsThreadId);
+    long get1To1ThreadFallbackThreadId(int rcsThreadId, String callingPackage);
 
-    int get1To1ThreadOtherParticipantId(int rcsThreadId);
+    int get1To1ThreadOtherParticipantId(int rcsThreadId, String callingPackage);
 
     /////////////////////////
     // RcsGroupThread APIs
     /////////////////////////
-    void setGroupThreadName(int rcsThreadId, String groupName);
+    void setGroupThreadName(int rcsThreadId, String groupName, String callingPackage);
 
-    String getGroupThreadName(int rcsThreadId);
+    String getGroupThreadName(int rcsThreadId, String callingPackage);
 
-    void setGroupThreadIcon(int rcsThreadId, in Uri groupIcon);
+    void setGroupThreadIcon(int rcsThreadId, in Uri groupIcon, String callingPackage);
 
-    Uri getGroupThreadIcon(int rcsThreadId);
+    Uri getGroupThreadIcon(int rcsThreadId, String callingPackage);
 
-    void setGroupThreadOwner(int rcsThreadId, int participantId);
+    void setGroupThreadOwner(int rcsThreadId, int participantId, String callingPackage);
 
-    int getGroupThreadOwner(int rcsThreadId);
+    int getGroupThreadOwner(int rcsThreadId, String callingPackage);
 
-    void setGroupThreadConferenceUri(int rcsThreadId, in Uri conferenceUri);
+    void setGroupThreadConferenceUri(int rcsThreadId, in Uri conferenceUri, String callingPackage);
 
-    Uri getGroupThreadConferenceUri(int rcsThreadId);
+    Uri getGroupThreadConferenceUri(int rcsThreadId, String callingPackage);
 
-    void addParticipantToGroupThread(int rcsThreadId, int participantId);
+    void addParticipantToGroupThread(int rcsThreadId, int participantId, String callingPackage);
 
-    void removeParticipantFromGroupThread(int rcsThreadId, int participantId);
+    void removeParticipantFromGroupThread(int rcsThreadId, int participantId, String callingPackage);
 
     /////////////////////////
     // RcsParticipant APIs
     /////////////////////////
 
     // Creates a new RcsParticipant and returns its rowId
-    int createRcsParticipant(String canonicalAddress, String alias);
+    int createRcsParticipant(String canonicalAddress, String alias, String callingPackage);
 
-    String getRcsParticipantCanonicalAddress(int participantId);
+    String getRcsParticipantCanonicalAddress(int participantId, String callingPackage);
 
-    String getRcsParticipantAlias(int participantId);
+    String getRcsParticipantAlias(int participantId, String callingPackage);
 
-    void setRcsParticipantAlias(int id, String alias);
+    void setRcsParticipantAlias(int id, String alias, String callingPackage);
 
-    String getRcsParticipantContactId(int participantId);
+    String getRcsParticipantContactId(int participantId, String callingPackage);
 
-    void setRcsParticipantContactId(int participantId, String contactId);
+    void setRcsParticipantContactId(int participantId, String contactId, String callingPackage);
 
     /////////////////////////
     // RcsMessage APIs
     /////////////////////////
-    void setMessageSubId(int messageId, boolean isIncoming, int subId);
+    void setMessageSubId(int messageId, boolean isIncoming, int subId, String callingPackage);
 
-    int getMessageSubId(int messageId, boolean isIncoming);
+    int getMessageSubId(int messageId, boolean isIncoming, String callingPackage);
 
-    void setMessageStatus(int messageId, boolean isIncoming, int status);
+    void setMessageStatus(int messageId, boolean isIncoming, int status, String callingPackage);
 
-    int getMessageStatus(int messageId, boolean isIncoming);
+    int getMessageStatus(int messageId, boolean isIncoming, String callingPackage);
 
-    void setMessageOriginationTimestamp(int messageId, boolean isIncoming, long originationTimestamp);
+    void setMessageOriginationTimestamp(int messageId, boolean isIncoming, long originationTimestamp, String callingPackage);
 
-    long getMessageOriginationTimestamp(int messageId, boolean isIncoming);
+    long getMessageOriginationTimestamp(int messageId, boolean isIncoming, String callingPackage);
 
-    void setGlobalMessageIdForMessage(int messageId, boolean isIncoming, String globalId);
+    void setGlobalMessageIdForMessage(int messageId, boolean isIncoming, String globalId, String callingPackage);
 
-    String getGlobalMessageIdForMessage(int messageId, boolean isIncoming);
+    String getGlobalMessageIdForMessage(int messageId, boolean isIncoming, String callingPackage);
 
-    void setMessageArrivalTimestamp(int messageId, boolean isIncoming, long arrivalTimestamp);
+    void setMessageArrivalTimestamp(int messageId, boolean isIncoming, long arrivalTimestamp, String callingPackage);
 
-    long getMessageArrivalTimestamp(int messageId, boolean isIncoming);
+    long getMessageArrivalTimestamp(int messageId, boolean isIncoming, String callingPackage);
 
-    void setMessageSeenTimestamp(int messageId, boolean isIncoming, long seenTimestamp);
+    void setMessageSeenTimestamp(int messageId, boolean isIncoming, long seenTimestamp, String callingPackage);
 
-    long getMessageSeenTimestamp(int messageId, boolean isIncoming);
+    long getMessageSeenTimestamp(int messageId, boolean isIncoming, String callingPackage);
 
-    void setTextForMessage(int messageId, boolean isIncoming, String text);
+    void setTextForMessage(int messageId, boolean isIncoming, String text, String callingPackage);
 
-    String getTextForMessage(int messageId, boolean isIncoming);
+    String getTextForMessage(int messageId, boolean isIncoming, String callingPackage);
 
-    void setLatitudeForMessage(int messageId, boolean isIncoming, double latitude);
+    void setLatitudeForMessage(int messageId, boolean isIncoming, double latitude, String callingPackage);
 
-    double getLatitudeForMessage(int messageId, boolean isIncoming);
+    double getLatitudeForMessage(int messageId, boolean isIncoming, String callingPackage);
 
-    void setLongitudeForMessage(int messageId, boolean isIncoming, double longitude);
+    void setLongitudeForMessage(int messageId, boolean isIncoming, double longitude, String callingPackage);
 
-    double getLongitudeForMessage(int messageId, boolean isIncoming);
+    double getLongitudeForMessage(int messageId, boolean isIncoming, String callingPackage);
 
     // Returns the ID's of the file transfers attached to the given message
-    int[] getFileTransfersAttachedToMessage(int messageId, boolean isIncoming);
+    int[] getFileTransfersAttachedToMessage(int messageId, boolean isIncoming, String callingPackage);
 
-    int getSenderParticipant(int messageId);
+    int getSenderParticipant(int messageId, String callingPackage);
 
     /////////////////////////
     // RcsOutgoingMessageDelivery APIs
     /////////////////////////
 
     // Returns the participant ID's that this message is intended to be delivered to
-    int[] getMessageRecipients(int messageId);
+    int[] getMessageRecipients(int messageId, String callingPackage);
 
-    long getOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId);
+    long getOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId, String callingPackage);
 
-    void setOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId, long deliveredTimestamp);
+    void setOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId, long deliveredTimestamp, String callingPackage);
 
-    long getOutgoingDeliverySeenTimestamp(int messageId, int participantId);
+    long getOutgoingDeliverySeenTimestamp(int messageId, int participantId, String callingPackage);
 
-    void setOutgoingDeliverySeenTimestamp(int messageId, int participantId, long seenTimestamp);
+    void setOutgoingDeliverySeenTimestamp(int messageId, int participantId, long seenTimestamp, String callingPackage);
 
-    int getOutgoingDeliveryStatus(int messageId, int participantId);
+    int getOutgoingDeliveryStatus(int messageId, int participantId, String callingPackage);
 
-    void setOutgoingDeliveryStatus(int messageId, int participantId, int status);
+    void setOutgoingDeliveryStatus(int messageId, int participantId, int status, String callingPackage);
 
     /////////////////////////
     // RcsFileTransferPart APIs
@@ -203,64 +203,64 @@
 
     // Performs the initial write to storage and returns the row ID.
     int storeFileTransfer(int messageId, boolean isIncoming,
-            in RcsFileTransferCreationParams fileTransferCreationParams);
+            in RcsFileTransferCreationParams fileTransferCreationParams, String callingPackage);
 
-    void deleteFileTransfer(int partId);
+    void deleteFileTransfer(int partId, String callingPackage);
 
-    void setFileTransferSessionId(int partId, String sessionId);
+    void setFileTransferSessionId(int partId, String sessionId, String callingPackage);
 
-    String getFileTransferSessionId(int partId);
+    String getFileTransferSessionId(int partId, String callingPackage);
 
-    void setFileTransferContentUri(int partId, in Uri contentUri);
+    void setFileTransferContentUri(int partId, in Uri contentUri, String callingPackage);
 
-    Uri getFileTransferContentUri(int partId);
+    Uri getFileTransferContentUri(int partId, String callingPackage);
 
-    void setFileTransferContentType(int partId, String contentType);
+    void setFileTransferContentType(int partId, String contentType, String callingPackage);
 
-    String getFileTransferContentType(int partId);
+    String getFileTransferContentType(int partId, String callingPackage);
 
-    void setFileTransferFileSize(int partId, long fileSize);
+    void setFileTransferFileSize(int partId, long fileSize, String callingPackage);
 
-    long getFileTransferFileSize(int partId);
+    long getFileTransferFileSize(int partId, String callingPackage);
 
-    void setFileTransferTransferOffset(int partId, long transferOffset);
+    void setFileTransferTransferOffset(int partId, long transferOffset, String callingPackage);
 
-    long getFileTransferTransferOffset(int partId);
+    long getFileTransferTransferOffset(int partId, String callingPackage);
 
-    void setFileTransferStatus(int partId, int transferStatus);
+    void setFileTransferStatus(int partId, int transferStatus, String callingPackage);
 
-    int getFileTransferStatus(int partId);
+    int getFileTransferStatus(int partId, String callingPackage);
 
-    void setFileTransferWidth(int partId, int width);
+    void setFileTransferWidth(int partId, int width, String callingPackage);
 
-    int getFileTransferWidth(int partId);
+    int getFileTransferWidth(int partId, String callingPackage);
 
-    void setFileTransferHeight(int partId, int height);
+    void setFileTransferHeight(int partId, int height, String callingPackage);
 
-    int getFileTransferHeight(int partId);
+    int getFileTransferHeight(int partId, String callingPackage);
 
-    void setFileTransferLength(int partId, long length);
+    void setFileTransferLength(int partId, long length, String callingPackage);
 
-    long getFileTransferLength(int partId);
+    long getFileTransferLength(int partId, String callingPackage);
 
-    void setFileTransferPreviewUri(int partId, in Uri uri);
+    void setFileTransferPreviewUri(int partId, in Uri uri, String callingPackage);
 
-    Uri getFileTransferPreviewUri(int partId);
+    Uri getFileTransferPreviewUri(int partId, String callingPackage);
 
-    void setFileTransferPreviewType(int partId, String type);
+    void setFileTransferPreviewType(int partId, String type, String callingPackage);
 
-    String getFileTransferPreviewType(int partId);
+    String getFileTransferPreviewType(int partId, String callingPackage);
 
     /////////////////////////
     // RcsEvent APIs
     /////////////////////////
-    int createGroupThreadNameChangedEvent(long timestamp, int threadId, int originationParticipantId, String newName);
+    int createGroupThreadNameChangedEvent(long timestamp, int threadId, int originationParticipantId, String newName, String callingPackage);
 
-    int createGroupThreadIconChangedEvent(long timestamp, int threadId, int originationParticipantId, in Uri newIcon);
+    int createGroupThreadIconChangedEvent(long timestamp, int threadId, int originationParticipantId, in Uri newIcon, String callingPackage);
 
-    int createGroupThreadParticipantJoinedEvent(long timestamp, int threadId, int originationParticipantId, int participantId);
+    int createGroupThreadParticipantJoinedEvent(long timestamp, int threadId, int originationParticipantId, int participantId, String callingPackage);
 
-    int createGroupThreadParticipantLeftEvent(long timestamp, int threadId, int originationParticipantId, int participantId);
+    int createGroupThreadParticipantLeftEvent(long timestamp, int threadId, int originationParticipantId, int participantId, String callingPackage);
 
-    int createParticipantAliasChangedEvent(long timestamp, int participantId, String newAlias);
+    int createParticipantAliasChangedEvent(long timestamp, int participantId, String newAlias, String callingPackage);
 }
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 0ad6096..71ea881 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1586,7 +1586,7 @@
     int getCardIdForDefaultEuicc(int subId, String callingPackage);
 
     /**
-     * Gets information about currently inserted UICCs and enabled eUICCs.
+     * Gets information about currently inserted UICCs and eUICCs.
      * <p>
      * Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      * <p>
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java
index 89d32ab..658c3ed 100644
--- a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java
@@ -49,9 +49,7 @@
                 RcsGroupThreadIconChangedEventDescriptor.CREATOR.createFromParcel(parcel);
 
         RcsGroupThreadIconChangedEvent iconChangedEvent =
-                iconChangedEventDescriptor.createRcsEvent();
-
-
+                iconChangedEventDescriptor.createRcsEvent(null);
 
         assertThat(iconChangedEvent.getNewIcon()).isEqualTo(newIconUri);
         assertThat(iconChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1);
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java
index 726b9cd..9fe67ad 100644
--- a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java
@@ -48,7 +48,7 @@
                 .createFromParcel(parcel);
 
         RcsGroupThreadNameChangedEvent nameChangedEvent =
-                nameChangedEventDescriptor.createRcsEvent();
+                nameChangedEventDescriptor.createRcsEvent(null);
 
         assertThat(nameChangedEvent.getNewName()).isEqualTo(newName);
         assertThat(nameChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1);
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java
index a109310..18d5621 100644
--- a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java
@@ -47,7 +47,7 @@
                 .createFromParcel(parcel);
 
         RcsGroupThreadParticipantJoinedEvent participantJoinedEvent =
-                participantJoinedEventDescriptor.createRcsEvent();
+                participantJoinedEventDescriptor.createRcsEvent(null);
 
         assertThat(participantJoinedEvent.getJoinedParticipant().getId()).isEqualTo(2);
         assertThat(participantJoinedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1);
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java
index de2688c..53a6bba 100644
--- a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java
@@ -48,7 +48,7 @@
                 .createFromParcel(parcel);
 
         RcsGroupThreadParticipantLeftEvent participantLeftEvent =
-                participantLeftEventDescriptor.createRcsEvent();
+                participantLeftEventDescriptor.createRcsEvent(null);
 
         assertThat(participantLeftEvent.getRcsGroupThread().getThreadId()).isEqualTo(1);
         assertThat(participantLeftEvent.getLeavingParticipant().getId()).isEqualTo(2);
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java
index 5724054..dcf68ff 100644
--- a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java
@@ -47,7 +47,7 @@
                 .createFromParcel(parcel);
 
         RcsParticipantAliasChangedEvent aliasChangedEvent =
-                aliasChangedEventDescriptor.createRcsEvent();
+                aliasChangedEventDescriptor.createRcsEvent(null);
 
         assertThat(aliasChangedEvent.getParticipant().getId()).isEqualTo(mParticipantId);
         assertThat(aliasChangedEvent.getNewAlias()).isEqualTo(NEW_ALIAS);
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java
index beb4f8a..551a228 100644
--- a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParamsTest.java
@@ -33,7 +33,7 @@
 
     @Test
     public void testCanUnparcel() {
-        RcsParticipant rcsParticipant = new RcsParticipant(1);
+        RcsParticipant rcsParticipant = new RcsParticipant(null, 1);
         RcsThreadQueryParams rcsThreadQueryParams = new RcsThreadQueryParams.Builder()
                 .setThreadType(THREAD_TYPE_GROUP)
                 .setParticipant(rcsParticipant)
diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp
index 3b2e34a..07525a6 100644
--- a/tests/net/common/Android.bp
+++ b/tests/net/common/Android.bp
@@ -23,6 +23,7 @@
         "androidx.test.rules",
         "frameworks-net-testutils",
         "junit",
+        "mockito-target-minus-junit4",
     ],
     libs: [
         "android.test.base.stubs",
diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java
new file mode 100644
index 0000000..eed7159f
--- /dev/null
+++ b/tests/net/common/java/android/net/CaptivePortalTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CaptivePortalTest {
+    private static final int DEFAULT_TIMEOUT_MS = 5000;
+    private static final String TEST_PACKAGE_NAME = "com.google.android.test";
+
+    private final class MyCaptivePortalImpl extends ICaptivePortal.Stub {
+        int mCode = -1;
+        String mPackageName = null;
+
+        @Override
+        public void appResponse(final int response) throws RemoteException {
+            mCode = response;
+        }
+
+        @Override
+        public void logEvent(int eventId, String packageName) throws RemoteException {
+            mCode = eventId;
+            mPackageName = packageName;
+        }
+    }
+
+    private interface TestFunctor {
+        void useCaptivePortal(CaptivePortal o);
+    }
+
+    private MyCaptivePortalImpl runCaptivePortalTest(TestFunctor f) {
+        final MyCaptivePortalImpl cp = new MyCaptivePortalImpl();
+        f.useCaptivePortal(new CaptivePortal(cp.asBinder()));
+        return cp;
+    }
+
+    @Test
+    public void testReportCaptivePortalDismissed() {
+        final MyCaptivePortalImpl result =
+                runCaptivePortalTest(c -> c.reportCaptivePortalDismissed());
+        assertEquals(result.mCode, CaptivePortal.APP_RETURN_DISMISSED);
+    }
+
+    @Test
+    public void testIgnoreNetwork() {
+        final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.ignoreNetwork());
+        assertEquals(result.mCode, CaptivePortal.APP_RETURN_UNWANTED);
+    }
+
+    @Test
+    public void testUseNetwork() {
+        final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.useNetwork());
+        assertEquals(result.mCode, CaptivePortal.APP_RETURN_WANTED_AS_IS);
+    }
+
+    @Test
+    public void testLogEvent() {
+        final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent(
+                MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
+                TEST_PACKAGE_NAME));
+        assertEquals(result.mCode, MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY);
+        assertEquals(result.mPackageName, TEST_PACKAGE_NAME);
+    }
+}
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index ad76388..6bc7c1b 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -33,6 +33,8 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
 import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 
@@ -585,4 +587,20 @@
         nc2.set(nc1);  // Overwrites, as opposed to combineCapabilities
         assertEquals(nc1, nc2);
     }
+
+    @Test
+    public void testGetTransportTypes() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        nc.addTransportType(TRANSPORT_CELLULAR);
+        nc.addTransportType(TRANSPORT_WIFI);
+        nc.addTransportType(TRANSPORT_VPN);
+        nc.addTransportType(TRANSPORT_TEST);
+
+        final int[] transportTypes = nc.getTransportTypes();
+        assertEquals(4, transportTypes.length);
+        assertEquals(TRANSPORT_CELLULAR, transportTypes[0]);
+        assertEquals(TRANSPORT_WIFI, transportTypes[1]);
+        assertEquals(TRANSPORT_VPN, transportTypes[2]);
+        assertEquals(TRANSPORT_TEST, transportTypes[3]);
+    }
 }
diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java
index 0bee7cd..bef66b2 100644
--- a/tests/net/common/java/android/net/NetworkTest.java
+++ b/tests/net/common/java/android/net/NetworkTest.java
@@ -155,4 +155,12 @@
     private static <T> void assertNotEqual(T t1, T t2) {
         assertFalse(Objects.equals(t1, t2));
     }
+
+    @Test
+    public void testGetPrivateDnsBypassingCopy() {
+        final Network copy = mNetwork.getPrivateDnsBypassingCopy();
+        assertEquals(mNetwork.netId, copy.netId);
+        assertNotEqual(copy.netId, copy.getNetIdForResolv());
+        assertNotEqual(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv());
+    }
 }
diff --git a/tests/net/common/java/android/net/StaticIpConfigurationTest.java b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
index 8449ca7..5096be2 100644
--- a/tests/net/common/java/android/net/StaticIpConfigurationTest.java
+++ b/tests/net/common/java/android/net/StaticIpConfigurationTest.java
@@ -31,7 +31,9 @@
 import org.junit.runner.RunWith;
 
 import java.net.InetAddress;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Objects;
 
 @RunWith(AndroidJUnit4.class)
@@ -46,6 +48,7 @@
     private static final InetAddress DNS2 = IpAddress("8.8.4.4");
     private static final InetAddress DNS3 = IpAddress("4.2.2.2");
     private static final String IFACE = "eth0";
+    private static final String FAKE_DOMAINS = "google.com";
 
     private static InetAddress IpAddress(String addr) {
         return InetAddress.parseNumericAddress(addr);
@@ -69,7 +72,7 @@
         s.dnsServers.add(DNS1);
         s.dnsServers.add(DNS2);
         s.dnsServers.add(DNS3);
-        s.domains = "google.com";
+        s.domains = FAKE_DOMAINS;
         return s;
     }
 
@@ -178,8 +181,8 @@
         expected.addDnsServer(DNS3);
         assertEquals(expected, s.toLinkProperties(IFACE));
 
-        s.domains = "google.com";
-        expected.setDomains("google.com");
+        s.domains = FAKE_DOMAINS;
+        expected.setDomains(FAKE_DOMAINS);
         assertEquals(expected, s.toLinkProperties(IFACE));
 
         s.gateway = null;
@@ -218,4 +221,53 @@
         StaticIpConfiguration s2 = passThroughParcel(s);
         assertEquals(s, s2);
     }
+
+    @Test
+    public void testBuilder() {
+        final ArrayList<InetAddress> dnsServers = new ArrayList<>();
+        dnsServers.add(DNS1);
+
+        final StaticIpConfiguration s = new StaticIpConfiguration.Builder()
+                .setIpAddress(ADDR)
+                .setGateway(GATEWAY)
+                .setDomains(FAKE_DOMAINS)
+                .setDnsServers(dnsServers)
+                .build();
+
+        assertEquals(s.ipAddress, s.getIpAddress());
+        assertEquals(ADDR, s.getIpAddress());
+        assertEquals(s.gateway, s.getGateway());
+        assertEquals(GATEWAY, s.getGateway());
+        assertEquals(s.domains, s.getDomains());
+        assertEquals(FAKE_DOMAINS, s.getDomains());
+        assertTrue(s.dnsServers.equals(s.getDnsServers()));
+        assertEquals(1, s.getDnsServers().size());
+        assertEquals(DNS1, s.getDnsServers().get(0));
+    }
+
+    @Test
+    public void testAddDnsServers() {
+        final StaticIpConfiguration s = new StaticIpConfiguration((StaticIpConfiguration) null);
+        checkEmpty(s);
+
+        s.addDnsServer(DNS1);
+        assertEquals(1, s.getDnsServers().size());
+        assertEquals(DNS1, s.getDnsServers().get(0));
+
+        s.addDnsServer(DNS2);
+        s.addDnsServer(DNS3);
+        assertEquals(3, s.getDnsServers().size());
+        assertEquals(DNS2, s.getDnsServers().get(1));
+        assertEquals(DNS3, s.getDnsServers().get(2));
+    }
+
+    @Test
+    public void testGetRoutes() {
+        final StaticIpConfiguration s = makeTestObject();
+        final List<RouteInfo> routeInfoList = s.getRoutes(IFACE);
+
+        assertEquals(2, routeInfoList.size());
+        assertEquals(new RouteInfo(ADDR, (InetAddress) null, IFACE), routeInfoList.get(0));
+        assertEquals(new RouteInfo((IpPrefix) null, GATEWAY, IFACE), routeInfoList.get(1));
+    }
 }
diff --git a/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
new file mode 100644
index 0000000..8d055c9
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ApfProgramEventTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.metrics;
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ApfProgramEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    private infix fun Int.hasFlag(flag: Int) = (this and (1 shl flag)) != 0
+
+    @Test
+    fun testBuilderAndParcel() {
+        val apfProgramEvent = ApfProgramEvent.Builder()
+                .setLifetime(1)
+                .setActualLifetime(2)
+                .setFilteredRas(3)
+                .setCurrentRas(4)
+                .setProgramLength(5)
+                .setFlags(true, true)
+                .build()
+
+        assertEquals(1, apfProgramEvent.lifetime)
+        assertEquals(2, apfProgramEvent.actualLifetime)
+        assertEquals(3, apfProgramEvent.filteredRas)
+        assertEquals(4, apfProgramEvent.currentRas)
+        assertEquals(5, apfProgramEvent.programLength)
+        assertEquals(ApfProgramEvent.flagsFor(true, true), apfProgramEvent.flags)
+
+        testParcel(apfProgramEvent, 6)
+    }
+
+    @Test
+    fun testFlagsFor() {
+        var flags = ApfProgramEvent.flagsFor(false, false)
+        assertFalse(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+        assertFalse(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+        flags = ApfProgramEvent.flagsFor(true, false)
+        assertTrue(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+        assertFalse(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+        flags = ApfProgramEvent.flagsFor(false, true)
+        assertFalse(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+        assertTrue(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+
+        flags = ApfProgramEvent.flagsFor(true, true)
+        assertTrue(flags hasFlag ApfProgramEvent.FLAG_HAS_IPV4_ADDRESS)
+        assertTrue(flags hasFlag ApfProgramEvent.FLAG_MULTICAST_FILTER_ON)
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/ApfStatsTest.kt b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
new file mode 100644
index 0000000..f8eb40c
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ApfStatsTest.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ApfStatsTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testBuilderAndParcel() {
+        val apfStats = ApfStats.Builder()
+                .setDurationMs(Long.MAX_VALUE)
+                .setReceivedRas(1)
+                .setMatchingRas(2)
+                .setDroppedRas(3)
+                .setZeroLifetimeRas(4)
+                .setParseErrors(5)
+                .setProgramUpdates(6)
+                .setProgramUpdatesAll(7)
+                .setProgramUpdatesAllowingMulticast(8)
+                .setMaxProgramSize(9)
+                .build()
+
+        assertEquals(Long.MAX_VALUE, apfStats.durationMs)
+        assertEquals(1, apfStats.receivedRas)
+        assertEquals(2, apfStats.matchingRas)
+        assertEquals(3, apfStats.droppedRas)
+        assertEquals(4, apfStats.zeroLifetimeRas)
+        assertEquals(5, apfStats.parseErrors)
+        assertEquals(6, apfStats.programUpdates)
+        assertEquals(7, apfStats.programUpdatesAll)
+        assertEquals(8, apfStats.programUpdatesAllowingMulticast)
+        assertEquals(9, apfStats.maxProgramSize)
+
+        testParcel(apfStats, 10)
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt
new file mode 100644
index 0000000..36e9f8c
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/DhcpClientEventTest.kt
@@ -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.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val FAKE_MESSAGE = "test"
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DhcpClientEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testBuilderAndParcel() {
+        val dhcpClientEvent = DhcpClientEvent.Builder()
+                .setMsg(FAKE_MESSAGE)
+                .setDurationMs(Integer.MAX_VALUE)
+                .build()
+
+        assertEquals(FAKE_MESSAGE, dhcpClientEvent.msg)
+        assertEquals(Integer.MAX_VALUE, dhcpClientEvent.durationMs)
+
+        testParcel(dhcpClientEvent, 2)
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
index e191953..e9d5e6d 100644
--- a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
+++ b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt
@@ -13,9 +13,7 @@
 import org.junit.runner.RunWith
 
 private const val TEST_ERROR_CODE = 12345
-/**
- * DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java)
- */
+//DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java due to it's protected)
 private const val DHCP_SUBNET_MASK = 1
 
 @RunWith(AndroidJUnit4::class)
diff --git a/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java b/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java
new file mode 100644
index 0000000..d4780d3
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/IpConnectivityLogTest.java
@@ -0,0 +1,161 @@
+/*
+ * 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.metrics;
+
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.Network;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.BitUtils;
+
+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.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpConnectivityLogTest {
+    private static final int FAKE_NET_ID = 100;
+    private static final int[] FAKE_TRANSPORT_TYPES = BitUtils.unpackBits(TRANSPORT_WIFI);
+    private static final long FAKE_TIME_STAMP = System.currentTimeMillis();
+    private static final String FAKE_INTERFACE_NAME = "test";
+    private static final IpReachabilityEvent FAKE_EV =
+            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
+
+    @Mock IIpConnectivityMetrics mMockService;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testLoggingEvents() throws Exception {
+        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+        assertTrue(logger.log(FAKE_EV));
+        assertTrue(logger.log(FAKE_TIME_STAMP, FAKE_EV));
+        assertTrue(logger.log(FAKE_NET_ID, FAKE_TRANSPORT_TYPES, FAKE_EV));
+        assertTrue(logger.log(new Network(FAKE_NET_ID), FAKE_TRANSPORT_TYPES, FAKE_EV));
+        assertTrue(logger.log(FAKE_INTERFACE_NAME, FAKE_EV));
+        assertTrue(logger.log(makeExpectedEvent(FAKE_TIME_STAMP, FAKE_NET_ID, TRANSPORT_WIFI,
+                FAKE_INTERFACE_NAME)));
+
+        List<ConnectivityMetricsEvent> got = verifyEvents(6);
+        assertEventsEqual(makeExpectedEvent(got.get(0).timestamp, 0, 0, null), got.get(0));
+        assertEventsEqual(makeExpectedEvent(FAKE_TIME_STAMP, 0, 0, null), got.get(1));
+        assertEventsEqual(makeExpectedEvent(got.get(2).timestamp, FAKE_NET_ID,
+                TRANSPORT_WIFI, null), got.get(2));
+        assertEventsEqual(makeExpectedEvent(got.get(3).timestamp, FAKE_NET_ID,
+                TRANSPORT_WIFI, null), got.get(3));
+        assertEventsEqual(makeExpectedEvent(got.get(4).timestamp, 0, 0, FAKE_INTERFACE_NAME),
+                got.get(4));
+        assertEventsEqual(makeExpectedEvent(FAKE_TIME_STAMP, FAKE_NET_ID,
+                TRANSPORT_WIFI, FAKE_INTERFACE_NAME), got.get(5));
+    }
+
+    @Test
+    public void testLoggingEventsWithMultipleCallers() throws Exception {
+        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
+
+        final int nCallers = 10;
+        final int nEvents = 10;
+        for (int n = 0; n < nCallers; n++) {
+            final int i = n;
+            new Thread() {
+                public void run() {
+                    for (int j = 0; j < nEvents; j++) {
+                        assertTrue(logger.log(makeExpectedEvent(
+                                FAKE_TIME_STAMP + i * 100 + j,
+                                FAKE_NET_ID + i * 100 + j,
+                                ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR,
+                                FAKE_INTERFACE_NAME)));
+                    }
+                }
+            }.start();
+        }
+
+        List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
+        Collections.sort(got, EVENT_COMPARATOR);
+        Iterator<ConnectivityMetricsEvent> iter = got.iterator();
+        for (int i = 0; i < nCallers; i++) {
+            for (int j = 0; j < nEvents; j++) {
+                final long expectedTimestamp = FAKE_TIME_STAMP + i * 100 + j;
+                final int expectedNetId = FAKE_NET_ID + i * 100 + j;
+                final long expectedTransports =
+                        ((i + j) % 2 == 0) ? TRANSPORT_WIFI : TRANSPORT_CELLULAR;
+                assertEventsEqual(makeExpectedEvent(expectedTimestamp, expectedNetId,
+                        expectedTransports, FAKE_INTERFACE_NAME), iter.next());
+            }
+        }
+    }
+
+    private List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
+        ArgumentCaptor<ConnectivityMetricsEvent> captor =
+                ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
+        verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
+        return captor.getAllValues();
+    }
+
+    private List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
+        return verifyEvents(n, 10);
+    }
+
+
+    private ConnectivityMetricsEvent makeExpectedEvent(long timestamp, int netId, long transports,
+            String ifname) {
+        ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
+        ev.timestamp = timestamp;
+        ev.data = FAKE_EV;
+        ev.netId = netId;
+        ev.transports = transports;
+        ev.ifname = ifname;
+        return ev;
+    }
+
+    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
+    private void assertEventsEqual(ConnectivityMetricsEvent expected,
+            ConnectivityMetricsEvent got) {
+        assertEquals(expected.data, got.data);
+        assertEquals(expected.timestamp, got.timestamp);
+        assertEquals(expected.netId, got.netId);
+        assertEquals(expected.transports, got.transports);
+        assertEquals(expected.ifname, got.ifname);
+    }
+
+    static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
+            Comparator.comparingLong((ev) -> ev.timestamp);
+}
diff --git a/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt b/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt
new file mode 100644
index 0000000..5144ca5
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/IpManagerEventTest.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class IpManagerEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testConstructorAndParcel() {
+        (IpManagerEvent.PROVISIONING_OK..IpManagerEvent.ERROR_INTERFACE_NOT_FOUND).forEach {
+            val ipManagerEvent = IpManagerEvent(it, Long.MAX_VALUE)
+            assertEquals(it, ipManagerEvent.eventType)
+            assertEquals(Long.MAX_VALUE, ipManagerEvent.durationMs)
+
+            testParcel(ipManagerEvent, 2)
+        }
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
new file mode 100644
index 0000000..d76ebf6
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/IpReachabilityEventTest.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class IpReachabilityEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testConstructorAndParcel() {
+        (IpReachabilityEvent.PROBE..IpReachabilityEvent.PROVISIONING_LOST_ORGANIC).forEach {
+            val ipReachabilityEvent = IpReachabilityEvent(it)
+            assertEquals(it, ipReachabilityEvent.eventType)
+
+            testParcel(ipReachabilityEvent, 1)
+        }
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/NetworkEventTest.kt b/tests/net/common/java/android/net/metrics/NetworkEventTest.kt
new file mode 100644
index 0000000..8b52e81
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/NetworkEventTest.kt
@@ -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.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testConstructorAndParcel() {
+        (NetworkEvent.NETWORK_CONNECTED..NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY).forEach {
+            var networkEvent = NetworkEvent(it)
+            assertEquals(it, networkEvent.eventType)
+            assertEquals(0, networkEvent.durationMs)
+
+            networkEvent = NetworkEvent(it, Long.MAX_VALUE)
+            assertEquals(it, networkEvent.eventType)
+            assertEquals(Long.MAX_VALUE, networkEvent.durationMs)
+
+            testParcel(networkEvent, 2)
+        }
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/RaEventTest.kt b/tests/net/common/java/android/net/metrics/RaEventTest.kt
new file mode 100644
index 0000000..f38d328
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/RaEventTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val NO_LIFETIME: Long = -1L
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class RaEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    @Test
+    fun testConstructorAndParcel() {
+        var raEvent = RaEvent.Builder().build()
+        assertEquals(NO_LIFETIME, raEvent.routerLifetime)
+        assertEquals(NO_LIFETIME, raEvent.prefixValidLifetime)
+        assertEquals(NO_LIFETIME, raEvent.prefixPreferredLifetime)
+        assertEquals(NO_LIFETIME, raEvent.routeInfoLifetime)
+        assertEquals(NO_LIFETIME, raEvent.rdnssLifetime)
+        assertEquals(NO_LIFETIME, raEvent.dnsslLifetime)
+
+        raEvent = RaEvent.Builder()
+                .updateRouterLifetime(1)
+                .updatePrefixValidLifetime(2)
+                .updatePrefixPreferredLifetime(3)
+                .updateRouteInfoLifetime(4)
+                .updateRdnssLifetime(5)
+                .updateDnsslLifetime(6)
+                .build()
+        assertEquals(1, raEvent.routerLifetime)
+        assertEquals(2, raEvent.prefixValidLifetime)
+        assertEquals(3, raEvent.prefixPreferredLifetime)
+        assertEquals(4, raEvent.routeInfoLifetime)
+        assertEquals(5, raEvent.rdnssLifetime)
+        assertEquals(6, raEvent.dnsslLifetime)
+
+        raEvent = RaEvent.Builder()
+                .updateRouterLifetime(Long.MIN_VALUE)
+                .updateRouterLifetime(Long.MAX_VALUE)
+                .build()
+        assertEquals(Long.MIN_VALUE, raEvent.routerLifetime)
+
+        raEvent = RaEvent(1, 2, 3, 4, 5, 6)
+        assertEquals(1, raEvent.routerLifetime)
+        assertEquals(2, raEvent.prefixValidLifetime)
+        assertEquals(3, raEvent.prefixPreferredLifetime)
+        assertEquals(4, raEvent.routeInfoLifetime)
+        assertEquals(5, raEvent.rdnssLifetime)
+        assertEquals(6, raEvent.dnsslLifetime)
+
+        testParcel(raEvent, 6)
+    }
+}
diff --git a/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
new file mode 100644
index 0000000..c0cef8f
--- /dev/null
+++ b/tests/net/common/java/android/net/metrics/ValidationProbeEventTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.metrics
+
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.ParcelableTestUtil
+import com.android.internal.util.TestUtils
+import java.lang.reflect.Modifier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val FIRST_VALIDATION: Int = 1 shl 8
+private const val REVALIDATION: Int = 2 shl 8
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class ValidationProbeEventTest {
+    private fun <T: Parcelable> testParcel(obj: T, fieldCount: Int) {
+        ParcelableTestUtil.assertFieldCountEquals(fieldCount, obj::class.java)
+        TestUtils.assertParcelingIsLossless(obj)
+    }
+
+    private infix fun Int.hasType(type: Int) = (type and this) == type
+
+    @Test
+    fun testBuilderAndParcel() {
+        var validationProbeEvent = ValidationProbeEvent.Builder()
+                .setProbeType(ValidationProbeEvent.PROBE_DNS, false).build()
+
+        assertTrue(validationProbeEvent.probeType hasType REVALIDATION)
+
+        validationProbeEvent = ValidationProbeEvent.Builder()
+                .setDurationMs(Long.MAX_VALUE)
+                .setProbeType(ValidationProbeEvent.PROBE_DNS, true)
+                .setReturnCode(ValidationProbeEvent.DNS_SUCCESS)
+                .build()
+
+        assertEquals(Long.MAX_VALUE, validationProbeEvent.durationMs)
+        assertTrue(validationProbeEvent.probeType hasType ValidationProbeEvent.PROBE_DNS)
+        assertTrue(validationProbeEvent.probeType hasType FIRST_VALIDATION)
+        assertEquals(ValidationProbeEvent.DNS_SUCCESS, validationProbeEvent.returnCode)
+
+        testParcel(validationProbeEvent, 3)
+    }
+
+    @Test
+    fun testGetProbeName() {
+        val probeFields = ValidationProbeEvent::class.java.declaredFields.filter {
+            it.type == Int::class.javaPrimitiveType
+              && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers)
+              && it.name.contains("PROBE")
+        }
+
+        probeFields.forEach {
+            val intValue = it.getInt(null)
+            val stringValue = ValidationProbeEvent.getProbeName(intValue)
+            assertEquals(it.name, stringValue)
+        }
+
+    }
+}
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
index 18c6768..8ff2de9 100644
--- a/tests/net/java/android/net/IpMemoryStoreTest.java
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -16,10 +16,26 @@
 
 package android.net;
 
-import static org.mockito.ArgumentMatchers.any;
+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.mockito.Mockito.any;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
+import android.net.ipmemorystore.Blob;
+import android.net.ipmemorystore.IOnStatusListener;
+import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.NetworkAttributesParcelable;
+import android.net.ipmemorystore.Status;
+import android.os.RemoteException;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -27,28 +43,57 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class IpMemoryStoreTest {
+    private static final String TAG = IpMemoryStoreTest.class.getSimpleName();
+    private static final String TEST_CLIENT_ID = "testClientId";
+    private static final String TEST_DATA_NAME = "testData";
+    private static final String TEST_OTHER_DATA_NAME = TEST_DATA_NAME + "Other";
+    private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
+            -128, 0, 89, 112, 91, -34 };
+    private static final NetworkAttributes TEST_NETWORK_ATTRIBUTES = buildTestNetworkAttributes(
+            "hint", 219);
+
     @Mock
     Context mMockContext;
     @Mock
     NetworkStackClient mNetworkStackClient;
     @Mock
     IIpMemoryStore mMockService;
+    @Mock
+    IOnStatusListener mIOnStatusListener;
     IpMemoryStore mStore;
 
+    @Captor
+    ArgumentCaptor<IIpMemoryStoreCallbacks> mCbCaptor;
+    @Captor
+    ArgumentCaptor<NetworkAttributesParcelable> mNapCaptor;
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        doAnswer(invocation -> {
-            ((IIpMemoryStoreCallbacks) invocation.getArgument(0))
-                    .onIpMemoryStoreFetched(mMockService);
-            return null;
-        }).when(mNetworkStackClient).fetchIpMemoryStore(any());
+    }
+
+    private void startIpMemoryStore(boolean supplyService) {
+        if (supplyService) {
+            doAnswer(invocation -> {
+                ((IIpMemoryStoreCallbacks) invocation.getArgument(0))
+                        .onIpMemoryStoreFetched(mMockService);
+                return null;
+            }).when(mNetworkStackClient).fetchIpMemoryStore(any());
+        } else {
+            doNothing().when(mNetworkStackClient).fetchIpMemoryStore(mCbCaptor.capture());
+        }
         mStore = new IpMemoryStore(mMockContext) {
             @Override
             protected NetworkStackClient getNetworkStackClient() {
@@ -57,24 +102,228 @@
         };
     }
 
-    @Test
-    public void testNetworkAttributes() {
-        // TODO : implement this
+    private static NetworkAttributes buildTestNetworkAttributes(String hint, int mtu) {
+        return new NetworkAttributes.Builder()
+                .setGroupHint(hint)
+                .setMtu(mtu)
+                .build();
     }
 
     @Test
-    public void testPrivateData() {
-        // TODO : implement this
+    public void testNetworkAttributes() throws Exception {
+        startIpMemoryStore(true);
+        final String l2Key = "fakeKey";
+
+        mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        verify(mMockService, times(1)).storeNetworkAttributes(eq(l2Key),
+                mNapCaptor.capture(), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+
+        mStore.retrieveNetworkAttributes(l2Key,
+                (status, key, attr) -> {
+                    assertTrue("Retrieve network attributes not successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+                });
+
+        verify(mMockService, times(1)).retrieveNetworkAttributes(eq(l2Key), any());
     }
 
     @Test
-    public void testFindL2Key() {
-        // TODO : implement this
+    public void testPrivateData() throws RemoteException {
+        startIpMemoryStore(true);
+        final Blob b = new Blob();
+        b.data = TEST_BLOB_DATA;
+        final String l2Key = "fakeKey";
+
+        mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        verify(mMockService, times(1)).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+                eq(b), any());
+
+        mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+                (status, key, name, data) -> {
+                    assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(name, TEST_DATA_NAME);
+                    assertTrue(Arrays.equals(b.data, data.data));
+                });
+        verify(mMockService, times(1)).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+                eq(TEST_OTHER_DATA_NAME), any());
     }
 
     @Test
-    public void testIsSameNetwork() {
-        // TODO : implement this
+    public void testFindL2Key()
+            throws UnknownHostException, RemoteException, Exception {
+        startIpMemoryStore(true);
+        final String l2Key = "fakeKey";
+
+        mStore.findL2Key(TEST_NETWORK_ATTRIBUTES,
+                (status, key) -> {
+                    assertTrue("Retrieve network sameness not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                });
+        verify(mMockService, times(1)).findL2Key(mNapCaptor.capture(), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
     }
 
+    @Test
+    public void testIsSameNetwork() throws UnknownHostException, RemoteException {
+        startIpMemoryStore(true);
+        final String l2Key1 = "fakeKey1";
+        final String l2Key2 = "fakeKey2";
+
+        mStore.isSameNetwork(l2Key1, l2Key2,
+                (status, answer) -> {
+                    assertFalse("Retrieve network sameness suspiciously successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode);
+                    assertNull(answer);
+                });
+        verify(mMockService, times(1)).isSameNetwork(eq(l2Key1), eq(l2Key2), any());
+    }
+
+    @Test
+    public void testEnqueuedIpMsRequests() throws Exception {
+        startIpMemoryStore(false);
+
+        final Blob b = new Blob();
+        b.data = TEST_BLOB_DATA;
+        final String l2Key = "fakeKey";
+
+        // enqueue multiple ipms requests
+        mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveNetworkAttributes(l2Key,
+                (status, key, attr) -> {
+                    assertTrue("Retrieve network attributes not successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+                });
+        mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+                (status, key, name, data) -> {
+                    assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(name, TEST_DATA_NAME);
+                    assertTrue(Arrays.equals(b.data, data.data));
+                });
+
+        // get ipms service ready
+        mCbCaptor.getValue().onIpMemoryStoreFetched(mMockService);
+
+        InOrder inOrder = inOrder(mMockService);
+
+        inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
+        inOrder.verify(mMockService).retrieveNetworkAttributes(eq(l2Key), any());
+        inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+                eq(b), any());
+        inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+                eq(TEST_OTHER_DATA_NAME), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+    }
+
+    @Test
+    public void testEnqueuedIpMsRequestsWithException() throws Exception {
+        startIpMemoryStore(true);
+        doThrow(RemoteException.class).when(mMockService).retrieveNetworkAttributes(any(), any());
+
+        final Blob b = new Blob();
+        b.data = TEST_BLOB_DATA;
+        final String l2Key = "fakeKey";
+
+        // enqueue multiple ipms requests
+        mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveNetworkAttributes(l2Key,
+                (status, key, attr) -> {
+                    assertTrue("Retrieve network attributes not successful : "
+                            + status.resultCode, status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(TEST_NETWORK_ATTRIBUTES, attr);
+                });
+        mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+                (status, key, name, data) -> {
+                    assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(name, TEST_DATA_NAME);
+                    assertTrue(Arrays.equals(b.data, data.data));
+                });
+
+        // verify the rest of the queue is still processed in order even if the remote exception
+        // occurs when calling one or more requests
+        InOrder inOrder = inOrder(mMockService);
+
+        inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(), any());
+        inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+                eq(b), any());
+        inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+                eq(TEST_OTHER_DATA_NAME), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+    }
+
+    @Test
+    public void testEnqueuedIpMsRequestsCallbackFunctionWithException() throws Exception {
+        startIpMemoryStore(true);
+
+        final Blob b = new Blob();
+        b.data = TEST_BLOB_DATA;
+        final String l2Key = "fakeKey";
+
+        // enqueue multiple ipms requests
+        mStore.storeNetworkAttributes(l2Key, TEST_NETWORK_ATTRIBUTES,
+                status -> {
+                    assertTrue("Store not successful : " + status.resultCode, status.isSuccess());
+                });
+        mStore.retrieveNetworkAttributes(l2Key,
+                (status, key, attr) -> {
+                    throw new RuntimeException("retrieveNetworkAttributes test");
+                });
+        mStore.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
+                status -> {
+                    throw new RuntimeException("storeBlob test");
+                });
+        mStore.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_OTHER_DATA_NAME,
+                (status, key, name, data) -> {
+                    assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    assertEquals(l2Key, key);
+                    assertEquals(name, TEST_DATA_NAME);
+                    assertTrue(Arrays.equals(b.data, data.data));
+                });
+
+        // verify the rest of the queue is still processed in order even if when one or more
+        // callback throw the remote exception
+        InOrder inOrder = inOrder(mMockService);
+
+        inOrder.verify(mMockService).storeNetworkAttributes(eq(l2Key), mNapCaptor.capture(),
+                any());
+        inOrder.verify(mMockService).storeBlob(eq(l2Key), eq(TEST_CLIENT_ID), eq(TEST_DATA_NAME),
+                eq(b), any());
+        inOrder.verify(mMockService).retrieveBlob(eq(l2Key), eq(TEST_CLIENT_ID),
+                eq(TEST_OTHER_DATA_NAME), any());
+        assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
+    }
 }
diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
index d8f01e9..f9dbdc7 100644
--- a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
+++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java
@@ -55,9 +55,10 @@
         mDhcpResults.serverAddress = (Inet4Address) parseNumericAddress("192.168.44.44");
         mDhcpResults.vendorInfo = "TEST_VENDOR_INFO";
         mDhcpResults.leaseDuration = 3600;
+        mDhcpResults.serverHostName = "dhcp.example.com";
         mDhcpResults.mtu = 1450;
         // Any added DhcpResults field must be included in equals() to be tested properly
-        assertFieldCountEquals(8, DhcpResults.class);
+        assertFieldCountEquals(9, DhcpResults.class);
     }
 
     @Test
@@ -101,6 +102,12 @@
         doDhcpResultsParcelUnparcelTest();
     }
 
+    @Test
+    public void testParcelUnparcelDhcpResults_NullServerHostName() {
+        mDhcpResults.serverHostName = null;
+        doDhcpResultsParcelUnparcelTest();
+    }
+
     private void doDhcpResultsParcelUnparcelTest() {
         final DhcpResults unparceled = fromStableParcelable(toStableParcelable(mDhcpResults));
         assertEquals(mDhcpResults, unparceled);
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 64672bd8a..3c5bb6a 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.NETID_UNSET;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
@@ -103,6 +105,7 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
@@ -146,6 +149,7 @@
 import android.net.shared.NetworkMonitorUtils;
 import android.net.shared.PrivateDnsConfig;
 import android.net.util.MultinetworkPolicyTracker;
+import android.os.Binder;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -272,6 +276,7 @@
     @Mock IDnsResolver mMockDnsResolver;
     @Mock INetd mMockNetd;
     @Mock NetworkStackClient mNetworkStack;
+    @Mock PackageManager mPackageManager;
     @Mock UserManager mUserManager;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
@@ -357,7 +362,12 @@
         public Resources getResources() {
             return mResources;
         }
-    }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+   }
 
     public void waitForIdle(int timeoutMsAsInt) {
         long timeoutMs = timeoutMsAsInt;
@@ -557,6 +567,16 @@
                 protected void preventAutomaticReconnect() {
                     mPreventReconnectReceived.open();
                 }
+
+                @Override
+                protected void addKeepalivePacketFilter(Message msg) {
+                    Log.i(TAG, "Add keepalive packet filter.");
+                }
+
+                @Override
+                protected void removeKeepalivePacketFilter(Message msg) {
+                    Log.i(TAG, "Remove keepalive packet filter.");
+                }
             };
 
             assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId);
@@ -1232,6 +1252,7 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
+        mockDefaultPackages();
 
         FakeSettingsProvider.clearSettingsProvider();
         mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
@@ -1284,7 +1305,24 @@
         FakeSettingsProvider.clearSettingsProvider();
     }
 
-    private static int transportToLegacyType(int transport) {
+    private void mockDefaultPackages() throws Exception {
+        final String testPackageName = mContext.getPackageName();
+        final PackageInfo testPackageInfo = mContext.getPackageManager().getPackageInfo(
+                testPackageName, PackageManager.GET_PERMISSIONS);
+        when(mPackageManager.getPackagesForUid(Binder.getCallingUid())).thenReturn(
+                new String[] {testPackageName});
+        when(mPackageManager.getPackageInfoAsUser(eq(testPackageName), anyInt(),
+                eq(UserHandle.getCallingUserId()))).thenReturn(testPackageInfo);
+
+        when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
+                Arrays.asList(new PackageInfo[] {
+                        buildPackageInfo(/* SYSTEM */ false, APP1_UID),
+                        buildPackageInfo(/* SYSTEM */ false, APP2_UID),
+                        buildPackageInfo(/* SYSTEM */ false, VPN_UID)
+                }));
+    }
+
+   private static int transportToLegacyType(int transport) {
         switch (transport) {
             case TRANSPORT_ETHERNET:
                 return TYPE_ETHERNET;
@@ -3816,6 +3854,9 @@
         networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
         testFactory.waitForRequests();
 
+        // unregister network callback - a no-op, but should not fail
+        mCm.unregisterNetworkCallback(networkCallback);
+
         testFactory.unregister();
         handlerThread.quit();
     }
@@ -4891,7 +4932,10 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         mCellNetworkAgent.connect(false);
         waitForIdle();
-        // CS tells netd about the empty DNS config for this network.
+
+        verify(mMockDnsResolver, times(1)).createNetworkCache(
+                eq(mCellNetworkAgent.getNetwork().netId));
+        // CS tells dnsresolver about the empty DNS config for this network.
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any());
         reset(mMockDnsResolver);
 
@@ -4975,6 +5019,8 @@
         mCellNetworkAgent.sendLinkProperties(cellLp);
         mCellNetworkAgent.connect(false);
         waitForIdle();
+        verify(mMockDnsResolver, times(1)).createNetworkCache(
+                eq(mCellNetworkAgent.getNetwork().netId));
         verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(
                 mResolverParamsParcelCaptor.capture());
         ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue();
@@ -5848,12 +5894,17 @@
         cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME));
         reset(mNetworkManagementService);
         reset(mMockDnsResolver);
+        reset(mMockNetd);
         when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME))
                 .thenReturn(getClatInterfaceConfig(myIpv4));
 
         // Connect with ipv6 link properties. Expect prefix discovery to be started.
         mCellNetworkAgent.sendLinkProperties(cellLp);
         mCellNetworkAgent.connect(true);
+
+        verify(mMockNetd, times(1)).networkCreatePhysical(eq(cellNetId), anyInt());
+        verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId));
+
         networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId);
 
@@ -6045,7 +6096,7 @@
         verify(mNetworkManagementService, times(0)).removeIdleTimer(eq(MOBILE_IFNAME));
         verify(mMockNetd, times(1)).networkDestroy(eq(mCellNetworkAgent.getNetwork().netId));
         verify(mMockDnsResolver, times(1))
-                .clearResolverConfiguration(eq(mCellNetworkAgent.getNetwork().netId));
+                .destroyNetworkCache(eq(mCellNetworkAgent.getNetwork().netId));
 
         // Disconnect wifi
         ConditionVariable cv = waitForConnectivityBroadcasts(1);
@@ -6169,7 +6220,6 @@
     }
 
     @Test
-    @Ignore
     public void testFullyRoutedVpnResultsInInterfaceFilteringRules() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
@@ -6196,7 +6246,6 @@
     }
 
     @Test
-    @Ignore
     public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
@@ -6210,7 +6259,6 @@
     }
 
     @Test
-    @Ignore
     public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule()
             throws Exception {
         LinkProperties lp = new LinkProperties();
@@ -6226,7 +6274,6 @@
     }
 
     @Test
-    @Ignore
     public void testVpnHandoverChangesInterfaceFilteringRule() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
@@ -6276,7 +6323,6 @@
     }
 
     @Test
-    @Ignore
     public void testUidUpdateChangesInterfaceFilteringRule() throws Exception {
         LinkProperties lp = new LinkProperties();
         lp.setInterfaceName("tun0");
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index d5b2c87..3a07166 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -21,11 +21,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -59,16 +56,11 @@
 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.PrintWriter;
 import java.io.StringWriter;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -98,48 +90,6 @@
     }
 
     @Test
-    public void testLoggingEvents() throws Exception {
-        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
-
-        assertTrue(logger.log(1, FAKE_EV));
-        assertTrue(logger.log(2, FAKE_EV));
-        assertTrue(logger.log(3, FAKE_EV));
-
-        List<ConnectivityMetricsEvent> got = verifyEvents(3);
-        assertEventsEqual(expectedEvent(1), got.get(0));
-        assertEventsEqual(expectedEvent(2), got.get(1));
-        assertEventsEqual(expectedEvent(3), got.get(2));
-    }
-
-    @Test
-    public void testLoggingEventsWithMultipleCallers() throws Exception {
-        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
-
-        final int nCallers = 10;
-        final int nEvents = 10;
-        for (int n = 0; n < nCallers; n++) {
-            final int i = n;
-            new Thread() {
-                public void run() {
-                    for (int j = 0; j < nEvents; j++) {
-                        assertTrue(logger.log(1 + i * 100 + j, FAKE_EV));
-                    }
-                }
-            }.start();
-        }
-
-        List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
-        Collections.sort(got, EVENT_COMPARATOR);
-        Iterator<ConnectivityMetricsEvent> iter = got.iterator();
-        for (int i = 0; i < nCallers; i++) {
-            for (int j = 0; j < nEvents; j++) {
-                int expectedTimestamp = 1 + i * 100 + j;
-                assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
-            }
-        }
-    }
-
-    @Test
     public void testBufferFlushing() {
         String output1 = getdump("flush");
         assertEquals("", output1);
@@ -653,16 +603,7 @@
         return nai;
     }
 
-    List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
-        ArgumentCaptor<ConnectivityMetricsEvent> captor =
-                ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
-        verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
-        return captor.getAllValues();
-    }
 
-    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
-        return verifyEvents(n, 10);
-    }
 
     static void verifySerialization(String want, String output) {
         try {
@@ -674,28 +615,4 @@
             fail(e.toString());
         }
     }
-
-    static String joinLines(String ... elems) {
-        StringBuilder b = new StringBuilder();
-        for (String s : elems) {
-            b.append(s).append("\n");
-        }
-        return b.toString();
-    }
-
-    static ConnectivityMetricsEvent expectedEvent(int timestamp) {
-        ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
-        ev.timestamp = timestamp;
-        ev.data = FAKE_EV;
-        return ev;
-    }
-
-    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
-    static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
-        assertEquals(expected.timestamp, got.timestamp);
-        assertEquals(expected.data, got.data);
-    }
-
-    static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
-        Comparator.comparingLong((ev) -> ev.timestamp);
 }
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
index a83faf3..fb84611 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.connectivity.ipmemorystore;
+package com.android.server.net.ipmemorystore;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index c856cc3..e883c6b 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -29,6 +29,7 @@
 FLAG_BLACKLIST = "blacklist"
 FLAG_GREYLIST_MAX_O = "greylist-max-o"
 FLAG_GREYLIST_MAX_P = "greylist-max-p"
+FLAG_GREYLIST_MAX_Q = "greylist-max-q"
 FLAG_CORE_PLATFORM_API = "core-platform-api"
 FLAG_PUBLIC_API = "public-api"
 FLAG_SYSTEM_API = "system-api"
@@ -41,6 +42,7 @@
     FLAG_BLACKLIST,
     FLAG_GREYLIST_MAX_O,
     FLAG_GREYLIST_MAX_P,
+    FLAG_GREYLIST_MAX_Q,
 ]
 ALL_FLAGS = FLAGS_API_LIST + [
     FLAG_CORE_PLATFORM_API,
diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java
index ef29146..5a5703e 100644
--- a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java
+++ b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SignatureBuilder.java
@@ -20,12 +20,13 @@
 import static javax.tools.Diagnostic.Kind.ERROR;
 import static javax.tools.Diagnostic.Kind.WARNING;
 
-import android.annotation.UnsupportedAppUsage;
-
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.sun.tools.javac.code.Type;
 
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -69,6 +70,7 @@
         public SignatureBuilderException(String message) {
             super(message);
         }
+
         public void report(Element offendingElement) {
             mMessager.printMessage(ERROR, getMessage(), offendingElement);
         }
@@ -153,7 +155,7 @@
     /**
      * Get the signature for an executable, either a method or a constructor.
      *
-     * @param name "<init>" for  constructor, else the method name
+     * @param name   "<init>" for  constructor, else the method name
      * @param method The executable element in question.
      */
     private String getExecutableSignature(CharSequence name, ExecutableElement method)
@@ -191,8 +193,13 @@
         return sig.toString();
     }
 
-    public String buildSignature(Element element) {
-        UnsupportedAppUsage uba = element.getAnnotation(UnsupportedAppUsage.class);
+    /**
+     * Creates the signature for an annotated element.
+     *
+     * @param annotationType type of annotation being processed.
+     * @param element        element for which we want to create a signature.
+     */
+    public String buildSignature(Class<? extends Annotation> annotationType, Element element) {
         try {
             String signature;
             switch (element.getKind()) {
@@ -208,18 +215,35 @@
                 default:
                     return null;
             }
-            // if we have an expected signature on the annotation, warn if it doesn't match.
-            if (!Strings.isNullOrEmpty(uba.expectedSignature())) {
-                if (!signature.equals(uba.expectedSignature())) {
-                    mMessager.printMessage(
-                            WARNING,
-                            String.format("Expected signature doesn't match generated signature.\n"
-                                            + " Expected:  %s\n Generated: %s",
-                                    uba.expectedSignature(), signature),
-                            element);
-                }
+            // Obtain annotation objects
+            Annotation annotation = element.getAnnotation(annotationType);
+            if (annotation == null) {
+                throw new IllegalStateException(
+                        "Element doesn't have any UnsupportedAppUsage annotation");
             }
-            return signature;
+            try {
+                Method expectedSignatureMethod = annotationType.getMethod("expectedSignature");
+                // If we have an expected signature on the annotation, warn if it doesn't match.
+                String expectedSignature = expectedSignatureMethod.invoke(annotation).toString();
+                if (!Strings.isNullOrEmpty(expectedSignature)) {
+                    if (!signature.equals(expectedSignature)) {
+                        mMessager.printMessage(
+                                WARNING,
+                                String.format(
+                                        "Expected signature doesn't match generated signature.\n"
+                                                + " Expected:  %s\n Generated: %s",
+                                        expectedSignature, signature),
+                                element);
+                    }
+                }
+                return signature;
+            } catch (NoSuchMethodException e) {
+                throw new IllegalStateException(
+                        "Annotation type does not have expectedSignature parameter", e);
+            } catch (IllegalAccessException | InvocationTargetException e) {
+                throw new IllegalStateException(
+                        "Could not get expectedSignature parameter for annotation", e);
+            }
         } catch (SignatureBuilderException problem) {
             problem.report(element);
             return null;
diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
index d368136..5bb956a 100644
--- a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
+++ b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
@@ -18,9 +18,8 @@
 
 import static javax.tools.StandardLocation.CLASS_OUTPUT;
 
-import android.annotation.UnsupportedAppUsage;
-
 import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
 import com.sun.tools.javac.model.JavacElements;
 import com.sun.tools.javac.tree.JCTree;
 import com.sun.tools.javac.util.Pair;
@@ -28,6 +27,7 @@
 
 import java.io.IOException;
 import java.io.PrintStream;
+import java.lang.annotation.Annotation;
 import java.net.URLEncoder;
 import java.util.Map;
 import java.util.Set;
@@ -47,14 +47,14 @@
 /**
  * Annotation processor for {@link UnsupportedAppUsage} annotations.
  *
- * This processor currently outputs two things:
- * 1. A greylist.txt containing dex signatures of all annotated elements.
- * 2. A CSV file with a mapping of dex signatures to corresponding source positions.
+ * This processor currently outputs a CSV file with a mapping of dex signatures to corresponding
+ * source positions.
  *
- * The first will be used at a later stage of the build to add access flags to the dex file. The
- * second is used for automating updates to the annotations themselves.
+ * This is used for automating updates to the annotations themselves.
  */
-@SupportedAnnotationTypes({"android.annotation.UnsupportedAppUsage"})
+@SupportedAnnotationTypes({"android.annotation.UnsupportedAppUsage",
+        "dalvik.annotation.compat.UnsupportedAppUsage"
+})
 public class UnsupportedAppUsageProcessor extends AbstractProcessor {
 
     // Package name for writing output. Output will be written to the "class output" location within
@@ -62,6 +62,13 @@
     private static final String PACKAGE = "unsupportedappusage";
     private static final String INDEX_CSV = "unsupportedappusage_index.csv";
 
+    private static final ImmutableSet<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS =
+            ImmutableSet.of(android.annotation.UnsupportedAppUsage.class,
+                    dalvik.annotation.compat.UnsupportedAppUsage.class);
+    private static final ImmutableSet<String> SUPPORTED_ANNOTATION_NAMES =
+            SUPPORTED_ANNOTATIONS.stream().map(annotation -> annotation.getCanonicalName()).collect(
+                    ImmutableSet.toImmutableSet());
+
     @Override
     public SourceVersion getSupportedSourceVersion() {
         return SourceVersion.latest();
@@ -92,8 +99,7 @@
     private AnnotationMirror getUnsupportedAppUsageAnnotationMirror(Element e) {
         for (AnnotationMirror m : e.getAnnotationMirrors()) {
             TypeElement type = (TypeElement) m.getAnnotationType().asElement();
-            if (type.getQualifiedName().toString().equals(
-                    UnsupportedAppUsage.class.getCanonicalName())) {
+            if (SUPPORTED_ANNOTATION_NAMES.contains(type.getQualifiedName().toString())) {
                 return m;
             }
         }
@@ -133,12 +139,12 @@
     /**
      * Maps an annotated element to the source position of the @UnsupportedAppUsage annotation
      * attached to it. It returns CSV in the format:
-     *   dex-signature,filename,start-line,start-col,end-line,end-col
+     * dex-signature,filename,start-line,start-col,end-line,end-col
      *
      * The positions refer to the annotation itself, *not* the annotated member. This can therefore
      * be used to read just the annotation from the file, and to perform in-place edits on it.
      *
-     * @param signature the dex signature for the element.
+     * @param signature        the dex signature for the element.
      * @param annotatedElement The annotated element
      * @return A single line of CSV text
      */
@@ -164,28 +170,34 @@
      */
     @Override
     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
-        Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith(
-                UnsupportedAppUsage.class);
-        if (annotated.size() == 0) {
-            return true;
-        }
-        // build signatures for each annotated member, and put them in a map of signature to member
         Map<String, Element> signatureMap = new TreeMap<>();
         SignatureBuilder sb = new SignatureBuilder(processingEnv.getMessager());
-        for (Element e : annotated) {
-            String sig = sb.buildSignature(e);
-            if (sig != null) {
-                signatureMap.put(sig, e);
+        for (Class<? extends Annotation> supportedAnnotation : SUPPORTED_ANNOTATIONS) {
+            Set<? extends Element> annotated = roundEnv.getElementsAnnotatedWith(
+                    supportedAnnotation);
+            if (annotated.size() == 0) {
+                continue;
+            }
+            // Build signatures for each annotated member and put them in a map from signature to
+            // member.
+            for (Element e : annotated) {
+                String sig = sb.buildSignature(supportedAnnotation, e);
+                if (sig != null) {
+                    signatureMap.put(sig, e);
+                }
             }
         }
-        try {
-            writeToFile(INDEX_CSV,
-                    getCsvHeaders(),
-                    signatureMap.entrySet()
-                            .stream()
-                            .map(e -> getAnnotationIndex(e.getKey() ,e.getValue())));
-        } catch (IOException e) {
-            throw new RuntimeException("Failed to write output", e);
+
+        if (!signatureMap.isEmpty()) {
+            try {
+                writeToFile(INDEX_CSV,
+                        getCsvHeaders(),
+                        signatureMap.entrySet()
+                                .stream()
+                                .map(e -> getAnnotationIndex(e.getKey(), e.getValue())));
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to write output", e);
+            }
         }
         return true;
     }
diff --git a/wifi/tests/runtests.sh b/wifi/tests/runtests.sh
index 4e52b8f..9eb9a92 100755
--- a/wifi/tests/runtests.sh
+++ b/wifi/tests/runtests.sh
@@ -19,7 +19,8 @@
 adb root
 adb wait-for-device
 
-adb install -r -g "$OUT/data/app/FrameworksWifiApiTests/FrameworksWifiApiTests.apk"
+TARGET_ARCH=$($ANDROID_BUILD_TOP/build/soong/soong_ui.bash --dumpvar-mode TARGET_ARCH)
+adb install -r -g "$OUT/testcases/FrameworksWifiApiTests/$TARGET_ARCH/FrameworksWifiApiTests.apk"
 
 adb shell am instrument --no-hidden-api-checks -w "$@" \
   'android.net.wifi.test/android.support.test.runner.AndroidJUnitRunner'