Merge "Carrier confirmation code."
diff --git a/Android.bp b/Android.bp
index e0e5ed9..b0493aa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -530,14 +530,7 @@
         "core/java/android/net/EventLogTags.logtags",
         "core/java/android/webkit/EventLogTags.logtags",
         "core/java/com/android/internal/logging/EventLogTags.logtags",
-    ],
-    logtags: [
-        "core/java/android/app/admin/SecurityLogTags.logtags",
-        "core/java/android/content/EventLogTags.logtags",
-        "core/java/android/speech/tts/EventLogTags.logtags",
-        "core/java/android/net/EventLogTags.logtags",
-        "core/java/android/webkit/EventLogTags.logtags",
-        "core/java/com/android/internal/logging/EventLogTags.logtags",
+        "core/java/com/android/server/DropboxLogTags.logtags",
     ],
 
     aidl: {
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 85550c7..90ae0e6 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -670,33 +670,6 @@
     }
 
     /**
-     * Get battery usage hint for Bluetooth Headset service.
-     * This is a monotonically increasing integer. Wraps to 0 at
-     * Integer.MAX_INT, and at boot.
-     * Current implementation returns the number of AT commands handled since
-     * boot. This is a good indicator for spammy headset/handsfree units that
-     * can keep the device awake by polling for cellular status updates. As a
-     * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
-     *
-     * @param device the bluetooth headset.
-     * @return monotonically increasing battery usage hint, or a negative error code on error
-     * @hide
-     */
-    public int getBatteryUsageHint(BluetoothDevice device) {
-        if (VDBG) log("getBatteryUsageHint()");
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
-            try {
-                return service.getBatteryUsageHint(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, Log.getStackTraceString(new Throwable()));
-            }
-        }
-        if (service == null) Log.w(TAG, "Proxy not attached to service");
-        return -1;
-    }
-
-    /**
      * Indicates if current platform supports voice dialing over bluetooth SCO.
      *
      * @return true if voice dialing over bluetooth is supported, false otherwise.
@@ -708,49 +681,6 @@
     }
 
     /**
-     * Accept the incoming connection.
-     * Note: This is an internal function and shouldn't be exposed
-     *
-     * @hide
-     */
-    public boolean acceptIncomingConnect(BluetoothDevice device) {
-        if (DBG) log("acceptIncomingConnect");
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.acceptIncomingConnect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
-     * Reject the incoming connection.
-     *
-     * @hide
-     */
-    public boolean rejectIncomingConnect(BluetoothDevice device) {
-        if (DBG) log("rejectIncomingConnect");
-        final IBluetoothHeadset service = mService;
-        if (service != null) {
-            try {
-                return service.rejectIncomingConnect(device);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
      * Get the current audio state of the Headset.
      * Note: This is an internal function and shouldn't be exposed
      *
@@ -1045,50 +975,6 @@
     }
 
     /**
-     * enable WBS codec setting.
-     *
-     * @return true if successful false if there was some error such as there is no connected
-     * headset
-     * @hide
-     */
-    public boolean enableWBS() {
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.enableWBS();
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
-     * disable WBS codec settting. It set NBS codec.
-     *
-     * @return true if successful false if there was some error such as there is no connected
-     * headset
-     * @hide
-     */
-    public boolean disableWBS() {
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                return service.disableWBS();
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-        return false;
-    }
-
-    /**
      * check if in-band ringing is supported for this platform.
      *
      * @return true if in-band ringing is supported false if in-band ringing is not supported
@@ -1099,30 +985,6 @@
                 com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
     }
 
-    /**
-     * Send Headset the BIND response from AG to report change in the status of the
-     * HF indicators to the headset
-     *
-     * @param indId Assigned Number of the indicator (defined by SIG)
-     * @param indStatus possible values- false-Indicator is disabled, no value changes shall be
-     * sent for this indicator true-Indicator is enabled, value changes may be sent for this
-     * indicator
-     * @hide
-     */
-    public void bindResponse(int indId, boolean indStatus) {
-        final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled()) {
-            try {
-                service.bindResponse(indId, indStatus);
-            } catch (RemoteException e) {
-                Log.e(TAG, e.toString());
-            }
-        } else {
-            Log.w(TAG, "Proxy not attached to service");
-            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
-        }
-    }
-
     private final IBluetoothProfileServiceConnection mConnection =
             new IBluetoothProfileServiceConnection.Stub() {
         @Override
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index 5a203d0..330a0bf 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -31,36 +31,33 @@
 import java.util.List;
 
 /**
- * Provides the public APIs to control the Bluetooth HID Device
- * profile.
+ * Provides the public APIs to control the Bluetooth HID Device profile.
  *
- * BluetoothHidDevice is a proxy object for controlling the Bluetooth HID
- * Device Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
- * the BluetoothHidDevice proxy object.
+ * <p>BluetoothHidDevice is a proxy object for controlling the Bluetooth HID Device Service via IPC.
+ * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHidDevice proxy object.
  *
- * {@hide}
+ * <p>{@hide}
  */
 public final class BluetoothHidDevice implements BluetoothProfile {
 
     private static final String TAG = BluetoothHidDevice.class.getSimpleName();
 
     /**
-     * Intent used to broadcast the change in connection state of the Input
-     * Host profile.
+     * Intent used to broadcast the change in connection state of the Input Host profile.
      *
      * <p>This intent will have 3 extras:
+     *
      * <ul>
-     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
-     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
-     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     *   <li>{@link #EXTRA_STATE} - The current state of the profile.
+     *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
+     *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
      * </ul>
      *
-     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
-     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
-     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
+     * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
+     * #STATE_DISCONNECTING}.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
-     * receive.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
@@ -69,9 +66,8 @@
     /**
      * Constants representing device subclass.
      *
-     * @see #registerApp
-     * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
-     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)
      */
     public static final byte SUBCLASS1_NONE = (byte) 0x00;
     public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
@@ -110,8 +106,8 @@
     public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
 
     /**
-     * Constants representing protocol mode used set by host. Default is always
-     * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
+     * Constants representing protocol mode used set by host. Default is always {@link
+     * #PROTOCOL_REPORT_MODE} unless notified otherwise.
      *
      * @see BluetoothHidDeviceCallback#onSetProtocol(BluetoothDevice, byte)
      */
@@ -126,8 +122,8 @@
 
     private BluetoothAdapter mAdapter;
 
-    private static class BluetoothHidDeviceCallbackWrapper extends
-            IBluetoothHidDeviceCallback.Stub {
+    private static class BluetoothHidDeviceCallbackWrapper
+            extends IBluetoothHidDeviceCallback.Stub {
 
         private BluetoothHidDeviceCallback mCallback;
 
@@ -184,13 +180,11 @@
                                     doBind();
                                 }
                             } catch (IllegalStateException e) {
-                                Log.e(TAG,
-                                        "onBluetoothStateChange: could not bind to HID Dev "
-                                                + "service: ", 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);
+                                Log.e(TAG, "onBluetoothStateChange: could not bind to HID Dev "
+                                        + "service: ", e);
                             }
                         } else {
                             Log.d(TAG, "Unbinding service...");
@@ -200,23 +194,25 @@
                 }
             };
 
-    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);
-            }
-        }
-    };
+    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) {
         Log.v(TAG, "BluetoothHidDevice");
@@ -280,9 +276,7 @@
         mServiceListener = null;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public List<BluetoothDevice> getConnectedDevices() {
         Log.v(TAG, "getConnectedDevices()");
@@ -301,9 +295,7 @@
         return new ArrayList<BluetoothDevice>();
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
         Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
@@ -322,9 +314,7 @@
         return new ArrayList<BluetoothDevice>();
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public int getConnectionState(BluetoothDevice device) {
         Log.v(TAG, "getConnectionState(): device=" + device);
@@ -344,33 +334,31 @@
     }
 
     /**
-     * Registers application to be used for HID device. Connections to HID
-     * Device are only possible when application is registered. Only one
-     * application can be registered at time. When no longer used, application
-     * should be unregistered using
-     * {@link #unregisterApp()}.
-     * The registration status should be tracked by the application by handling callback from
-     * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related
-     * to the return value of this method.
+     * Registers application to be used for HID device. Connections to HID Device are only possible
+     * when application is registered. Only one application can be registered at one time. When an
+     * application is registered, the HID Host service will be disabled until it is unregistered.
+     * When no longer used, application should be unregistered using {@link #unregisterApp()}. The
+     * registration status should be tracked by the application by handling callback from
+     * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related to
+     * the return value of this method.
      *
-     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record.
-     * The HID Device SDP record is required.
-     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings.
-     * The Incoming QoS Settings is not required. Use null or default
-     * BluetoothHidDeviceAppQosSettings.Builder for default values.
-     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings.
-     * The Outgoing QoS Settings is not required. Use null or default
-     * BluetoothHidDeviceAppQosSettings.Builder for default values.
+     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. The HID
+     *     Device SDP record is required.
+     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. The
+     *     Incoming QoS Settings is not required. Use null or default
+     *     BluetoothHidDeviceAppQosSettings.Builder for default values.
+     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. The
+     *     Outgoing QoS Settings is not required. Use null or default
+     *     BluetoothHidDeviceAppQosSettings.Builder for default values.
      * @param callback {@link BluetoothHidDeviceCallback} object to which callback messages will be
-     * sent.
-     * The BluetoothHidDeviceCallback object is required.
+     *     sent. The BluetoothHidDeviceCallback object is required.
      * @return true if the command is successfully sent; otherwise false.
      */
     public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
             BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
             BluetoothHidDeviceCallback callback) {
         Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
-                + " callback=" + callback);
+                        + " callback=" + callback);
 
         boolean result = false;
 
@@ -395,14 +383,13 @@
     }
 
     /**
-     * Unregisters application. Active connection will be disconnected and no
-     * new connections will be allowed until registered again using
-     * {@link #registerApp
+     * Unregisters application. Active connection will be disconnected and no new connections will
+     * be allowed until registered again using {@link #registerApp
      * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
-     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)}
-     * The registration status should be tracked by the application by handling callback from
-     * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related
-     * to the return value of this method.
+     * BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceCallback)} The registration status should
+     * be tracked by the application by handling callback from
+     * BluetoothHidDeviceCallback#onAppStatusChanged. The app registration status is not related to
+     * the return value of this method.
      *
      * @return true if the command is successfully sent; otherwise false.
      */
@@ -429,7 +416,7 @@
      * Sends report to remote host using interrupt channel.
      *
      * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id are not defined in
-     * descriptor.
+     *     descriptor.
      * @param data Report data, not including Report Id.
      * @return true if the command is successfully sent; otherwise false.
      */
@@ -451,8 +438,8 @@
     }
 
     /**
-     * Sends report to remote host as reply for GET_REPORT request from
-     * {@link BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}.
+     * Sends report to remote host as reply for GET_REPORT request from {@link
+     * BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)}.
      *
      * @param type Report Type, as in request.
      * @param id Report Id, as in request.
@@ -479,8 +466,8 @@
     }
 
     /**
-     * Sends error handshake message as reply for invalid SET_REPORT request
-     * from {@link BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
+     * Sends error handshake message as reply for invalid SET_REPORT request from {@link
+     * BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
      *
      * @param error Error to be sent for SET_REPORT via HANDSHAKE.
      * @return true if the command is successfully sent; otherwise false.
@@ -508,6 +495,7 @@
      * Sends Virtual Cable Unplug to currently connected host.
      *
      * @return
+     * {@hide}
      */
     public boolean unplug(BluetoothDevice device) {
         Log.v(TAG, "unplug(): device=" + device);
@@ -529,11 +517,11 @@
     }
 
     /**
-     * Initiates connection to host which is currently paired with this device.
-     * If the application is not registered, #connect(BluetoothDevice) will fail.
-     * The connection state should be tracked by the application by handling callback from
-     * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related
-     * to the return value of this method.
+     * Initiates connection to host which is currently paired with this device. If the application
+     * is not registered, #connect(BluetoothDevice) will fail. The connection state should be
+     * tracked by the application by handling callback from
+     * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related to
+     * the return value of this method.
      *
      * @return true if the command is successfully sent; otherwise false.
      */
@@ -557,10 +545,9 @@
     }
 
     /**
-     * Disconnects from currently connected host.
-     * The connection state should be tracked by the application by handling callback from
-     * BluetoothHidDeviceCallback#onConnectionStateChanged. The connection state is not related
-     * to the return value of this method.
+     * Disconnects from currently connected host. The connection state should be tracked by the
+     * application by handling callback from BluetoothHidDeviceCallback#onConnectionStateChanged.
+     * The connection state is not related to the return value of this method.
      *
      * @return true if the command is successfully sent; otherwise false.
      */
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
index 881ae98..4609d52 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -20,15 +20,14 @@
 import android.os.Parcelable;
 
 /**
- * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device
- * application.
+ * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device application.
  *
- * The BluetoothHidDevice framework will update the L2CAP QoS settings for the
- * app during registration.
+ * <p>The BluetoothHidDevice framework will update the L2CAP QoS settings for the app during
+ * registration.
  *
- * {@see BluetoothHidDevice}
+ * <p>{@see BluetoothHidDevice}
  *
- * {@hide}
+ * <p>{@hide}
  */
 public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
 
@@ -46,13 +45,12 @@
     public static final int MAX = (int) 0xffffffff;
 
     /**
-     * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel.
-     * The QoS Settings is optional.
-     * Recommended to use BluetoothHidDeviceAppQosSettings.Builder.
-     * {@see <a href="https://www.bluetooth.com/specifications/profiles-overview">
-     *     https://www.bluetooth.com/specifications/profiles-overview
-     *     </a>
-     *     Bluetooth HID Specfication v1.1.1 Section 5.2 and Appendix D }
+     * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel. The QoS
+     * Settings is optional. Recommended to use BluetoothHidDeviceAppQosSettings.Builder. {@see <a
+     * href="https://www.bluetooth.com/specifications/profiles-overview">
+     * https://www.bluetooth.com/specifications/profiles-overview </a> Bluetooth HID Specfication
+     * v1.1.1 Section 5.2 and Appendix D }
+     *
      * @param serviceType L2CAP service type
      * @param tokenRate L2CAP token rate
      * @param tokenBucketSize L2CAP token bucket size
@@ -123,13 +121,11 @@
     /** @return an int array representation of this instance */
     public int[] toArray() {
         return new int[] {
-                serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
+            serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
         };
     }
 
-    /**
-     * A helper to build the BluetoothHidDeviceAppQosSettings object.
-     */
+    /** A helper to build the BluetoothHidDeviceAppQosSettings object. */
     public static class Builder {
         // Optional parameters - initialized to default values
         private int mServiceType = SERVICE_BEST_EFFORT;
@@ -141,8 +137,9 @@
 
         /**
          * Set the service type.
+         *
          * @param val service type. Should be one of {SERVICE_NO_TRAFFIC, SERVICE_BEST_EFFORT,
-         * SERVICE_GUARANTEED}, with SERVICE_BEST_EFFORT being the default one.
+         *     SERVICE_GUARANTEED}, with SERVICE_BEST_EFFORT being the default one.
          * @return BluetoothHidDeviceAppQosSettings Builder with specified service type.
          */
         public Builder serviceType(int val) {
@@ -151,6 +148,7 @@
         }
         /**
          * Set the token rate.
+         *
          * @param val token rate
          * @return BluetoothHidDeviceAppQosSettings Builder with specified token rate.
          */
@@ -161,6 +159,7 @@
 
         /**
          * Set the bucket size.
+         *
          * @param val bucket size
          * @return BluetoothHidDeviceAppQosSettings Builder with specified bucket size.
          */
@@ -171,6 +170,7 @@
 
         /**
          * Set the peak bandwidth.
+         *
          * @param val peak bandwidth
          * @return BluetoothHidDeviceAppQosSettings Builder with specified peak bandwidth.
          */
@@ -180,6 +180,7 @@
         }
         /**
          * Set the latency.
+         *
          * @param val latency
          * @return BluetoothHidDeviceAppQosSettings Builder with specified latency.
          */
@@ -190,6 +191,7 @@
 
         /**
          * Set the delay variation.
+         *
          * @param val delay variation
          * @return BluetoothHidDeviceAppQosSettings Builder with specified delay variation.
          */
@@ -200,6 +202,7 @@
 
         /**
          * Build the BluetoothHidDeviceAppQosSettings object.
+         *
          * @return BluetoothHidDeviceAppQosSettings object with current settings.
          */
         public BluetoothHidDeviceAppQosSettings build() {
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
index 4669637..2da64e5 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -22,16 +22,14 @@
 import java.util.Arrays;
 
 /**
- * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth
- * HID Device application.
+ * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth HID Device application.
  *
- * The BluetoothHidDevice framework adds the SDP record during app
- * registration, so that the Android device can be discovered as a Bluetooth
- * HID Device.
+ * <p>The BluetoothHidDevice framework adds the SDP record during app registration, so that the
+ * Android device can be discovered as a Bluetooth HID Device.
  *
- * {@see BluetoothHidDevice}
+ * <p>{@see BluetoothHidDevice}
  *
- * {@hide}
+ * <p>{@hide}
  */
 public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
 
@@ -43,18 +41,19 @@
 
     /**
      * Create a BluetoothHidDeviceAppSdpSettings object for the Bluetooth SDP record.
+     *
      * @param name Name of this Bluetooth HID device. Maximum length is 50 bytes.
      * @param description Description for this Bluetooth HID device. Maximum length is 50 bytes.
      * @param provider Provider of this Bluetooth HID device. Maximum length is 50 bytes.
-     * @param subclass Subclass of this Bluetooth HID device.
-     * See <a href="www.usb.org/developers/hidpage/HID1_11.pdf">
+     * @param subclass Subclass of this Bluetooth HID device. See <a
+     *     href="www.usb.org/developers/hidpage/HID1_11.pdf">
      *     www.usb.org/developers/hidpage/HID1_11.pdf Section 4.2</a>
-     * @param descriptors Descriptors of this Bluetooth HID device.
-     * See <a href="www.usb.org/developers/hidpage/HID1_11.pdf">
+     * @param descriptors Descriptors of this Bluetooth HID device. See <a
+     *     href="www.usb.org/developers/hidpage/HID1_11.pdf">
      *     www.usb.org/developers/hidpage/HID1_11.pdf Chapter 6</a> Maximum length is 2048 bytes.
      */
-    public BluetoothHidDeviceAppSdpSettings(String name, String description, String provider,
-            byte subclass, byte[] descriptors) {
+    public BluetoothHidDeviceAppSdpSettings(
+            String name, String description, String provider, byte subclass, byte[] descriptors) {
         this.name = name;
         this.description = description;
         this.provider = provider;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index dc6f9fa..6ed1965 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -19,46 +19,43 @@
 import android.util.Log;
 
 /**
- * The template class that applications use to call callback functions on
- * events from the HID host. Callback functions are wrapped in this class and
- * registered to the Android system during app registration.
+ * The template class that applications use to call callback functions on events from the HID host.
+ * Callback functions are wrapped in this class and registered to the Android system during app
+ * registration.
  *
- * {@see BluetoothHidDevice}
+ * <p>{@see BluetoothHidDevice}
  *
- * {@hide}
+ * <p>{@hide}
  */
 public abstract class BluetoothHidDeviceCallback {
 
     private static final String TAG = "BluetoothHidDevCallback";
 
     /**
-     * Callback called when application registration state changes. Usually it's
-     * called due to either
-     * {@link BluetoothHidDevice#registerApp
-     * (String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
-     * or
-     * {@link BluetoothHidDevice#unregisterApp()}
-     * , but can be also unsolicited in case e.g. Bluetooth was turned off in
-     * which case application is unregistered automatically.
+     * Callback called when application registration state changes. Usually it's called due to
+     * either {@link BluetoothHidDevice#registerApp (String, String, String, byte, byte[],
+     * BluetoothHidDeviceCallback)} or {@link BluetoothHidDevice#unregisterApp()} , but can be also
+     * unsolicited in case e.g. Bluetooth was turned off in which case application is unregistered
+     * automatically.
      *
      * @param pluggedDevice {@link BluetoothDevice} object which represents host that currently has
-     * Virtual Cable established with device. Only valid when application is registered, can be
-     * <code>null</code>.
+     *     Virtual Cable established with device. Only valid when application is registered, can be
+     *     <code>null</code>.
      * @param registered <code>true</code> if application is registered, <code>false</code>
-     * otherwise.
+     *     otherwise.
      */
     public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
-        Log.d(TAG, "onAppStatusChanged: pluggedDevice=" + pluggedDevice + " registered="
-                + registered);
+        Log.d(TAG,
+                "onAppStatusChanged: pluggedDevice=" + pluggedDevice + " registered=" + registered);
     }
 
     /**
-     * Callback called when connection state with remote host was changed.
-     * Application can assume than Virtual Cable is established when called with
-     * {@link BluetoothProfile#STATE_CONNECTED} <code>state</code>.
+     * Callback called when connection state with remote host was changed. Application can assume
+     * than Virtual Cable is established when called with {@link BluetoothProfile#STATE_CONNECTED}
+     * <code>state</code>.
      *
      * @param device {@link BluetoothDevice} object representing host device which connection state
-     * was changed.
+     *     was changed.
      * @param state Connection state as defined in {@link BluetoothProfile}.
      */
     public void onConnectionStateChanged(BluetoothDevice device, int state) {
@@ -66,14 +63,14 @@
     }
 
     /**
-     * Callback called when GET_REPORT is received from remote host. Should be
-     * replied by application using
-     * {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte, byte[])}.
+     * Callback called when GET_REPORT is received from remote host. Should be replied by
+     * application using {@link BluetoothHidDevice#replyReport(BluetoothDevice, byte, byte,
+     * byte[])}.
      *
      * @param type Requested Report Type.
      * @param id Requested Report Id, can be 0 if no Report Id are defined in descriptor.
      * @param bufferSize Requested buffer size, application shall respond with at least given number
-     * of bytes.
+     *     of bytes.
      */
     public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
         Log.d(TAG, "onGetReport: device=" + device + " type=" + type + " id=" + id + " bufferSize="
@@ -81,9 +78,9 @@
     }
 
     /**
-     * Callback called when SET_REPORT is received from remote host. In case
-     * received data are invalid, application shall respond with
-     * {@link BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
+     * Callback called when SET_REPORT is received from remote host. In case received data are
+     * invalid, application shall respond with {@link
+     * BluetoothHidDevice#reportError(BluetoothDevice, byte)}.
      *
      * @param type Report Type.
      * @param id Report Id.
@@ -94,10 +91,9 @@
     }
 
     /**
-     * Callback called when SET_PROTOCOL is received from remote host.
-     * Application shall use this information to send only reports valid for
-     * given protocol mode. By default,
-     * {@link BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed.
+     * Callback called when SET_PROTOCOL is received from remote host. Application shall use this
+     * information to send only reports valid for given protocol mode. By default, {@link
+     * BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed.
      *
      * @param protocol Protocol Mode.
      */
@@ -106,9 +102,8 @@
     }
 
     /**
-     * Callback called when report data is received over interrupt channel.
-     * Report Type is assumed to be
-     * {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}.
+     * Callback called when report data is received over interrupt channel. Report Type is assumed
+     * to be {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}.
      *
      * @param reportId Report Id.
      * @param data Report data.
@@ -118,10 +113,8 @@
     }
 
     /**
-     * Callback called when Virtual Cable is removed. This can be either due to
-     * {@link BluetoothHidDevice#unplug(BluetoothDevice)} or request from remote
-     * side. After this callback is received connection will be disconnected
-     * automatically.
+     * Callback called when Virtual Cable is removed. After this callback is
+     * received connection will be disconnected automatically.
      */
     public void onVirtualCableUnplug(BluetoothDevice device) {
         Log.d(TAG, "onVirtualCableUnplug: device=" + device);
diff --git a/core/java/android/net/metrics/DefaultNetworkEvent.java b/core/java/android/net/metrics/DefaultNetworkEvent.java
index 8ff8e4f..6f383b4 100644
--- a/core/java/android/net/metrics/DefaultNetworkEvent.java
+++ b/core/java/android/net/metrics/DefaultNetworkEvent.java
@@ -74,7 +74,7 @@
             j.add("final_score=" + finalScore);
         }
         j.add(String.format("duration=%.0fs", durationMs / 1000.0));
-        j.add(String.format("validation=%4.1f%%", (validatedMs * 100.0) / durationMs));
+        j.add(String.format("validation=%04.1f%%", (validatedMs * 100.0) / durationMs));
         return j.toString();
     }
 
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 6a0edef..82affe2 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -151,7 +151,7 @@
 
     private void setStacked(boolean stacked) {
         setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
-        setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
+        setGravity(stacked ? Gravity.END : Gravity.BOTTOM);
 
         final View spacer = findViewById(R.id.spacer);
         if (spacer != null) {
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 4354486..8848e393 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -33,6 +33,7 @@
 import android.provider.Downloads;
 import android.text.TextUtils;
 import android.util.AtomicFile;
+import android.util.EventLog;
 import android.util.Slog;
 import android.util.Xml;
 
@@ -40,6 +41,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
+import com.android.server.DropboxLogTags;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -297,6 +299,7 @@
         Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
         db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n") +
                 footers);
+        EventLog.writeEvent(DropboxLogTags.DROPBOX_FILE_COPY, filename, maxSize, tag);
     }
 
     private static void addAuditErrorsToDropBox(DropBoxManager db,
diff --git a/core/java/com/android/server/DropboxLogTags.logtags b/core/java/com/android/server/DropboxLogTags.logtags
new file mode 100644
index 0000000..c461cfe
--- /dev/null
+++ b/core/java/com/android/server/DropboxLogTags.logtags
@@ -0,0 +1,12 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+# The java package name happens to be the same as frameworks/base/services/core
+# /java/com/android/server/EventLogTags.logtags. To avoid conflict, this file's name cannot
+# be EventLogTags.logtags because it generates a class with the same name.
+
+option java_package com.android.server;
+
+# -----------------------------
+# BootReceiver.java
+# -----------------------------
+81002 dropbox_file_copy (FileName|3),(Size|1),(Tag|3)
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e9357df..befebd9 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4422,7 +4422,9 @@
     <string name="stk_cc_ussd_to_dial">USSD request is modified to DIAL request.</string>
     <string name="stk_cc_ussd_to_ss">USSD request is modified to SS request.</string>
     <string name="stk_cc_ussd_to_ussd">USSD request is modified to new USSD request.</string>
+    <string name="stk_cc_ussd_to_dial_video">USSD request is modified to Video DIAL request.</string>
     <string name="stk_cc_ss_to_dial">SS request is modified to DIAL request.</string>
+    <string name="stk_cc_ss_to_dial_video">SS request is modified to Video DIAL request.</string>
     <string name="stk_cc_ss_to_ussd">SS request is modified to USSD request.</string>
     <string name="stk_cc_ss_to_ss">SS request is modified to new SS request.</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 451a285..1703ef0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1031,9 +1031,11 @@
   <java-symbol type="string" name="stk_cc_ss_to_dial" />
   <java-symbol type="string" name="stk_cc_ss_to_ss" />
   <java-symbol type="string" name="stk_cc_ss_to_ussd" />
+  <java-symbol type="string" name="stk_cc_ss_to_dial_video" />
   <java-symbol type="string" name="stk_cc_ussd_to_dial" />
   <java-symbol type="string" name="stk_cc_ussd_to_ss" />
   <java-symbol type="string" name="stk_cc_ussd_to_ussd" />
+  <java-symbol type="string" name="stk_cc_ussd_to_dial_video" />
   <java-symbol type="string" name="safe_media_volume_warning" />
   <java-symbol type="string" name="media_route_status_scanning" />
   <java-symbol type="string" name="media_route_status_connecting" />
diff --git a/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
new file mode 100644
index 0000000..69490cc
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_azerbaijani.kcm
@@ -0,0 +1,312 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Azerbaijan (AZ) keyboard layout.
+#
+
+type OVERLAY
+
+### ROW 1
+
+key GRAVE {
+    label:                              '`'
+    base:                               '`'
+    shift:                              '~'
+}
+
+key 1 {
+    label:                              '1'
+    base:                               '1'
+    shift:                              '!'
+}
+
+key 2 {
+    label:                              '2'
+    base:                               '2'
+    shift:                              '"'
+    ralt:                               '@'
+}
+
+key 3 {
+    label:                              '3'
+    base:                               '3'
+    shift:                              '\u2166'
+}
+
+key 4 {
+    label:                              '4'
+    base:                               '4'
+    shift:                              ';'
+}
+
+key 5 {
+    label:                              '5'
+    base:                               '5'
+    shift:                              '%'
+}
+
+key 6 {
+    label:                              '6'
+    base:                               '6'
+    shift:                              ':'
+    shift+ralt:                         '^'
+}
+
+key 7 {
+    label:                              '7'
+    base:                               '7'
+    shift:                              '?'
+    ralt:                               '&'
+}
+
+key 8 {
+    label:                              '8'
+    base:                               '8'
+    shift:                              '*'
+}
+
+key 9 {
+    label:                              '9'
+    base:                               '9'
+    shift:                              '('
+}
+
+key 0 {
+    label:                              '0'
+    base:                               '0'
+    shift:                              ')'
+}
+
+key MINUS {
+    label:                              '-'
+    base:                               '-'
+    shift:                              '_'
+}
+
+key EQUALS {
+    label:                              '='
+    base:                               '='
+    shift:                              '+'
+}
+
+### ROW 2
+
+key Q {
+    label:                              'Q'
+    base:                               'q'
+    shift, capslock:                    'Q'
+}
+
+key W {
+    label:                              '\u00dc'
+    base:                               '\u00fc'
+    shift, capslock:                    '\u00dc'
+}
+
+key E {
+    label:                              'E'
+    base:                               'e'
+    shift, capslock:                    'E'
+}
+
+key R {
+    label:                              'R'
+    base:                               'r'
+    shift, capslock:                    'R'
+}
+
+key T {
+    label:                              'T'
+    base:                               't'
+    shift, capslock:                    'T'
+}
+
+key Y {
+    label:                              'Y'
+    base:                               'y'
+    shift, capslock:                    'Y'
+}
+
+key U {
+    label:                              'U'
+    base:                               'u'
+    shift, capslock:                    'U'
+}
+
+key I {
+    label:                              '\u0130'
+    base:                               'i'
+    shift, capslock:                    '\u0130'
+}
+
+key O {
+    label:                              'O'
+    base:                               'o'
+    shift, capslock:                    'O'
+}
+
+key P {
+    label:                              'P'
+    base:                               'p'
+    shift, capslock:                    'P'
+}
+
+key LEFT_BRACKET {
+    label:                              '\u00d6'
+    base:                               '\u00f6'
+    shift:                              '\u00d6'
+}
+
+key RIGHT_BRACKET {
+    label:                              '\u011e'
+    base:                               '\u011f'
+    shift:                              '\u011e'
+}
+
+key BACKSLASH {
+    label:                              '\\'
+    base:                               '\\'
+    shift:                              '|'
+}
+
+### ROW 3
+
+key A {
+    label:                              'A'
+    base:                               'a'
+    shift, capslock:                    'A'
+}
+
+key S {
+    label:                              'S'
+    base:                               's'
+    shift, capslock:                    'S'
+}
+
+key D {
+    label:                              'D'
+    base:                               'd'
+    shift, capslock:                    'D'
+}
+
+key F {
+    label:                              'F'
+    base:                               'f'
+    shift, capslock:                    'F'
+}
+
+key G {
+    label:                              'G'
+    base:                               'g'
+    shift, capslock:                    'G'
+}
+
+key H {
+    label:                              'H'
+    base:                               'h'
+    shift, capslock:                    'H'
+}
+
+key J {
+    label:                              'J'
+    base:                               'j'
+    shift, capslock:                    'J'
+}
+
+key K {
+    label:                              'K'
+    base:                               'k'
+    shift, capslock:                    'K'
+}
+
+key L {
+    label:                              'L'
+    base:                               'l'
+    shift, capslock:                    'L'
+}
+
+key SEMICOLON {
+    label:                              'I'
+    base:                               '\u0131'
+    shift:                              'I'
+}
+
+key APOSTROPHE {
+    label:                              '\u018f'
+    base:                               '\u0259'
+    shift:                              '\u018f'
+}
+
+### ROW 4
+
+key Z {
+    label:                              'Z'
+    base:                               'z'
+    shift, capslock:                    'Z'
+}
+
+key X {
+    label:                              'X'
+    base:                               'x'
+    shift, capslock:                    'X'
+}
+
+key C {
+    label:                              'C'
+    base:                               'c'
+    shift, capslock:                    'C'
+}
+
+key V {
+    label:                              'V'
+    base:                               'v'
+    shift, capslock:                    'V'
+}
+
+key B {
+    label:                              'B'
+    base:                               'b'
+    shift, capslock:                    'B'
+}
+
+key N {
+    label:                              'N'
+    base:                               'n'
+    shift, capslock:                    'N'
+}
+
+key M {
+    label:                              'M'
+    base:                               'm'
+    shift, capslock:                    'M'
+}
+
+key COMMA {
+    label:                              '\u00c7'
+    base:                               '\u00e7'
+    shift:                              '\u00c7'
+}
+
+key PERIOD {
+    label:                              '\u015e'
+    base:                               '\u015f'
+    shift:                              '\u015e'
+}
+
+key SLASH {
+    label:                              '.'
+    base:                               '.'
+    shift:                              ','
+}
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 1e26226..61d3234 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -122,4 +122,7 @@
 
     <!-- Persian keyboard layout label. [CHAR LIMIT=35] -->
     <string name="keyboard_layout_persian">Persian</string>
+
+    <!-- Azerbaijani keyboard layout label. [CHAR LIMIT=35] -->
+    <string name="keyboard_layout_azerbaijani">Azerbaijani</string>
 </resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index c55711a..c6bfc1f 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -155,4 +155,8 @@
     <keyboard-layout android:name="keyboard_layout_persian"
             android:label="@string/keyboard_layout_persian"
             android:keyboardLayout="@raw/keyboard_layout_persian" />
+
+    <keyboard-layout android:name="keyboard_layout_azerbaijani"
+            android:label="@string/keyboard_layout_azerbaijani"
+            android:keyboardLayout="@raw/keyboard_layout_azerbaijani" />
 </keyboard-layouts>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index cb23cf3..cbcbf2d6 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -364,4 +364,20 @@
     <!-- Fingerprint hint message when finger was not recognized.-->
     <string name="fingerprint_not_recognized">Not recognized</string>
 
+    <!-- Instructions telling the user remaining times when enter SIM PIN view.  -->
+    <plurals name="kg_password_default_pin_message">
+        <item quantity="one">Enter SIM PIN, you have <xliff:g id="number">%d</xliff:g> remaining
+attempt before you must contact your carrier to unlock your device.</item>
+        <item quantity="other">Enter SIM PIN, you have <xliff:g id="number">%d</xliff:g> remaining
+attempts.</item>
+    </plurals>
+
+    <!-- Instructions telling the user remaining times when enter SIM PUK view.  -->
+    <plurals name="kg_password_default_puk_message">
+        <item quantity="one">SIM is now disabled. Enter PUK code to continue. You have <xliff:g id="
+number">%d</xliff:g> remaining attempt before SIM becomes permanently unusable. Contact carrier for details.</item>
+        <item quantity="other">SIM is now disabled. Enter PUK code to continue. You have <xliff:g id="
+number">%d</xliff:g> remaining attempts before SIM becomes permanently unusable. Contact carrier for details.</item>
+    </plurals>
+
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 432b406..6e0b56e2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -53,8 +53,13 @@
     private ProgressDialog mSimUnlockProgressDialog = null;
     private CheckSimPin mCheckSimPinThread;
 
+    // Below flag is set to true during power-up or when a new SIM card inserted on device.
+    // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would
+    // be displayed to inform user about the number of remaining PIN attempts left.
+    private boolean mShowDefaultMessage = true;
+    private int mRemainingAttempts = -1;
     private AlertDialog mRemainingAttemptsDialog;
-    private int mSubId;
+    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private ImageView mSimImageView;
 
     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
@@ -91,34 +96,71 @@
     public void resetState() {
         super.resetState();
         if (DEBUG) Log.v(TAG, "Resetting state");
-        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
-        mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
-        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-        if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
-            int count = TelephonyManager.getDefault().getSimCount();
-            Resources rez = getResources();
-            String msg;
-            int color = Color.WHITE;
-            if (count < 2) {
-                msg = rez.getString(R.string.kg_sim_pin_instructions);
-            } else {
-                SubscriptionInfo info = monitor.getSubscriptionInfoForSubId(mSubId);
-                CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
-                msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
-                if (info != null) {
-                    color = info.getIconTint();
-                }
-            }
-            if (isEsimLocked) {
-                msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
-            }
-            mSecurityMessageDisplay.setMessage(msg);
-            mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+        handleSubInfoChangeIfNeeded();
+        if (mShowDefaultMessage) {
+            showDefaultMessage();
         }
+        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
+
         KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
         esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
     }
 
+    private void showDefaultMessage() {
+        if (mRemainingAttempts >= 0) {
+            mSecurityMessageDisplay.setMessage(getPinPasswordErrorMessage(
+                    mRemainingAttempts, true));
+            return;
+        }
+
+        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
+        int count = TelephonyManager.getDefault().getSimCount();
+        Resources rez = getResources();
+        String msg;
+        int color = Color.WHITE;
+        if (count < 2) {
+            msg = rez.getString(R.string.kg_sim_pin_instructions);
+        } else {
+            SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
+                    getSubscriptionInfoForSubId(mSubId);
+            CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash
+            msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName);
+            if (info != null) {
+                color = info.getIconTint();
+            }
+        }
+
+        if (isEsimLocked) {
+            msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
+        }
+
+        mSecurityMessageDisplay.setMessage(msg);
+        mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+
+        // Sending empty PIN here to query the number of remaining PIN attempts
+        new CheckSimPin("", mSubId) {
+            void onSimCheckResponse(final int result, final int attemptsRemaining) {
+                Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result +
+                        " attemptsRemaining=" + attemptsRemaining);
+                if (attemptsRemaining >= 0) {
+                    mRemainingAttempts = attemptsRemaining;
+                    mSecurityMessageDisplay.setMessage(
+                            getPinPasswordErrorMessage(attemptsRemaining, true));
+                }
+            }
+        }.start();
+    }
+
+    private void handleSubInfoChangeIfNeeded() {
+        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        int subId = monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED);
+        if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
+            mSubId = subId;
+            mShowDefaultMessage = true;
+            mRemainingAttempts = -1;
+        }
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -131,17 +173,19 @@
         return 0;
     }
 
-    private String getPinPasswordErrorMessage(int attemptsRemaining) {
+    private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
         String displayMessage;
-
+        int msgId;
         if (attemptsRemaining == 0) {
             displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked);
         } else if (attemptsRemaining > 0) {
+            msgId = isDefault ? R.plurals.kg_password_default_pin_message :
+                     R.plurals.kg_password_wrong_pin_code;
             displayMessage = getContext().getResources()
-                    .getQuantityString(R.plurals.kg_password_wrong_pin_code, attemptsRemaining,
-                            attemptsRemaining);
+                    .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
         } else {
-            displayMessage = getContext().getString(R.string.kg_password_pin_failed);
+            msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
+            displayMessage = getContext().getString(msgId);
         }
         if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:"
                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
@@ -252,7 +296,7 @@
     }
 
     private Dialog getSimRemainingAttemptsDialog(int remaining) {
-        String msg = getPinPasswordErrorMessage(remaining);
+        String msg = getPinPasswordErrorMessage(remaining, false);
         if (mRemainingAttemptsDialog == null) {
             Builder builder = new AlertDialog.Builder(mContext);
             builder.setMessage(msg);
@@ -288,6 +332,7 @@
                     post(new Runnable() {
                         @Override
                         public void run() {
+                            mRemainingAttempts = attemptsRemaining;
                             if (mSimUnlockProgressDialog != null) {
                                 mSimUnlockProgressDialog.hide();
                             }
@@ -296,8 +341,13 @@
                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
                                 KeyguardUpdateMonitor.getInstance(getContext())
                                         .reportSimUnlocked(mSubId);
-                                mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                mRemainingAttempts = -1;
+                                mShowDefaultMessage = true;
+                                if (mCallback != null) {
+                                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                }
                             } else {
+                                mShowDefaultMessage = false;
                                 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
                                     if (attemptsRemaining <= 2) {
                                         // this is getting critical - show dialog
@@ -305,7 +355,7 @@
                                     } else {
                                         // show message
                                         mSecurityMessageDisplay.setMessage(
-                                                getPinPasswordErrorMessage(attemptsRemaining));
+                                                getPinPasswordErrorMessage(attemptsRemaining, false));
                                     }
                                 } else {
                                     // "PIN operation failed!" - no idea what this was and no way to
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 7f79008..876d170 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -52,11 +52,17 @@
 
     private ProgressDialog mSimUnlockProgressDialog = null;
     private CheckSimPuk mCheckSimPukThread;
+
+    // Below flag is set to true during power-up or when a new SIM card inserted on device.
+    // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would
+    // be displayed to inform user about the number of remaining PUK attempts left.
+    private boolean mShowDefaultMessage = true;
+    private int mRemainingAttempts = -1;
     private String mPukText;
     private String mPinText;
     private StateMachine mStateMachine = new StateMachine();
     private AlertDialog mRemainingAttemptsDialog;
-    private int mSubId;
+    private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private ImageView mSimImageView;
 
     KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
@@ -132,34 +138,17 @@
             }
         }
 
+
         void reset() {
             mPinText="";
             mPukText="";
             state = ENTER_PUK;
-            KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
-            mSubId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
-            boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
-            if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
-                int count = TelephonyManager.getDefault().getSimCount();
-                Resources rez = getResources();
-                String msg;
-                int color = Color.WHITE;
-                if (count < 2) {
-                    msg = rez.getString(R.string.kg_puk_enter_puk_hint);
-                } else {
-                    SubscriptionInfo info = monitor.getSubscriptionInfoForSubId(mSubId);
-                    CharSequence displayName = info != null ? info.getDisplayName() : "";
-                    msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
-                    if (info != null) {
-                        color = info.getIconTint();
-                    }
-                }
-                if (isEsimLocked) {
-                    msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
-                }
-                mSecurityMessageDisplay.setMessage(msg);
-                mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+            handleSubInfoChangeIfNeeded();
+            if (mShowDefaultMessage) {
+                showDefaultMessage();
             }
+            boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
+
             KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
             esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
             mPasswordEntry.requestFocus();
@@ -168,23 +157,79 @@
 
     }
 
+    private void showDefaultMessage() {
+        if (mRemainingAttempts >= 0) {
+            mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
+                    mRemainingAttempts, true));
+            return;
+        }
+
+        boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
+        int count = TelephonyManager.getDefault().getSimCount();
+        Resources rez = getResources();
+        String msg;
+        int color = Color.WHITE;
+        if (count < 2) {
+            msg = rez.getString(R.string.kg_puk_enter_puk_hint);
+        } else {
+            SubscriptionInfo info = KeyguardUpdateMonitor.getInstance(mContext).
+                    getSubscriptionInfoForSubId(mSubId);
+            CharSequence displayName = info != null ? info.getDisplayName() : "";
+            msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName);
+            if (info != null) {
+                color = info.getIconTint();
+            }
+        }
+        if (isEsimLocked) {
+            msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim);
+        }
+        mSecurityMessageDisplay.setMessage(msg);
+        mSimImageView.setImageTintList(ColorStateList.valueOf(color));
+
+        // Sending empty PUK here to query the number of remaining PIN attempts
+        new CheckSimPuk("", "", mSubId) {
+            void onSimLockChangedResponse(final int result, final int attemptsRemaining) {
+                Log.d(LOG_TAG, "onSimCheckResponse " + " dummy One result" + result +
+                        " attemptsRemaining=" + attemptsRemaining);
+                if (attemptsRemaining >= 0) {
+                    mRemainingAttempts = attemptsRemaining;
+                    mSecurityMessageDisplay.setMessage(
+                            getPukPasswordErrorMessage(attemptsRemaining, true));
+                }
+            }
+        }.start();
+    }
+
+    private void handleSubInfoChangeIfNeeded() {
+        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
+        int subId = monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED);
+        if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) {
+            mSubId = subId;
+            mShowDefaultMessage = true;
+            mRemainingAttempts = -1;
+        }
+    }
+
     @Override
     protected int getPromtReasonStringRes(int reason) {
         // No message on SIM Puk
         return 0;
     }
 
-    private String getPukPasswordErrorMessage(int attemptsRemaining) {
+    private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) {
         String displayMessage;
 
         if (attemptsRemaining == 0) {
             displayMessage = getContext().getString(R.string.kg_password_wrong_puk_code_dead);
         } else if (attemptsRemaining > 0) {
+            int msgId = isDefault ? R.plurals.kg_password_default_puk_message :
+                    R.plurals.kg_password_wrong_puk_code;
             displayMessage = getContext().getResources()
-                    .getQuantityString(R.plurals.kg_password_wrong_puk_code, attemptsRemaining,
-                            attemptsRemaining);
+                    .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
         } else {
-            displayMessage = getContext().getString(R.string.kg_password_puk_failed);
+            int msgId = isDefault ? R.string.kg_puk_enter_puk_hint :
+                    R.string.kg_password_puk_failed;
+            displayMessage = getContext().getString(msgId);
         }
         if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:"
                 + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage);
@@ -303,7 +348,7 @@
     }
 
     private Dialog getPukRemainingAttemptsDialog(int remaining) {
-        String msg = getPukPasswordErrorMessage(remaining);
+        String msg = getPukPasswordErrorMessage(remaining, false);
         if (mRemainingAttemptsDialog == null) {
             AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
             builder.setMessage(msg);
@@ -359,16 +404,25 @@
                             if (result == PhoneConstants.PIN_RESULT_SUCCESS) {
                                 KeyguardUpdateMonitor.getInstance(getContext())
                                         .reportSimUnlocked(mSubId);
-                                mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                mRemainingAttempts = -1;
+                                mShowDefaultMessage = true;
+                                if (mCallback != null) {
+                                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                                }
                             } else {
+                                mShowDefaultMessage = false;
                                 if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) {
+                                    // show message
+                                    mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage(
+                                            attemptsRemaining, false));
                                     if (attemptsRemaining <= 2) {
                                         // this is getting critical - show dialog
                                         getPukRemainingAttemptsDialog(attemptsRemaining).show();
                                     } else {
                                         // show message
                                         mSecurityMessageDisplay.setMessage(
-                                                getPukPasswordErrorMessage(attemptsRemaining));
+                                                getPukPasswordErrorMessage(
+                                                attemptsRemaining, false));
                                     }
                                 } else {
                                     mSecurityMessageDisplay.setMessage(getContext().getString(
diff --git a/services/Android.bp b/services/Android.bp
new file mode 100644
index 0000000..84c45fe
--- /dev/null
+++ b/services/Android.bp
@@ -0,0 +1,8 @@
+// native library
+// =============================================================
+
+cc_library_shared {
+    name: "libandroid_servers",
+    defaults: ["libservices.core-libs"],
+    whole_static_libs: ["libservices.core"],
+}
diff --git a/services/Android.mk b/services/Android.mk
index ed2ba1f8..81d8181 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -52,23 +52,6 @@
 
 include $(BUILD_JAVA_LIBRARY)
 
-# native library
-# =============================================================
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES :=
-LOCAL_SHARED_LIBRARIES :=
-
-# include all the jni subdirs to collect their sources
-include $(wildcard $(LOCAL_PATH)/*/jni/Android.mk)
-
-LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
-
-LOCAL_MODULE:= libandroid_servers
-
-include $(BUILD_SHARED_LIBRARY)
-
 # =============================================================
 
 ifeq (,$(ONE_SHOT_MAKEFILE))
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 763a4e4..d9713a5 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -1628,7 +1628,7 @@
 
                         //Do enable request
                         try {
-                            if (mQuietEnable) {
+                            if (!mQuietEnable) {
                                 if (!mBluetooth.enable()) {
                                     Slog.e(TAG, "IBluetooth.enable() returned false");
                                 }
diff --git a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
index 28c3585..bd2e96e 100644
--- a/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
+++ b/services/core/java/com/android/server/connectivity/DefaultNetworkMetrics.java
@@ -51,6 +51,7 @@
     // Information about the current status of the default network.
     @GuardedBy("this")
     private DefaultNetworkEvent mCurrentDefaultNetwork;
+    // True if the current default network has been validated.
     @GuardedBy("this")
     private boolean mIsCurrentlyValid;
     @GuardedBy("this")
@@ -71,6 +72,8 @@
             printEvent(localTimeMs, pw, ev);
         }
         mCurrentDefaultNetwork.updateDuration(timeMs);
+        // When printing default network events for bug reports, update validation time
+        // and refresh the last validation timestmap for future validation time updates.
         if (mIsCurrentlyValid) {
             updateValidationTime(timeMs);
             mLastValidationTimeMs = timeMs;
@@ -92,11 +95,13 @@
     }
 
     public synchronized void logDefaultNetworkValidity(long timeMs, boolean isValid) {
+        // Transition from valid to invalid: update validity duration since last update
         if (!isValid && mIsCurrentlyValid) {
             mIsCurrentlyValid = false;
             updateValidationTime(timeMs);
         }
 
+        // Transition from invalid to valid: simply mark the validation timestamp.
         if (isValid && !mIsCurrentlyValid) {
             mIsCurrentlyValid = true;
             mLastValidationTimeMs = timeMs;
@@ -114,6 +119,9 @@
     }
 
     private void logCurrentDefaultNetwork(long timeMs, NetworkAgentInfo oldNai) {
+        if (mIsCurrentlyValid) {
+            updateValidationTime(timeMs);
+        }
         DefaultNetworkEvent ev = mCurrentDefaultNetwork;
         ev.updateDuration(timeMs);
         ev.previousTransports = mLastTransports;
@@ -122,7 +130,6 @@
             // The system acquired a new default network.
             fillLinkInfo(ev, oldNai);
             ev.finalScore = oldNai.getCurrentScore();
-            ev.validatedMs = ev.durationMs;
         }
         // Only change transport of the previous default network if the event currently logged
         // corresponds to an existing default network, and not to the absence of a default network.
@@ -143,9 +150,10 @@
             fillLinkInfo(ev, newNai);
             ev.initialScore = newNai.getCurrentScore();
             if (newNai.lastValidated) {
-                mIsCurrentlyValid = true;
-                mLastValidationTimeMs = timeMs;
+                logDefaultNetworkValidity(timeMs, true);
             }
+        } else {
+            mIsCurrentlyValid = false;
         }
         mCurrentDefaultNetwork = ev;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 920deb9..08c8813 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -14629,14 +14629,13 @@
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return null;
         }
-        // writer
-        synchronized (mPackages) {
-            if (!isExternalMediaAvailable()) {
+        if (!isExternalMediaAvailable()) {
                 // If the external storage is no longer mounted at this point,
                 // the caller may not have been able to delete all of this
                 // packages files and can not delete any more.  Bail.
-                return null;
-            }
+            return null;
+        }
+        synchronized (mPackages) {
             final ArrayList<PackageCleanItem> pkgs = mSettings.mPackagesToBeCleaned;
             if (lastPackage != null) {
                 pkgs.remove(lastPackage);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
new file mode 100644
index 0000000..27acaee
--- /dev/null
+++ b/services/core/jni/Android.bp
@@ -0,0 +1,121 @@
+cc_library_static {
+    name: "libservices.core",
+    defaults: ["libservices.core-libs"],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+
+        "-DEGL_EGLEXT_PROTOTYPES",
+        "-DGL_GLEXT_PROTOTYPES",
+    ],
+
+    srcs: [
+        "BroadcastRadio/JavaRef.cpp",
+        "BroadcastRadio/NativeCallbackThread.cpp",
+        "BroadcastRadio/BroadcastRadioService.cpp",
+        "BroadcastRadio/Tuner.cpp",
+        "BroadcastRadio/TunerCallback.cpp",
+        "BroadcastRadio/convert.cpp",
+        "BroadcastRadio/regions.cpp",
+        "com_android_server_AlarmManagerService.cpp",
+        "com_android_server_am_BatteryStatsService.cpp",
+        "com_android_server_connectivity_Vpn.cpp",
+        "com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
+        "com_android_server_ConsumerIrService.cpp",
+        "com_android_server_HardwarePropertiesManagerService.cpp",
+        "com_android_server_hdmi_HdmiCecController.cpp",
+        "com_android_server_input_InputApplicationHandle.cpp",
+        "com_android_server_input_InputManagerService.cpp",
+        "com_android_server_input_InputWindowHandle.cpp",
+        "com_android_server_lights_LightsService.cpp",
+        "com_android_server_location_ContextHubService.cpp",
+        "com_android_server_location_GnssLocationProvider.cpp",
+        "com_android_server_locksettings_SyntheticPasswordManager.cpp",
+        "com_android_server_power_PowerManagerService.cpp",
+        "com_android_server_SerialService.cpp",
+        "com_android_server_storage_AppFuseBridge.cpp",
+        "com_android_server_SystemServer.cpp",
+        "com_android_server_tv_TvUinputBridge.cpp",
+        "com_android_server_tv_TvInputHal.cpp",
+        "com_android_server_vr_VrManagerService.cpp",
+        "com_android_server_UsbDeviceManager.cpp",
+        "com_android_server_UsbDescriptorParser.cpp",
+        "com_android_server_UsbMidiDevice.cpp",
+        "com_android_server_UsbHostManager.cpp",
+        "com_android_server_VibratorService.cpp",
+        "com_android_server_PersistentDataBlockService.cpp",
+        "com_android_server_GraphicsStatsService.cpp",
+        "onload.cpp",
+    ],
+
+    include_dirs: [
+        "frameworks/base/libs",
+        "frameworks/native/services",
+        "system/gatekeeper/include",
+    ],
+}
+
+cc_defaults {
+    name: "libservices.core-libs",
+    shared_libs: [
+        "libandroid_runtime",
+        "libandroidfw",
+        "libaudioclient",
+        "libbase",
+        "libappfuse",
+        "libbinder",
+        "libcutils",
+        "libcrypto",
+        "liblog",
+        "libhardware",
+        "libhardware_legacy",
+        "libhidlbase",
+        "libkeystore_binder",
+        "libnativehelper",
+        "libutils",
+        "libui",
+        "libinput",
+        "libinputflinger",
+        "libinputservice",
+        "libschedulerservicehidl",
+        "libsensorservice",
+        "libsensorservicehidl",
+        "libskia",
+        "libgui",
+        "libusbhost",
+        "libsuspend",
+        "libEGL",
+        "libGLESv2",
+        "libnetutils",
+        "libhidlbase",
+        "libhidltransport",
+        "libhwbinder",
+        "libutils",
+        "libhwui",
+        "android.hardware.audio.common@2.0",
+        "android.hardware.broadcastradio@1.0",
+        "android.hardware.broadcastradio@1.1",
+        "android.hardware.contexthub@1.0",
+        "android.hardware.gnss@1.0",
+        "android.hardware.ir@1.0",
+        "android.hardware.light@2.0",
+        "android.hardware.power@1.0",
+        "android.hardware.power@1.1",
+        "android.hardware.tetheroffload.config@1.0",
+        "android.hardware.thermal@1.0",
+        "android.hardware.tv.cec@1.0",
+        "android.hardware.tv.input@1.0",
+        "android.hardware.vibrator@1.0",
+        "android.hardware.vibrator@1.1",
+        "android.hardware.vr@1.0",
+        "android.frameworks.schedulerservice@1.0",
+        "android.frameworks.sensorservice@1.0",
+    ],
+
+    static_libs: [
+        "android.hardware.broadcastradio@1.1-utils-lib",
+        "libscrypt_static",
+    ],
+}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
deleted file mode 100644
index bf9f941..0000000
--- a/services/core/jni/Android.mk
+++ /dev/null
@@ -1,114 +0,0 @@
-# This file is included by the top level services directory to collect source
-# files
-LOCAL_REL_DIR := core/jni
-
-LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter
-
-LOCAL_SRC_FILES += \
-    $(LOCAL_REL_DIR)/BroadcastRadio/JavaRef.cpp \
-    $(LOCAL_REL_DIR)/BroadcastRadio/NativeCallbackThread.cpp \
-    $(LOCAL_REL_DIR)/BroadcastRadio/BroadcastRadioService.cpp \
-    $(LOCAL_REL_DIR)/BroadcastRadio/Tuner.cpp \
-    $(LOCAL_REL_DIR)/BroadcastRadio/TunerCallback.cpp \
-    $(LOCAL_REL_DIR)/BroadcastRadio/convert.cpp \
-    $(LOCAL_REL_DIR)/BroadcastRadio/regions.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_HardwarePropertiesManagerService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_hdmi_HdmiCecController.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_input_InputApplicationHandle.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_input_InputWindowHandle.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_lights_LightsService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_location_ContextHubService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_locksettings_SyntheticPasswordManager.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_storage_AppFuseBridge.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_tv_TvUinputBridge.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_vr_VrManagerService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_UsbDescriptorParser.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
-    $(LOCAL_REL_DIR)/com_android_server_GraphicsStatsService.cpp \
-    $(LOCAL_REL_DIR)/onload.cpp
-
-LOCAL_C_INCLUDES += \
-    $(JNI_H_INCLUDE) \
-    external/scrypt/lib/crypto \
-    frameworks/base/services \
-    frameworks/base/libs \
-    frameworks/base/core/jni \
-    frameworks/native/services \
-    system/core/libappfuse/include \
-    system/gatekeeper/include \
-    system/security/keystore/include \
-    $(call include-path-for, libhardware)/hardware \
-    $(call include-path-for, libhardware_legacy)/hardware_legacy \
-
-LOCAL_SHARED_LIBRARIES += \
-    libandroid_runtime \
-    libandroidfw \
-    libaudioclient \
-    libbase \
-    libappfuse \
-    libbinder \
-    libcutils \
-    libcrypto \
-    liblog \
-    libhardware \
-    libhardware_legacy \
-    libhidlbase \
-    libkeystore_binder \
-    libnativehelper \
-    libutils \
-    libui \
-    libinput \
-    libinputflinger \
-    libinputservice \
-    libschedulerservicehidl \
-    libsensorservice \
-    libsensorservicehidl \
-    libskia \
-    libgui \
-    libusbhost \
-    libsuspend \
-    libEGL \
-    libGLESv2 \
-    libnetutils \
-    libhidlbase \
-    libhidltransport \
-    libhwbinder \
-    libutils \
-    libhwui \
-    android.hardware.audio.common@2.0 \
-    android.hardware.broadcastradio@1.0 \
-    android.hardware.broadcastradio@1.1 \
-    android.hardware.contexthub@1.0 \
-    android.hardware.gnss@1.0 \
-    android.hardware.ir@1.0 \
-    android.hardware.light@2.0 \
-    android.hardware.power@1.0 \
-    android.hardware.power@1.1 \
-    android.hardware.tetheroffload.config@1.0 \
-    android.hardware.thermal@1.0 \
-    android.hardware.tv.cec@1.0 \
-    android.hardware.tv.input@1.0 \
-    android.hardware.vibrator@1.0 \
-    android.hardware.vibrator@1.1 \
-    android.hardware.vr@1.0 \
-    android.frameworks.schedulerservice@1.0 \
-    android.frameworks.sensorservice@1.0 \
-
-LOCAL_STATIC_LIBRARIES += \
-    android.hardware.broadcastradio@1.1-utils-lib \
-    libscrypt_static \
diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
index 1925c39..6cf4fa9a 100644
--- a/services/net/java/android/net/ip/ConnectivityPacketTracker.java
+++ b/services/net/java/android/net/ip/ConnectivityPacketTracker.java
@@ -19,7 +19,7 @@
 import static android.system.OsConstants.*;
 
 import android.net.NetworkUtils;
-import android.net.util.BlockingSocketReader;
+import android.net.util.PacketReader;
 import android.net.util.ConnectivityPacketSummary;
 import android.os.Handler;
 import android.system.ErrnoException;
@@ -65,7 +65,7 @@
 
     private final String mTag;
     private final LocalLog mLog;
-    private final BlockingSocketReader mPacketListener;
+    private final PacketReader mPacketListener;
     private boolean mRunning;
     private String mDisplayName;
 
@@ -101,7 +101,7 @@
         mDisplayName = null;
     }
 
-    private final class PacketListener extends BlockingSocketReader {
+    private final class PacketListener extends PacketReader {
         private final int mIfIndex;
         private final byte mHwAddr[];
 
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 70983c8..fdb366c 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -815,6 +815,15 @@
         pw.println(Objects.toString(provisioningConfig, "N/A"));
         pw.decreaseIndent();
 
+        final IpReachabilityMonitor iprm = mIpReachabilityMonitor;
+        if (iprm != null) {
+            pw.println();
+            pw.println(mTag + " current IpReachabilityMonitor state:");
+            pw.increaseIndent();
+            iprm.dump(pw);
+            pw.decreaseIndent();
+        }
+
         pw.println();
         pw.println(mTag + " StateMachine dump:");
         pw.increaseIndent();
@@ -1237,6 +1246,7 @@
             mIpReachabilityMonitor = new IpReachabilityMonitor(
                     mContext,
                     mInterfaceName,
+                    getHandler(),
                     mLog,
                     new IpReachabilityMonitor.Callback() {
                         @Override
diff --git a/services/net/java/android/net/ip/IpNeighborMonitor.java b/services/net/java/android/net/ip/IpNeighborMonitor.java
new file mode 100644
index 0000000..6807334
--- /dev/null
+++ b/services/net/java/android/net/ip/IpNeighborMonitor.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import android.net.netlink.NetlinkConstants;
+import android.net.netlink.NetlinkErrorMessage;
+import android.net.netlink.NetlinkMessage;
+import android.net.netlink.NetlinkSocket;
+import android.net.netlink.RtNetlinkNeighborMessage;
+import android.net.netlink.StructNdMsg;
+import android.net.netlink.StructNlMsgHdr;
+import android.net.util.PacketReader;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.NetlinkSocketAddress;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+
+import com.android.internal.util.BitUtils;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.StringJoiner;
+
+
+/**
+ * IpNeighborMonitor.
+ *
+ * Monitors the kernel rtnetlink neighbor notifications and presents to callers
+ * NeighborEvents describing each event. Callers can provide a consumer instance
+ * to both filter (e.g. by interface index and IP address) and handle the
+ * generated NeighborEvents.
+ *
+ * @hide
+ */
+public class IpNeighborMonitor extends PacketReader {
+    private static final String TAG = IpNeighborMonitor.class.getSimpleName();
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    /**
+     * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
+     * for the given IP address on the specified interface index.
+     *
+     * @return 0 if the request was successfully passed to the kernel; otherwise return
+     *         a non-zero error code.
+     */
+    public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
+        final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
+        if (DBG) { Log.d(TAG, msgSnippet); }
+
+        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
+                1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
+
+        try {
+            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Error " + msgSnippet + ": " + e);
+            return -e.errno;
+        }
+
+        return 0;
+    }
+
+    public static class NeighborEvent {
+        final long elapsedMs;
+        final short msgType;
+        final int ifindex;
+        final InetAddress ip;
+        final short nudState;
+        final byte[] linkLayerAddr;
+
+        public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
+                short nudState, byte[] linkLayerAddr) {
+            this.elapsedMs = elapsedMs;
+            this.msgType = msgType;
+            this.ifindex = ifindex;
+            this.ip = ip;
+            this.nudState = nudState;
+            this.linkLayerAddr = linkLayerAddr;
+        }
+
+        boolean isConnected() {
+            return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
+                   StructNdMsg.isNudStateConnected(nudState);
+        }
+
+        boolean isValid() {
+            return (msgType != NetlinkConstants.RTM_DELNEIGH) &&
+                   StructNdMsg.isNudStateValid(nudState);
+        }
+
+        @Override
+        public String toString() {
+            final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
+            return j.add("@" + elapsedMs)
+                    .add(NetlinkConstants.stringForNlMsgType(msgType))
+                    .add("if=" + ifindex)
+                    .add(ip.getHostAddress())
+                    .add(StructNdMsg.stringForNudState(nudState))
+                    .add("[" + NetlinkConstants.hexify(linkLayerAddr) + "]")
+                    .toString();
+        }
+    }
+
+    public interface NeighborEventConsumer {
+        // Every neighbor event received on the netlink socket is passed in
+        // here. Subclasses should filter for events of interest.
+        public void accept(NeighborEvent event);
+    }
+
+    private final SharedLog mLog;
+    private final NeighborEventConsumer mConsumer;
+
+    public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
+        super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
+        mLog = log.forSubComponent(TAG);
+        mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
+    }
+
+    @Override
+    protected FileDescriptor createFd() {
+        FileDescriptor fd = null;
+
+        try {
+            fd = NetlinkSocket.forProto(OsConstants.NETLINK_ROUTE);
+            Os.bind(fd, (SocketAddress)(new NetlinkSocketAddress(0, OsConstants.RTMGRP_NEIGH)));
+            Os.connect(fd, (SocketAddress)(new NetlinkSocketAddress(0, 0)));
+
+            if (VDBG) {
+                final NetlinkSocketAddress nlAddr = (NetlinkSocketAddress) Os.getsockname(fd);
+                Log.d(TAG, "bound to sockaddr_nl{"
+                        + BitUtils.uint32(nlAddr.getPortId()) + ", "
+                        + nlAddr.getGroupsMask()
+                        + "}");
+            }
+        } catch (ErrnoException|SocketException e) {
+            logError("Failed to create rtnetlink socket", e);
+            IoUtils.closeQuietly(fd);
+            return null;
+        }
+
+        return fd;
+    }
+
+    @Override
+    protected void handlePacket(byte[] recvbuf, int length) {
+        final long whenMs = SystemClock.elapsedRealtime();
+
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        parseNetlinkMessageBuffer(byteBuffer, whenMs);
+    }
+
+    private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
+        while (byteBuffer.remaining() > 0) {
+            final int position = byteBuffer.position();
+            final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
+            if (nlMsg == null || nlMsg.getHeader() == null) {
+                byteBuffer.position(position);
+                mLog.e("unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
+                break;
+            }
+
+            final int srcPortId = nlMsg.getHeader().nlmsg_pid;
+            if (srcPortId !=  0) {
+                mLog.e("non-kernel source portId: " + BitUtils.uint32(srcPortId));
+                break;
+            }
+
+            if (nlMsg instanceof NetlinkErrorMessage) {
+                mLog.e("netlink error: " + nlMsg);
+                continue;
+            } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
+                mLog.i("non-rtnetlink neighbor msg: " + nlMsg);
+                continue;
+            }
+
+            evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
+        }
+    }
+
+    private void evaluateRtNetlinkNeighborMessage(
+            RtNetlinkNeighborMessage neighMsg, long whenMs) {
+        final short msgType = neighMsg.getHeader().nlmsg_type;
+        final StructNdMsg ndMsg = neighMsg.getNdHeader();
+        if (ndMsg == null) {
+            mLog.e("RtNetlinkNeighborMessage without ND message header!");
+            return;
+        }
+
+        final int ifindex = ndMsg.ndm_ifindex;
+        final InetAddress destination = neighMsg.getDestination();
+        final short nudState =
+                (msgType == NetlinkConstants.RTM_DELNEIGH)
+                ? StructNdMsg.NUD_NONE
+                : ndMsg.ndm_state;
+
+        final NeighborEvent event = new NeighborEvent(
+                whenMs, msgType, ifindex, destination, nudState, neighMsg.getLinkLayerAddress());
+
+        if (VDBG) {
+            Log.d(TAG, neighMsg.toString());
+        }
+        if (DBG) {
+            Log.d(TAG, event.toString());
+        }
+
+        mConsumer.accept(event);
+    }
+}
diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java
index 714b35a..b31ffbb 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -22,30 +22,27 @@
 import android.net.LinkProperties.ProvisioningChange;
 import android.net.ProxyInfo;
 import android.net.RouteInfo;
+import android.net.ip.IpNeighborMonitor.NeighborEvent;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpReachabilityEvent;
-import android.net.netlink.NetlinkConstants;
-import android.net.netlink.NetlinkErrorMessage;
-import android.net.netlink.NetlinkMessage;
-import android.net.netlink.NetlinkSocket;
-import android.net.netlink.RtNetlinkNeighborMessage;
 import android.net.netlink.StructNdMsg;
-import android.net.netlink.StructNdaCacheInfo;
-import android.net.netlink.StructNlMsgHdr;
 import android.net.util.MultinetworkPolicyTracker;
 import android.net.util.SharedLog;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
 import android.system.ErrnoException;
-import android.system.NetlinkSocketAddress;
 import android.system.OsConstants;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.DumpUtils.Dump;
 
 import java.io.InterruptedIOException;
+import java.io.PrintWriter;
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -134,6 +131,8 @@
  *          state it may be best for the link to disconnect completely and
  *          reconnect afresh.
  *
+ * Accessing an instance of this class from multiple threads is NOT safe.
+ *
  * @hide
  */
 public class IpReachabilityMonitor {
@@ -169,64 +168,33 @@
         }
     }
 
-    private final Object mLock = new Object();
     private final String mInterfaceName;
     private final int mInterfaceIndex;
+    private final IpNeighborMonitor mIpNeighborMonitor;
     private final SharedLog mLog;
     private final Callback mCallback;
     private final Dependencies mDependencies;
     private final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
-    private final NetlinkSocketObserver mNetlinkSocketObserver;
-    private final Thread mObserverThread;
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
-    @GuardedBy("mLock")
     private LinkProperties mLinkProperties = new LinkProperties();
-    // TODO: consider a map to a private NeighborState class holding more
-    // information than a single NUD state entry.
-    @GuardedBy("mLock")
-    private Map<InetAddress, Short> mIpWatchList = new HashMap<>();
-    @GuardedBy("mLock")
-    private int mIpWatchListVersion;
-    private volatile boolean mRunning;
+    private Map<InetAddress, NeighborEvent> mNeighborWatchList = new HashMap<>();
     // Time in milliseconds of the last forced probe request.
     private volatile long mLastProbeTimeMs;
 
-    /**
-     * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
-     * for the given IP address on the specified interface index.
-     *
-     * @return 0 if the request was successfully passed to the kernel; otherwise return
-     *         a non-zero error code.
-     */
-    private static int probeNeighbor(int ifIndex, InetAddress ip) {
-        final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
-        if (DBG) { Log.d(TAG, msgSnippet); }
-
-        final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
-                1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
-
-        try {
-            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg);
-        } catch (ErrnoException e) {
-            Log.e(TAG, "Error " + msgSnippet + ": " + e);
-            return -e.errno;
-        }
-
-        return 0;
+    public IpReachabilityMonitor(
+            Context context, String ifName, Handler h, SharedLog log, Callback callback) {
+        this(context, ifName, h, log, callback, null);
     }
 
-    public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) {
-        this(context, ifName, log, callback, null);
-    }
-
-    public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback,
+    public IpReachabilityMonitor(
+            Context context, String ifName, Handler h, SharedLog log, Callback callback,
             MultinetworkPolicyTracker tracker) {
-        this(ifName, getInterfaceIndex(ifName), log, callback, tracker,
+        this(ifName, getInterfaceIndex(ifName), h, log, callback, tracker,
                 Dependencies.makeDefault(context, ifName));
     }
 
     @VisibleForTesting
-    IpReachabilityMonitor(String ifName, int ifIndex, SharedLog log, Callback callback,
+    IpReachabilityMonitor(String ifName, int ifIndex, Handler h, SharedLog log, Callback callback,
             MultinetworkPolicyTracker tracker, Dependencies dependencies) {
         mInterfaceName = ifName;
         mLog = log.forSubComponent(TAG);
@@ -234,47 +202,56 @@
         mMultinetworkPolicyTracker = tracker;
         mInterfaceIndex = ifIndex;
         mDependencies = dependencies;
-        mNetlinkSocketObserver = new NetlinkSocketObserver();
-        mObserverThread = new Thread(mNetlinkSocketObserver);
-        mObserverThread.start();
+
+        mIpNeighborMonitor = new IpNeighborMonitor(h, mLog,
+                (NeighborEvent event) -> {
+                    if (mInterfaceIndex != event.ifindex) return;
+                    if (!mNeighborWatchList.containsKey(event.ip)) return;
+
+                    final NeighborEvent prev = mNeighborWatchList.put(event.ip, event);
+
+                    // TODO: Consider what to do with other states that are not within
+                    // NeighborEvent#isValid() (i.e. NUD_NONE, NUD_INCOMPLETE).
+                    if (event.nudState == StructNdMsg.NUD_FAILED) {
+                        mLog.w("ALERT neighbor went from: " + prev + " to: " + event);
+                        handleNeighborLost(event);
+                    }
+                });
+        mIpNeighborMonitor.start();
     }
 
     public void stop() {
-        mRunning = false;
+        mIpNeighborMonitor.stop();
         clearLinkProperties();
-        mNetlinkSocketObserver.clearNetlinkSocket();
     }
 
-    // TODO: add a public dump() method that can be called during a bug report.
+    public void dump(PrintWriter pw) {
+        DumpUtils.dumpAsync(
+                mIpNeighborMonitor.getHandler(),
+                new Dump() {
+                    @Override
+                    public void dump(PrintWriter pw, String prefix) {
+                        pw.println(describeWatchList("\n"));
+                    }
+                },
+                pw, "", 1000);
+    }
 
-    private String describeWatchList() {
-        final String delimiter = ", ";
-        StringBuilder sb = new StringBuilder();
-        synchronized (mLock) {
-            sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}, ");
-            sb.append("v{" + mIpWatchListVersion + "}, ");
-            sb.append("ntable=[");
-            boolean firstTime = true;
-            for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
-                if (firstTime) {
-                    firstTime = false;
-                } else {
-                    sb.append(delimiter);
-                }
-                sb.append(entry.getKey().getHostAddress() + "/" +
-                        StructNdMsg.stringForNudState(entry.getValue()));
-            }
-            sb.append("]");
+    private String describeWatchList() { return describeWatchList(" "); }
+
+    private String describeWatchList(String sep) {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("iface{" + mInterfaceName + "/" + mInterfaceIndex + "}," + sep);
+        sb.append("ntable=[" + sep);
+        String delimiter = "";
+        for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
+            sb.append(delimiter).append(entry.getKey().getHostAddress() + "/" + entry.getValue());
+            delimiter = "," + sep;
         }
+        sb.append("]");
         return sb.toString();
     }
 
-    private boolean isWatching(InetAddress ip) {
-        synchronized (mLock) {
-            return mRunning && mIpWatchList.containsKey(ip);
-        }
-    }
-
     private static boolean isOnLink(List<RouteInfo> routes, InetAddress ip) {
         for (RouteInfo route : routes) {
             if (!route.hasGateway() && route.matches(ip)) {
@@ -284,13 +261,6 @@
         return false;
     }
 
-    private short getNeighborStateLocked(InetAddress ip) {
-        if (mIpWatchList.containsKey(ip)) {
-            return mIpWatchList.get(ip);
-        }
-        return StructNdMsg.NUD_NONE;
-    }
-
     public void updateLinkProperties(LinkProperties lp) {
         if (!mInterfaceName.equals(lp.getInterfaceName())) {
             // TODO: figure out whether / how to cope with interface changes.
@@ -299,70 +269,63 @@
             return;
         }
 
-        synchronized (mLock) {
-            mLinkProperties = new LinkProperties(lp);
-            Map<InetAddress, Short> newIpWatchList = new HashMap<>();
+        mLinkProperties = new LinkProperties(lp);
+        Map<InetAddress, NeighborEvent> newNeighborWatchList = new HashMap<>();
 
-            final List<RouteInfo> routes = mLinkProperties.getRoutes();
-            for (RouteInfo route : routes) {
-                if (route.hasGateway()) {
-                    InetAddress gw = route.getGateway();
-                    if (isOnLink(routes, gw)) {
-                        newIpWatchList.put(gw, getNeighborStateLocked(gw));
-                    }
+        final List<RouteInfo> routes = mLinkProperties.getRoutes();
+        for (RouteInfo route : routes) {
+            if (route.hasGateway()) {
+                InetAddress gw = route.getGateway();
+                if (isOnLink(routes, gw)) {
+                    newNeighborWatchList.put(gw, mNeighborWatchList.getOrDefault(gw, null));
                 }
             }
-
-            for (InetAddress nameserver : lp.getDnsServers()) {
-                if (isOnLink(routes, nameserver)) {
-                    newIpWatchList.put(nameserver, getNeighborStateLocked(nameserver));
-                }
-            }
-
-            mIpWatchList = newIpWatchList;
-            mIpWatchListVersion++;
         }
+
+        for (InetAddress dns : lp.getDnsServers()) {
+            if (isOnLink(routes, dns)) {
+                newNeighborWatchList.put(dns, mNeighborWatchList.getOrDefault(dns, null));
+            }
+        }
+
+        mNeighborWatchList = newNeighborWatchList;
         if (DBG) { Log.d(TAG, "watch: " + describeWatchList()); }
     }
 
     public void clearLinkProperties() {
-        synchronized (mLock) {
-            mLinkProperties.clear();
-            mIpWatchList.clear();
-            mIpWatchListVersion++;
-        }
+        mLinkProperties.clear();
+        mNeighborWatchList.clear();
         if (DBG) { Log.d(TAG, "clear: " + describeWatchList()); }
     }
 
-    private void handleNeighborLost(String msg) {
+    private void handleNeighborLost(NeighborEvent event) {
+        final LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
+
         InetAddress ip = null;
-        final ProvisioningChange delta;
-        synchronized (mLock) {
-            LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
+        for (Map.Entry<InetAddress, NeighborEvent> entry : mNeighborWatchList.entrySet()) {
+            // TODO: Consider using NeighborEvent#isValid() here; it's more
+            // strict but may interact badly if other entries are somehow in
+            // NUD_INCOMPLETE (say, during network attach).
+            if (entry.getValue().nudState != StructNdMsg.NUD_FAILED) continue;
 
-            for (Map.Entry<InetAddress, Short> entry : mIpWatchList.entrySet()) {
-                if (entry.getValue() != StructNdMsg.NUD_FAILED) {
-                    continue;
-                }
-
-                ip = entry.getKey();
-                for (RouteInfo route : mLinkProperties.getRoutes()) {
-                    if (ip.equals(route.getGateway())) {
-                        whatIfLp.removeRoute(route);
-                    }
-                }
-
-                if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
-                    // We should do this unconditionally, but alas we cannot: b/31827713.
-                    whatIfLp.removeDnsServer(ip);
+            ip = entry.getKey();
+            for (RouteInfo route : mLinkProperties.getRoutes()) {
+                if (ip.equals(route.getGateway())) {
+                    whatIfLp.removeRoute(route);
                 }
             }
 
-            delta = LinkProperties.compareProvisioning(mLinkProperties, whatIfLp);
+            if (avoidingBadLinks() || !(ip instanceof Inet6Address)) {
+                // We should do this unconditionally, but alas we cannot: b/31827713.
+                whatIfLp.removeDnsServer(ip);
+            }
         }
 
+        final ProvisioningChange delta = LinkProperties.compareProvisioning(
+                mLinkProperties, whatIfLp);
+
         if (delta == ProvisioningChange.LOST_PROVISIONING) {
-            final String logMsg = "FAILURE: LOST_PROVISIONING, " + msg;
+            final String logMsg = "FAILURE: LOST_PROVISIONING, " + event;
             Log.w(TAG, logMsg);
             if (mCallback != null) {
                 // TODO: remove |ip| when the callback signature no longer has
@@ -378,12 +341,9 @@
     }
 
     public void probeAll() {
-        final List<InetAddress> ipProbeList;
-        synchronized (mLock) {
-            ipProbeList = new ArrayList<>(mIpWatchList.keySet());
-        }
+        final List<InetAddress> ipProbeList = new ArrayList<>(mNeighborWatchList.keySet());
 
-        if (!ipProbeList.isEmpty() && mRunning) {
+        if (!ipProbeList.isEmpty()) {
             // Keep the CPU awake long enough to allow all ARP/ND
             // probes a reasonable chance at success. See b/23197666.
             //
@@ -394,13 +354,10 @@
         }
 
         for (InetAddress target : ipProbeList) {
-            if (!mRunning) {
-                break;
-            }
-            final int returnValue = probeNeighbor(mInterfaceIndex, target);
+            final int rval = IpNeighborMonitor.startKernelNeighborProbe(mInterfaceIndex, target);
             mLog.log(String.format("put neighbor %s into NUD_PROBE state (rval=%d)",
-                     target.getHostAddress(), returnValue));
-            logEvent(IpReachabilityEvent.PROBE, returnValue);
+                     target.getHostAddress(), rval));
+            logEvent(IpReachabilityEvent.PROBE, rval);
         }
         mLastProbeTimeMs = SystemClock.elapsedRealtime();
     }
@@ -446,153 +403,4 @@
         int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost);
         mMetricsLog.log(mInterfaceName, new IpReachabilityEvent(eventType));
     }
-
-    // TODO: simplify the number of objects by making this extend Thread.
-    private final class NetlinkSocketObserver implements Runnable {
-        private NetlinkSocket mSocket;
-
-        @Override
-        public void run() {
-            if (VDBG) { Log.d(TAG, "Starting observing thread."); }
-            mRunning = true;
-
-            try {
-                setupNetlinkSocket();
-            } catch (ErrnoException | SocketException e) {
-                Log.e(TAG, "Failed to suitably initialize a netlink socket", e);
-                mRunning = false;
-            }
-
-            while (mRunning) {
-                final ByteBuffer byteBuffer;
-                try {
-                    byteBuffer = recvKernelReply();
-                } catch (ErrnoException e) {
-                    if (mRunning) { Log.w(TAG, "ErrnoException: ", e); }
-                    break;
-                }
-                final long whenMs = SystemClock.elapsedRealtime();
-                if (byteBuffer == null) {
-                    continue;
-                }
-                parseNetlinkMessageBuffer(byteBuffer, whenMs);
-            }
-
-            clearNetlinkSocket();
-
-            mRunning = false; // Not a no-op when ErrnoException happened.
-            if (VDBG) { Log.d(TAG, "Finishing observing thread."); }
-        }
-
-        private void clearNetlinkSocket() {
-            if (mSocket != null) {
-                mSocket.close();
-            }
-        }
-
-            // TODO: Refactor the main loop to recreate the socket upon recoverable errors.
-        private void setupNetlinkSocket() throws ErrnoException, SocketException {
-            clearNetlinkSocket();
-            mSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
-
-            final NetlinkSocketAddress listenAddr = new NetlinkSocketAddress(
-                    0, OsConstants.RTMGRP_NEIGH);
-            mSocket.bind(listenAddr);
-
-            if (VDBG) {
-                final NetlinkSocketAddress nlAddr = mSocket.getLocalAddress();
-                Log.d(TAG, "bound to sockaddr_nl{"
-                        + ((long) (nlAddr.getPortId() & 0xffffffff)) + ", "
-                        + nlAddr.getGroupsMask()
-                        + "}");
-            }
-        }
-
-        private ByteBuffer recvKernelReply() throws ErrnoException {
-            try {
-                return mSocket.recvMessage(0);
-            } catch (InterruptedIOException e) {
-                // Interruption or other error, e.g. another thread closed our file descriptor.
-            } catch (ErrnoException e) {
-                if (e.errno != OsConstants.EAGAIN) {
-                    throw e;
-                }
-            }
-            return null;
-        }
-
-        private void parseNetlinkMessageBuffer(ByteBuffer byteBuffer, long whenMs) {
-            while (byteBuffer.remaining() > 0) {
-                final int position = byteBuffer.position();
-                final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer);
-                if (nlMsg == null || nlMsg.getHeader() == null) {
-                    byteBuffer.position(position);
-                    Log.e(TAG, "unparsable netlink msg: " + NetlinkConstants.hexify(byteBuffer));
-                    break;
-                }
-
-                final int srcPortId = nlMsg.getHeader().nlmsg_pid;
-                if (srcPortId !=  0) {
-                    Log.e(TAG, "non-kernel source portId: " + ((long) (srcPortId & 0xffffffff)));
-                    break;
-                }
-
-                if (nlMsg instanceof NetlinkErrorMessage) {
-                    Log.e(TAG, "netlink error: " + nlMsg);
-                    continue;
-                } else if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
-                    if (DBG) {
-                        Log.d(TAG, "non-rtnetlink neighbor msg: " + nlMsg);
-                    }
-                    continue;
-                }
-
-                evaluateRtNetlinkNeighborMessage((RtNetlinkNeighborMessage) nlMsg, whenMs);
-            }
-        }
-
-        private void evaluateRtNetlinkNeighborMessage(
-                RtNetlinkNeighborMessage neighMsg, long whenMs) {
-            final StructNdMsg ndMsg = neighMsg.getNdHeader();
-            if (ndMsg == null || ndMsg.ndm_ifindex != mInterfaceIndex) {
-                return;
-            }
-
-            final InetAddress destination = neighMsg.getDestination();
-            if (!isWatching(destination)) {
-                return;
-            }
-
-            final short msgType = neighMsg.getHeader().nlmsg_type;
-            final short nudState = ndMsg.ndm_state;
-            final String eventMsg = "NeighborEvent{"
-                    + "elapsedMs=" + whenMs + ", "
-                    + destination.getHostAddress() + ", "
-                    + "[" + NetlinkConstants.hexify(neighMsg.getLinkLayerAddress()) + "], "
-                    + NetlinkConstants.stringForNlMsgType(msgType) + ", "
-                    + StructNdMsg.stringForNudState(nudState)
-                    + "}";
-
-            if (VDBG) {
-                Log.d(TAG, neighMsg.toString());
-            } else if (DBG) {
-                Log.d(TAG, eventMsg);
-            }
-
-            synchronized (mLock) {
-                if (mIpWatchList.containsKey(destination)) {
-                    final short value =
-                            (msgType == NetlinkConstants.RTM_DELNEIGH)
-                            ? StructNdMsg.NUD_NONE
-                            : nudState;
-                    mIpWatchList.put(destination, value);
-                }
-            }
-
-            if (nudState == StructNdMsg.NUD_FAILED) {
-                Log.w(TAG, "ALERT: " + eventMsg);
-                handleNeighborLost(eventMsg);
-            }
-        }
-    }
 }
diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java
index f5f211d..5af3c29 100644
--- a/services/net/java/android/net/netlink/NetlinkSocket.java
+++ b/services/net/java/android/net/netlink/NetlinkSocket.java
@@ -16,16 +16,24 @@
 
 package android.net.netlink;
 
+import static android.system.OsConstants.AF_NETLINK;
+import static android.system.OsConstants.EIO;
+import static android.system.OsConstants.EPROTO;
+import static android.system.OsConstants.ETIMEDOUT;
+import static android.system.OsConstants.SO_RCVBUF;
+import static android.system.OsConstants.SO_RCVTIMEO;
+import static android.system.OsConstants.SO_SNDTIMEO;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
+
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
 import android.system.Os;
-import android.system.OsConstants;
 import android.system.StructTimeval;
 import android.util.Log;
 import libcore.io.IoUtils;
 import libcore.io.Libcore;
 
-import java.io.Closeable;
 import java.io.FileDescriptor;
 import java.io.InterruptedIOException;
 import java.net.SocketAddress;
@@ -37,28 +45,27 @@
 /**
  * NetlinkSocket
  *
- * A small wrapper class to assist with AF_NETLINK socket operations.
+ * A small static class to assist with AF_NETLINK socket operations.
  *
  * @hide
  */
-public class NetlinkSocket implements Closeable {
+public class NetlinkSocket {
     private static final String TAG = "NetlinkSocket";
-    private static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
-    private static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
 
-    final private FileDescriptor mDescriptor;
-    private NetlinkSocketAddress mAddr;
-    private long mLastRecvTimeoutMs;
-    private long mLastSendTimeoutMs;
+    public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024;
+    public static final int SOCKET_RECV_BUFSIZE = 64 * 1024;
 
     public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException {
         final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage";
+        final long IO_TIMEOUT = 300L;
 
-        try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) {
-            final long IO_TIMEOUT = 300L;
-            nlSocket.connectToKernel();
-            nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT);
-            final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT);
+        FileDescriptor fd;
+
+        try {
+            fd = forProto(nlProto);
+            connectToKernel(fd);
+            sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT);
+            final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT);
             // recvMessage() guaranteed to not return null if it did not throw.
             final NetlinkMessage response = NetlinkMessage.parse(bytes);
             if (response != null && response instanceof NetlinkErrorMessage &&
@@ -81,61 +88,30 @@
                     errmsg = response.toString();
                 }
                 Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
-                throw new ErrnoException(errmsg, OsConstants.EPROTO);
+                throw new ErrnoException(errmsg, EPROTO);
             }
         } catch (InterruptedIOException e) {
             Log.e(TAG, errPrefix, e);
-            throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e);
+            throw new ErrnoException(errPrefix, ETIMEDOUT, e);
         } catch (SocketException e) {
             Log.e(TAG, errPrefix, e);
-            throw new ErrnoException(errPrefix, OsConstants.EIO, e);
+            throw new ErrnoException(errPrefix, EIO, e);
         }
+
+        IoUtils.closeQuietly(fd);
     }
 
-    public NetlinkSocket(int nlProto) throws ErrnoException {
-        mDescriptor = Os.socket(
-                OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto);
-
-        Os.setsockoptInt(
-                mDescriptor, OsConstants.SOL_SOCKET,
-                OsConstants.SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+    public static FileDescriptor forProto(int nlProto) throws ErrnoException {
+        final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto);
+        Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE);
+        return fd;
     }
 
-    public NetlinkSocketAddress getLocalAddress() throws ErrnoException {
-        return (NetlinkSocketAddress) Os.getsockname(mDescriptor);
+    public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException {
+        Os.connect(fd, (SocketAddress) (new NetlinkSocketAddress(0, 0)));
     }
 
-    public void bind(NetlinkSocketAddress localAddr) throws ErrnoException, SocketException {
-        Os.bind(mDescriptor, (SocketAddress)localAddr);
-    }
-
-    public void connectTo(NetlinkSocketAddress peerAddr)
-            throws ErrnoException, SocketException {
-        Os.connect(mDescriptor, (SocketAddress) peerAddr);
-    }
-
-    public void connectToKernel() throws ErrnoException, SocketException {
-        connectTo(new NetlinkSocketAddress(0, 0));
-    }
-
-    /**
-     * Wait indefinitely (or until underlying socket error) for a
-     * netlink message of at most DEFAULT_RECV_BUFSIZE size.
-     */
-    public ByteBuffer recvMessage()
-            throws ErrnoException, InterruptedIOException {
-        return recvMessage(DEFAULT_RECV_BUFSIZE, 0);
-    }
-
-    /**
-     * Wait up to |timeoutMs| (or until underlying socket error) for a
-     * netlink message of at most DEFAULT_RECV_BUFSIZE size.
-     */
-    public ByteBuffer recvMessage(long timeoutMs) throws ErrnoException, InterruptedIOException {
-        return recvMessage(DEFAULT_RECV_BUFSIZE, timeoutMs);
-    }
-
-    private void checkTimeout(long timeoutMs) {
+    private static void checkTimeout(long timeoutMs) {
         if (timeoutMs < 0) {
             throw new IllegalArgumentException("Negative timeouts not permitted");
         }
@@ -147,21 +123,14 @@
      *
      * Multi-threaded calls with different timeouts will cause unexpected results.
      */
-    public ByteBuffer recvMessage(int bufsize, long timeoutMs)
+    public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs)
             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
         checkTimeout(timeoutMs);
 
-        synchronized (mDescriptor) {
-            if (mLastRecvTimeoutMs != timeoutMs) {
-                Os.setsockoptTimeval(mDescriptor,
-                        OsConstants.SOL_SOCKET, OsConstants.SO_RCVTIMEO,
-                        StructTimeval.fromMillis(timeoutMs));
-                mLastRecvTimeoutMs = timeoutMs;
-            }
-        }
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs));
 
         ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize);
-        int length = Os.read(mDescriptor, byteBuffer);
+        int length = Os.read(fd, byteBuffer);
         if (length == bufsize) {
             Log.w(TAG, "maximum read");
         }
@@ -172,39 +141,16 @@
     }
 
     /**
-     * Send a message to a peer to which this socket has previously connected.
-     *
-     * This blocks until completion or an error occurs.
-     */
-    public boolean sendMessage(byte[] bytes, int offset, int count)
-            throws ErrnoException, InterruptedIOException {
-        return sendMessage(bytes, offset, count, 0);
-    }
-
-    /**
      * Send a message to a peer to which this socket has previously connected,
      * waiting at most |timeoutMs| milliseconds for the send to complete.
      *
      * Multi-threaded calls with different timeouts will cause unexpected results.
      */
-    public boolean sendMessage(byte[] bytes, int offset, int count, long timeoutMs)
+    public static int sendMessage(
+            FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs)
             throws ErrnoException, IllegalArgumentException, InterruptedIOException {
         checkTimeout(timeoutMs);
-
-        synchronized (mDescriptor) {
-            if (mLastSendTimeoutMs != timeoutMs) {
-                Os.setsockoptTimeval(mDescriptor,
-                        OsConstants.SOL_SOCKET, OsConstants.SO_SNDTIMEO,
-                        StructTimeval.fromMillis(timeoutMs));
-                mLastSendTimeoutMs = timeoutMs;
-            }
-        }
-
-        return (count == Os.write(mDescriptor, bytes, offset, count));
-    }
-
-    @Override
-    public void close() {
-        IoUtils.closeQuietly(mDescriptor);
+        Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs));
+        return Os.write(fd, bytes, offset, count);
     }
 }
diff --git a/services/net/java/android/net/netlink/StructNdMsg.java b/services/net/java/android/net/netlink/StructNdMsg.java
index b68ec0b..e34ec39 100644
--- a/services/net/java/android/net/netlink/StructNdMsg.java
+++ b/services/net/java/android/net/netlink/StructNdMsg.java
@@ -63,6 +63,11 @@
         return ((nudState & (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)) != 0);
     }
 
+    public static boolean isNudStateValid(short nudState) {
+        return (isNudStateConnected(nudState) ||
+                ((nudState & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
+    }
+
     // Neighbor Cache Entry Flags
     public static byte NTF_USE       = (byte) 0x01;
     public static byte NTF_SELF      = (byte) 0x02;
@@ -143,7 +148,7 @@
     }
 
     public boolean nudValid() {
-        return (nudConnected() || ((ndm_state & (NUD_PROBE|NUD_STALE|NUD_DELAY)) != 0));
+        return isNudStateValid(ndm_state);
     }
 
     @Override
diff --git a/services/net/java/android/net/util/BlockingSocketReader.java b/services/net/java/android/net/util/PacketReader.java
similarity index 97%
rename from services/net/java/android/net/util/BlockingSocketReader.java
rename to services/net/java/android/net/util/PacketReader.java
index 99bf469..10da2a5 100644
--- a/services/net/java/android/net/util/BlockingSocketReader.java
+++ b/services/net/java/android/net/util/PacketReader.java
@@ -67,7 +67,7 @@
  *
  * @hide
  */
-public abstract class BlockingSocketReader {
+public abstract class PacketReader {
     private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
     private static final int UNREGISTER_THIS_FD = 0;
 
@@ -83,11 +83,11 @@
         IoUtils.closeQuietly(fd);
     }
 
-    protected BlockingSocketReader(Handler h) {
+    protected PacketReader(Handler h) {
         this(h, DEFAULT_RECV_BUF_SIZE);
     }
 
-    protected BlockingSocketReader(Handler h, int recvbufsize) {
+    protected PacketReader(Handler h, int recvbufsize) {
         mHandler = h;
         mQueue = mHandler.getLooper().getQueue();
         mPacket = new byte[Math.max(recvbufsize, DEFAULT_RECV_BUF_SIZE)];
@@ -115,6 +115,8 @@
         }
     }
 
+    public Handler getHandler() { return mHandler; }
+
     public final int recvBufSize() { return mPacket.length; }
 
     public final long numPacketsReceived() { return mPacketsReceived; }
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index c3a2ceb..56e1e64 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -280,6 +280,36 @@
      * {@hide}
      */
     public static final int NORMAL_UNSPECIFIED = 65;
+
+    /**
+     * Stk Call Control modified DIAL request to video DIAL request.
+     * {@hide}
+     */
+    public static final int DIAL_MODIFIED_TO_DIAL_VIDEO = 66;
+
+    /**
+     * Stk Call Control modified Video DIAL request to SS request.
+     * {@hide}
+     */
+    public static final int DIAL_VIDEO_MODIFIED_TO_SS = 67;
+
+    /**
+     * Stk Call Control modified Video DIAL request to USSD request.
+     * {@hide}
+     */
+    public static final int DIAL_VIDEO_MODIFIED_TO_USSD = 68;
+
+    /**
+     * Stk Call Control modified Video DIAL request to DIAL request.
+     * {@hide}
+     */
+    public static final int DIAL_VIDEO_MODIFIED_TO_DIAL = 69;
+
+    /**
+     * Stk Call Control modified Video DIAL request to Video DIAL request.
+     * {@hide}
+     */
+    public static final int DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 70;
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Update toString() with the newly added disconnect type.
@@ -382,6 +412,16 @@
             return "DIAL_MODIFIED_TO_SS";
         case DIAL_MODIFIED_TO_DIAL:
             return "DIAL_MODIFIED_TO_DIAL";
+        case DIAL_MODIFIED_TO_DIAL_VIDEO:
+            return "DIAL_MODIFIED_TO_DIAL_VIDEO";
+        case DIAL_VIDEO_MODIFIED_TO_SS:
+            return "DIAL_VIDEO_MODIFIED_TO_SS";
+        case DIAL_VIDEO_MODIFIED_TO_USSD:
+            return "DIAL_VIDEO_MODIFIED_TO_USSD";
+        case DIAL_VIDEO_MODIFIED_TO_DIAL:
+            return "DIAL_VIDEO_MODIFIED_TO_DIAL";
+        case DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO:
+            return "DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO";
         case ERROR_UNSPECIFIED:
             return "ERROR_UNSPECIFIED";
         case OUTGOING_FAILURE:
diff --git a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
index b371efb..daa74c8 100644
--- a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java
@@ -21,6 +21,7 @@
 
 import com.android.ims.ImsCallForwardInfo;
 import com.android.ims.ImsReasonInfo;
+import com.android.ims.ImsSsData;
 import com.android.ims.ImsSsInfo;
 import com.android.ims.internal.IImsUt;
 import com.android.ims.internal.IImsUtListener;
@@ -85,4 +86,10 @@
     public void utConfigurationCallWaitingQueried(IImsUt ut, int id, ImsSsInfo[] cwInfo)
             throws RemoteException {
     }
+
+    /**
+     * Notifies client when Supplementary Service indication is received
+     */
+    @Override
+    public void onSupplementaryServiceIndication(ImsSsData ssData) {}
 }
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index cdfc1fd..6ad54c1 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -111,6 +111,16 @@
     // and this capability is not supported by the network.
     public static final int CODE_IMEI_NOT_ACCEPTED = 243;
 
+    //STK CC errors
+    public static final int CODE_DIAL_MODIFIED_TO_USSD = 244;
+    public static final int CODE_DIAL_MODIFIED_TO_SS = 245;
+    public static final int CODE_DIAL_MODIFIED_TO_DIAL = 246;
+    public static final int CODE_DIAL_MODIFIED_TO_DIAL_VIDEO = 247;
+    public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL = 248;
+    public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 249;
+    public static final int CODE_DIAL_VIDEO_MODIFIED_TO_SS = 250;
+    public static final int CODE_DIAL_VIDEO_MODIFIED_TO_USSD = 251;
+
     /**
      * STATUSCODE (SIP response code) (IMS -> Telephony)
      */
@@ -217,6 +227,11 @@
     public static final int CODE_UT_OPERATION_NOT_ALLOWED = 803;
     public static final int CODE_UT_NETWORK_ERROR = 804;
     public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821;
+    //STK CC errors
+    public static final int CODE_UT_SS_MODIFIED_TO_DIAL = 822;
+    public static final int CODE_UT_SS_MODIFIED_TO_USSD = 823;
+    public static final int CODE_UT_SS_MODIFIED_TO_SS = 824;
+    public static final int CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO = 825;
 
     /**
      * ECBM
diff --git a/telephony/java/com/android/ims/ImsSsData.aidl b/telephony/java/com/android/ims/ImsSsData.aidl
new file mode 100644
index 0000000..33f8306
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsSsData.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+parcelable ImsSsData;
diff --git a/telephony/java/com/android/ims/ImsSsData.java b/telephony/java/com/android/ims/ImsSsData.java
new file mode 100644
index 0000000..7336c13
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsSsData.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+
+/**
+ * Provided STK Call Control Suplementary Service information
+ *
+ * {@hide}
+ */
+public class ImsSsData implements Parcelable {
+
+    //ServiceType
+    public static final int SS_CFU = 0;
+    public static final int SS_CF_BUSY = 1;
+    public static final int SS_CF_NO_REPLY = 2;
+    public static final int SS_CF_NOT_REACHABLE = 3;
+    public static final int SS_CF_ALL = 4;
+    public static final int SS_CF_ALL_CONDITIONAL = 5;
+    public static final int SS_CFUT = 6;
+    public static final int SS_CLIP = 7;
+    public static final int SS_CLIR = 8;
+    public static final int SS_COLP = 9;
+    public static final int SS_COLR = 10;
+    public static final int SS_CNAP = 11;
+    public static final int SS_WAIT = 12;
+    public static final int SS_BAOC = 13;
+    public static final int SS_BAOIC = 14;
+    public static final int SS_BAOIC_EXC_HOME = 15;
+    public static final int SS_BAIC = 16;
+    public static final int SS_BAIC_ROAMING = 17;
+    public static final int SS_ALL_BARRING = 18;
+    public static final int SS_OUTGOING_BARRING = 19;
+    public static final int SS_INCOMING_BARRING = 20;
+    public static final int SS_INCOMING_BARRING_DN = 21;
+    public static final int SS_INCOMING_BARRING_ANONYMOUS = 22;
+
+    //SSRequestType
+    public static final int SS_ACTIVATION = 0;
+    public static final int SS_DEACTIVATION = 1;
+    public static final int SS_INTERROGATION = 2;
+    public static final int SS_REGISTRATION = 3;
+    public static final int SS_ERASURE = 4;
+
+    //TeleserviceType
+    public static final int SS_ALL_TELE_AND_BEARER_SERVICES = 0;
+    public static final int SS_ALL_TELESEVICES = 1;
+    public static final int SS_TELEPHONY = 2;
+    public static final int SS_ALL_DATA_TELESERVICES = 3;
+    public static final int SS_SMS_SERVICES = 4;
+    public static final int SS_ALL_TELESERVICES_EXCEPT_SMS = 5;
+
+    // Refer to ServiceType
+    public int serviceType;
+    // Refere to SSRequestType
+    public int requestType;
+    // Refer to TeleserviceType
+    public int teleserviceType;
+    // Service Class
+    public int serviceClass;
+    // Error information
+    public int result;
+
+    public int[] ssInfo; /* Valid for all supplementary services.
+                             This field will be empty for RequestType SS_INTERROGATION
+                             and ServiceType SS_CF_*, SS_INCOMING_BARRING_DN,
+                             SS_INCOMING_BARRING_ANONYMOUS.*/
+
+    public ImsCallForwardInfo[] cfInfo; /* Valid only for supplementary services
+                                            ServiceType SS_CF_* and RequestType SS_INTERROGATION */
+
+    public ImsSsInfo[] imsSsInfo;   /* Valid only for ServiceType SS_INCOMING_BARRING_DN and
+                                        ServiceType SS_INCOMING_BARRING_ANONYMOUS */
+
+    public ImsSsData() {}
+
+    public ImsSsData(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public static final Creator<ImsSsData> CREATOR = new Creator<ImsSsData>() {
+        @Override
+        public ImsSsData createFromParcel(Parcel in) {
+            return new ImsSsData(in);
+        }
+
+        @Override
+        public ImsSsData[] newArray(int size) {
+            return new ImsSsData[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(serviceType);
+        out.writeInt(requestType);
+        out.writeInt(teleserviceType);
+        out.writeInt(serviceClass);
+        out.writeInt(result);
+        out.writeIntArray(ssInfo);
+        out.writeParcelableArray(cfInfo, 0);
+    }
+
+    private void readFromParcel(Parcel in) {
+        serviceType = in.readInt();
+        requestType = in.readInt();
+        teleserviceType = in.readInt();
+        serviceClass = in.readInt();
+        result = in.readInt();
+        ssInfo = in.createIntArray();
+        cfInfo = (ImsCallForwardInfo[])in.readParcelableArray(this.getClass().getClassLoader());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public boolean isTypeCF() {
+        return (serviceType == SS_CFU || serviceType == SS_CF_BUSY ||
+              serviceType == SS_CF_NO_REPLY || serviceType == SS_CF_NOT_REACHABLE ||
+              serviceType == SS_CF_ALL || serviceType == SS_CF_ALL_CONDITIONAL);
+    }
+
+    public boolean isTypeUnConditional() {
+        return (serviceType == SS_CFU || serviceType == SS_CF_ALL);
+    }
+
+    public boolean isTypeCW() {
+        return (serviceType == SS_WAIT);
+    }
+
+    public boolean isTypeClip() {
+        return (serviceType == SS_CLIP);
+    }
+
+    public boolean isTypeColr() {
+        return (serviceType == SS_COLR);
+    }
+
+    public boolean isTypeColp() {
+        return (serviceType == SS_COLP);
+    }
+
+    public boolean isTypeClir() {
+        return (serviceType == SS_CLIR);
+    }
+
+    public boolean isTypeIcb() {
+        return (serviceType == SS_INCOMING_BARRING_DN ||
+                serviceType == SS_INCOMING_BARRING_ANONYMOUS);
+    }
+
+    public boolean isTypeBarring() {
+        return (serviceType == SS_BAOC || serviceType == SS_BAOIC ||
+              serviceType == SS_BAOIC_EXC_HOME || serviceType == SS_BAIC ||
+              serviceType == SS_BAIC_ROAMING || serviceType == SS_ALL_BARRING ||
+              serviceType == SS_OUTGOING_BARRING || serviceType == SS_INCOMING_BARRING);
+    }
+
+    public boolean isTypeInterrogation() {
+        return (requestType == SS_INTERROGATION);
+    }
+
+    public String toString() {
+        return "[ImsSsData] " + "ServiceType: " + serviceType
+            + " RequestType: " + requestType
+            + " TeleserviceType: " + teleserviceType
+            + " ServiceClass: " + serviceClass
+            + " Result: " + result;
+    }
+}
diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java
index 250371f..14c184a 100644
--- a/telephony/java/com/android/ims/ImsUtInterface.java
+++ b/telephony/java/com/android/ims/ImsUtInterface.java
@@ -16,6 +16,7 @@
 
 package com.android.ims;
 
+import android.os.Handler;
 import android.os.Message;
 
 /**
@@ -188,4 +189,18 @@
      * Updates the configuration of the COLP supplementary service.
      */
     public void updateCOLP(boolean enable, Message result);
+
+    /**
+     * Register for UNSOL_ON_SS indications.
+     * @param handler the {@link Handler} that is notified when there is an ss indication.
+     * @param event  Supplimentary service indication event.
+     * @param Object user object.
+     */
+    public void registerForSuppServiceIndication(Handler handler, int event, Object object);
+
+    /**
+     * Deregister for UNSOL_ON_SS indications.
+     * @param handler the {@link Handler} that is notified when there is an ss indication.
+     */
+    public void unregisterForSuppServiceIndication(Handler handler);
 }
diff --git a/telephony/java/com/android/ims/internal/IImsUtListener.aidl b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
index 300273a..1bc0369 100644
--- a/telephony/java/com/android/ims/internal/IImsUtListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsUtListener.aidl
@@ -19,6 +19,7 @@
 import android.os.Bundle;
 
 import com.android.ims.ImsCallForwardInfo;
+import com.android.ims.ImsSsData;
 import com.android.ims.ImsSsInfo;
 import com.android.ims.internal.IImsUt;
 import com.android.ims.ImsReasonInfo;
@@ -56,4 +57,11 @@
      */
     void utConfigurationCallWaitingQueried(in IImsUt ut,
             int id, in ImsSsInfo[] cwInfo);
+
+    /**
+     * Notifies client when Supplementary Service indication is received
+     *
+     * @param ssData Details of SS request and response information
+     */
+    void onSupplementaryServiceIndication(in ImsSsData ssData);
 }
diff --git a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
index f849689..54776db 100644
--- a/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
+++ b/tests/net/java/android/net/ip/IpReachabilityMonitorTest.java
@@ -18,10 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.when;
 
 import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.Looper;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -42,14 +44,18 @@
     @Mock IpReachabilityMonitor.Callback mCallback;
     @Mock IpReachabilityMonitor.Dependencies mDependencies;
     @Mock SharedLog mLog;
+    Handler mHandler;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mLog.forSubComponent(anyString())).thenReturn(mLog);
+        mHandler = new Handler(Looper.getMainLooper());
     }
 
     IpReachabilityMonitor makeMonitor() {
-        return new IpReachabilityMonitor("fake0", 1, mLog, mCallback, null, mDependencies);
+        return new IpReachabilityMonitor(
+                "fake0", 1, mHandler, mLog, mCallback, null, mDependencies);
     }
 
     @Test
diff --git a/tests/net/java/android/net/netlink/NetlinkSocketTest.java b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
index bd36bac8..11be40b 100644
--- a/tests/net/java/android/net/netlink/NetlinkSocketTest.java
+++ b/tests/net/java/android/net/netlink/NetlinkSocketTest.java
@@ -16,6 +16,8 @@
 
 package android.net.netlink;
 
+import static android.net.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
+import static android.system.OsConstants.NETLINK_ROUTE;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -28,10 +30,12 @@
 import android.support.test.filters.SmallTest;
 import android.system.ErrnoException;
 import android.system.NetlinkSocketAddress;
-import android.system.OsConstants;
+import android.system.Os;
 import android.util.Log;
+import libcore.io.IoUtils;
 
 import java.io.InterruptedIOException;
+import java.io.FileDescriptor;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
@@ -46,29 +50,28 @@
 
     @Test
     public void testBasicWorkingGetNeighborsQuery() throws Exception {
-        NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
-        assertNotNull(s);
+        final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE);
+        assertNotNull(fd);
 
-        s.connectToKernel();
+        NetlinkSocket.connectToKernel(fd);
 
-        NetlinkSocketAddress localAddr = s.getLocalAddress();
+        final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd);
         assertNotNull(localAddr);
         assertEquals(0, localAddr.getGroupsMask());
         assertTrue(0 != localAddr.getPortId());
 
         final int TEST_SEQNO = 5;
-        final byte[] request = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
-        assertNotNull(request);
+        final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO);
+        assertNotNull(req);
 
         final long TIMEOUT = 500;
-        assertTrue(s.sendMessage(request, 0, request.length, TIMEOUT));
+        assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, TIMEOUT));
 
         int neighMessageCount = 0;
         int doneMessageCount = 0;
 
         while (doneMessageCount == 0) {
-            ByteBuffer response = null;
-            response = s.recvMessage(TIMEOUT);
+            ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
             assertNotNull(response);
             assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit());
             assertEquals(0, response.position());
@@ -100,30 +103,6 @@
         // TODO: make sure this test passes sanely in airplane mode.
         assertTrue(neighMessageCount > 0);
 
-        s.close();
-    }
-
-    @Test
-    public void testRepeatedCloseCallsAreQuiet() throws Exception {
-        // Create a working NetlinkSocket.
-        NetlinkSocket s = new NetlinkSocket(OsConstants.NETLINK_ROUTE);
-        assertNotNull(s);
-        s.connectToKernel();
-        NetlinkSocketAddress localAddr = s.getLocalAddress();
-        assertNotNull(localAddr);
-        assertEquals(0, localAddr.getGroupsMask());
-        assertTrue(0 != localAddr.getPortId());
-        // Close once.
-        s.close();
-        // Test that it is closed.
-        boolean expectedErrorSeen = false;
-        try {
-            localAddr = s.getLocalAddress();
-        } catch (ErrnoException e) {
-            expectedErrorSeen = true;
-        }
-        assertTrue(expectedErrorSeen);
-        // Close once more.
-        s.close();
+        IoUtils.closeQuietly(fd);
     }
 }
diff --git a/tests/net/java/android/net/util/BlockingSocketReaderTest.java b/tests/net/java/android/net/util/PacketReaderTest.java
similarity index 91%
rename from tests/net/java/android/net/util/BlockingSocketReaderTest.java
rename to tests/net/java/android/net/util/PacketReaderTest.java
index 29dfa4c..dced743 100644
--- a/tests/net/java/android/net/util/BlockingSocketReaderTest.java
+++ b/tests/net/java/android/net/util/PacketReaderTest.java
@@ -16,7 +16,7 @@
 
 package android.net.util;
 
-import static android.net.util.BlockingSocketReader.DEFAULT_RECV_BUF_SIZE;
+import static android.net.util.PacketReader.DEFAULT_RECV_BUF_SIZE;
 import static android.system.OsConstants.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -53,13 +53,13 @@
 import libcore.io.IoBridge;
 
 /**
- * Tests for BlockingSocketReader.
+ * Tests for PacketReader.
  *
  * @hide
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class BlockingSocketReaderTest {
+public class PacketReaderTest {
     static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
     static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
 
@@ -69,9 +69,9 @@
     protected byte[] mLastRecvBuf;
     protected boolean mStopped;
     protected HandlerThread mHandlerThread;
-    protected BlockingSocketReader mReceiver;
+    protected PacketReader mReceiver;
 
-    class UdpLoopbackReader extends BlockingSocketReader {
+    class UdpLoopbackReader extends PacketReader {
         public UdpLoopbackReader(Handler h) {
             super(h);
         }
@@ -121,7 +121,7 @@
         mLastRecvBuf = null;
         mStopped = false;
 
-        mHandlerThread = new HandlerThread(BlockingSocketReaderTest.class.getSimpleName());
+        mHandlerThread = new HandlerThread(PacketReaderTest.class.getSimpleName());
         mHandlerThread.start();
     }
 
@@ -188,8 +188,8 @@
         mReceiver = null;
     }
 
-    class NullBlockingSocketReader extends BlockingSocketReader {
-        public NullBlockingSocketReader(Handler h, int recvbufsize) {
+    class NullPacketReader extends PacketReader {
+        public NullPacketReader(Handler h, int recvbufsize) {
             super(h, recvbufsize);
         }
 
@@ -202,7 +202,7 @@
         final Handler h = mHandlerThread.getThreadHandler();
 
         for (int i : new int[]{-1, 0, 1, DEFAULT_RECV_BUF_SIZE-1}) {
-            final BlockingSocketReader b = new NullBlockingSocketReader(h, i);
+            final PacketReader b = new NullPacketReader(h, i);
             assertEquals(DEFAULT_RECV_BUF_SIZE, b.recvBufSize());
         }
     }
diff --git a/wifi/java/android/net/wifi/aware/PeerHandle.java b/wifi/java/android/net/wifi/aware/PeerHandle.java
index cd45c52..b525212 100644
--- a/wifi/java/android/net/wifi/aware/PeerHandle.java
+++ b/wifi/java/android/net/wifi/aware/PeerHandle.java
@@ -32,4 +32,22 @@
 
     /** @hide */
     public int peerId;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof PeerHandle)) {
+            return false;
+        }
+
+        return peerId == ((PeerHandle) o).peerId;
+    }
+
+    @Override
+    public int hashCode() {
+        return peerId;
+    }
 }