Merge "Set isConnected, isBound, implCreated on server-side LocalSockets"
diff --git a/Android.mk b/Android.mk
index bac3802..5dfa58a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -125,6 +125,8 @@
 	core/java/android/bluetooth/IBluetoothSap.aidl \
 	core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
 	core/java/android/bluetooth/IBluetoothHeadsetClient.aidl \
+	core/java/android/bluetooth/IBluetoothInputHost.aidl \
+	core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGatt.aidl \
 	core/java/android/bluetooth/IBluetoothGattCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index 89800d2..86b2119 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -36932,6 +36932,7 @@
     field public static final java.lang.String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
     field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
     field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
+    field public static final java.lang.String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
     field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
@@ -37469,6 +37470,7 @@
     method public java.lang.String getSimOperatorName();
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
+    method public int getSimState(int);
     method public java.lang.String getSubscriberId();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
@@ -37551,7 +37553,11 @@
     field public static final int PHONE_TYPE_NONE = 0; // 0x0
     field public static final int PHONE_TYPE_SIP = 3; // 0x3
     field public static final int SIM_STATE_ABSENT = 1; // 0x1
+    field public static final int SIM_STATE_CARD_IO_ERROR = 8; // 0x8
+    field public static final int SIM_STATE_CARD_RESTRICTED = 9; // 0x9
     field public static final int SIM_STATE_NETWORK_LOCKED = 4; // 0x4
+    field public static final int SIM_STATE_NOT_READY = 6; // 0x6
+    field public static final int SIM_STATE_PERM_DISABLED = 7; // 0x7
     field public static final int SIM_STATE_PIN_REQUIRED = 2; // 0x2
     field public static final int SIM_STATE_PUK_REQUIRED = 3; // 0x3
     field public static final int SIM_STATE_READY = 5; // 0x5
diff --git a/api/system-current.txt b/api/system-current.txt
index f0ae379..b2f2a3c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -25673,7 +25673,7 @@
     field public static final java.lang.String EXTRA_SEQUENCE = "android.net.extra.SEQUENCE";
   }
 
-  public static final class NetworkRecommendationProvider.ResultCallback {
+  public static class NetworkRecommendationProvider.ResultCallback {
     method public void onResult(android.net.RecommendationResult);
   }
 
@@ -25761,9 +25761,11 @@
   }
 
   public final class RecommendationResult implements android.os.Parcelable {
-    ctor public RecommendationResult(android.net.wifi.WifiConfiguration);
+    method public static android.net.RecommendationResult createConnectRecommendation(android.net.wifi.WifiConfiguration);
+    method public static android.net.RecommendationResult createDoNotConnectRecommendation();
     method public int describeContents();
     method public android.net.wifi.WifiConfiguration getWifiConfiguration();
+    method public boolean hasRecommendation();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.net.RecommendationResult> CREATOR;
   }
@@ -25824,8 +25826,9 @@
     ctor public ScoredNetwork(android.net.NetworkKey, android.net.RssiCurve, boolean, android.os.Bundle);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final java.lang.String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL = "android.net.attributes.key.HAS_CAPTIVE_PORTAL";
+    field public static final java.lang.String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET = "android.net.attributes.key.RANKING_SCORE_OFFSET";
     field public static final android.os.Parcelable.Creator<android.net.ScoredNetwork> CREATOR;
-    field public static final java.lang.String EXTRA_HAS_CAPTIVE_PORTAL = "android.net.extra.HAS_CAPTIVE_PORTAL";
     field public final android.os.Bundle attributes;
     field public final boolean meteredHint;
     field public final android.net.NetworkKey networkKey;
@@ -26812,6 +26815,7 @@
     field public int level;
     field public java.lang.CharSequence operatorFriendlyName;
     field public long timestamp;
+    field public boolean untrusted;
     field public java.lang.CharSequence venueName;
   }
 
@@ -26855,6 +26859,7 @@
     field public boolean hiddenSSID;
     field public java.lang.String lastUpdateName;
     field public int lastUpdateUid;
+    field public boolean meteredHint;
     field public int networkId;
     field public int numAssociation;
     field public int numScorerOverride;
@@ -40060,6 +40065,7 @@
     field public static final java.lang.String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
     field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
     field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
+    field public static final java.lang.String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
     field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
@@ -40635,6 +40641,7 @@
     method public java.lang.String getSimOperatorName();
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
+    method public int getSimState(int);
     method public java.lang.String getSubscriberId();
     method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
     method public java.lang.String getVoiceMailAlphaTag();
@@ -40748,7 +40755,11 @@
     field public static final int SIM_ACTIVATION_RESULT_IN_PROGRESS = 2; // 0x2
     field public static final int SIM_ACTIVATION_RESULT_NOT_SUPPORTED = 1; // 0x1
     field public static final int SIM_STATE_ABSENT = 1; // 0x1
+    field public static final int SIM_STATE_CARD_IO_ERROR = 8; // 0x8
+    field public static final int SIM_STATE_CARD_RESTRICTED = 9; // 0x9
     field public static final int SIM_STATE_NETWORK_LOCKED = 4; // 0x4
+    field public static final int SIM_STATE_NOT_READY = 6; // 0x6
+    field public static final int SIM_STATE_PERM_DISABLED = 7; // 0x7
     field public static final int SIM_STATE_PIN_REQUIRED = 2; // 0x2
     field public static final int SIM_STATE_PUK_REQUIRED = 3; // 0x3
     field public static final int SIM_STATE_READY = 5; // 0x5
diff --git a/api/test-current.txt b/api/test-current.txt
index 71a8711..e3a395b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -37014,6 +37014,7 @@
     field public static final java.lang.String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
     field public static final java.lang.String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
     field public static final java.lang.String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
+    field public static final java.lang.String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final java.lang.String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
     field public static final java.lang.String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
     field public static final java.lang.String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
@@ -37551,6 +37552,7 @@
     method public java.lang.String getSimOperatorName();
     method public java.lang.String getSimSerialNumber();
     method public int getSimState();
+    method public int getSimState(int);
     method public java.lang.String getSubscriberId();
     method public java.lang.String getVoiceMailAlphaTag();
     method public java.lang.String getVoiceMailNumber();
@@ -37633,7 +37635,11 @@
     field public static final int PHONE_TYPE_NONE = 0; // 0x0
     field public static final int PHONE_TYPE_SIP = 3; // 0x3
     field public static final int SIM_STATE_ABSENT = 1; // 0x1
+    field public static final int SIM_STATE_CARD_IO_ERROR = 8; // 0x8
+    field public static final int SIM_STATE_CARD_RESTRICTED = 9; // 0x9
     field public static final int SIM_STATE_NETWORK_LOCKED = 4; // 0x4
+    field public static final int SIM_STATE_NOT_READY = 6; // 0x6
+    field public static final int SIM_STATE_PERM_DISABLED = 7; // 0x7
     field public static final int SIM_STATE_PIN_REQUIRED = 2; // 0x2
     field public static final int SIM_STATE_PUK_REQUIRED = 3; // 0x3
     field public static final int SIM_STATE_READY = 5; // 0x5
diff --git a/compiled-classes-phone b/compiled-classes-phone
index 221d687..ec3371e 100644
--- a/compiled-classes-phone
+++ b/compiled-classes-phone
@@ -632,6 +632,7 @@
 android.bluetooth.BluetoothAudioConfig
 android.bluetooth.BluetoothClass
 android.bluetooth.BluetoothClass$1
+android.bluetooth.BluetoothCodecConfig
 android.bluetooth.BluetoothDevice
 android.bluetooth.BluetoothDevice$1
 android.bluetooth.BluetoothDevice$2
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 8f42467..857a361 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -509,10 +509,23 @@
      * Common-path handling of app data dir creation
      */
     private static File ensurePrivateDirExists(File file) {
+        return ensurePrivateDirExists(file, 0771, -1);
+    }
+
+    private static File ensurePrivateCacheDirExists(File file) {
+        final int gid = UserHandle.getCacheAppGid(Process.myUid());
+        return ensurePrivateDirExists(file, 02771, gid);
+    }
+
+    private static File ensurePrivateDirExists(File file, int mode, int gid) {
         if (!file.exists()) {
+            final String path = file.getAbsolutePath();
             try {
-                Os.mkdir(file.getAbsolutePath(), 0771);
-                Os.chmod(file.getAbsolutePath(), 0771);
+                Os.mkdir(path, mode);
+                Os.chmod(path, mode);
+                if (gid != -1) {
+                    Os.chown(path, -1, gid);
+                }
             } catch (ErrnoException e) {
                 if (e.errno == OsConstants.EEXIST) {
                     // We must have raced with someone; that's okay
@@ -581,7 +594,7 @@
             if (mCacheDir == null) {
                 mCacheDir = new File(getDataDir(), "cache");
             }
-            return ensurePrivateDirExists(mCacheDir);
+            return ensurePrivateCacheDirExists(mCacheDir);
         }
     }
 
@@ -591,7 +604,7 @@
             if (mCodeCacheDir == null) {
                 mCodeCacheDir = new File(getDataDir(), "code_cache");
             }
-            return ensurePrivateDirExists(mCodeCacheDir);
+            return ensurePrivateCacheDirExists(mCodeCacheDir);
         }
     }
 
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 9ea16a7..afe9651 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -51,6 +51,7 @@
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
+import dalvik.system.BaseDexClassLoader;
 import dalvik.system.VMRuntime;
 
 import java.io.File;
@@ -600,6 +601,40 @@
 
         VMRuntime.registerAppInfo(profileFile.getPath(), mApplicationInfo.dataDir,
                 codePaths.toArray(new String[codePaths.size()]), foreignDexProfilesFile.getPath());
+
+        // Setup the reporter to notify package manager of any relevant dex loads.
+        // At this point the primary apk is loaded and will not be reported.
+        // Anything loaded from now on will be tracked as a potential secondary
+        // or foreign dex file. The goal is to enable:
+        //    1) monitoring and compilation of secondary dex file
+        //    2) track foreign dex file usage (used to determined the
+        //       compilation filter of apks).
+        if (BaseDexClassLoader.getReporter() != DexLoadReporter.INSTANCE) {
+            // Set the dex load reporter if not already set.
+            // Note that during the app's life cycle different LoadedApks may be
+            // created and loaded (e.g. if two different apps share the same runtime).
+            BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE);
+        }
+    }
+
+    private static class DexLoadReporter implements BaseDexClassLoader.Reporter {
+        private static final DexLoadReporter INSTANCE = new DexLoadReporter();
+
+        private DexLoadReporter() {}
+
+        @Override
+        public void report(List<String> dexPaths) {
+            if (dexPaths.isEmpty()) {
+                return;
+            }
+            String packageName = ActivityThread.currentPackageName();
+            try {
+                ActivityThread.getPackageManager().notifyDexLoad(
+                        packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet());
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 353c640..1165fce 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -102,6 +102,27 @@
         "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
 
     /**
+     * Intent used to broadcast the change in the Audio Codec state of the
+     * A2DP Source profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     *   <li> {@link #EXTRA_CODEC_CONFIG} - The current codec configuration. </li>
+     *   <li> {@link #EXTRA_PREVIOUS_CODEC_CONFIG} - The previous codec configuration. </li>
+     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently
+     *   connected, otherwise it is not included.</li>
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CODEC_CONFIG_CHANGED =
+        "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED";
+
+    /**
      * A2DP sink device is streaming music. This state can be one of
      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
@@ -543,6 +564,54 @@
     }
 
     /**
+     * Gets the current codec configuration.
+     *
+     * @return the current codec configuration
+     * @hide
+     */
+    public BluetoothCodecConfig getCodecConfig() {
+        if (DBG) Log.d(TAG, "getCodecConfig");
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null && isEnabled()) {
+                return mService.getCodecConfig();
+            }
+            if (mService == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in getCodecConfig()", e);
+            return null;
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Sets the codec configuration preference.
+     *
+     * @param codecConfig the codec configuration preference
+     * @hide
+     */
+    public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
+        if (DBG) Log.d(TAG, "setCodecConfigPreference");
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null && isEnabled()) {
+                mService.setCodecConfigPreference(codecConfig);
+            }
+            if (mService == null) Log.w(TAG, "Proxy not attached to service");
+            return;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e);
+            return;
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+    }
+
+    /**
      * Helper for converting a state to a string.
      *
      * For debug use only - strings are not internationalized.
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 4a97b07..f9be3a1 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -680,30 +680,7 @@
     }
 
     /**
-     * Performs action based on user action to turn BT ON
-     * or OFF if BT is in BLE_ON state
-     */
-    private void notifyUserAction(boolean enable) {
-        try {
-            mServiceLock.readLock().lock();
-            if (mService == null) {
-                Log.e(TAG, "mService is null");
-                return;
-            }
-            if (enable) {
-                mService.onLeServiceUp(); //NA:TODO implementation pending
-            } else {
-                mService.onBrEdrDown(); //NA:TODO implementation pending
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "", e);
-        } finally {
-            mServiceLock.readLock().unlock();
-        }
-    }
-
-    /**
-     * Turns off Bluetooth LE which was earlier turned on by calling EnableBLE().
+     * Turns off Bluetooth LE which was earlier turned on by calling enableBLE().
      *
      * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition
      * to STATE_OFF and completely shut-down Bluetooth
@@ -733,61 +710,50 @@
         if (!isBleScanAlwaysAvailable()) return false;
 
         int state = getLeState();
-        if (state == BluetoothAdapter.STATE_ON) {
-            if (DBG) Log.d (TAG, "STATE_ON: shouldn't disable");
+        if (state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_BLE_ON) {
+            String packageName = ActivityThread.currentPackageName();
+            if (DBG) Log.d (TAG, "disableBLE(): de-registering " + packageName);
             try {
-                mManagerService.updateBleAppCount(mToken, false);
+                mManagerService.updateBleAppCount(mToken, false, packageName);
             } catch (RemoteException e) {
                 Log.e(TAG, "", e);
             }
             return true;
-
-        } else if (state == BluetoothAdapter.STATE_BLE_ON) {
-            if (DBG) Log.d (TAG, "STATE_BLE_ON");
-            int bleAppCnt = 0;
-            try {
-                bleAppCnt = mManagerService.updateBleAppCount(mToken, false);
-            } catch (RemoteException e) {
-                Log.e(TAG, "", e);
-            }
-            if (bleAppCnt == 0) {
-                // Disable only if there are no other clients
-                notifyUserAction(false);
-            }
-            return true;
         }
 
-        if (DBG) Log.d (TAG, "STATE_OFF: Already disabled");
+        if (DBG) Log.d (TAG, "disableBLE(): Already disabled");
         return false;
     }
 
     /**
-     * Special Applications who want to only turn on Bluetooth Low Energy (BLE) would
-     * EnableBLE, EnableBLE brings-up Bluetooth so that application can access
-     * only LE related feature (Bluetooth GATT layers interfaces using the respective class)
-     * EnableBLE in turn registers the existance of a special App which wants to
-     * turn on Bluetooth Low enrgy part without making it visible at the settings UI
-     * as Bluetooth ON.
-     * <p>Invoking EnableBLE when Bluetooth is already in ON state, would just registers
-     * the existance of special Application and doesn't do anything to current BT state.
-     * when user turn OFF Bluetooth from UI, if there is an existance of special app, Bluetooth
-     * would stay in BLE_ON state so that LE features are still acessible to the special
-     * Applications.
+     * Applications who want to only use Bluetooth Low Energy (BLE) can call enableBLE.
      *
-     * <p>This is an asynchronous call: it will return immediately, and
+     * enableBLE registers the existence of an app using only LE functions.
+     *
+     * enableBLE may enable Bluetooth to an LE only mode so that an app can use
+     * LE related features (BluetoothGatt or BluetoothGattServer classes)
+     *
+     * If the user disables Bluetooth while an app is registered to use LE only features,
+     * Bluetooth will remain on in LE only mode for the app.
+     *
+     * When Bluetooth is in LE only mode, it is not shown as ON to the UI.
+     *
+     * <p>This is an asynchronous call: it returns immediately, and
      * clients should listen for {@link #ACTION_BLE_STATE_CHANGED}
-     * to be notified of subsequent adapter state changes. If this call returns
-     * true, then the adapter state will immediately transition from {@link
-     * #STATE_OFF} to {@link #STATE_BLE_TURNING_ON}, and some time
-     * later transition to either {@link #STATE_OFF} or {@link
-     * #STATE_BLE_ON}. If this call returns false then there was an
-     * immediate problem that will prevent the adapter from being turned on -
-     * such as Airplane mode, or the adapter is already turned on.
-     * (@link #ACTION_BLE_STATE_CHANGED) returns the Bluetooth Adapter's various
+     * to be notified of adapter state changes.
+     *
+     * If this call returns * true, then the adapter state is either in a mode where
+     * LE is available, or will transition from {@link #STATE_OFF} to {@link #STATE_BLE_TURNING_ON},
+     * and some time later transition to either {@link #STATE_OFF} or {@link #STATE_BLE_ON}.
+     *
+     * If this call returns false then there was an immediate problem that prevents the
+     * adapter from being turned on - such as Airplane mode.
+     *
+     * {@link #ACTION_BLE_STATE_CHANGED} returns the Bluetooth Adapter's various
      * states, It includes all the classic Bluetooth Adapter states along with
      * internal BLE only states
      *
-     * @return true to indicate Bluetooth LE start-up has begun, or false on
+     * @return true to indicate Bluetooth LE will be available, or false on
      *         immediate error
      * @hide
      */
@@ -796,13 +762,14 @@
         if (!isBleScanAlwaysAvailable()) return false;
 
         try {
-            mManagerService.updateBleAppCount(mToken, true);
+            String packageName = ActivityThread.currentPackageName();
+            mManagerService.updateBleAppCount(mToken, true, packageName);
             if (isLeEnabled()) {
                 if (DBG) Log.d(TAG, "enableBLE(): Bluetooth already enabled");
                 return true;
             }
             if (DBG) Log.d(TAG, "enableBLE(): Calling enable");
-            return mManagerService.enable(ActivityThread.currentPackageName());
+            return mManagerService.enable(packageName);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1940,6 +1907,9 @@
         } else if (profile == BluetoothProfile.MAP_CLIENT) {
             BluetoothMapClient mapClient = new BluetoothMapClient(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.INPUT_HOST) {
+            BluetoothInputHost iHost = new BluetoothInputHost(context, listener);
+            return true;
         } else {
             return false;
         }
@@ -2016,6 +1986,10 @@
                 BluetoothMapClient mapClient = (BluetoothMapClient)proxy;
                 mapClient.close();
                 break;
+            case BluetoothProfile.INPUT_HOST:
+                BluetoothInputHost iHost = (BluetoothInputHost) proxy;
+                iHost.close();
+                break;
         }
     }
 
@@ -2087,7 +2061,7 @@
             return true;
         }
         try {
-            return mManagerService.enableNoAutoConnect();
+            return mManagerService.enableNoAutoConnect(ActivityThread.currentPackageName());
         } catch (RemoteException e) {Log.e(TAG, "", e);}
         return false;
     }
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.aidl b/core/java/android/bluetooth/BluetoothCodecConfig.aidl
new file mode 100644
index 0000000..553e66e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+parcelable BluetoothCodecConfig;
diff --git a/core/java/android/bluetooth/BluetoothCodecConfig.java b/core/java/android/bluetooth/BluetoothCodecConfig.java
new file mode 100644
index 0000000..5cc1277
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothCodecConfig.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Represents the codec configuration for a Bluetooth A2DP source device.
+ *
+ * {@see BluetoothA2dp}
+ *
+ * {@hide}
+ */
+public final class BluetoothCodecConfig implements Parcelable {
+
+    /**
+     * Extra for the codec configuration intents of the individual profiles.
+     *
+     * This extra represents the current codec configuration of the A2DP
+     * profile.
+     */
+    public static final String EXTRA_CODEC_CONFIG = "android.bluetooth.codec.extra.CODEC_CONFIG";
+
+    /**
+     * Extra for the codec configuration intents of the individual profiles.
+     *
+     * This extra represents the previous codec configuration of the A2DP
+     * profile.
+     */
+    public static final String EXTRA_PREVIOUS_CODEC_CONFIG =
+        "android.bluetooth.codec.extra.PREVIOUS_CODEC_CONFIG";
+
+    public static final int SOURCE_CODEC_TYPE_SBC     = 0;
+    public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
+
+    public static final int CODEC_PRIORITY_DEFAULT = 0;
+    public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000;
+
+    public static final int SAMPLE_RATE_NONE   = 0;
+    public static final int SAMPLE_RATE_44100  = 0x1 << 0;
+    public static final int SAMPLE_RATE_48000  = 0x1 << 1;
+    public static final int SAMPLE_RATE_88200  = 0x1 << 2;
+    public static final int SAMPLE_RATE_96000  = 0x1 << 3;
+    public static final int SAMPLE_RATE_176400 = 0x1 << 4;
+    public static final int SAMPLE_RATE_192000 = 0x1 << 5;
+
+    public static final int BITS_PER_SAMPLE_NONE = 0;
+    public static final int BITS_PER_SAMPLE_16   = 0x1 << 0;
+    public static final int BITS_PER_SAMPLE_24   = 0x1 << 1;
+    public static final int BITS_PER_SAMPLE_32   = 0x1 << 2;
+
+    public static final int CHANNEL_MODE_NONE   = 0;
+    public static final int CHANNEL_MODE_MONO   = 0x1 << 0;
+    public static final int CHANNEL_MODE_STEREO = 0x1 << 1;
+
+    private final int mCodecType;
+    private final int mCodecPriority;
+    private final int mSampleRate;
+    private final int mBitsPerSample;
+    private final int mChannelMode;
+    private final long mCodecSpecific1;
+    private final long mCodecSpecific2;
+    private final long mCodecSpecific3;
+    private final long mCodecSpecific4;
+
+    public BluetoothCodecConfig(int codecType, int codecPriority,
+                                int sampleRate, int bitsPerSample,
+                                int channelMode,long codecSpecific1,
+                                long codecSpecific2, long codecSpecific3,
+                                long codecSpecific4) {
+        mCodecType = codecType;
+        mCodecPriority = codecPriority;
+        mSampleRate = sampleRate;
+        mBitsPerSample = bitsPerSample;
+        mChannelMode = channelMode;
+        mCodecSpecific1 = codecSpecific1;
+        mCodecSpecific2 = codecSpecific2;
+        mCodecSpecific3 = codecSpecific3;
+        mCodecSpecific4 = codecSpecific4;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothCodecConfig) {
+            BluetoothCodecConfig other = (BluetoothCodecConfig)o;
+            return (other.mCodecType == mCodecType &&
+                    other.mCodecPriority == mCodecPriority &&
+                    other.mSampleRate == mSampleRate &&
+                    other.mBitsPerSample == mBitsPerSample &&
+                    other.mChannelMode == mChannelMode &&
+                    other.mCodecSpecific1 == mCodecSpecific1 &&
+                    other.mCodecSpecific2 == mCodecSpecific2 &&
+                    other.mCodecSpecific3 == mCodecSpecific3 &&
+                    other.mCodecSpecific4 == mCodecSpecific4);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCodecType, mCodecPriority, mSampleRate,
+                            mBitsPerSample, mChannelMode, mCodecSpecific1,
+                            mCodecSpecific2, mCodecSpecific3, mCodecSpecific4);
+    }
+
+    @Override
+    public String toString() {
+        return "{mCodecType:" + mCodecType +
+            ",mCodecPriority:" + mCodecPriority +
+            ",mSampleRate:" + String.format("0x%x", mSampleRate) +
+            ",mBitsPerSample:" + String.format("0x%x", mBitsPerSample) +
+            ",mChannelMode:" + String.format("0x%x", mChannelMode) +
+            ",mCodecSpecific1:" + mCodecSpecific1 +
+            ",mCodecSpecific2:" + mCodecSpecific2 +
+            ",mCodecSpecific3:" + mCodecSpecific3 +
+            ",mCodecSpecific4:" + mCodecSpecific4 + "}";
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothCodecConfig> CREATOR =
+            new Parcelable.Creator<BluetoothCodecConfig>() {
+        public BluetoothCodecConfig createFromParcel(Parcel in) {
+            final int codecType = in.readInt();
+            final int codecPriority = in.readInt();
+            final int sampleRate = in.readInt();
+            final int bitsPerSample = in.readInt();
+            final int channelMode = in.readInt();
+            final long codecSpecific1 = in.readLong();
+            final long codecSpecific2 = in.readLong();
+            final long codecSpecific3 = in.readLong();
+            final long codecSpecific4 = in.readLong();
+            return new BluetoothCodecConfig(codecType, codecPriority,
+                                            sampleRate, bitsPerSample,
+                                            channelMode, codecSpecific1,
+                                            codecSpecific2, codecSpecific3,
+                                            codecSpecific4);
+        }
+        public BluetoothCodecConfig[] newArray(int size) {
+            return new BluetoothCodecConfig[size];
+        }
+    };
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mCodecType);
+        out.writeInt(mCodecPriority);
+        out.writeInt(mSampleRate);
+        out.writeInt(mBitsPerSample);
+        out.writeInt(mChannelMode);
+        out.writeLong(mCodecSpecific1);
+        out.writeLong(mCodecSpecific2);
+        out.writeLong(mCodecSpecific3);
+        out.writeLong(mCodecSpecific4);
+    }
+
+    /**
+     * Returns the codec type.
+     * See {@link android.bluetooth.BluetoothCodecConfig#SOURCE_CODEC_TYPE_SBC}.
+     *
+     * @return the codec type
+     */
+    public int getCodecType() {
+        return mCodecType;
+    }
+
+    /**
+     * Returns the codec selection priority.
+     * The codec selection priority is relative to other codecs: larger value
+     * means higher priority. If 0, reset to default.
+     *
+     * @return the codec priority
+     */
+    public int getCodecPriority() {
+        return mCodecPriority;
+    }
+
+    /**
+     * Returns the codec sample rate. The value can be a bitmask with all
+     * supported sample rates:
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_NONE} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_44100} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_48000} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_88200} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_96000} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_176400} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_192000}
+     *
+     * @return the codec sample rate
+     */
+    public int getSampleRate() {
+        return mSampleRate;
+    }
+
+    /**
+     * Returns the codec bits per sample. The value can be a bitmask with all
+     * bits per sample supported:
+     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_NONE} or
+     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_16} or
+     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_24} or
+     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_32}
+     *
+     * @return the codec bits per sample
+     */
+    public int getBitsPerSample() {
+        return mBitsPerSample;
+    }
+
+    /**
+     * Returns the codec channel mode. The value can be a bitmask with all
+     * supported channel modes:
+     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_NONE} or
+     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_MONO} or
+     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_STEREO}
+     *
+     * @return the codec channel mode
+     */
+    public int getChannelMode() {
+        return mChannelMode;
+    }
+
+    /**
+     * Returns a codec specific value1.
+     *
+     * @return a codec specific value1.
+     */
+    public long getCodecSpecific1() {
+        return mCodecSpecific1;
+    }
+
+    /**
+     * Returns a codec specific value2.
+     *
+     * @return a codec specific value2
+     */
+    public long getCodecSpecific2() {
+        return mCodecSpecific2;
+    }
+
+    /**
+     * Returns a codec specific value3.
+     *
+     * @return a codec specific value3
+     */
+    public long getCodecSpecific3() {
+        return mCodecSpecific3;
+    }
+
+    /**
+     * Returns a codec specific value4.
+     *
+     * @return a codec specific value4
+     */
+    public long getCodecSpecific4() {
+        return mCodecSpecific4;
+    }
+
+    /**
+     * Checks whether the audio feeding parameters are same.
+     *
+     * @param other the codec config to compare against
+     * @return true if the audio feeding parameters are same, otherwise false
+     */
+    public boolean sameAudioFeedingParameters(BluetoothCodecConfig other) {
+        return (other != null && other.mSampleRate == mSampleRate &&
+                other.mBitsPerSample == mBitsPerSample &&
+                other.mChannelMode == mChannelMode);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.aidl b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.aidl
new file mode 100644
index 0000000..283a717
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.bluetooth;
+
+parcelable BluetoothHidDeviceAppConfiguration;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
new file mode 100644
index 0000000..05ba64e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Random;
+
+/** @hide */
+public final class BluetoothHidDeviceAppConfiguration implements Parcelable {
+    private final long mHash;
+
+    BluetoothHidDeviceAppConfiguration() {
+        Random rnd = new Random();
+        mHash = rnd.nextLong();
+    }
+
+    BluetoothHidDeviceAppConfiguration(long hash) {
+        mHash = hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothHidDeviceAppConfiguration) {
+            BluetoothHidDeviceAppConfiguration config = (BluetoothHidDeviceAppConfiguration) o;
+            return mHash == config.mHash;
+        }
+        return false;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothHidDeviceAppConfiguration> CREATOR =
+        new Parcelable.Creator<BluetoothHidDeviceAppConfiguration>() {
+
+        @Override
+        public BluetoothHidDeviceAppConfiguration createFromParcel(Parcel in) {
+            long hash = in.readLong();
+            return new BluetoothHidDeviceAppConfiguration(hash);
+        }
+
+        @Override
+        public BluetoothHidDeviceAppConfiguration[] newArray(int size) {
+            return new BluetoothHidDeviceAppConfiguration[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mHash);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.aidl b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.aidl
new file mode 100644
index 0000000..14f9114
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.bluetooth;
+
+parcelable BluetoothHidDeviceAppQosSettings;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
new file mode 100644
index 0000000..0d6530c
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Random;
+
+/** @hide */
+public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
+
+    final public int serviceType;
+    final public int tokenRate;
+    final public int tokenBucketSize;
+    final public int peakBandwidth;
+    final public int latency;
+    final public int delayVariation;
+
+    final static public int SERVICE_NO_TRAFFIC = 0x00;
+    final static public int SERVICE_BEST_EFFORT = 0x01;
+    final static public int SERVICE_GUARANTEED = 0x02;
+
+    final static public int MAX = (int) 0xffffffff;
+
+    public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize,
+            int peakBandwidth,
+            int latency, int delayVariation) {
+        this.serviceType = serviceType;
+        this.tokenRate = tokenRate;
+        this.tokenBucketSize = tokenBucketSize;
+        this.peakBandwidth = peakBandwidth;
+        this.latency = latency;
+        this.delayVariation = delayVariation;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothHidDeviceAppQosSettings) {
+            BluetoothHidDeviceAppQosSettings qos = (BluetoothHidDeviceAppQosSettings) o;
+            return false;
+        }
+        return false;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothHidDeviceAppQosSettings> CREATOR =
+        new Parcelable.Creator<BluetoothHidDeviceAppQosSettings>() {
+
+        @Override
+        public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) {
+
+            return new BluetoothHidDeviceAppQosSettings(in.readInt(), in.readInt(), in.readInt(),
+                    in.readInt(),
+                    in.readInt(), in.readInt());
+        }
+
+        @Override
+        public BluetoothHidDeviceAppQosSettings[] newArray(int size) {
+            return new BluetoothHidDeviceAppQosSettings[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(serviceType);
+        out.writeInt(tokenRate);
+        out.writeInt(tokenBucketSize);
+        out.writeInt(peakBandwidth);
+        out.writeInt(latency);
+        out.writeInt(delayVariation);
+    }
+
+    public int[] toArray() {
+        return new int[] {
+                serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
+        };
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.aidl b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.aidl
new file mode 100644
index 0000000..87dd10e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.bluetooth;
+
+parcelable BluetoothHidDeviceAppSdpSettings;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
new file mode 100644
index 0000000..f9a2245
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Random;
+
+/** @hide */
+public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
+
+    final public String name;
+    final public String description;
+    final public String provider;
+    final public byte subclass;
+    final public byte[] descriptors;
+
+    public BluetoothHidDeviceAppSdpSettings(String name, String description, String provider,
+            byte subclass, byte[] descriptors) {
+        this.name = name;
+        this.description = description;
+        this.provider = provider;
+        this.subclass = subclass;
+        this.descriptors = descriptors.clone();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothHidDeviceAppSdpSettings) {
+            BluetoothHidDeviceAppSdpSettings sdp = (BluetoothHidDeviceAppSdpSettings) o;
+            return false;
+        }
+        return false;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothHidDeviceAppSdpSettings> CREATOR =
+        new Parcelable.Creator<BluetoothHidDeviceAppSdpSettings>() {
+
+        @Override
+        public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) {
+
+            return new BluetoothHidDeviceAppSdpSettings(in.readString(), in.readString(),
+                    in.readString(), in.readByte(), in.createByteArray());
+        }
+
+        @Override
+        public BluetoothHidDeviceAppSdpSettings[] newArray(int size) {
+            return new BluetoothHidDeviceAppSdpSettings[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(name);
+        out.writeString(description);
+        out.writeString(provider);
+        out.writeByte(subclass);
+        out.writeByteArray(descriptors);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
new file mode 100644
index 0000000..f519776
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.util.Log;
+
+/** @hide */
+public abstract class BluetoothHidDeviceCallback {
+
+    private static final String TAG = BluetoothHidDeviceCallback.class.getSimpleName();
+
+    /**
+     * 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(BluetoothHidDeviceAppConfiguration)}
+     * , 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>
+     *            .
+     * @param config {@link BluetoothHidDeviceAppConfiguration} object which
+     *            represents token required to unregister application using
+     *            {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)}
+     *            .
+     * @param registered <code>true</code> if application is registered,
+     *            <code>false</code> otherwise.
+     */
+    public void onAppStatusChanged(BluetoothDevice pluggedDevice,
+            BluetoothHidDeviceAppConfiguration config, boolean 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>.
+     *
+     * @param device {@link BluetoothDevice} object representing host device
+     *            which connection state was changed.
+     * @param state Connection state as defined in {@link BluetoothProfile}.
+     */
+    public void onConnectionStateChanged(BluetoothDevice device, int state) {
+        Log.d(TAG, "onConnectionStateChanged: device=" + device + " state=" + state);
+    }
+
+    /**
+     * 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.
+     */
+    public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
+        Log.d(TAG, "onGetReport: device=" + device + " type=" + type + " id=" + id + " bufferSize="
+                + bufferSize);
+    }
+
+    /**
+     * Callback called when SET_REPORT is received from remote host. In case
+     * received data are invalid, application shall respond with
+     * {@link BluetoothHidDevice#reportError(BluetoothDevice)}.
+     *
+     * @param type Report Type.
+     * @param id Report Id.
+     * @param data Report data.
+     */
+    public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+        Log.d(TAG, "onSetReport: device=" + device + " type=" + type + " id=" + id);
+    }
+
+    /**
+     * 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.
+     */
+    public void onSetProtocol(BluetoothDevice device, byte protocol) {
+        Log.d(TAG, "onSetProtocol: device=" + device + " protocol=" + protocol);
+    }
+
+    /**
+     * 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.
+     */
+    public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
+        Log.d(TAG, "onIntrData: device=" + device + " reportId=" + reportId);
+    }
+
+    /**
+     * 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.
+     */
+    public void onVirtualCableUnplug(BluetoothDevice device) {
+        Log.d(TAG, "onVirtualCableUnplug: device=" + device);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothInputHost.java b/core/java/android/bluetooth/BluetoothInputHost.java
new file mode 100644
index 0000000..68d105f
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothInputHost.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public final class BluetoothInputHost implements BluetoothProfile {
+
+    private static final String TAG = BluetoothInputHost.class.getSimpleName();
+
+    /**
+     * 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>
+     * </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>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Constants representing device subclass.
+     *
+     * @see #registerApp(String, String, String, byte, byte[],
+     *      BluetoothHidDeviceCallback)
+     */
+    public static final byte SUBCLASS1_NONE = (byte) 0x00;
+    public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
+    public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
+    public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
+
+    public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
+    public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
+    public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
+    public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
+    public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
+    public static final byte SUBCLASS2_DIGITIZER_TABLED = (byte) 0x05;
+    public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
+
+    /**
+     * Constants representing report types.
+     *
+     * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int)
+     * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
+     * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[])
+     */
+    public static final byte REPORT_TYPE_INPUT = (byte) 1;
+    public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
+    public static final byte REPORT_TYPE_FEATURE = (byte) 3;
+
+    /**
+     * Constants representing error response for Set Report.
+     *
+     * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_SUCCESS = (byte) 0;
+    public static final byte ERROR_RSP_NOT_READY = (byte) 1;
+    public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
+    public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
+    public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
+    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.
+     *
+     * @see BluetoothHidDeviceCallback#onSetProtocol(byte)
+     */
+    public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
+    public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
+
+    private Context mContext;
+
+    private ServiceListener mServiceListener;
+
+    private IBluetoothInputHost mService;
+
+    private BluetoothAdapter mAdapter;
+
+    private static class BluetoothHidDeviceCallbackWrapper extends IBluetoothHidDeviceCallback.Stub {
+
+        private BluetoothHidDeviceCallback mCallback;
+
+        public BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onAppStatusChanged(BluetoothDevice pluggedDevice,
+                BluetoothHidDeviceAppConfiguration config, boolean registered) {
+            mCallback.onAppStatusChanged(pluggedDevice, config, registered);
+        }
+
+        @Override
+        public void onConnectionStateChanged(BluetoothDevice device, int state) {
+            mCallback.onConnectionStateChanged(device, state);
+        }
+
+        @Override
+        public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
+            mCallback.onGetReport(device, type, id, bufferSize);
+        }
+
+        @Override
+        public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+            mCallback.onSetReport(device, type, id, data);
+        }
+
+        @Override
+        public void onSetProtocol(BluetoothDevice device, byte protocol) {
+            mCallback.onSetProtocol(device, protocol);
+        }
+
+        @Override
+        public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
+            mCallback.onIntrData(device, reportId, data);
+        }
+
+        @Override
+        public void onVirtualCableUnplug(BluetoothDevice device) {
+            mCallback.onVirtualCableUnplug(device);
+        }
+    }
+
+    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+        new IBluetoothStateChangeCallback.Stub() {
+
+        public void onBluetoothStateChange(boolean up) {
+            Log.d(TAG, "onBluetoothStateChange: up=" + up);
+            synchronized (mConnection) {
+                if (!up) {
+                    Log.d(TAG,"Unbinding service...");
+                    if (mService != null) {
+                        mService = null;
+                        try {
+                            mContext.unbindService(mConnection);
+                        } catch (IllegalArgumentException e) {
+                            Log.e(TAG,"onBluetoothStateChange: could not unbind service:", e);
+                        }
+                    }
+                } else {
+                    try {
+                        if (mService == null) {
+                            Log.d(TAG,"Binding HID Device service...");
+                            doBind();
+                        }
+                    } catch (IllegalStateException e) {
+                        Log.e(TAG,"onBluetoothStateChange: could not bind to HID Dev service: ", e);
+                    } catch (SecurityException e) {
+                        Log.e(TAG,"onBluetoothStateChange: could not bind to HID Dev service: ", e);
+                    }
+                }
+            }
+        }
+    };
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Log.d(TAG, "onServiceConnected()");
+
+            mService = IBluetoothInputHost.Stub.asInterface(service);
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.INPUT_HOST,
+                    BluetoothInputHost.this);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            Log.d(TAG, "onServiceDisconnected()");
+
+            mService = null;
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.INPUT_HOST);
+            }
+        }
+    };
+
+    BluetoothInputHost(Context context, ServiceListener listener) {
+        Log.v(TAG, "BluetoothInputHost");
+
+        mContext = context;
+        mServiceListener = listener;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        doBind();
+    }
+
+    boolean doBind() {
+        Intent intent = new Intent(IBluetoothInputHost.class.getName());
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(comp);
+        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                android.os.Process.myUserHandle())) {
+            Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
+            return false;
+        }
+        Log.d(TAG, "Bound to HID Device Service");
+        return true;
+    }
+
+    void close() {
+        Log.v(TAG, "close()");
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                mService = null;
+                try {
+                    mContext.unbindService(mConnection);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG,"close: could not unbind HID Dev service: ", e);
+                }
+           }
+        }
+
+        mServiceListener = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        Log.v(TAG, "getConnectedDevices()");
+
+        if (mService != null) {
+            try {
+                return mService.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
+
+        if (mService != null) {
+            try {
+                return mService.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getConnectionState(BluetoothDevice device) {
+        Log.v(TAG, "getConnectionState(): device=" + device);
+
+        if (mService != null) {
+            try {
+                return mService.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return STATE_DISCONNECTED;
+    }
+
+    /**
+     * 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(BluetoothHidDeviceAppConfiguration)}.
+     *
+     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of
+     *             HID Device SDP record.
+     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of
+     *             Incoming QoS Settings.
+     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of
+     *             Outgoing QoS Settings.
+     * @param callback {@link BluetoothHidDeviceCallback} object to which
+     *            callback messages will be sent.
+     * @return
+     */
+    public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
+            BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
+            BluetoothHidDeviceCallback callback) {
+        Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
+                + " callback=" + callback);
+
+        boolean result = false;
+
+        if (sdp == null || callback == null) {
+            return false;
+        }
+
+        if (mService != null) {
+            try {
+                BluetoothHidDeviceAppConfiguration config =
+                    new BluetoothHidDeviceAppConfiguration();
+                BluetoothHidDeviceCallbackWrapper cbw =
+                    new BluetoothHidDeviceCallbackWrapper(callback);
+                result = mService.registerApp(config, sdp, inQos, outQos, cbw);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Unregisters application. Active connection will be disconnected and no
+     * new connections will be allowed until registered again using
+     * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
+     *
+     * @param config {@link BluetoothHidDeviceAppConfiguration} object as
+     *            obtained from
+     *            {@link BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
+     *            BluetoothHidDeviceAppConfiguration, boolean)}
+     *
+     * @return
+     */
+    public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
+        Log.v(TAG, "unregisterApp()");
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.unregisterApp(config);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * 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.
+     * @param data Report data, not including Report Id.
+     * @return
+     */
+    public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.sendReport(device, id, data);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * 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.
+     * @param data Report data, not including Report Id.
+     * @return
+     */
+    public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+        Log.v(TAG, "replyReport(): device=" + device + " type=" + type + " id=" + id);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.replyReport(device, type, id, data);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * 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
+     */
+    public boolean reportError(BluetoothDevice device, byte error) {
+        Log.v(TAG, "reportError(): device=" + device + " error=" + error);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.reportError(device, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends Virtual Cable Unplug to currently connected host.
+     *
+     * @return
+     */
+    public boolean unplug(BluetoothDevice device) {
+        Log.v(TAG, "unplug(): device=" + device);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.unplug(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Initiates connection to host which currently has Virtual Cable
+     * established with device.
+     *
+     * @return
+     */
+    public boolean connect(BluetoothDevice device) {
+        Log.v(TAG, "connect(): device=" + device);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Disconnects from currently connected host.
+     *
+     * @return
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        Log.v(TAG, "disconnect(): device=" + device);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index f363607..2f64c71 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -143,11 +143,17 @@
     public static final int MAP_CLIENT = 18;
 
     /**
+     * Input Host
+     * @hide
+     */
+    static public final int INPUT_HOST = 19;
+
+    /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
      * the largest value assigned to a profile.
      * @hide
      */
-    public static final int MAX_PROFILE_ID = 17;
+    public static final int MAX_PROFILE_ID = 19;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index 26ff9e27..5b524eb 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -16,6 +16,7 @@
 
 package android.bluetooth;
 
+import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothDevice;
 
 /**
@@ -36,4 +37,6 @@
     oneway void adjustAvrcpAbsoluteVolume(int direction);
     oneway void setAvrcpAbsoluteVolume(int volume);
     boolean isA2dpPlaying(in BluetoothDevice device);
+    BluetoothCodecConfig getCodecConfig();
+    oneway void setCodecConfigPreference(in BluetoothCodecConfig codecConfig);
 }
diff --git a/core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl b/core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl
new file mode 100644
index 0000000..a737198
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDeviceAppConfiguration;
+
+/** @hide */
+interface IBluetoothHidDeviceCallback {
+   void onAppStatusChanged(in BluetoothDevice device, in BluetoothHidDeviceAppConfiguration config, boolean registered);
+   void onConnectionStateChanged(in BluetoothDevice device, in int state);
+   void onGetReport(in BluetoothDevice device, in byte type, in byte id, in int bufferSize);
+   void onSetReport(in BluetoothDevice device, in byte type, in byte id, in byte[] data);
+   void onSetProtocol(in BluetoothDevice device, in byte protocol);
+   void onIntrData(in BluetoothDevice device, in byte reportId, in byte[] data);
+   void onVirtualCableUnplug(in BluetoothDevice device);
+}
diff --git a/core/java/android/bluetooth/IBluetoothInputHost.aidl b/core/java/android/bluetooth/IBluetoothInputHost.aidl
new file mode 100644
index 0000000..6c4993f
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothInputHost.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDeviceAppConfiguration;
+import android.bluetooth.IBluetoothHidDeviceCallback;
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.bluetooth.BluetoothHidDeviceAppQosSettings;
+
+/** @hide */
+interface IBluetoothInputHost {
+    boolean registerApp(in BluetoothHidDeviceAppConfiguration config,
+            in BluetoothHidDeviceAppSdpSettings sdp, in BluetoothHidDeviceAppQosSettings inQos,
+            in BluetoothHidDeviceAppQosSettings outQos, in IBluetoothHidDeviceCallback callback);
+    boolean unregisterApp(in BluetoothHidDeviceAppConfiguration config);
+    boolean sendReport(in BluetoothDevice device, in int id, in byte[] data);
+    boolean replyReport(in BluetoothDevice device, in byte type, in byte id, in byte[] data);
+    boolean reportError(in BluetoothDevice device, byte error);
+    boolean unplug(in BluetoothDevice device);
+    boolean connect(in BluetoothDevice device);
+    boolean disconnect(in BluetoothDevice device);
+    List<BluetoothDevice> getConnectedDevices();
+    List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
+    int getConnectionState(in BluetoothDevice device);
+}
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 2ab9ae8..5afd774 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -35,8 +35,8 @@
     void unregisterStateChangeCallback(in IBluetoothStateChangeCallback callback);
     boolean isEnabled();
     boolean enable(String packageName);
-    boolean enableNoAutoConnect();
-    boolean disable( String packageName, boolean persist);
+    boolean enableNoAutoConnect(String packageName);
+    boolean disable(String packageName, boolean persist);
     int getState();
     IBluetoothGatt getBluetoothGatt();
 
@@ -47,6 +47,6 @@
     String getName();
 
     boolean isBleScanAlwaysAvailable();
-    int updateBleAppCount(IBinder b, boolean enable);
+    int updateBleAppCount(IBinder b, boolean enable, String packageName);
     boolean isBleAppPresent();
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 11f0eb6..f35b13d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -458,6 +458,15 @@
     void notifyPackageUse(String packageName, int reason);
 
     /**
+     * Notify the package manager that a list of dex files have been loaded.
+     *
+     * @param loadingPackageName the name of the package who performs the load
+     * @param dexPats the list of the dex files paths that have been loaded
+     * @param loaderIsa the ISA of the loader process
+     */
+    void notifyDexLoad(String loadingPackageName, in List<String> dexPaths, String loaderIsa);
+
+    /**
      * Ask the package manager to perform dex-opt (if needed) on the given
      * package if it already hasn't done so.
      *
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2093124..3011a69 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -342,6 +342,7 @@
         public final int[] splitRevisionCodes;
 
         public final boolean coreApp;
+        public final boolean debuggable;
         public final boolean multiArch;
         public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
@@ -359,6 +360,7 @@
             this.baseRevisionCode = baseApk.revisionCode;
             this.splitRevisionCodes = splitRevisionCodes;
             this.coreApp = baseApk.coreApp;
+            this.debuggable = baseApk.debuggable;
             this.multiArch = baseApk.multiArch;
             this.use32bitAbi = baseApk.use32bitAbi;
             this.extractNativeLibs = baseApk.extractNativeLibs;
@@ -388,6 +390,7 @@
         public final Signature[] signatures;
         public final Certificate[][] certificates;
         public final boolean coreApp;
+        public final boolean debuggable;
         public final boolean multiArch;
         public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
@@ -395,7 +398,8 @@
         public ApkLite(String codePath, String packageName, String splitName, int versionCode,
                 int revisionCode, int installLocation, List<VerifierInfo> verifiers,
                 Signature[] signatures, Certificate[][] certificates, boolean coreApp,
-                boolean multiArch, boolean use32bitAbi, boolean extractNativeLibs) {
+                boolean debuggable, boolean multiArch, boolean use32bitAbi,
+                boolean extractNativeLibs) {
             this.codePath = codePath;
             this.packageName = packageName;
             this.splitName = splitName;
@@ -406,6 +410,7 @@
             this.signatures = signatures;
             this.certificates = certificates;
             this.coreApp = coreApp;
+            this.debuggable = debuggable;
             this.multiArch = multiArch;
             this.use32bitAbi = use32bitAbi;
             this.extractNativeLibs = extractNativeLibs;
@@ -1440,6 +1445,7 @@
         int versionCode = 0;
         int revisionCode = 0;
         boolean coreApp = false;
+        boolean debuggable = false;
         boolean multiArch = false;
         boolean use32bitAbi = false;
         boolean extractNativeLibs = true;
@@ -1479,6 +1485,9 @@
             if (parser.getDepth() == searchDepth && "application".equals(parser.getName())) {
                 for (int i = 0; i < attrs.getAttributeCount(); ++i) {
                     final String attr = attrs.getAttributeName(i);
+                    if ("debuggable".equals(attr)) {
+                        debuggable = attrs.getAttributeBooleanValue(i, false);
+                    }
                     if ("multiArch".equals(attr)) {
                         multiArch = attrs.getAttributeBooleanValue(i, false);
                     }
@@ -1494,7 +1503,7 @@
 
         return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
                 revisionCode, installLocation, verifiers, signatures, certificates, coreApp,
-                multiArch, use32bitAbi, extractNativeLibs);
+                debuggable, multiArch, use32bitAbi, extractNativeLibs);
     }
 
     /**
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index d4dcacc..0c4573b 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -391,7 +391,7 @@
     public ContextHubManager(Context context, Looper mainLooper) {
         mMainLooper = mainLooper;
 
-        IBinder b = ServiceManager.getService(ContextHubService.CONTEXTHUB_SERVICE);
+        IBinder b = ServiceManager.getService(Context.CONTEXTHUB_SERVICE);
         if (b != null) {
             mContextHubService = IContextHubService.Stub.asInterface(b);
 
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 11b861a..22850b4 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -298,8 +298,11 @@
         cal.set(Calendar.MINUTE, 0);
         cal.set(Calendar.SECOND, 0);
         if (cycleDay > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
-            cal.set(Calendar.DAY_OF_MONTH, 1);
             cal.add(Calendar.MONTH, 1);
+            cal.set(Calendar.DAY_OF_MONTH, 1);
+            cal.set(Calendar.HOUR_OF_DAY, 0);
+            cal.set(Calendar.MINUTE, 0);
+            cal.set(Calendar.SECOND, 0);
             cal.add(Calendar.SECOND, -1);
         } else {
             cal.set(Calendar.DAY_OF_MONTH, cycleDay);
diff --git a/core/java/android/net/NetworkRecommendationProvider.java b/core/java/android/net/NetworkRecommendationProvider.java
index af5a052c..16ae867 100644
--- a/core/java/android/net/NetworkRecommendationProvider.java
+++ b/core/java/android/net/NetworkRecommendationProvider.java
@@ -75,7 +75,7 @@
      * A callback implementing applications should invoke when a {@link RecommendationResult}
      * is available.
      */
-    public static final class ResultCallback {
+    public static class ResultCallback {
         private final IRemoteCallback mCallback;
         private final int mSequence;
         private final AtomicBoolean mCallbackRun;
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index e08767c..1825956 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -183,7 +183,7 @@
         if (app == null) {
             return null;
         }
-        return app.mPackageName;
+        return app.packageName;
     }
 
     /**
@@ -272,19 +272,11 @@
      * @hide
      */
     public boolean requestScores(NetworkKey[] networks) throws SecurityException {
-        String activeScorer = getActiveScorerPackage();
-        if (activeScorer == null) {
-            return false;
+        try {
+            return mService.requestScores(networks);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
-        Intent intent = new Intent(ACTION_SCORE_NETWORKS);
-        intent.setPackage(activeScorer);
-        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks);
-        // A scorer should never become active if its package doesn't hold SCORE_NETWORKS, but
-        // ensure the package still holds it to be extra safe.
-        // TODO: http://b/23422763
-        mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, Manifest.permission.SCORE_NETWORKS);
-        return true;
     }
 
     /**
@@ -344,6 +336,8 @@
     /**
      * Request a recommendation for which network to connect to.
      *
+     * <p>It is not safe to call this method from the main thread.
+     *
      * @param request a {@link RecommendationRequest} instance containing additional
      *                request details
      * @return a {@link RecommendationResult} instance containing the recommended network
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index ebb31c9..4282ca7 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -19,160 +19,176 @@
 import android.Manifest;
 import android.Manifest.permission;
 import android.annotation.Nullable;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
-
+import com.android.internal.R;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * Internal class for managing the primary network scorer application.
- *
- * TODO: Rename this to something more generic.
+ * Internal class for discovering and managing the network scorer/recommendation application.
  *
  * @hide
  */
 public class NetworkScorerAppManager {
     private static final String TAG = "NetworkScorerAppManager";
-
-    private static final Intent SCORE_INTENT =
-            new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
-
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private final Context mContext;
 
     public NetworkScorerAppManager(Context context) {
       mContext = context;
     }
 
+    /**
+     * Holds metadata about a discovered network scorer/recommendation application.
+     */
     public static class NetworkScorerAppData {
         /** Package name of this scorer app. */
-        public final String mPackageName;
+        public final String packageName;
 
         /** UID of the scorer app. */
-        public final int mPackageUid;
-
-        /** Name of this scorer app for display. */
-        public final CharSequence mScorerName;
+        public final int packageUid;
 
         /**
-         * Optional class name of a configuration activity. Null if none is set.
-         *
-         * @see NetworkScoreManager#ACTION_CUSTOM_ENABLE
+         * Name of the recommendation service we can bind to.
          */
-        public final String mConfigurationActivityClassName;
+        public final String recommendationServiceClassName;
 
-        /**
-         * Optional class name of the scoring service we can bind to. Null if none is set.
-         */
-        public final String mScoringServiceClassName;
-
-        public NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName,
-                @Nullable String configurationActivityClassName,
-                @Nullable String scoringServiceClassName) {
-            mScorerName = scorerName;
-            mPackageName = packageName;
-            mPackageUid = packageUid;
-            mConfigurationActivityClassName = configurationActivityClassName;
-            mScoringServiceClassName = scoringServiceClassName;
+        public NetworkScorerAppData(String packageName, int packageUid,
+                String recommendationServiceClassName) {
+            this.packageName = packageName;
+            this.packageUid = packageUid;
+            this.recommendationServiceClassName = recommendationServiceClassName;
         }
 
         @Override
         public String toString() {
             final StringBuilder sb = new StringBuilder("NetworkScorerAppData{");
-            sb.append("mPackageName='").append(mPackageName).append('\'');
-            sb.append(", mPackageUid=").append(mPackageUid);
-            sb.append(", mScorerName=").append(mScorerName);
-            sb.append(", mConfigurationActivityClassName='").append(mConfigurationActivityClassName)
-                    .append('\'');
-            sb.append(", mScoringServiceClassName='").append(mScoringServiceClassName).append('\'');
+            sb.append("mPackageName='").append(packageName).append('\'');
+            sb.append(", packageUid=").append(packageUid);
+            sb.append(", recommendationServiceClassName='")
+                    .append(recommendationServiceClassName).append('\'');
             sb.append('}');
             return sb.toString();
         }
     }
 
     /**
-     * Returns the list of available scorer apps.
+     * @return A {@link NetworkScorerAppData} instance containing information about the
+     *         best configured network recommendation provider installed or {@code null}
+     *         if none of the configured packages can recommend networks.
      *
-     * <p>A network scorer is any application which:
+     * <p>A network recommendation provider is any application which:
      * <ul>
+     * <li>Is listed in the <code>config_networkRecommendationPackageNames</code> config.
      * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission.
-     * <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the
-     *     {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
+     * <li>Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}.
      * </ul>
-     *
-     * @return the list of scorers, or the empty list if there are no valid scorers.
      */
-    public Collection<NetworkScorerAppData> getAllValidScorers() {
-        // Network scorer apps can only run as the primary user so exit early if we're not the
-        // primary user.
+    public NetworkScorerAppData getNetworkRecommendationProviderData() {
+        // Network recommendation apps can only run as the primary user right now.
+        // http://b/23422763
         if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) {
+            return null;
+        }
+
+        final List<String> potentialPkgs = getPotentialRecommendationProviderPackages();
+        if (potentialPkgs.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG, "No Network Recommendation Providers specified.");
+            }
+            return null;
+        }
+
+        final PackageManager pm = mContext.getPackageManager();
+        for (int i = 0; i < potentialPkgs.size(); i++) {
+            final String potentialPkg = potentialPkgs.get(i);
+
+            // Look for the recommendation service class and required receiver.
+            final ResolveInfo resolveServiceInfo = findRecommendationService(potentialPkg);
+            if (resolveServiceInfo != null) {
+                return new NetworkScorerAppData(potentialPkg,
+                    resolveServiceInfo.serviceInfo.applicationInfo.uid,
+                    resolveServiceInfo.serviceInfo.name);
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, potentialPkg + " does not have the required components, skipping.");
+                }
+            }
+        }
+
+        // None of the configured packages are valid.
+        return null;
+    }
+
+    /**
+     * @return A priority order list of package names that have been granted the
+     *         permission needed for them to act as a network recommendation provider.
+     *         The packages in the returned list may not contain the other required
+     *         network recommendation provider components so additional checks are required
+     *         before making a package the network recommendation provider.
+     */
+    public List<String> getPotentialRecommendationProviderPackages() {
+        final String[] packageArray = mContext.getResources().getStringArray(
+                R.array.config_networkRecommendationPackageNames);
+        if (packageArray == null || packageArray.length == 0) {
+            if (DEBUG) {
+                Log.d(TAG, "No Network Recommendation Providers specified.");
+            }
             return Collections.emptyList();
         }
 
-        List<NetworkScorerAppData> scorers = new ArrayList<>();
-        PackageManager pm = mContext.getPackageManager();
-        // Only apps installed under the primary user of the device can be scorers.
-        // TODO: http://b/23422763
-        List<ResolveInfo> receivers =
-                pm.queryBroadcastReceiversAsUser(SCORE_INTENT, 0 /* flags */, UserHandle.USER_SYSTEM);
-        for (ResolveInfo receiver : receivers) {
-            // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo
-            final ActivityInfo receiverInfo = receiver.activityInfo;
-            if (receiverInfo == null) {
-                // Should never happen with queryBroadcastReceivers, but invalid nonetheless.
-                continue;
-            }
-            if (!permission.BROADCAST_NETWORK_PRIVILEGED.equals(receiverInfo.permission)) {
-                // Receiver doesn't require the BROADCAST_NETWORK_PRIVILEGED permission, which
-                // means anyone could trigger network scoring and flood the framework with score
-                // requests.
-                continue;
-            }
-            if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) !=
-                    PackageManager.PERMISSION_GRANTED) {
-                // Application doesn't hold the SCORE_NETWORKS permission, so the user never
-                // approved it as a network scorer.
-                continue;
-            }
-
-            // Optionally, this package may specify a configuration activity.
-            String configurationActivityClassName = null;
-            Intent intent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE);
-            intent.setPackage(receiverInfo.packageName);
-            List<ResolveInfo> configActivities = pm.queryIntentActivities(intent, 0 /* flags */);
-            if (configActivities != null && !configActivities.isEmpty()) {
-                ActivityInfo activityInfo = configActivities.get(0).activityInfo;
-                if (activityInfo != null) {
-                    configurationActivityClassName = activityInfo.name;
-                }
-            }
-
-            // Find the scoring service class we can bind to, if any.
-            String scoringServiceClassName = null;
-            Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
-            serviceIntent.setPackage(receiverInfo.packageName);
-            ResolveInfo resolveServiceInfo = pm.resolveService(serviceIntent, 0 /* flags */);
-            if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
-                scoringServiceClassName = resolveServiceInfo.serviceInfo.name;
-            }
-
-            // NOTE: loadLabel will attempt to load the receiver's label and fall back to the
-            // app label if none is present.
-            scorers.add(new NetworkScorerAppData(receiverInfo.packageName,
-                    receiverInfo.applicationInfo.uid, receiverInfo.loadLabel(pm),
-                    configurationActivityClassName, scoringServiceClassName));
+        if (VERBOSE) {
+            Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray));
         }
 
-        return scorers;
+        List<String> packages = new ArrayList<>();
+        final PackageManager pm = mContext.getPackageManager();
+        for (String potentialPkg : packageArray) {
+            if (pm.checkPermission(permission.SCORE_NETWORKS, potentialPkg)
+                    == PackageManager.PERMISSION_GRANTED) {
+                packages.add(potentialPkg);
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, potentialPkg + " has not been granted " + permission.SCORE_NETWORKS
+                            + ", skipping.");
+                }
+            }
+        }
+
+        return packages;
+    }
+
+    private ResolveInfo findRecommendationService(String packageName) {
+        final PackageManager pm = mContext.getPackageManager();
+        final int resolveFlags = 0;
+
+        final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
+        serviceIntent.setPackage(packageName);
+        final ResolveInfo resolveServiceInfo =
+                pm.resolveService(serviceIntent, resolveFlags);
+
+        if (VERBOSE) {
+            Log.d(TAG, "Resolved " + serviceIntent + " to " + resolveServiceInfo);
+        }
+
+        if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
+            return resolveServiceInfo;
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, packageName + " does not have a service for " + serviceIntent);
+        }
+        return null;
     }
 
     /**
@@ -182,10 +198,15 @@
      *     selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because
      *     it was disabled or uninstalled).
      */
+    @Nullable
     public NetworkScorerAppData getActiveScorer() {
-        String scorerPackage = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.NETWORK_SCORER_APP);
-        return getScorer(scorerPackage);
+        if (isNetworkRecommendationsDisabled()) {
+            // If recommendations are disabled then there can't be an active scorer.
+            return null;
+        }
+
+        // Otherwise return the recommendation provider (which may be null).
+        return getNetworkRecommendationProviderData();
     }
 
     /**
@@ -195,33 +216,13 @@
      *
      * @param packageName the packageName of the new scorer to use. If null, scoring will be
      *     disabled. Otherwise, the scorer will only be set if it is a valid scorer application.
-     * @return true if the scorer was changed, or false if the package is not a valid scorer.
+     * @return true if the scorer was changed, or false if the package is not a valid scorer or
+     *         a valid network recommendation provider exists.
+     * @deprecated Scorers are now selected from a configured list.
      */
+    @Deprecated
     public boolean setActiveScorer(String packageName) {
-        String oldPackageName = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.NETWORK_SCORER_APP);
-        if (TextUtils.equals(oldPackageName, packageName)) {
-            // No change.
-            return true;
-        }
-
-        Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName);
-
-        if (packageName == null) {
-            Settings.Global.putString(mContext.getContentResolver(),
-                    Settings.Global.NETWORK_SCORER_APP, null);
-            return true;
-        } else {
-            // We only make the change if the new package is valid.
-            if (getScorer(packageName) != null) {
-                Settings.Global.putString(mContext.getContentResolver(),
-                        Settings.Global.NETWORK_SCORER_APP, packageName);
-                return true;
-            } else {
-                Log.w(TAG, "Requested network scorer is not valid: " + packageName);
-                return false;
-            }
-        }
+        return false;
     }
 
     /** Determine whether the application with the given UID is the enabled scorer. */
@@ -230,7 +231,7 @@
         if (defaultApp == null) {
             return false;
         }
-        if (callingUid != defaultApp.mPackageUid) {
+        if (callingUid != defaultApp.packageUid) {
             return false;
         }
         // To be extra safe, ensure the caller holds the SCORE_NETWORKS permission. It always
@@ -239,17 +240,9 @@
                 PackageManager.PERMISSION_GRANTED;
     }
 
-    /** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */
-    public NetworkScorerAppData getScorer(String packageName) {
-        if (TextUtils.isEmpty(packageName)) {
-            return null;
-        }
-        Collection<NetworkScorerAppData> applications = getAllValidScorers();
-        for (NetworkScorerAppData app : applications) {
-            if (packageName.equals(app.mPackageName)) {
-                return app;
-            }
-        }
-        return null;
+    private boolean isNetworkRecommendationsDisabled() {
+        final ContentResolver cr = mContext.getContentResolver();
+        // A value of 1 indicates enabled.
+        return Settings.Global.getInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) != 1;
     }
 }
diff --git a/core/java/android/net/RecommendationRequest.java b/core/java/android/net/RecommendationRequest.java
index 05ca1aa..a96f90d 100644
--- a/core/java/android/net/RecommendationRequest.java
+++ b/core/java/android/net/RecommendationRequest.java
@@ -105,7 +105,16 @@
     }
 
     protected RecommendationRequest(Parcel in) {
-        mScanResults = (ScanResult[]) in.readParcelableArray(ScanResult.class.getClassLoader());
+        final int resultCount = in.readInt();
+        if (resultCount > 0) {
+            mScanResults = new ScanResult[resultCount];
+            for (int i = 0; i < resultCount; i++) {
+                mScanResults[i] = in.readParcelable(ScanResult.class.getClassLoader());
+            }
+        } else {
+            mScanResults = null;
+        }
+
         mCurrentSelectedConfig = in.readParcelable(WifiConfiguration.class.getClassLoader());
         mRequiredCapabilities = in.readParcelable(NetworkCapabilities.class.getClassLoader());
     }
@@ -117,7 +126,14 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelableArray(mScanResults, flags);
+        if (mScanResults != null) {
+            dest.writeInt(mScanResults.length);
+            for (int i = 0; i < mScanResults.length; i++) {
+                dest.writeParcelable(mScanResults[i], flags);
+            }
+        } else {
+            dest.writeInt(0);
+        }
         dest.writeParcelable(mCurrentSelectedConfig, flags);
         dest.writeParcelable(mRequiredCapabilities, flags);
     }
diff --git a/core/java/android/net/RecommendationResult.java b/core/java/android/net/RecommendationResult.java
index a330d84..70cf09c 100644
--- a/core/java/android/net/RecommendationResult.java
+++ b/core/java/android/net/RecommendationResult.java
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.net.wifi.WifiConfiguration;
@@ -23,6 +24,7 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 
 /**
  * The result of a network recommendation.
@@ -34,7 +36,32 @@
 public final class RecommendationResult implements Parcelable {
     private final WifiConfiguration mWifiConfiguration;
 
-    public RecommendationResult(@Nullable WifiConfiguration wifiConfiguration) {
+    /**
+     * Create a {@link RecommendationResult} that indicates that no network connection should be
+     * attempted at this time.
+     *
+     * @return a {@link RecommendationResult}
+     */
+    public static RecommendationResult createDoNotConnectRecommendation() {
+        return new RecommendationResult((WifiConfiguration) null);
+    }
+
+    /**
+     * Create a {@link RecommendationResult} that indicates that a connection attempt should be
+     * made for the given Wi-Fi network.
+     *
+     * @param wifiConfiguration {@link WifiConfiguration} with at least SSID and BSSID set.
+     * @return a {@link RecommendationResult}
+     */
+    public static RecommendationResult createConnectRecommendation(
+            @NonNull WifiConfiguration wifiConfiguration) {
+        Preconditions.checkNotNull(wifiConfiguration, "wifiConfiguration must not be null");
+        Preconditions.checkNotNull(wifiConfiguration.SSID, "SSID must not be null");
+        Preconditions.checkNotNull(wifiConfiguration.BSSID, "BSSID must not be null");
+        return new RecommendationResult(wifiConfiguration);
+    }
+
+    private RecommendationResult(@Nullable WifiConfiguration wifiConfiguration) {
         mWifiConfiguration = wifiConfiguration;
     }
 
@@ -43,14 +70,29 @@
     }
 
     /**
-     * @return The recommended {@link WifiConfiguration} to connect to. A {@code null} value
-     *         indicates that no WiFi connection should be attempted at this time.
+     * @return {@code true} if a network recommendation exists. {@code false} indicates that
+     *         no connection should be attempted at this time.
      */
-    public WifiConfiguration getWifiConfiguration() {
+    public boolean hasRecommendation() {
+        return mWifiConfiguration != null;
+    }
+
+    /**
+     * @return The recommended {@link WifiConfiguration} to connect to. A {@code null} value
+     *         is returned if {@link #hasRecommendation} returns {@code false}.
+     */
+    @Nullable public WifiConfiguration getWifiConfiguration() {
         return mWifiConfiguration;
     }
 
     @Override
+    public String toString() {
+      return "RecommendationResult{" +
+          "mWifiConfiguration=" + mWifiConfiguration +
+          "}";
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/net/ScoredNetwork.java b/core/java/android/net/ScoredNetwork.java
index cf81e91..94e5187 100644
--- a/core/java/android/net/ScoredNetwork.java
+++ b/core/java/android/net/ScoredNetwork.java
@@ -16,11 +16,14 @@
 
 package android.net;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.lang.Math;
+import java.lang.UnsupportedOperationException;
 import java.util.Objects;
 
 /**
@@ -43,7 +46,17 @@
      * <p>
      * If no value is associated with this key then it's unknown.
      */
-    public static final String EXTRA_HAS_CAPTIVE_PORTAL = "android.net.extra.HAS_CAPTIVE_PORTAL";
+    public static final String ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL =
+            "android.net.attributes.key.HAS_CAPTIVE_PORTAL";
+
+    /**
+     * Key used with the {@link #attributes} bundle to define the rankingScoreOffset int value.
+     *
+     * <p>The rankingScoreOffset is used when calculating the ranking score used to rank networks
+     * against one another. See {@link #calculateRankingScore} for more information.
+     */
+    public static final String ATTRIBUTES_KEY_RANKING_SCORE_OFFSET =
+            "android.net.attributes.key.RANKING_SCORE_OFFSET";
 
     /** A {@link NetworkKey} uniquely identifying this network. */
     public final NetworkKey networkKey;
@@ -71,8 +84,10 @@
      * An additional collection of optional attributes set by
      * the Network Recommendation Provider.
      *
-     * @see #EXTRA_HAS_CAPTIVE_PORTAL
+     * @see #ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL
+     * @see #ATTRIBUTES_KEY_RANKING_SCORE_OFFSET_KEY
      */
+    @Nullable
     public final Bundle attributes;
 
     /**
@@ -122,7 +137,7 @@
      * @param attributes optional provider specific attributes
      */
     public ScoredNetwork(NetworkKey networkKey, RssiCurve rssiCurve, boolean meteredHint,
-            Bundle attributes) {
+            @Nullable Bundle attributes) {
         this.networkKey = networkKey;
         this.rssiCurve = rssiCurve;
         this.meteredHint = meteredHint;
@@ -136,7 +151,7 @@
         } else {
             rssiCurve = null;
         }
-        meteredHint = in.readByte() != 0;
+        meteredHint = (in.readByte() == 1);
         attributes = in.readBundle();
     }
 
@@ -156,7 +171,6 @@
         }
         out.writeByte((byte) (meteredHint ? 1 : 0));
         out.writeBundle(attributes);
-
     }
 
     @Override
@@ -187,6 +201,54 @@
                 '}';
     }
 
+    /**
+     * Returns true if a ranking score can be calculated for this network.
+     *
+     * @hide
+     */
+    public boolean hasRankingScore() {
+        return (rssiCurve != null)
+                || (attributes != null
+                        && attributes.containsKey(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET));
+    }
+
+    /**
+     * Returns a ranking score for a given RSSI which can be used to comparatively
+     * rank networks.
+     *
+     * <p>The score obtained by the rssiCurve is bitshifted left by 8 bits to expand it to an
+     * integer and then the offset is added. If the addition operation overflows or underflows,
+     * Integer.MAX_VALUE and Integer.MIN_VALUE will be returned respectively.
+     *
+     * <p>{@link #hasRankingScore} should be called first to ensure this network is capable
+     * of returning a ranking score.
+     *
+     * @throws UnsupportedOperationException if there is no RssiCurve and no rankingScoreOffset
+     * for this network (hasRankingScore returns false).
+     *
+     * @hide
+     */
+    public int calculateRankingScore(int rssi) throws UnsupportedOperationException {
+        if (!hasRankingScore()) {
+            throw new UnsupportedOperationException(
+                    "Either rssiCurve or rankingScoreOffset is required to calculate the "
+                            + "ranking score");
+        }
+
+        int offset = 0;
+        if (attributes != null) {
+             offset += attributes.getInt(ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, 0 /* default */);
+        }
+
+        int score = (rssiCurve == null) ? 0 : rssiCurve.lookupScore(rssi) << Byte.SIZE;
+
+        try {
+            return Math.addExact(score, offset);
+        } catch (ArithmeticException e) {
+            return (score < 0) ? Integer.MIN_VALUE : Integer.MAX_VALUE;
+        }
+    }
+
     public static final Parcelable.Creator<ScoredNetwork> CREATOR =
             new Parcelable.Creator<ScoredNetwork>() {
                 @Override
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 151239b..f6edee0 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -783,7 +783,7 @@
      */
     public static boolean isBuildConsistent() {
         // Don't care on eng builds.  Incremental build may trigger false negative.
-        if ("eng".equals(TYPE)) return true;
+        if (IS_ENG) return true;
 
         final String system = SystemProperties.get("ro.build.fingerprint");
         final String vendor = SystemProperties.get("ro.vendor.build.fingerprint");
@@ -847,6 +847,10 @@
     public static final boolean IS_DEBUGGABLE =
             SystemProperties.getInt("ro.debuggable", 0) == 1;
 
+    /** {@hide} */
+    public static final boolean IS_ENG =
+            "eng".equals(getString("ro.build.type"));
+
     /**
      * Specifies whether the permissions needed by a legacy app should be
      * reviewed before any of its components can run. A legacy app is one
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index 481b2dc..bcc3468 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -33,11 +33,14 @@
                 mNativeContext);
     }
 
+    @Override
     public final native void transact(
-            int code, HwParcel request, HwParcel reply, int flags);
+            int code, HwParcel request, HwParcel reply, int flags)
+        throws RemoteException;
 
     public abstract void onTransact(
-            int code, HwParcel request, HwParcel reply, int flags);
+            int code, HwParcel request, HwParcel reply, int flags)
+        throws RemoteException;
 
     public native final void registerService(
             ArrayList<String> interfaceChain,
diff --git a/core/java/android/os/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java
index 83866b3..2f89ce6 100644
--- a/core/java/android/os/HwRemoteBinder.java
+++ b/core/java/android/os/HwRemoteBinder.java
@@ -32,12 +32,18 @@
                 mNativeContext);
     }
 
+    @Override
     public IHwInterface queryLocalInterface(String descriptor) {
         return null;
     }
 
+    @Override
     public native final void transact(
-            int code, HwParcel request, HwParcel reply, int flags);
+            int code, HwParcel request, HwParcel reply, int flags)
+        throws RemoteException;
+
+    public native boolean linkToDeath(DeathRecipient recipient, long cookie);
+    public native boolean unlinkToDeath(DeathRecipient recipient);
 
     private static native final long native_init();
 
@@ -52,5 +58,9 @@
                 128 /* size */);
     }
 
+    private static final void sendDeathNotice(DeathRecipient recipient, long cookie) {
+        recipient.serviceDied(cookie);
+    }
+
     private long mNativeContext;
 }
diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java
index 76e881e..2a65679 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -23,7 +23,20 @@
     public static final int FLAG_ONEWAY = 1;
 
     public void transact(
-            int code, HwParcel request, HwParcel reply, int flags);
+            int code, HwParcel request, HwParcel reply, int flags)
+        throws RemoteException;
 
     public IHwInterface queryLocalInterface(String descriptor);
+
+    /**
+     * Interface for receiving a callback when the process hosting a service
+     * has gone away.
+     */
+    public interface DeathRecipient {
+        public void serviceDied(long cookie);
+    }
+
+    public boolean linkToDeath(DeathRecipient recipient, long cookie);
+
+    public boolean unlinkToDeath(DeathRecipient recipient);
 }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 4eee854..d6688e3 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -184,6 +184,11 @@
      */
     public static final int LAST_SHARED_APPLICATION_GID = 59999;
 
+    /** {@hide} */
+    public static final int FIRST_APPLICATION_CACHE_GID = 20000;
+    /** {@hide} */
+    public static final int LAST_APPLICATION_CACHE_GID = 29999;
+
     /**
      * Standard priority of application threads.
      * Use with {@link #setThreadPriority(int)} and
@@ -398,6 +403,10 @@
      * make easily identifyable processes even if you are using the same base
      * <var>processClass</var> to start them.
      * 
+     * When invokeWith is not null, the process will be started as a fresh app
+     * and not a zygote fork. Note that this is only allowed for uid 0 or when
+     * debugFlags contains DEBUG_ENABLE_DEBUGGER.
+     *
      * @param processClass The class to use as the process's main entry
      *                     point.
      * @param niceName A more readable name to use for the process.
@@ -410,6 +419,7 @@
      * @param abi non-null the ABI this app should be started with.
      * @param instructionSet null-ok the instruction set to use.
      * @param appDataDir null-ok the data directory of the app.
+     * @param invokeWith null-ok the command to invoke with.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      * 
      * @return An object that describes the result of the attempt to start the process.
@@ -426,10 +436,11 @@
                                   String abi,
                                   String instructionSet,
                                   String appDataDir,
+                                  String invokeWith,
                                   String[] zygoteArgs) {
         return zygoteProcess.start(processClass, niceName, uid, gid, gids,
                     debugFlags, mountExternal, targetSdkVersion, seInfo,
-                    abi, instructionSet, appDataDir, zygoteArgs);
+                    abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
     }
 
     /** @hide */
@@ -442,10 +453,11 @@
                                   String abi,
                                   String instructionSet,
                                   String appDataDir,
+                                  String invokeWith,
                                   String[] zygoteArgs) {
         return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
                     debugFlags, mountExternal, targetSdkVersion, seInfo,
-                    abi, instructionSet, appDataDir, zygoteArgs);
+                    abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index b3f4453..535a05a 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -213,6 +213,15 @@
     }
 
     /**
+     * Returns the cache GID for a given UID or appId.
+     * @hide
+     */
+    public static int getCacheAppGid(int id) {
+        return Process.FIRST_APPLICATION_CACHE_GID + (id % PER_USER_RANGE)
+                - Process.FIRST_APPLICATION_UID;
+    }
+
+    /**
      * Generate a text representation of the uid, breaking out its individual
      * components -- user, app, isolated, etc.
      * @hide
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index c45fe5a..5ac33a1 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -170,6 +170,10 @@
      * make easily identifyable processes even if you are using the same base
      * <var>processClass</var> to start them.
      *
+     * When invokeWith is not null, the process will be started as a fresh app
+     * and not a zygote fork. Note that this is only allowed for uid 0 or when
+     * debugFlags contains DEBUG_ENABLE_DEBUGGER.
+     *
      * @param processClass The class to use as the process's main entry
      *                     point.
      * @param niceName A more readable name to use for the process.
@@ -182,6 +186,7 @@
      * @param abi non-null the ABI this app should be started with.
      * @param instructionSet null-ok the instruction set to use.
      * @param appDataDir null-ok the data directory of the app.
+     * @param invokeWith null-ok the command to invoke with.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      *
      * @return An object that describes the result of the attempt to start the process.
@@ -196,11 +201,12 @@
                                                   String abi,
                                                   String instructionSet,
                                                   String appDataDir,
+                                                  String invokeWith,
                                                   String[] zygoteArgs) {
         try {
             return startViaZygote(processClass, niceName, uid, gid, gids,
                     debugFlags, mountExternal, targetSdkVersion, seInfo,
-                    abi, instructionSet, appDataDir, zygoteArgs);
+                    abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -330,6 +336,7 @@
                                                       String abi,
                                                       String instructionSet,
                                                       String appDataDir,
+                                                      String invokeWith,
                                                       String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
         ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -407,6 +414,11 @@
             argsForZygote.add("--app-data-dir=" + appDataDir);
         }
 
+        if (invokeWith != null) {
+            argsForZygote.add("--invoke-with");
+            argsForZygote.add(invokeWith);
+        }
+
         argsForZygote.add(processClass);
 
         if (extraArgs != null) {
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 78d3b7b..0216a07 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -579,7 +579,7 @@
             throws SignatureNotFoundException {
         // Look up the offset of ZIP Central Directory.
         long centralDirOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocd);
-        if (centralDirOffset >= eocdOffset) {
+        if (centralDirOffset > eocdOffset) {
             throw new SignatureNotFoundException(
                     "ZIP Central Directory offset out of range: " + centralDirOffset
                     + ". ZIP End of Central Directory offset: " + eocdOffset);
diff --git a/core/java/android/util/apk/ZipUtils.java b/core/java/android/util/apk/ZipUtils.java
index cdbac18..fa5477e 100644
--- a/core/java/android/util/apk/ZipUtils.java
+++ b/core/java/android/util/apk/ZipUtils.java
@@ -160,7 +160,7 @@
         }
         int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);
         int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;
-        for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength;
+        for (int expectedCommentLength = 0; expectedCommentLength <= maxCommentLength;
                 expectedCommentLength++) {
             int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;
             if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index f479f4f..83b7d2f 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -76,6 +76,7 @@
         final long[] apkHandles;
         final boolean multiArch;
         final boolean extractNativeLibs;
+        final boolean debuggable;
 
         public static Handle create(File packageFile) throws IOException {
             try {
@@ -89,15 +90,17 @@
         public static Handle create(Package pkg) throws IOException {
             return create(pkg.getAllCodePaths(),
                     (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0,
-                    (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0);
+                    (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0,
+                    (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
         }
 
         public static Handle create(PackageLite lite) throws IOException {
-            return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs);
+            return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs,
+                    lite.debuggable);
         }
 
         private static Handle create(List<String> codePaths, boolean multiArch,
-                boolean extractNativeLibs) throws IOException {
+                boolean extractNativeLibs, boolean debuggable) throws IOException {
             final int size = codePaths.size();
             final long[] apkHandles = new long[size];
             for (int i = 0; i < size; i++) {
@@ -112,13 +115,15 @@
                 }
             }
 
-            return new Handle(apkHandles, multiArch, extractNativeLibs);
+            return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable);
         }
 
-        Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs) {
+        Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs,
+                boolean debuggable) {
             this.apkHandles = apkHandles;
             this.multiArch = multiArch;
             this.extractNativeLibs = extractNativeLibs;
+            this.debuggable = debuggable;
             mGuard.open("close");
         }
 
@@ -149,15 +154,17 @@
     private static native long nativeOpenApk(String path);
     private static native void nativeClose(long handle);
 
-    private static native long nativeSumNativeBinaries(long handle, String cpuAbi);
+    private static native long nativeSumNativeBinaries(long handle, String cpuAbi,
+            boolean debuggable);
 
     private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath,
-            String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge);
+            String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge,
+            boolean debuggable);
 
     private static long sumNativeBinaries(Handle handle, String abi) {
         long sum = 0;
         for (long apkHandle : handle.apkHandles) {
-            sum += nativeSumNativeBinaries(apkHandle, abi);
+            sum += nativeSumNativeBinaries(apkHandle, abi, handle.debuggable);
         }
         return sum;
     }
@@ -173,7 +180,7 @@
     public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) {
         for (long apkHandle : handle.apkHandles) {
             int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,
-                    handle.extractNativeLibs, HAS_NATIVE_BRIDGE);
+                    handle.extractNativeLibs, HAS_NATIVE_BRIDGE, handle.debuggable);
             if (res != INSTALL_SUCCEEDED) {
                 return res;
             }
@@ -191,7 +198,7 @@
     public static int findSupportedAbi(Handle handle, String[] supportedAbis) {
         int finalRes = NO_NATIVE_LIBRARIES;
         for (long apkHandle : handle.apkHandles) {
-            final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
+            final int res = nativeFindSupportedAbi(apkHandle, supportedAbis, handle.debuggable);
             if (res == NO_NATIVE_LIBRARIES) {
                 // No native code, keep looking through all APKs.
             } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
@@ -213,7 +220,8 @@
         return finalRes;
     }
 
-    private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis);
+    private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis,
+            boolean debuggable);
 
     // Convenience method to call removeNativeBinariesFromDirLI(File)
     public static void removeNativeBinariesLI(String nativeLibraryPath) {
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 66b294d..44c6e85 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -697,9 +697,11 @@
             throws ZygoteSecurityException {
         int peerUid = peer.getUid();
 
-        if (args.invokeWith != null && peerUid != 0) {
-            throw new ZygoteSecurityException("Peer is not permitted to specify "
-                    + "an explicit invoke-with wrapper command");
+        if (args.invokeWith != null && peerUid != 0 &&
+            (args.debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) == 0) {
+            throw new ZygoteSecurityException("Peer is permitted to specify an"
+                    + "explicit invoke-with wrapper command only for debuggable"
+                    + "applications.");
         }
     }
 
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 9a596c6..70e9004 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -20,10 +20,6 @@
     LOCAL_CFLAGS += -DENABLE_CPUSETS
 endif
 
-ifneq ($(ENABLE_SCHED_BOOST),)
-    LOCAL_CFLAGS += -DENABLE_SCHED_BOOST
-endif
-
 LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
 
 LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0
@@ -185,6 +181,7 @@
     hwbinder/EphemeralStorage.cpp \
 
 LOCAL_C_INCLUDES += \
+    $(LOCAL_PATH)/include \
     $(JNI_H_INCLUDE) \
     $(LOCAL_PATH)/android/graphics \
     $(LOCAL_PATH)/../../libs/hwui \
@@ -279,8 +276,10 @@
 # <bionic_tls.h> in com_google_android_gles_jni_GLImpl.cpp
 LOCAL_C_INCLUDES += bionic/libc/private
 
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+
 # AndroidRuntime.h depends on nativehelper/jni.h
-LOCAL_EXPORT_C_INCLUDE_DIRS := libnativehelper/include
+LOCAL_EXPORT_C_INCLUDE_DIRS += libnativehelper/include
 
 LOCAL_MODULE:= libandroid_runtime
 
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 4976002..d30e6eb 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -457,6 +457,18 @@
 }
 
 static jint
+android_media_AudioSystem_handleDeviceConfigChange(JNIEnv *env, jobject thiz, jint device, jstring device_address, jstring device_name)
+{
+    const char *c_address = env->GetStringUTFChars(device_address, NULL);
+    const char *c_name = env->GetStringUTFChars(device_name, NULL);
+    int status = check_AudioSystem_Command(AudioSystem::handleDeviceConfigChange(static_cast <audio_devices_t>(device),
+                                          c_address, c_name));
+    env->ReleaseStringUTFChars(device_address, c_address);
+    env->ReleaseStringUTFChars(device_name, c_name);
+    return (jint) status;
+}
+
+static jint
 android_media_AudioSystem_setPhoneState(JNIEnv *env, jobject thiz, jint state)
 {
     return (jint) check_AudioSystem_Command(AudioSystem::setPhoneState((audio_mode_t) state));
@@ -1757,6 +1769,7 @@
     {"newAudioSessionId",   "()I",      (void *)android_media_AudioSystem_newAudioSessionId},
     {"setDeviceConnectionState", "(IILjava/lang/String;Ljava/lang/String;)I", (void *)android_media_AudioSystem_setDeviceConnectionState},
     {"getDeviceConnectionState", "(ILjava/lang/String;)I",  (void *)android_media_AudioSystem_getDeviceConnectionState},
+    {"handleDeviceConfigChange", "(ILjava/lang/String;Ljava/lang/String;)I", (void *)android_media_AudioSystem_handleDeviceConfigChange},
     {"setPhoneState",       "(I)I",     (void *)android_media_AudioSystem_setPhoneState},
     {"setForceUse",         "(II)I",    (void *)android_media_AudioSystem_setForceUse},
     {"getForceUse",         "(I)I",     (void *)android_media_AudioSystem_getForceUse},
diff --git a/core/jni/android_net_LocalSocketImpl.cpp b/core/jni/android_net_LocalSocketImpl.cpp
index d6d4310..37b6df1 100644
--- a/core/jni/android_net_LocalSocketImpl.cpp
+++ b/core/jni/android_net_LocalSocketImpl.cpp
@@ -202,9 +202,7 @@
     msg.msg_control = cmsgbuf;
     msg.msg_controllen = sizeof(cmsgbuf);
 
-    do {
-        ret = recvmsg(fd, &msg, MSG_NOSIGNAL);
-    } while (ret < 0 && errno == EINTR);
+    ret = TEMP_FAILURE_RETRY(recvmsg(fd, &msg, MSG_NOSIGNAL | MSG_CMSG_CLOEXEC));
 
     if (ret < 0 && errno == EPIPE) {
         // Treat this as an end of stream
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 740b24d..c456d62 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -26,10 +26,11 @@
 #include <JNIHelp.h>
 #include <android/hidl/manager/1.0/IServiceManager.h>
 #include <android/hidl/base/1.0/IBase.h>
-#include <android/hidl/base/1.0/IHwBase.h>
+#include <android/hidl/base/1.0/BpBase.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <hidl/ServiceManagement.h>
 #include <hidl/Status.h>
+#include <hidl/HidlTransportSupport.h>
 #include <hwbinder/ProcessState.h>
 #include <nativehelper/ScopedLocalRef.h>
 
@@ -51,6 +52,8 @@
     jmethodID get;
 } gArrayListMethods;
 
+static jclass gErrorClass;
+
 static struct fields_t {
     jfieldID contextID;
     jmethodID onTransactID;
@@ -143,6 +146,22 @@
             replyObj.get(),
             flags);
 
+    if (env->ExceptionCheck()) {
+        jthrowable excep = env->ExceptionOccurred();
+        env->ExceptionDescribe();
+
+        if (env->IsInstanceOf(excep, gErrorClass)) {
+            /* It's an error */
+            LOG(ERROR) << "Forcefully exiting";
+            exit(1);
+        } else {
+            env->ExceptionClear();
+            LOG(ERROR) << "Uncaught exception!";
+        }
+
+        env->DeleteLocalRef(excep);
+    }
+
     status_t err = OK;
 
     if (!replyContext->wasSent()) {
@@ -242,14 +261,18 @@
 
     sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz);
 
-    sp<hidl::base::V1_0::IBase> base = hidl::base::V1_0::IHwBase::asInterface(binder);
-    if (base.get() == nullptr) {
-        LOG(ERROR) << "IBinder object cannot be casted to the base interface.";
+    /* TODO(b/33440494) this is not right */
+    sp<hidl::base::V1_0::IBase> base = new hidl::base::V1_0::BpBase(binder);
+
+    auto manager = hardware::defaultServiceManager();
+
+    if (manager == nullptr) {
+        LOG(ERROR) << "Could not get hwservicemanager.";
         signalExceptionForError(env, UNKNOWN_ERROR);
         return;
     }
 
-    bool ok = hardware::defaultServiceManager()->add(
+    bool ok = manager->add(
                 interfaceChain,
                 serviceName,
                 base);
@@ -294,13 +317,21 @@
               << serviceName
               << "'";
 
+    auto manager = hardware::defaultServiceManager();
+
+    if (manager == nullptr) {
+        LOG(ERROR) << "Could not get hwservicemanager.";
+        signalExceptionForError(env, UNKNOWN_ERROR);
+        return NULL;
+    }
+
     sp<hardware::IBinder> service;
-    hardware::defaultServiceManager()->get(
+    manager->get(
             ifaceName,
             serviceName,
             [&service](sp<hidl::base::V1_0::IBase> out) {
                 service = hardware::toBinder<
-                        hidl::base::V1_0::IBase, hidl::base::V1_0::IHwBase
+                        hidl::base::V1_0::IBase, hidl::base::V1_0::BpBase
                     >(out);
             });
 
@@ -343,6 +374,9 @@
     gArrayListMethods.size = GetMethodIDOrDie(env, arrayListClass, "size", "()I");
     gArrayListMethods.get = GetMethodIDOrDie(env, arrayListClass, "get", "(I)Ljava/lang/Object;");
 
+    jclass errorClass = FindClassOrDie(env, "java/lang/Error");
+    gErrorClass = MakeGlobalRefOrDie(env, errorClass);
+
     return RegisterMethodsOrDie(env, CLASS_PATH, gMethods, NELEM(gMethods));
 }
 
diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp
index a10d807..b9d810a 100644
--- a/core/jni/android_os_HwParcel.cpp
+++ b/core/jni/android_os_HwParcel.cpp
@@ -49,7 +49,7 @@
 
 } gFields;
 
-void signalExceptionForError(JNIEnv *env, status_t err) {
+void signalExceptionForError(JNIEnv *env, status_t err, bool canThrowRemoteException) {
     switch (err) {
         case OK:
             break;
@@ -114,8 +114,13 @@
 
         default:
         {
+            std::stringstream ss;
+            ss << "HwBinder Error: (" << err << ")";
+
             jniThrowException(
-                    env, "java/lang/RuntimeException", "Unknown error");
+                    env,
+                    canThrowRemoteException ? "android/os/RemoteException" : "java/lang/RuntimeException",
+                    ss.str().c_str());
 
             break;
         }
diff --git a/core/jni/android_os_HwParcel.h b/core/jni/android_os_HwParcel.h
index 708bbba..f81de9b 100644
--- a/core/jni/android_os_HwParcel.h
+++ b/core/jni/android_os_HwParcel.h
@@ -67,7 +67,7 @@
     DISALLOW_COPY_AND_ASSIGN(JHwParcel);
 };
 
-void signalExceptionForError(JNIEnv *env, status_t err);
+void signalExceptionForError(JNIEnv *env, status_t err, bool canThrowRemoteException = false);
 int register_android_os_HwParcel(JNIEnv *env);
 
 }  // namespace android
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index 1d5d6d5..f2f8e52 100644
--- a/core/jni/android_os_HwRemoteBinder.cpp
+++ b/core/jni/android_os_HwRemoteBinder.cpp
@@ -25,6 +25,7 @@
 #include <JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <hidl/Status.h>
+#include <ScopedUtfChars.h>
 #include <nativehelper/ScopedLocalRef.h>
 
 #include "core_jni_helpers.h"
@@ -38,26 +39,196 @@
 namespace android {
 
 static struct fields_t {
+    jclass proxy_class;
     jfieldID contextID;
     jmethodID constructID;
+    jmethodID sendDeathNotice;
+} gProxyOffsets;
 
-} gFields;
+static struct class_offsets_t
+{
+    jmethodID mGetName;
+} gClassOffsets;
+
+static JavaVM* jnienv_to_javavm(JNIEnv* env)
+{
+    JavaVM* vm;
+    return env->GetJavaVM(&vm) >= 0 ? vm : NULL;
+}
+
+static JNIEnv* javavm_to_jnienv(JavaVM* vm)
+{
+    JNIEnv* env;
+    return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL;
+}
+
+// ----------------------------------------------------------------------------
+class HwBinderDeathRecipient : public hardware::IBinder::DeathRecipient
+{
+public:
+    HwBinderDeathRecipient(JNIEnv* env, jobject object, jlong cookie, const sp<HwBinderDeathRecipientList>& list)
+        : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)),
+          mObjectWeak(NULL), mCookie(cookie), mList(list)
+    {
+        // These objects manage their own lifetimes so are responsible for final bookkeeping.
+        // The list holds a strong reference to this object.
+        list->add(this);
+    }
+
+    void binderDied(const wp<hardware::IBinder>& who)
+    {
+        if (mObject != NULL) {
+            JNIEnv* env = javavm_to_jnienv(mVM);
+
+            env->CallStaticVoidMethod(gProxyOffsets.proxy_class, gProxyOffsets.sendDeathNotice, mObject, mCookie);
+            if (env->ExceptionCheck()) {
+                ALOGE("Uncaught exception returned from death notification.");
+                env->ExceptionClear();
+            }
+
+            // Serialize with our containing HwBinderDeathRecipientList so that we can't
+            // delete the global ref on mObject while the list is being iterated.
+            sp<HwBinderDeathRecipientList> list = mList.promote();
+            if (list != NULL) {
+                AutoMutex _l(list->lock());
+
+                // Demote from strong ref to weak after binderDied() has been delivered,
+                // to allow the DeathRecipient and BinderProxy to be GC'd if no longer needed.
+                mObjectWeak = env->NewWeakGlobalRef(mObject);
+                env->DeleteGlobalRef(mObject);
+                mObject = NULL;
+            }
+        }
+    }
+
+    void clearReference()
+    {
+        sp<HwBinderDeathRecipientList> list = mList.promote();
+        if (list != NULL) {
+            list->remove(this);
+        } else {
+            ALOGE("clearReference() on JDR %p but DRL wp purged", this);
+        }
+    }
+
+    bool matches(jobject obj) {
+        bool result;
+        JNIEnv* env = javavm_to_jnienv(mVM);
+
+        if (mObject != NULL) {
+            result = env->IsSameObject(obj, mObject);
+        } else {
+            jobject me = env->NewLocalRef(mObjectWeak);
+            result = env->IsSameObject(obj, me);
+            env->DeleteLocalRef(me);
+        }
+        return result;
+    }
+
+    void warnIfStillLive() {
+        if (mObject != NULL) {
+            // Okay, something is wrong -- we have a hard reference to a live death
+            // recipient on the VM side, but the list is being torn down.
+            JNIEnv* env = javavm_to_jnienv(mVM);
+            ScopedLocalRef<jclass> objClassRef(env, env->GetObjectClass(mObject));
+            ScopedLocalRef<jstring> nameRef(env,
+                    (jstring) env->CallObjectMethod(objClassRef.get(), gClassOffsets.mGetName));
+            ScopedUtfChars nameUtf(env, nameRef.get());
+            if (nameUtf.c_str() != NULL) {
+                ALOGW("BinderProxy is being destroyed but the application did not call "
+                        "unlinkToDeath to unlink all of its death recipients beforehand.  "
+                        "Releasing leaked death recipient: %s", nameUtf.c_str());
+            } else {
+                ALOGW("BinderProxy being destroyed; unable to get DR object name");
+                env->ExceptionClear();
+            }
+        }
+    }
+
+protected:
+    virtual ~HwBinderDeathRecipient()
+    {
+        JNIEnv* env = javavm_to_jnienv(mVM);
+        if (mObject != NULL) {
+            env->DeleteGlobalRef(mObject);
+        } else {
+            env->DeleteWeakGlobalRef(mObjectWeak);
+        }
+    }
+
+private:
+    JavaVM* const mVM;
+    jobject mObject;
+    jweak mObjectWeak; // will be a weak ref to the same VM-side DeathRecipient after binderDied()
+    jlong mCookie;
+    wp<HwBinderDeathRecipientList> mList;
+};
+// ----------------------------------------------------------------------------
+
+HwBinderDeathRecipientList::HwBinderDeathRecipientList() {
+}
+
+HwBinderDeathRecipientList::~HwBinderDeathRecipientList() {
+    AutoMutex _l(mLock);
+
+    for (const sp<HwBinderDeathRecipient>& deathRecipient : mList) {
+        deathRecipient->warnIfStillLive();
+    }
+}
+
+void HwBinderDeathRecipientList::add(const sp<HwBinderDeathRecipient>& recipient) {
+    AutoMutex _l(mLock);
+
+    mList.push_back(recipient);
+}
+
+void HwBinderDeathRecipientList::remove(const sp<HwBinderDeathRecipient>& recipient) {
+    AutoMutex _l(mLock);
+
+    List< sp<HwBinderDeathRecipient> >::iterator iter;
+    for (iter = mList.begin(); iter != mList.end(); iter++) {
+        if (*iter == recipient) {
+            mList.erase(iter);
+            return;
+        }
+    }
+}
+
+sp<HwBinderDeathRecipient> HwBinderDeathRecipientList::find(jobject recipient) {
+    AutoMutex _l(mLock);
+
+    for (const sp<HwBinderDeathRecipient>& deathRecipient : mList) {
+        if (deathRecipient->matches(recipient)) {
+            return deathRecipient;
+        }
+    }
+    return NULL;
+}
+
+Mutex& HwBinderDeathRecipientList::lock() {
+    return mLock;
+}
 
 // static
 void JHwRemoteBinder::InitClass(JNIEnv *env) {
-    ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));
+    jclass clazz = FindClassOrDie(env, CLASS_PATH);
 
-    gFields.contextID =
-        GetFieldIDOrDie(env, clazz.get(), "mNativeContext", "J");
+    gProxyOffsets.proxy_class = MakeGlobalRefOrDie(env, clazz);
+    gProxyOffsets.contextID =
+        GetFieldIDOrDie(env, clazz, "mNativeContext", "J");
+    gProxyOffsets.constructID = GetMethodIDOrDie(env, clazz, "<init>", "()V");
+    gProxyOffsets.sendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
+            "(Landroid/os/IHwBinder$DeathRecipient;J)V");
 
-    gFields.constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V");
+    clazz = FindClassOrDie(env, "java/lang/Class");
+    gClassOffsets.mGetName = GetMethodIDOrDie(env, clazz, "getName", "()Ljava/lang/String;");
 }
 
 // static
 sp<JHwRemoteBinder> JHwRemoteBinder::SetNativeContext(
         JNIEnv *env, jobject thiz, const sp<JHwRemoteBinder> &context) {
     sp<JHwRemoteBinder> old =
-        (JHwRemoteBinder *)env->GetLongField(thiz, gFields.contextID);
+        (JHwRemoteBinder *)env->GetLongField(thiz, gProxyOffsets.contextID);
 
     if (context != NULL) {
         context->incStrong(NULL /* id */);
@@ -67,7 +238,7 @@
         old->decStrong(NULL /* id */);
     }
 
-    env->SetLongField(thiz, gFields.contextID, (long)context.get());
+    env->SetLongField(thiz, gProxyOffsets.contextID, (long)context.get());
 
     return old;
 }
@@ -75,7 +246,7 @@
 // static
 sp<JHwRemoteBinder> JHwRemoteBinder::GetNativeContext(
         JNIEnv *env, jobject thiz) {
-    return (JHwRemoteBinder *)env->GetLongField(thiz, gFields.contextID);
+    return (JHwRemoteBinder *)env->GetLongField(thiz, gProxyOffsets.contextID);
 }
 
 // static
@@ -84,7 +255,7 @@
     ScopedLocalRef<jclass> clazz(env, FindClassOrDie(env, CLASS_PATH));
 
     // XXX Have to look up the constructor here because otherwise that static
-    // class initializer isn't called and gFields.constructID is undefined :(
+    // class initializer isn't called and gProxyOffsets.constructID is undefined :(
 
     jmethodID constructID = GetMethodIDOrDie(env, clazz.get(), "<init>", "()V");
 
@@ -97,6 +268,7 @@
 JHwRemoteBinder::JHwRemoteBinder(
         JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder)
     : mBinder(binder) {
+    mDeathRecipientList = new HwBinderDeathRecipientList();
     jclass clazz = env->GetObjectClass(thiz);
     CHECK(clazz != NULL);
 
@@ -114,7 +286,7 @@
     mClass = NULL;
 }
 
-sp<hardware::IBinder> JHwRemoteBinder::getBinder() {
+sp<hardware::IBinder> JHwRemoteBinder::getBinder() const {
     return mBinder;
 }
 
@@ -122,6 +294,10 @@
     mBinder = binder;
 }
 
+sp<HwBinderDeathRecipientList> JHwRemoteBinder::getDeathRecipientList() const {
+    return mDeathRecipientList;
+}
+
 }  // namespace android
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -171,7 +347,74 @@
         JHwParcel::GetNativeContext(env, replyObj)->getParcel();
 
     status_t err = binder->transact(code, *request, reply, flags);
-    signalExceptionForError(env, err);
+    signalExceptionForError(env, err, true /* canThrowRemoteException */);
+}
+
+static jboolean JHwRemoteBinder_linkToDeath(JNIEnv* env, jobject thiz,
+        jobject recipient, jlong cookie)
+{
+    if (recipient == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return JNI_FALSE;
+    }
+
+    sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, thiz);
+    sp<hardware::IBinder> binder = context->getBinder();
+
+    if (!binder->localBinder()) {
+        HwBinderDeathRecipientList* list = (context->getDeathRecipientList()).get();
+        sp<HwBinderDeathRecipient> jdr = new HwBinderDeathRecipient(env, recipient, cookie, list);
+        status_t err = binder->linkToDeath(jdr, NULL, 0);
+        if (err != NO_ERROR) {
+            // Failure adding the death recipient, so clear its reference
+            // now.
+            jdr->clearReference();
+            return JNI_FALSE;
+        }
+    }
+
+    return JNI_TRUE;
+}
+
+static jboolean JHwRemoteBinder_unlinkToDeath(JNIEnv* env, jobject thiz,
+                                                 jobject recipient)
+{
+    jboolean res = JNI_FALSE;
+    if (recipient == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return res;
+    }
+
+    sp<JHwRemoteBinder> context = JHwRemoteBinder::GetNativeContext(env, thiz);
+    sp<hardware::IBinder> binder = context->getBinder();
+
+    if (!binder->localBinder()) {
+        status_t err = NAME_NOT_FOUND;
+
+        // If we find the matching recipient, proceed to unlink using that
+        HwBinderDeathRecipientList* list = (context->getDeathRecipientList()).get();
+        sp<HwBinderDeathRecipient> origJDR = list->find(recipient);
+        if (origJDR != NULL) {
+            wp<hardware::IBinder::DeathRecipient> dr;
+            err = binder->unlinkToDeath(origJDR, NULL, 0, &dr);
+            if (err == NO_ERROR && dr != NULL) {
+                sp<hardware::IBinder::DeathRecipient> sdr = dr.promote();
+                HwBinderDeathRecipient* jdr = static_cast<HwBinderDeathRecipient*>(sdr.get());
+                if (jdr != NULL) {
+                    jdr->clearReference();
+                }
+            }
+        }
+
+        if (err == NO_ERROR || err == DEAD_OBJECT) {
+            res = JNI_TRUE;
+        } else {
+            jniThrowException(env, "java/util/NoSuchElementException",
+                              "Death link does not exist");
+        }
+    }
+
+    return res;
 }
 
 static JNINativeMethod gMethods[] = {
@@ -183,6 +426,14 @@
     { "transact",
         "(IL" PACKAGE_PATH "/HwParcel;L" PACKAGE_PATH "/HwParcel;I)V",
         (void *)JHwRemoteBinder_native_transact },
+
+    {"linkToDeath",
+        "(Landroid/os/IHwBinder$DeathRecipient;J)Z",
+        (void*)JHwRemoteBinder_linkToDeath},
+
+    {"unlinkToDeath",
+        "(Landroid/os/IHwBinder$DeathRecipient;)Z",
+        (void*)JHwRemoteBinder_unlinkToDeath},
 };
 
 namespace android {
diff --git a/core/jni/android_os_HwRemoteBinder.h b/core/jni/android_os_HwRemoteBinder.h
index fd33338..77a0278 100644
--- a/core/jni/android_os_HwRemoteBinder.h
+++ b/core/jni/android_os_HwRemoteBinder.h
@@ -20,10 +20,33 @@
 #include <android-base/macros.h>
 #include <hwbinder/Binder.h>
 #include <jni.h>
+#include <utils/List.h>
+#include <utils/Mutex.h>
 #include <utils/RefBase.h>
 
 namespace android {
 
+// Per-IBinder death recipient bookkeeping.  This is how we reconcile local jobject
+// death recipient references passed in through JNI with the permanent corresponding
+// HwBinderDeathRecipient objects.
+
+class HwBinderDeathRecipient;
+
+class HwBinderDeathRecipientList : public RefBase {
+    List< sp<HwBinderDeathRecipient> > mList;
+    Mutex mLock;
+
+public:
+    HwBinderDeathRecipientList();
+    ~HwBinderDeathRecipientList();
+
+    void add(const sp<HwBinderDeathRecipient>& recipient);
+    void remove(const sp<HwBinderDeathRecipient>& recipient);
+    sp<HwBinderDeathRecipient> find(jobject recipient);
+
+    Mutex& lock();  // Use with care; specifically for mutual exclusion during binder death
+};
+
 struct JHwRemoteBinder : public RefBase {
     static void InitClass(JNIEnv *env);
 
@@ -37,8 +60,9 @@
     JHwRemoteBinder(
             JNIEnv *env, jobject thiz, const sp<hardware::IBinder> &binder);
 
-    sp<hardware::IBinder> getBinder();
+    sp<hardware::IBinder> getBinder() const;
     void setBinder(const sp<hardware::IBinder> &binder);
+    sp<HwBinderDeathRecipientList> getDeathRecipientList() const;
 
 protected:
     virtual ~JHwRemoteBinder();
@@ -48,7 +72,7 @@
     jobject mObject;
 
     sp<hardware::IBinder> mBinder;
-
+    sp<HwBinderDeathRecipientList> mDeathRecipientList;
     DISALLOW_COPY_AND_ASSIGN(JHwRemoteBinder);
 };
 
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 1cfbd97..b57f2362 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -240,7 +240,7 @@
         t_pri = getpriority(PRIO_PROCESS, t_pid);
 
         if (t_pri <= ANDROID_PRIORITY_AUDIO) {
-            int scheduler = sched_getscheduler(t_pid);
+            int scheduler = sched_getscheduler(t_pid) & ~SCHED_RESET_ON_FORK;
             if ((scheduler == SCHED_FIFO) || (scheduler == SCHED_RR)) {
                 // This task wants to stay in its current audio group so it can keep its budget
                 // don't update its cpuset or cgroup
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 7e01657..f8f9efe 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -49,9 +49,6 @@
 
 #define RS_BITCODE_SUFFIX ".bc"
 
-#define GDBSERVER "gdbserver"
-#define GDBSERVER_LEN (sizeof(GDBSERVER) - 1)
-
 #define TMP_FILE_PATTERN "/tmp.XXXXXX"
 #define TMP_FILE_PATTERN_LEN (sizeof(TMP_FILE_PATTERN) - 1)
 
@@ -246,7 +243,7 @@
         return INSTALL_FAILED_INTERNAL_ERROR;
     }
 
-    *(localFileName + nativeLibPath.size()) = '/';
+    *(localTmpFileName + nativeLibPath.size()) = '/';
 
     if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN,
                     TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) {
@@ -313,20 +310,20 @@
  */
 class NativeLibrariesIterator {
 private:
-    NativeLibrariesIterator(ZipFileRO* zipFile, void* cookie)
-        : mZipFile(zipFile), mCookie(cookie), mLastSlash(NULL) {
+    NativeLibrariesIterator(ZipFileRO* zipFile, bool debuggable, void* cookie)
+        : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(NULL) {
         fileName[0] = '\0';
     }
 
 public:
-    static NativeLibrariesIterator* create(ZipFileRO* zipFile) {
+    static NativeLibrariesIterator* create(ZipFileRO* zipFile, bool debuggable) {
         void* cookie = NULL;
         // Do not specify a suffix to find both .so files and gdbserver.
         if (!zipFile->startIteration(&cookie, APK_LIB, NULL /* suffix */)) {
             return NULL;
         }
 
-        return new NativeLibrariesIterator(zipFile, cookie);
+        return new NativeLibrariesIterator(zipFile, debuggable, cookie);
     }
 
     ZipEntryRO next() {
@@ -347,15 +344,8 @@
             const char* lastSlash = strrchr(fileName, '/');
             ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName);
 
-            // Exception: If we find the gdbserver binary, return it.
-            if (!strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) {
-                mLastSlash = lastSlash;
-                break;
-            }
-
-            // Make sure the filename starts with lib and ends with ".so".
-            if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN)
-                || strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)) {
+            // Skip directories.
+            if (*(lastSlash + 1) == 0) {
                 continue;
             }
 
@@ -364,6 +354,14 @@
                 continue;
             }
 
+            if (!mDebuggable) {
+              // Make sure the filename starts with lib and ends with ".so".
+              if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN)
+                  || strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)) {
+                  continue;
+              }
+            }
+
             mLastSlash = lastSlash;
             break;
         }
@@ -386,19 +384,21 @@
 
     char fileName[PATH_MAX];
     ZipFileRO* const mZipFile;
+    const bool mDebuggable;
     void* mCookie;
     const char* mLastSlash;
 };
 
 static install_status_t
 iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi,
-                       iterFunc callFunc, void* callArg) {
+                       jboolean debuggable, iterFunc callFunc, void* callArg) {
     ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
     if (zipFile == NULL) {
         return INSTALL_FAILED_INVALID_APK;
     }
 
-    std::unique_ptr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
+    std::unique_ptr<NativeLibrariesIterator> it(
+            NativeLibrariesIterator::create(zipFile, debuggable));
     if (it.get() == NULL) {
         return INSTALL_FAILED_INVALID_APK;
     }
@@ -432,7 +432,8 @@
 }
 
 
-static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) {
+static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray,
+        jboolean debuggable) {
     const int numAbis = env->GetArrayLength(supportedAbisArray);
     Vector<ScopedUtfChars*> supportedAbis;
 
@@ -446,7 +447,8 @@
         return INSTALL_FAILED_INVALID_APK;
     }
 
-    std::unique_ptr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile));
+    std::unique_ptr<NativeLibrariesIterator> it(
+            NativeLibrariesIterator::create(zipFile, debuggable));
     if (it.get() == NULL) {
         return INSTALL_FAILED_INVALID_APK;
     }
@@ -488,29 +490,29 @@
 static jint
 com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
         jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
-        jboolean extractNativeLibs, jboolean hasNativeBridge)
+        jboolean extractNativeLibs, jboolean hasNativeBridge, jboolean debuggable)
 {
     void* args[] = { &javaNativeLibPath, &extractNativeLibs, &hasNativeBridge };
-    return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi,
+    return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable,
             copyFileIfChanged, reinterpret_cast<void*>(args));
 }
 
 static jlong
 com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz,
-        jlong apkHandle, jstring javaCpuAbi)
+        jlong apkHandle, jstring javaCpuAbi, jboolean debuggable)
 {
     size_t totalSize = 0;
 
-    iterateOverNativeFiles(env, apkHandle, javaCpuAbi, sumFiles, &totalSize);
+    iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, sumFiles, &totalSize);
 
     return totalSize;
 }
 
 static jint
 com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz,
-        jlong apkHandle, jobjectArray javaCpuAbisToSearch)
+        jlong apkHandle, jobjectArray javaCpuAbisToSearch, jboolean debuggable)
 {
-    return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch);
+    return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch, debuggable);
 }
 
 enum bitcode_scan_result_t {
@@ -569,13 +571,13 @@
             "(J)V",
             (void *)com_android_internal_content_NativeLibraryHelper_close},
     {"nativeCopyNativeBinaries",
-            "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
+            "(JLjava/lang/String;Ljava/lang/String;ZZZ)I",
             (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
     {"nativeSumNativeBinaries",
-            "(JLjava/lang/String;)J",
+            "(JLjava/lang/String;Z)J",
             (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
     {"nativeFindSupportedAbi",
-            "(J[Ljava/lang/String;)I",
+            "(J[Ljava/lang/String;Z)I",
             (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
     {"hasRenderscriptBitcode", "(J)I",
             (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index d081549..fec8f4e 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -37,6 +37,7 @@
 #include <sys/prctl.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <sys/utsname.h>
 #include <sys/wait.h>
@@ -153,6 +154,24 @@
   }
 }
 
+// Resets nice priority for zygote process. Zygote priority can be set
+// to high value during boot phase to speed it up. We want to ensure
+// zygote is running at normal priority before childs are forked from it.
+//
+// This ends up being called repeatedly before each fork(), but there's
+// no real harm in that.
+static void ResetNicePriority(JNIEnv* env) {
+  errno = 0;
+  int prio = getpriority(PRIO_PROCESS, 0);
+  if (prio == -1 && errno != 0) {
+    ALOGW("getpriority failed: %s\n", strerror(errno));
+  }
+  if (prio != 0 && setpriority(PRIO_PROCESS, 0, 0) != 0) {
+    ALOGE("setpriority(%d, 0, 0) failed: %s", PRIO_PROCESS, strerror(errno));
+    RuntimeAbort(env, __LINE__, "setpriority failed");
+  }
+}
+
 // Sets the SIGCHLD handler back to default behavior in zygote children.
 static void UnsetSigChldHandler() {
   struct sigaction sa;
@@ -418,27 +437,6 @@
   }
 }
 
-#ifdef ENABLE_SCHED_BOOST
-static void SetForkLoad(bool boost) {
-  // set scheduler knob to boost forked processes
-  pid_t currentPid = getpid();
-  // fits at most "/proc/XXXXXXX/sched_init_task_load\0"
-  char schedPath[35];
-  snprintf(schedPath, sizeof(schedPath), "/proc/%u/sched_init_task_load", currentPid);
-  int schedBoostFile = open(schedPath, O_WRONLY);
-  if (schedBoostFile < 0) {
-    ALOGW("Unable to set zygote scheduler boost");
-    return;
-  }
-  if (boost) {
-    write(schedBoostFile, "100\0", 4);
-  } else {
-    write(schedBoostFile, "0\0", 2);
-  }
-  close(schedBoostFile);
-}
-#endif
-
 // The list of open zygote file descriptors.
 static FileDescriptorTable* gOpenFdTable = NULL;
 
@@ -452,10 +450,6 @@
                                      jstring instructionSet, jstring dataDir) {
   SetSigChldHandler();
 
-#ifdef ENABLE_SCHED_BOOST
-  SetForkLoad(true);
-#endif
-
   // Close any logging related FDs before we start evaluating the list of
   // file descriptors.
   __android_log_close();
@@ -472,6 +466,8 @@
     RuntimeAbort(env, __LINE__, "Unable to restat file descriptor table.");
   }
 
+  ResetNicePriority(env);
+
   pid_t pid = fork();
 
   if (pid == 0) {
@@ -614,12 +610,6 @@
     }
   } else if (pid > 0) {
     // the parent process
-
-#ifdef ENABLE_SCHED_BOOST
-    // unset scheduler knob
-    SetForkLoad(false);
-#endif
-
   }
   return pid;
 }
@@ -750,4 +740,3 @@
   return RegisterMethodsOrDie(env, "com/android/internal/os/Zygote", gMethods, NELEM(gMethods));
 }
 }  // namespace android
-
diff --git a/core/jni/fd_utils-inl.h b/core/jni/fd_utils-inl.h
index af27069..b78b8ff 100644
--- a/core/jni/fd_utils-inl.h
+++ b/core/jni/fd_utils-inl.h
@@ -51,6 +51,7 @@
   "/dev/null",
   "/dev/socket/zygote",
   "/dev/socket/zygote_secondary",
+  "/dev/socket/webview_zygote",
   "/sys/kernel/debug/tracing/trace_marker",
   "/system/framework/framework-res.apk",
   "/dev/urandom",
diff --git a/include/android_runtime/AndroidRuntime.h b/core/jni/include/android_runtime/AndroidRuntime.h
similarity index 98%
rename from include/android_runtime/AndroidRuntime.h
rename to core/jni/include/android_runtime/AndroidRuntime.h
index ed77d9a..c2189d4 100644
--- a/include/android_runtime/AndroidRuntime.h
+++ b/core/jni/include/android_runtime/AndroidRuntime.h
@@ -98,7 +98,7 @@
      * Called when the Java application exits to perform additional cleanup actions
      * before the process is terminated.
      */
-    virtual void onExit(int code) { }
+    virtual void onExit(int /*code*/) { }
 
     /** create a new thread that is visible from Java */
     static android_thread_id_t createJavaThread(const char* name, void (*start)(void *),
diff --git a/include/android_runtime/Log.h b/core/jni/include/android_runtime/Log.h
similarity index 100%
rename from include/android_runtime/Log.h
rename to core/jni/include/android_runtime/Log.h
diff --git a/include/android_runtime/android_app_NativeActivity.h b/core/jni/include/android_runtime/android_app_NativeActivity.h
similarity index 100%
rename from include/android_runtime/android_app_NativeActivity.h
rename to core/jni/include/android_runtime/android_app_NativeActivity.h
diff --git a/include/android_runtime/android_content_res_Configuration.h b/core/jni/include/android_runtime/android_content_res_Configuration.h
similarity index 100%
rename from include/android_runtime/android_content_res_Configuration.h
rename to core/jni/include/android_runtime/android_content_res_Configuration.h
diff --git a/include/android_runtime/android_graphics_SurfaceTexture.h b/core/jni/include/android_runtime/android_graphics_SurfaceTexture.h
similarity index 100%
rename from include/android_runtime/android_graphics_SurfaceTexture.h
rename to core/jni/include/android_runtime/android_graphics_SurfaceTexture.h
diff --git a/include/android_runtime/android_hardware_camera2_CameraMetadata.h b/core/jni/include/android_runtime/android_hardware_camera2_CameraMetadata.h
similarity index 100%
rename from include/android_runtime/android_hardware_camera2_CameraMetadata.h
rename to core/jni/include/android_runtime/android_hardware_camera2_CameraMetadata.h
diff --git a/include/android_runtime/android_util_AssetManager.h b/core/jni/include/android_runtime/android_util_AssetManager.h
similarity index 100%
rename from include/android_runtime/android_util_AssetManager.h
rename to core/jni/include/android_runtime/android_util_AssetManager.h
diff --git a/include/android_runtime/android_view_InputQueue.h b/core/jni/include/android_runtime/android_view_InputQueue.h
similarity index 100%
rename from include/android_runtime/android_view_InputQueue.h
rename to core/jni/include/android_runtime/android_view_InputQueue.h
diff --git a/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
similarity index 100%
rename from include/android_runtime/android_view_Surface.h
rename to core/jni/include/android_runtime/android_view_Surface.h
diff --git a/include/android_runtime/android_view_SurfaceSession.h b/core/jni/include/android_runtime/android_view_SurfaceSession.h
similarity index 100%
rename from include/android_runtime/android_view_SurfaceSession.h
rename to core/jni/include/android_runtime/android_view_SurfaceSession.h
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4e98e34..3bd0acc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -167,6 +167,8 @@
     <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED" />
     <protected-broadcast
+        android:name="android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED" />
@@ -181,6 +183,8 @@
     <protected-broadcast
         android:name="android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS" />
     <protected-broadcast
+        android:name="android.bluetooth.inputhost.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast
         android:name="android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED" />
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
new file mode 100644
index 0000000..bd1eb41
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_0_bars.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+            android:fillColor="#FFFFFF"
+            android:fillAlpha="0.3" />
+    </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
new file mode 100644
index 0000000..aedb12c
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_1_bar.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+            android:fillColor="#FFFFFF"
+            android:fillAlpha="0.3" />
+        <path
+            android:pathData="M-377 308.5c0 -0.20001 0 -0.29999 0.10001 -0.5 0 0 0 0 -0.10001 0 -2.10001 0 -3.60001 1.20001 -3.79999 1.29999L-377 314l1.60001 -2.10001c-0.9 -0.79998 -1.60001 -2 -1.60001 -3.39999z"
+            android:fillColor="#FFFFFF" />
+    </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
new file mode 100644
index 0000000..6f07cb5
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_2_bars.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+            android:fillColor="#FFFFFF"
+            android:fillAlpha="0.3" />
+        <path
+            android:pathData="M-377 308.5c0 -0.89999 0.29999 -1.70001 0.70001 -2.5 -0.20001 0 -0.5 0 -0.70001 0 -2.79999 0 -4.79999 1.60001 -5 1.79999l5 6.20001 0 0 0 0 1.60001 -2c-1 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+            android:fillColor="#FFFFFF" />
+    </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
new file mode 100644
index 0000000..c41a8ca
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_3_bars.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+            android:fillColor="#FFFFFF"
+            android:fillAlpha="0.3" />
+        <path
+            android:pathData="M-375.39999 311.89999c-1 -0.79998 -1.60001 -2.1 -1.60001 -3.39999 0 -1.79999 1.10001 -3.39999 2.70001 -4.10001C-375.09998 304.19998 -376 304 -377 304c-3.60001 0 -6 1.89999 -6.29999 2.20001L-377 314l0 0 0 0"
+            android:fillColor="#FFFFFF" />
+    </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
new file mode 100644
index 0000000..ec0a52f
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4_bars.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-377 308.5c0 -2.5 2 -4.5 4.5 -4.5l3.5 0 0.79999 -1c-0.29999 -0.29999 -3.70001 -3 -8.79999 -3 -5.09998 0 -8.5 2.79999 -8.79999 3l8.79999 11 0 0 0 0 1.60001 -2c-0.9 -0.89999 -1.60001 -2.10001 -1.60001 -3.5z"
+            android:fillColor="#FFFFFF" />
+    </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_4k.xml b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
new file mode 100644
index 0000000..78bd0a0
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_4k.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-373.04999 308.79999l0.5 0 0 0.89999 -0.5 0 0 1.20001 -1.1 0 0 -1.20001 -1.9 0 -0.1 -0.70001 1.89999 -3.70001 1.10001 0 0 3.50003 0.1 0zm-1.89999 0l0.89999 0 0 -1.9 0 0 -0.89999 1.9z"
+            android:fillColor="#FFFFFF" />
+        <path
+            android:pathData="M-370.44998 308.70001l-0.5 0.60001 0 1.70001 -1.10001 0 0 -5.70001 1.10001 0 0 2.5 0.39999 -0.60001 1.10001 -1.89999 1.39999 0 -1.6 2.5 1.70001 3.20001 -1.29999 0 -1.20001 -2.30002z"
+            android:fillColor="#FFFFFF" />
+    </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_hd.xml b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
new file mode 100644
index 0000000..78085c2f
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_hd.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-371.79999 311l-1.1 0 0 -2.29999 -0.79999 0 0 2.29999 -1.10001 0 0 -5.70001 1.10001 0 0 2.39999 0.79999 0 0 -2.39999 1.1 0 0 5.70001z"
+            android:fillColor="#FFFFFF" />
+        <path
+            android:pathData="M-371.33557 310.98651l0 -5.68701 1.39068 0c0.27848 0 0.53336 0.0532 0.76465 0.16016 0.2313 0.10693 0.42954 0.2622 0.59568 0.46679 0.16519 0.20459 0.29357 0.45557 0.38421 0.75391 0.0906 0.29834 0.13593 0.63867 0.13593 1.02148l0 0.88672c0 0.38281 -0.0453 0.72363 -0.13593 1.021 -0.0906 0.29785 -0.21902 0.5498 -0.38421 0.7539 -0.16614 0.20411 -0.36628 0.35938 -0.59946 0.46485 -0.23316 0.10547 -0.49182 0.1582 -0.77786 0.1582l-1.37369 0zm1.06879 -4.76904l0 3.85107 0.26333 0c0.15491 0 0.28452 -0.0283 0.38971 -0.084 0.10516 -0.0557 0.19077 -0.14356 0.25681 -0.26367 0.066 -0.12012 0.11331 -0.27198 0.14184 -0.4585 0.0285 -0.18652 0.0424 -0.41113 0.0424 -0.67383l0 -0.89453c0 -0.26562 -0.0138 -0.49219 -0.0424 -0.67969 -0.0285 -0.1875 -0.0758 -0.33984 -0.14102 -0.45703 -0.0644 -0.11719 -0.1492 -0.20312 -0.25275 -0.25781 -0.10437 -0.0547 -0.23071 -0.082 -0.37991 -0.082l-0.27801 0z"
+            android:fillColor="#FFFFFF" />
+    </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_ld.xml b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
new file mode 100644
index 0000000..f660ab7
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_ld.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-371.33557 310.98651l0 -5.68701 1.39068 0c0.27848 0 0.53336 0.0532 0.76465 0.16016 0.2313 0.10693 0.42954 0.2622 0.59568 0.46679 0.16519 0.20459 0.29357 0.45557 0.38421 0.75391 0.0906 0.29834 0.13593 0.63867 0.13593 1.02148l0 0.88672c0 0.38281 -0.0453 0.72363 -0.13593 1.021 -0.0906 0.29785 -0.21902 0.5498 -0.38421 0.7539 -0.16614 0.20411 -0.36628 0.35938 -0.59946 0.46485 -0.23316 0.10547 -0.49182 0.1582 -0.77786 0.1582l-1.37369 0zm1.06879 -4.76904l0 3.85107 0.26333 0c0.15491 0 0.28452 -0.0283 0.38971 -0.084 0.10516 -0.0557 0.19077 -0.14356 0.25681 -0.26367 0.066 -0.12012 0.11331 -0.27198 0.14184 -0.4585 0.0285 -0.18652 0.0424 -0.41113 0.0424 -0.67383l0 -0.89453c0 -0.26562 -0.0138 -0.49219 -0.0424 -0.67969 -0.0285 -0.1875 -0.0758 -0.33984 -0.14102 -0.45703 -0.0644 -0.11719 -0.1492 -0.20312 -0.25275 -0.25781 -0.10437 -0.0547 -0.23071 -0.082 -0.37991 -0.082l-0.27801 0z"
+            android:fillColor="#FFFFFF" />
+        <path
+            android:pathData="M-373.13333 310.13333l1.33334 0 0 0.86667 -2.46667 0 0 -5.66666 1.13333 0 0 4.79999z"
+            android:fillColor="#FFFFFF" />
+    </group>
+</vector>
diff --git a/core/res/res/drawable/ic_signal_wifi_badged_sd.xml b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
new file mode 100644
index 0000000..43b8653
--- /dev/null
+++ b/core/res/res/drawable/ic_signal_wifi_badged_sd.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
+    android:viewportWidth="18"
+    android:viewportHeight="18"
+    android:width="18dp"
+    android:height="18dp">
+    <group
+        android:translateX="386"
+        android:translateY="-298">
+        <path
+            android:pathData="M-371.33557 310.98651l0 -5.68701 1.39068 0c0.27848 0 0.53336 0.0532 0.76465 0.16016 0.2313 0.10693 0.42954 0.2622 0.59568 0.46679 0.16519 0.20459 0.29357 0.45557 0.38421 0.75391 0.0906 0.29834 0.13593 0.63867 0.13593 1.02148l0 0.88672c0 0.38281 -0.0453 0.72363 -0.13593 1.021 -0.0906 0.29785 -0.21902 0.5498 -0.38421 0.7539 -0.16614 0.20411 -0.36628 0.35938 -0.59946 0.46485 -0.23316 0.10547 -0.49182 0.1582 -0.77786 0.1582l-1.37369 0zm1.06879 -4.76904l0 3.85107 0.26333 0c0.15491 0 0.28452 -0.0283 0.38971 -0.084 0.10516 -0.0557 0.19077 -0.14356 0.25681 -0.26367 0.066 -0.12012 0.11331 -0.27198 0.14184 -0.4585 0.0285 -0.18652 0.0424 -0.41113 0.0424 -0.67383l0 -0.89453c0 -0.26562 -0.0138 -0.49219 -0.0424 -0.67969 -0.0285 -0.1875 -0.0758 -0.33984 -0.14102 -0.45703 -0.0644 -0.11719 -0.1492 -0.20312 -0.25275 -0.25781 -0.10437 -0.0547 -0.23071 -0.082 -0.37991 -0.082l-0.27801 0z"
+            android:fillColor="#FFFFFF" />
+        <path
+            android:pathData="M-372.87598 309.47461c0 -0.10645 -0.01 -0.20117 -0.0303 -0.28223 -0.0205 -0.0811 -0.0576 -0.15527 -0.11035 -0.22265 -0.0537 -0.0674 -0.12598 -0.12891 -0.21777 -0.18457 -0.0908 -0.0566 -0.20704 -0.11231 -0.34668 -0.16797 -0.24903 -0.0889 -0.47657 -0.18457 -0.68165 -0.28614 -0.20605 -0.10156 -0.38281 -0.2207 -0.53027 -0.35839 -0.14746 -0.13721 -0.26172 -0.29639 -0.34277 -0.47803 -0.0811 -0.18164 -0.12207 -0.39697 -0.12207 -0.646 0 -0.23144 0.042 -0.4414 0.12793 -0.63086 0.085 -0.18945 0.20312 -0.35205 0.35644 -0.48779 0.15235 -0.13623 0.33496 -0.2417 0.54883 -0.31641 0.21289 -0.0752 0.44824 -0.1123 0.70508 -0.1123 0.2666 0 0.50683 0.0425 0.71973 0.12744 0.21386 0.0854 0.39648 0.2041 0.54687 0.35645 0.15137 0.15234 0.26758 0.333 0.34766 0.54101 0.0791 0.2085 0.11914 0.43604 0.11914 0.68262l-1.07422 0c0 -0.11963 -0.0127 -0.22998 -0.0381 -0.33154 -0.0254 -0.10205 -0.0654 -0.18897 -0.12011 -0.26123 -0.0547 -0.0723 -0.125 -0.12891 -0.20997 -0.16993 -0.085 -0.0405 -0.1875 -0.0605 -0.30664 -0.0605 -0.11132 0 -0.208 0.0171 -0.29004 0.0513 -0.0811 0.0342 -0.14843 0.0815 -0.20117 0.14111 -0.0537 0.0596 -0.0928 0.13037 -0.11816 0.21143 -0.0254 0.0815 -0.0381 0.16894 -0.0381 0.26318 0 0.0937 0.0166 0.17725 0.0508 0.24951 0.0342 0.0723 0.0869 0.14014 0.15625 0.20361 0.0703 0.064 0.16016 0.125 0.26856 0.18311 0.10937 0.0586 0.23926 0.11963 0.38965 0.1831 0.24316 0.084 0.46093 0.17823 0.65136 0.2837 0.19043 0.10498 0.35059 0.22998 0.48047 0.37158 0.12891 0.14258 0.22754 0.30762 0.29493 0.49316 0.0674 0.1875 0.10156 0.40235 0.10156 0.64649 0 0.24121 -0.04 0.458 -0.12012 0.64843 -0.0801 0.19043 -0.19434 0.35059 -0.3418 0.48145 -0.14746 0.13086 -0.32617 0.23144 -0.53711 0.30176 -0.21093 0.0693 -0.44726 0.10449 -0.70898 0.10449 -0.23633 0 -0.46777 -0.0361 -0.69531 -0.1084 -0.22754 -0.0723 -0.43067 -0.18359 -0.61035 -0.33203 -0.17872 -0.14844 -0.32325 -0.33691 -0.43262 -0.56543 -0.10938 -0.22852 -0.16309 -0.49805 -0.16309 -0.80859l1.07813 0c0 0.17089 0.0166 0.31445 0.0498 0.43261 0.0332 0.11817 0.084 0.21485 0.15235 0.29004 0.0684 0.0752 0.15429 0.12891 0.25683 0.16211 0.10352 0.0332 0.22461 0.0488 0.36426 0.0488 0.11719 0 0.21582 -0.0156 0.2959 -0.0488 0.0801 -0.0332 0.14355 -0.0781 0.19238 -0.13574 0.0478 -0.0566 0.082 -0.125 0.10254 -0.2041 0.0205 -0.0781 0.0303 -0.16504 0.0303 -0.25879z"
+            android:fillColor="#FFFFFF" />
+    </group>
+</vector>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 25021b8..4cf1226 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1287,7 +1287,8 @@
          This may be empty if network scoring and recommending isn't supported.
          -->
     <string-array name="config_networkRecommendationPackageNames" translatable="false">
-        <!-- Add packages here -->
+        <!-- The standard AOSP network recommendation provider -->
+        <item>com.android.networkrecommendation</item>
     </string-array>
 
     <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ddf8f25..fe88cd1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1230,6 +1230,15 @@
   <java-symbol type="drawable" name="platlogo" />
   <java-symbol type="drawable" name="stat_notify_sync_error" />
   <java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_0_bars" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_1_bar" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_2_bars" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_3_bars" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_4_bars" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_4k" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_hd" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_sd" />
+  <java-symbol type="drawable" name="ic_signal_wifi_badged_ld" />
   <java-symbol type="drawable" name="stat_notify_rssi_in_range" />
   <java-symbol type="drawable" name="stat_sys_gps_on" />
   <java-symbol type="drawable" name="stat_sys_tether_wifi" />
diff --git a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
index 9a81401..bdc0200 100644
--- a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
+++ b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
@@ -77,7 +77,7 @@
         final NetworkRecommendationProvider.ResultCallback callback =
                 new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence);
 
-        final RecommendationResult result = new RecommendationResult(null);
+        final RecommendationResult result = RecommendationResult.createDoNotConnectRecommendation();
         callback.onResult(result);
 
         final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -93,7 +93,7 @@
         final NetworkRecommendationProvider.ResultCallback callback =
                 new NetworkRecommendationProvider.ResultCallback(mMockRemoteCallback, sequence);
 
-        final RecommendationResult result = new RecommendationResult(null);
+        final RecommendationResult result = RecommendationResult.createDoNotConnectRecommendation();
         callback.onResult(result);
 
         try {
diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
index 02c2517..5bfff26 100644
--- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
+++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
@@ -16,32 +16,33 @@
 
 package android.net;
 
+import static org.mockito.Mockito.when;
+
 import android.Manifest.permission;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
-import android.os.UserHandle;
+import android.provider.Settings;
 import android.test.InstrumentationTestCase;
-
+import com.android.internal.R;
+import java.util.List;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
 public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
     @Mock private Context mMockContext;
     @Mock private PackageManager mMockPm;
-
+    @Mock private Resources mResources;
+    @Mock private ContentResolver mContentResolver;
+    private Context mTargetContext;
     private NetworkScorerAppManager mNetworkScorerAppManager;
 
     @Override
@@ -49,154 +50,161 @@
         super.setUp();
 
         // Configuration needed to make mockito/dexcache work.
-        System.setProperty("dexmaker.dexcache",
-                getInstrumentation().getTargetContext().getCacheDir().getPath());
+        mTargetContext = getInstrumentation().getTargetContext();
+        System.setProperty("dexmaker.dexcache", mTargetContext.getCacheDir().getPath());
         ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader();
         Thread.currentThread().setContextClassLoader(newClassLoader);
 
         MockitoAnnotations.initMocks(this);
-        Mockito.when(mMockContext.getPackageManager()).thenReturn(mMockPm);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPm);
+        when(mMockContext.getResources()).thenReturn(mResources);
+        when(mMockContext.getContentResolver()).thenReturn(mTargetContext.getContentResolver());
         mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext);
     }
 
-    public void testGetAllValidScorers() throws Exception {
-        // Package 1 - Valid scorer.
-        ResolveInfoHolder package1 = buildResolveInfo("package1", 1, true, true, false, false);
-
-        // Package 2 - Receiver does not have BROADCAST_NETWORK_PRIVILEGED permission.
-        ResolveInfoHolder package2 = buildResolveInfo("package2", 2, false, true, false, false);
-
-        // Package 3 - App does not have SCORE_NETWORKS permission.
-        ResolveInfoHolder package3 = buildResolveInfo("package3", 3, true, false, false, false);
-
-        // Package 4 - Valid scorer w/ optional config activity.
-        ResolveInfoHolder package4 = buildResolveInfo("package4", 4, true, true, true, false);
-
-        // Package 5 - Valid scorer w/ optional service to bind to.
-        ResolveInfoHolder package5 = buildResolveInfo("package5", 5, true, true, false, true);
-
-        List<ResolveInfoHolder> scorers = new ArrayList<>();
-        scorers.add(package1);
-        scorers.add(package2);
-        scorers.add(package3);
-        scorers.add(package4);
-        scorers.add(package5);
-        setScorers(scorers);
-
-        Iterator<NetworkScorerAppData> result =
-                mNetworkScorerAppManager.getAllValidScorers().iterator();
-
-        assertTrue(result.hasNext());
-        NetworkScorerAppData next = result.next();
-        assertEquals("package1", next.mPackageName);
-        assertEquals(1, next.mPackageUid);
-        assertNull(next.mConfigurationActivityClassName);
-
-        assertTrue(result.hasNext());
-        next = result.next();
-        assertEquals("package4", next.mPackageName);
-        assertEquals(4, next.mPackageUid);
-        assertEquals(".ConfigActivity", next.mConfigurationActivityClassName);
-
-        assertTrue(result.hasNext());
-        next = result.next();
-        assertEquals("package5", next.mPackageName);
-        assertEquals(5, next.mPackageUid);
-        assertEquals(".ScoringService", next.mScoringServiceClassName);
-
-        assertFalse(result.hasNext());
+    public void testGetPotentialRecommendationProviderPackages_emptyConfig() throws Exception {
+        setNetworkRecommendationPackageNames(/*no configured packages*/);
+        assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty());
     }
 
-    private void setScorers(List<ResolveInfoHolder> scorers) {
-        List<ResolveInfo> receivers = new ArrayList<>();
-        for (final ResolveInfoHolder scorer : scorers) {
-            receivers.add(scorer.scorerResolveInfo);
-            if (scorer.configActivityResolveInfo != null) {
-                // This scorer has a config activity.
-                Mockito.when(mMockPm.queryIntentActivities(
-                        Mockito.argThat(new ArgumentMatcher<Intent>() {
-                            @Override
-                            public boolean matches(Object object) {
-                                Intent intent = (Intent) object;
-                                return NetworkScoreManager.ACTION_CUSTOM_ENABLE.equals(
-                                        intent.getAction())
-                                        && scorer.scorerResolveInfo.activityInfo.packageName.equals(
-                                                intent.getPackage());
-                            }
-                        }), Mockito.eq(0))).thenReturn(
-                                Collections.singletonList(scorer.configActivityResolveInfo));
-            }
+    public void testGetPotentialRecommendationProviderPackages_permissionNotGranted()
+            throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksDenied("package1");
 
-            if (scorer.serviceResolveInfo != null) {
-                // This scorer has a service to bind to
-                Mockito.when(mMockPm.resolveService(
-                        Mockito.argThat(new ArgumentMatcher<Intent>() {
-                            @Override
-                            public boolean matches(Object object) {
-                                Intent intent = (Intent) object;
-                                return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(
-                                        intent.getAction())
-                                        && scorer.scorerResolveInfo.activityInfo.packageName.equals(
-                                        intent.getPackage());
-                            }
-                        }), Mockito.eq(0))).thenReturn(scorer.serviceResolveInfo);
-            }
+        assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty());
+    }
+
+    public void testGetPotentialRecommendationProviderPackages_permissionGranted()
+            throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+
+        List<String> potentialProviderPackages =
+                mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
+
+        assertFalse(potentialProviderPackages.isEmpty());
+        assertEquals("package1", potentialProviderPackages.get(0));
+    }
+
+    public void testGetPotentialRecommendationProviderPackages_multipleConfigured()
+            throws Exception {
+        setNetworkRecommendationPackageNames("package1", "package2");
+        mockScoreNetworksDenied("package1");
+        mockScoreNetworksGranted("package2");
+
+        List<String> potentialProviderPackages =
+                mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
+
+        assertEquals(1, potentialProviderPackages.size());
+        assertEquals("package2", potentialProviderPackages.get(0));
+    }
+
+    public void testGetNetworkRecommendationProviderData_noPotentialPackages() throws Exception {
+        setNetworkRecommendationPackageNames(/*no configured packages*/);
+        assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
+    }
+
+    public void testGetNetworkRecommendationProviderData_serviceMissing() throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+
+        assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
+    }
+
+    public void testGetNetworkRecommendationProviderData_scoreNetworksNotGranted()
+            throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksDenied("package1");
+        mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
+
+        assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
+    }
+
+    public void testGetNetworkRecommendationProviderData_available() throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+        mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
+
+        NetworkScorerAppData appData =
+                mNetworkScorerAppManager.getNetworkRecommendationProviderData();
+        assertNotNull(appData);
+        assertEquals("package1", appData.packageName);
+        assertEquals(924, appData.packageUid);
+        assertEquals(".RecommendationService", appData.recommendationServiceClassName);
+    }
+
+    public void testGetActiveScorer_providerAvailable() throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+        mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
+
+        ContentResolver cr = mTargetContext.getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNotNull(activeScorer);
+        assertEquals("package1", activeScorer.packageName);
+        assertEquals(924, activeScorer.packageUid);
+        assertEquals(".RecommendationService", activeScorer.recommendationServiceClassName);
+    }
+
+    public void testGetActiveScorer_providerNotAvailable()
+            throws Exception {
+        ContentResolver cr = mTargetContext.getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNull(activeScorer);
+    }
+
+    public void testGetActiveScorer_recommendationsDisabled() throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+        mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
+        ContentResolver cr = mTargetContext.getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNull(activeScorer);
+    }
+
+    private void setNetworkRecommendationPackageNames(String... names) {
+        if (names == null) {
+            names = new String[0];
         }
+        when(mResources.getStringArray(R.array.config_networkRecommendationPackageNames))
+                .thenReturn(names);
+    }
 
-        Mockito.when(mMockPm.queryBroadcastReceiversAsUser(
+    private void mockScoreNetworksGranted(String packageName) {
+        when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void mockScoreNetworksDenied(String packageName) {
+        when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+    }
+
+    private void mockRecommendationServiceAvailable(final String packageName, int packageUid) {
+        final ResolveInfo serviceInfo = new ResolveInfo();
+        serviceInfo.serviceInfo = new ServiceInfo();
+        serviceInfo.serviceInfo.name = ".RecommendationService";
+        serviceInfo.serviceInfo.packageName = packageName;
+        serviceInfo.serviceInfo.applicationInfo = new ApplicationInfo();
+        serviceInfo.serviceInfo.applicationInfo.uid = packageUid;
+
+        final int flags = 0;
+        when(mMockPm.resolveService(
                 Mockito.argThat(new ArgumentMatcher<Intent>() {
                     @Override
                     public boolean matches(Object object) {
                         Intent intent = (Intent) object;
-                        return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction());
+                        return NetworkScoreManager.ACTION_RECOMMEND_NETWORKS
+                                .equals(intent.getAction())
+                                && packageName.equals(intent.getPackage());
                     }
-                }), Mockito.eq(0), Mockito.eq(UserHandle.USER_SYSTEM)))
-                .thenReturn(receivers);
-    }
-
-    private ResolveInfoHolder buildResolveInfo(String packageName, int packageUid,
-            boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity,
-            boolean hasServiceInfo) throws Exception {
-        Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
-                .thenReturn(hasScorePermission ?
-                        PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
-
-        ResolveInfo resolveInfo = new ResolveInfo();
-        resolveInfo.activityInfo = new ActivityInfo();
-        resolveInfo.activityInfo.packageName = packageName;
-        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
-        resolveInfo.activityInfo.applicationInfo.uid = packageUid;
-        if (hasReceiverPermission) {
-            resolveInfo.activityInfo.permission = permission.BROADCAST_NETWORK_PRIVILEGED;
-        }
-
-        ResolveInfo configActivityInfo = null;
-        if (hasConfigActivity) {
-            configActivityInfo = new ResolveInfo();
-            configActivityInfo.activityInfo = new ActivityInfo();
-            configActivityInfo.activityInfo.name = ".ConfigActivity";
-        }
-
-        ResolveInfo serviceInfo = null;
-        if (hasServiceInfo) {
-            serviceInfo = new ResolveInfo();
-            serviceInfo.serviceInfo = new ServiceInfo();
-            serviceInfo.serviceInfo.name = ".ScoringService";
-        }
-
-        return new ResolveInfoHolder(resolveInfo, configActivityInfo, serviceInfo);
-    }
-
-    private static class ResolveInfoHolder {
-        final ResolveInfo scorerResolveInfo;
-        final ResolveInfo configActivityResolveInfo;
-        final ResolveInfo serviceResolveInfo;
-
-        public ResolveInfoHolder(ResolveInfo scorerResolveInfo,
-                ResolveInfo configActivityResolveInfo, ResolveInfo serviceResolveInfo) {
-            this.scorerResolveInfo = scorerResolveInfo;
-            this.configActivityResolveInfo = configActivityResolveInfo;
-            this.serviceResolveInfo = serviceResolveInfo;
-        }
+                }), Mockito.eq(flags))).thenReturn(serviceInfo);
     }
 }
diff --git a/core/tests/coretests/src/android/net/RecommendationRequestTest.java b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
new file mode 100644
index 0000000..31560b0
--- /dev/null
+++ b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
@@ -0,0 +1,84 @@
+package android.net;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+public class RecommendationRequestTest extends AndroidTestCase {
+    private ScanResult[] mScanResults;
+    private WifiConfiguration mConfiguration;
+    private NetworkCapabilities mCapabilities;
+
+    @Override
+    public void setUp() throws Exception {
+        mScanResults = new ScanResult[2];
+        mScanResults[0] = new ScanResult();
+        mScanResults[1] = new ScanResult(
+                "ssid",
+                "bssid",
+                0L /*hessid*/,
+                1 /*anqpDominId*/,
+                "caps",
+                2 /*level*/,
+                3 /*frequency*/,
+                4L /*tsf*/,
+                5 /*distCm*/,
+                6 /*distSdCm*/,
+                7 /*channelWidth*/,
+                8 /*centerFreq0*/,
+                9 /*centerFreq1*/,
+                false /*is80211McRTTResponder*/);
+        mConfiguration = new WifiConfiguration();
+        mConfiguration.SSID = "RecommendationRequestTest";
+        mCapabilities = new NetworkCapabilities()
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
+    }
+
+    public void testParceling() throws Exception {
+        RecommendationRequest request = new RecommendationRequest.Builder()
+                .setCurrentRecommendedWifiConfig(mConfiguration)
+                .setScanResults(mScanResults)
+                .setNetworkCapabilities(mCapabilities)
+                .build();
+
+        RecommendationRequest parceled = passThroughParcel(request);
+        assertEquals(request.getCurrentSelectedConfig().SSID,
+                parceled.getCurrentSelectedConfig().SSID);
+        assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities());
+        ScanResult[] parceledScanResults = parceled.getScanResults();
+        assertNotNull(parceledScanResults);
+        assertEquals(mScanResults.length, parceledScanResults.length);
+        for (int i = 0; i < mScanResults.length; i++) {
+            assertEquals(mScanResults[i].SSID, parceledScanResults[i].SSID);
+        }
+    }
+
+    public void testParceling_nullScanResults() throws Exception {
+        RecommendationRequest request = new RecommendationRequest.Builder()
+                .setCurrentRecommendedWifiConfig(mConfiguration)
+                .setNetworkCapabilities(mCapabilities)
+                .build();
+
+        RecommendationRequest parceled = passThroughParcel(request);
+        assertEquals(request.getCurrentSelectedConfig().SSID,
+                parceled.getCurrentSelectedConfig().SSID);
+        assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities());
+        ScanResult[] parceledScanResults = parceled.getScanResults();
+        assertNull(parceledScanResults);
+    }
+
+    private RecommendationRequest passThroughParcel(RecommendationRequest request) {
+        Parcel p = Parcel.obtain();
+        RecommendationRequest output = null;
+        try {
+            request.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            output = RecommendationRequest.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+        assertNotNull(output);
+        return output;
+    }
+}
diff --git a/core/tests/coretests/src/android/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
new file mode 100644
index 0000000..9c3346e
--- /dev/null
+++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
@@ -0,0 +1,169 @@
+/*
+ t Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.net;
+
+import static org.junit.Assert.*;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/** Unit tests for {@link ScoredNetwork}. */
+@RunWith(AndroidJUnit4.class)
+public class ScoredNetworkTest {
+
+    private static final int RSSI_START = -110;
+    private static final int TEST_RSSI = -50;
+    private static final byte TEST_SCORE = 5;
+    private static final RssiCurve CURVE =
+            new RssiCurve(RSSI_START, 10, new byte[] {-1, 0, 1, 2, 3, 4, TEST_SCORE, 6, 7});
+
+    private static final byte RANKING_SCORE_OFFSET = 13;
+    private static final Bundle ATTRIBUTES;
+    static {
+        ATTRIBUTES = new Bundle();
+        ATTRIBUTES.putInt(
+                ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, RANKING_SCORE_OFFSET);
+    }
+
+    private static final NetworkKey KEY
+        = new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00"));
+
+    @Test
+    public void calculateRankingOffsetShouldThrowUnsupportedOperationException() {
+        // No curve or ranking score offset set in curve
+        ScoredNetwork scoredNetwork = new ScoredNetwork(KEY, null);
+        try {
+            scoredNetwork.calculateRankingScore(TEST_RSSI);
+            fail("Should have thrown UnsupportedOperationException");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void calculateRankingOffsetWithRssiCurveShouldReturnExpectedScore() {
+        ScoredNetwork scoredNetwork = new ScoredNetwork(KEY, CURVE);
+        assertEquals(TEST_SCORE << Byte.SIZE, scoredNetwork.calculateRankingScore(TEST_RSSI));
+    }
+
+    @Test
+    public void rankingScoresShouldDifferByRankingScoreOffset() {
+        ScoredNetwork scoredNetwork1 = new ScoredNetwork(KEY, CURVE);
+        ScoredNetwork scoredNetwork2
+            = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, ATTRIBUTES);
+        int scoreDifference =
+            scoredNetwork2.calculateRankingScore(TEST_RSSI)
+            - scoredNetwork1.calculateRankingScore(TEST_RSSI);
+        assertEquals(RANKING_SCORE_OFFSET, scoreDifference);
+    }
+
+    @Test
+    public void calculateRankingScoreShouldNotResultInIntegerOverflow() {
+        Bundle attr = new Bundle();
+        attr.putInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, Integer.MAX_VALUE);
+        ScoredNetwork scoredNetwork
+            = new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr);
+        assertEquals(Integer.MAX_VALUE, scoredNetwork.calculateRankingScore(TEST_RSSI));
+    }
+
+    @Test
+    public void calculateRankingScoreShouldNotResultInIntegerUnderflow() {
+        Bundle attr = new Bundle();
+        attr.putInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET, Integer.MIN_VALUE);
+        ScoredNetwork scoredNetwork =
+                new ScoredNetwork(KEY, CURVE, false /* meteredHint */, attr);
+        assertEquals(Integer.MIN_VALUE, scoredNetwork.calculateRankingScore(RSSI_START));
+    }
+
+    @Test
+    public void hasRankingScoreShouldReturnFalse() {
+        ScoredNetwork network = new ScoredNetwork(KEY, null /* rssiCurve */);
+        assertFalse(network.hasRankingScore());
+    }
+
+    @Test
+    public void hasRankingScoreShouldReturnTrueWhenAttributesHasRankingScoreOffset() {
+        ScoredNetwork network =
+                new ScoredNetwork(KEY, null /* rssiCurve */, false /* meteredHint */, ATTRIBUTES);
+        assertTrue(network.hasRankingScore());
+    }
+
+    @Test
+    public void hasRankingScoreShouldReturnTrueWhenCurveIsPresent() {
+        ScoredNetwork network =
+                new ScoredNetwork(KEY, CURVE , false /* meteredHint */);
+        assertTrue(network.hasRankingScore());
+    }
+
+    @Test
+    public void shouldWriteAndReadFromParcelWhenAllFieldsSet() {
+        ScoredNetwork network = new ScoredNetwork(KEY, CURVE, true /* meteredHint */, ATTRIBUTES);
+        ScoredNetwork newNetwork;
+
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            network.writeToParcel(parcel, 0 /* flags */);
+            parcel.setDataPosition(0);
+            newNetwork = ScoredNetwork.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+        assertEquals(CURVE.start, newNetwork.rssiCurve.start);
+        assertEquals(CURVE.bucketWidth, newNetwork.rssiCurve.bucketWidth);
+        assertTrue(Arrays.equals(CURVE.rssiBuckets, newNetwork.rssiCurve.rssiBuckets));
+        assertTrue(newNetwork.meteredHint);
+        assertNotNull(newNetwork.attributes);
+        assertEquals(
+                RANKING_SCORE_OFFSET,
+                newNetwork.attributes.getInt(ScoredNetwork.ATTRIBUTES_KEY_RANKING_SCORE_OFFSET));
+    }
+
+    @Test
+    public void shouldWriteAndReadFromParcelWithoutBundle() {
+        ScoredNetwork network = new ScoredNetwork(KEY, CURVE, true /* meteredHint */);
+        ScoredNetwork newNetwork;
+
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            network.writeToParcel(parcel, 0 /* flags */);
+            parcel.setDataPosition(0);
+            newNetwork = ScoredNetwork.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+        assertEquals(CURVE.start, newNetwork.rssiCurve.start);
+        assertEquals(CURVE.bucketWidth, newNetwork.rssiCurve.bucketWidth);
+        assertTrue(Arrays.equals(CURVE.rssiBuckets, newNetwork.rssiCurve.rssiBuckets));
+        assertTrue(newNetwork.meteredHint);
+        assertNull(newNetwork.attributes);
+    }
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 33c1c3f..ce75bb49 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3278,6 +3278,20 @@
         return delay;
     }
 
+     /**
+     * Indicate A2DP device configuration has changed.
+     * @param device Bluetooth device whose configuration has changed.
+     * {@hide}
+     */
+    public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device) {
+        IAudioService service = getService();
+        try {
+            service.handleBluetoothA2dpDeviceConfigChange(device);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** {@hide} */
     public IRingtonePlayer getRingtonePlayer() {
         try {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index f597440..8a28255 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -678,6 +678,9 @@
     public static native int setDeviceConnectionState(int device, int state,
                                                       String device_address, String device_name);
     public static native int getDeviceConnectionState(int device, String device_address);
+    public static native int handleDeviceConfigChange(int device,
+                                                      String device_address,
+                                                      String device_name);
     public static native int setPhoneState(int state);
     public static native int setForceUse(int usage, int config);
     public static native int getForceUse(int usage);
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index c7931fc..9e5ac72 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -133,6 +133,8 @@
 
     int setBluetoothA2dpDeviceConnectionState(in BluetoothDevice device, int state, int profile);
 
+    void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device);
+
     AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
 
     boolean isCameraSoundForced();
diff --git a/media/java/android/mtp/MtpServer.java b/media/java/android/mtp/MtpServer.java
index 3c2ea58..44c8b0b 100644
--- a/media/java/android/mtp/MtpServer.java
+++ b/media/java/android/mtp/MtpServer.java
@@ -29,9 +29,21 @@
         System.loadLibrary("media_jni");
     }
 
-    public MtpServer(MtpDatabase database, boolean usePtp) {
+    public MtpServer(
+            MtpDatabase database,
+            boolean usePtp,
+            String deviceInfoManufacturer,
+            String deviceInfoModel,
+            String deviceInfoDeviceVersion,
+            String deviceInfoSerialNumber) {
         mDatabase = database;
-        native_setup(database, usePtp);
+        native_setup(
+                database,
+                usePtp,
+                deviceInfoManufacturer,
+                deviceInfoModel,
+                deviceInfoDeviceVersion,
+                deviceInfoSerialNumber);
         database.setServer(this);
     }
 
@@ -72,7 +84,13 @@
     }
 
     public static native final void native_configure(boolean usePtp);
-    private native final void native_setup(MtpDatabase database, boolean usePtp);
+    private native final void native_setup(
+            MtpDatabase database,
+            boolean usePtp,
+            String deviceInfoManufacturer,
+            String deviceInfoModel,
+            String deviceInfoDeviceVersion,
+            String deviceInfoSerialNumber);
     private native final void native_run();
     private native final void native_cleanup();
     private native final void native_send_object_added(int handle);
diff --git a/media/jni/android_mtp_MtpServer.cpp b/media/jni/android_mtp_MtpServer.cpp
index afd3082..c325f4e 100644
--- a/media/jni/android_mtp_MtpServer.cpp
+++ b/media/jni/android_mtp_MtpServer.cpp
@@ -61,10 +61,34 @@
 }
 
 static void
-android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jboolean usePtp)
+android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jboolean usePtp,
+        jstring deviceInfoManufacturer,
+        jstring deviceInfoModel,
+        jstring deviceInfoDeviceVersion,
+        jstring deviceInfoSerialNumber)
 {
+    const char *deviceInfoManufacturerStr = env->GetStringUTFChars(deviceInfoManufacturer, NULL);
+    const char *deviceInfoModelStr = env->GetStringUTFChars(deviceInfoModel, NULL);
+    const char *deviceInfoDeviceVersionStr = env->GetStringUTFChars(deviceInfoDeviceVersion, NULL);
+    const char *deviceInfoSerialNumberStr = env->GetStringUTFChars(deviceInfoSerialNumber, NULL);
     MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase),
-            usePtp, AID_MEDIA_RW, 0664, 0775);
+            usePtp, AID_MEDIA_RW, 0664, 0775,
+            MtpString((deviceInfoManufacturerStr != NULL) ? deviceInfoManufacturerStr : ""),
+            MtpString((deviceInfoModelStr != NULL) ? deviceInfoModelStr : ""),
+            MtpString((deviceInfoDeviceVersionStr != NULL) ? deviceInfoDeviceVersionStr : ""),
+            MtpString((deviceInfoSerialNumberStr != NULL) ? deviceInfoSerialNumberStr : ""));
+    if (deviceInfoManufacturerStr != NULL) {
+        env->ReleaseStringUTFChars(deviceInfoManufacturer, deviceInfoManufacturerStr);
+    }
+    if (deviceInfoModelStr != NULL) {
+        env->ReleaseStringUTFChars(deviceInfoModel, deviceInfoModelStr);
+    }
+    if (deviceInfoDeviceVersionStr != NULL) {
+        env->ReleaseStringUTFChars(deviceInfoDeviceVersion, deviceInfoDeviceVersionStr);
+    }
+    if (deviceInfoSerialNumberStr != NULL) {
+        env->ReleaseStringUTFChars(deviceInfoSerialNumber, deviceInfoSerialNumberStr);
+    }
     env->SetLongField(thiz, field_MtpServer_nativeContext, (jlong)server);
 }
 
@@ -180,7 +204,7 @@
 
 static const JNINativeMethod gMethods[] = {
     {"native_configure",              "(Z)V",  (void *)android_mtp_configure},
-    {"native_setup",                "(Landroid/mtp/MtpDatabase;Z)V",
+    {"native_setup",                "(Landroid/mtp/MtpDatabase;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
                                             (void *)android_mtp_MtpServer_setup},
     {"native_run",                  "()V",  (void *)android_mtp_MtpServer_run},
     {"native_cleanup",              "()V",  (void *)android_mtp_MtpServer_cleanup},
diff --git a/packages/PrintRecommendationService/AndroidManifest.xml b/packages/PrintRecommendationService/AndroidManifest.xml
index c6736d7..2e9342c 100644
--- a/packages/PrintRecommendationService/AndroidManifest.xml
+++ b/packages/PrintRecommendationService/AndroidManifest.xml
@@ -18,11 +18,11 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.printservice.recommendation"
-    android:versionCode="1"
-    android:versionName="1.0.0">
+    android:versionCode="2"
+    android:versionName="1.1.0">
 
     <uses-sdk android:minSdkVersion="24"
-        android:targetSdkVersion="24" />
+        android:targetSdkVersion="25" />
 
     <uses-permission android:name="android.permission.INTERNET" />
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 3a16a75..252aaab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -254,7 +254,7 @@
 
     public boolean matches(WifiConfiguration config) {
         if (config.isPasspoint() && mConfig != null && mConfig.isPasspoint()) {
-            return config.FQDN.equals(mConfig.FQDN);
+            return ssid.equals(removeDoubleQuotes(config.SSID)) && config.FQDN.equals(mConfig.FQDN);
         } else {
             return ssid.equals(removeDoubleQuotes(config.SSID))
                     && security == getSecurity(config)
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 9eceeac..c965067 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -31,6 +31,7 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVoiceInteractionSessionListener;
 import com.android.internal.app.IVoiceInteractionSessionShowCallback;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.BaseStatusBar;
 import com.android.systemui.statusbar.CommandQueue;
@@ -282,7 +283,7 @@
 
     @Nullable
     private ComponentName getAssistInfo() {
-        return mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT);
+        return mAssistUtils.getAssistComponentForUser(KeyguardUpdateMonitor.getCurrentUser());
     }
 
     public void showDisclosure() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 5c8a6e2..a172e19 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -358,6 +358,11 @@
     private final TouchAnimator.Listener mNonFirstPageListener =
             new TouchAnimator.ListenerAdapter() {
                 @Override
+                public void onAnimationAtEnd() {
+                    mQuickQsPanel.setVisibility(View.INVISIBLE);
+                }
+
+                @Override
                 public void onAnimationStarted() {
                     mQuickQsPanel.setVisibility(View.VISIBLE);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index dfc89fa..612eba7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -345,7 +345,10 @@
 
             @Override
             public void onAnnouncementRequested(CharSequence announcement) {
-                announceForAccessibility(announcement);
+                if (announcement != null) {
+                    mHandler.obtainMessage(H.ANNOUNCE_FOR_ACCESSIBILITY, announcement)
+                            .sendToTarget();
+                }
             }
         };
         r.tile.addCallback(callback);
@@ -514,10 +517,13 @@
     private class H extends Handler {
         private static final int SHOW_DETAIL = 1;
         private static final int SET_TILE_VISIBILITY = 2;
+        private static final int ANNOUNCE_FOR_ACCESSIBILITY = 3;
         @Override
         public void handleMessage(Message msg) {
             if (msg.what == SHOW_DETAIL) {
                 handleShowDetail((Record)msg.obj, msg.arg1 != 0);
+            } else if (msg.what == ANNOUNCE_FOR_ACCESSIBILITY) {
+                announceForAccessibility((CharSequence)msg.obj);
             }
         }
     }
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 4175312..adc59de 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -57,14 +57,16 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.util.Slog;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.Map;
 
+
 class BluetoothManagerService extends IBluetoothManager.Stub {
     private static final String TAG = "BluetoothManagerService";
     private static final boolean DBG = true;
@@ -137,16 +139,46 @@
         new ReentrantReadWriteLock();
     private boolean mBinding;
     private boolean mUnbinding;
+
     // used inside handler thread
     private boolean mQuietEnable = false;
-    // configuarion from external IBinder call which is used to
+    private boolean mEnable;
+
+    /**
+     * Used for tracking apps that enabled / disabled Bluetooth.
+     */
+    private class ActiveLog {
+        private String mPackageName;
+        private boolean mEnable;
+        private long mTimestamp;
+
+        public ActiveLog(String packageName, boolean enable, long timestamp) {
+            mPackageName = packageName;
+            mEnable = enable;
+            mTimestamp = timestamp;
+        }
+
+        public long getTime() {
+            return mTimestamp;
+        }
+
+        public String toString() {
+            return android.text.format.DateFormat.format("MM-dd hh:mm:ss ", mTimestamp) +
+                    (mEnable ? "  Enabled " : " Disabled ") + " by " + mPackageName;
+        }
+
+    }
+
+    private LinkedList<ActiveLog> mActiveLogs;
+
+    // configuration from external IBinder call which is used to
     // synchronize with broadcast receiver.
     private boolean mQuietEnableExternal;
-    // configuarion from external IBinder call which is used to
-    // synchronize with broadcast receiver.
     private boolean mEnableExternal;
-    // used inside handler thread
-    private boolean mEnable;
+
+    // Map of apps registered to keep BLE scanning on.
+    private Map<IBinder, ClientDeathRecipient> mBleApps = new ConcurrentHashMap<IBinder, ClientDeathRecipient>();
+
     private int mState;
     private final BluetoothHandler mHandler;
     private int mErrorRecoveryRetryCounter;
@@ -172,7 +204,7 @@
         }
     }
 
-    private final IBluetoothCallback mBluetoothCallback =  new IBluetoothCallback.Stub() {
+    private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() {
         @Override
         public void onBluetoothStateChange(int prevState, int newState) throws RemoteException  {
             Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE,prevState,newState);
@@ -251,12 +283,12 @@
                         } else if (st == BluetoothAdapter.STATE_ON){
                             // disable without persisting the setting
                             Slog.d(TAG, "Calling disable");
-                            sendDisableMsg();
+                            sendDisableMsg("airplane mode");
                         }
                     } else if (mEnableExternal) {
                         // enable without persisting the setting
                         Slog.d(TAG, "Calling enable");
-                        sendEnableMsg(mQuietEnableExternal);
+                        sendEnableMsg(mQuietEnableExternal, "airplane mode");
                     }
                 }
             }
@@ -272,6 +304,7 @@
                     || context.getResources().getBoolean(
                 com.android.internal.R.bool.config_permissionReviewRequired);
 
+        mActiveLogs = new LinkedList<ActiveLog>();
         mBluetooth = null;
         mBluetoothBinder = null;
         mBluetoothGatt = null;
@@ -298,15 +331,15 @@
             mEnableExternal = true;
         }
 
-        int sysUiUid = -1;
+        int systemUiUid = -1;
         try {
-            sysUiUid = mContext.getPackageManager().getPackageUidAsUser("com.android.systemui",
+            systemUiUid = mContext.getPackageManager().getPackageUidAsUser("com.android.systemui",
                     PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
         } catch (PackageManager.NameNotFoundException e) {
             // Some platforms, such as wearables do not have a system ui.
             Slog.w(TAG, "Unable to resolve SystemUI's UID.", e);
         }
-        mSystemUiUid = sysUiUid;
+        mSystemUiUid = systemUiUid;
     }
 
     /**
@@ -335,12 +368,15 @@
 
     /**
      *  Save the Bluetooth on/off state
-     *
      */
     private void persistBluetoothSetting(int value) {
+        if (DBG) Slog.d(TAG, "Persisting Bluetooth Setting: " + value);
+        // waive WRITE_SECURE_SETTINGS permission check
+        long callingIdentity = Binder.clearCallingIdentity();
         Settings.Global.putInt(mContext.getContentResolver(),
                                Settings.Global.BLUETOOTH_ON,
                                value);
+        Binder.restoreCallingIdentity(callingIdentity);
     }
 
     /**
@@ -481,8 +517,14 @@
     }
 
     class ClientDeathRecipient implements IBinder.DeathRecipient {
+        private String mPackageName;
+
+        public ClientDeathRecipient(String packageName) {
+            mPackageName = packageName;
+        }
+
         public void binderDied() {
-            if (DBG) Slog.d(TAG, "Binder is dead - unregister Ble App");
+            if (DBG) Slog.d(TAG, "Binder is dead - unregister " + mPackageName);
             if (isBleAppPresent()) {
               // Nothing to do, another app is here.
               return;
@@ -501,10 +543,11 @@
                 mBluetoothLock.readLock().unlock();
             }
         }
-    }
 
-    /** Internal death rec list */
-    Map<IBinder, ClientDeathRecipient> mBleApps = new ConcurrentHashMap<IBinder, ClientDeathRecipient>();
+        public String getPackageName() {
+            return mPackageName;
+        }
+    }
 
     @Override
     public boolean isBleScanAlwaysAvailable() {
@@ -562,28 +605,22 @@
         }
     }
 
-    public int updateBleAppCount(IBinder token, boolean enable) {
-        if (enable) {
-            ClientDeathRecipient r = mBleApps.get(token);
-            if (r == null) {
-                ClientDeathRecipient deathRec = new ClientDeathRecipient();
-                try {
-                    token.linkToDeath(deathRec, 0);
-                } catch (RemoteException ex) {
-                    throw new IllegalArgumentException("Wake lock is already dead.");
-                }
-                mBleApps.put(token, deathRec);
-                if (DBG) Slog.d(TAG, "Registered for death Notification");
+    public int updateBleAppCount(IBinder token, boolean enable, String packageName) {
+        ClientDeathRecipient r = mBleApps.get(token);
+        if (r == null && enable) {
+            ClientDeathRecipient deathRec = new ClientDeathRecipient(packageName);
+            try {
+                token.linkToDeath(deathRec, 0);
+            } catch (RemoteException ex) {
+                throw new IllegalArgumentException("BLE app (" + packageName + ") already dead!");
             }
-
-        } else  {
-            ClientDeathRecipient r = mBleApps.get(token);
-            if (r != null) {
-                // Unregister death recipient as the app goes away.
-                token.unlinkToDeath(r, 0);
-                mBleApps.remove(token);
-                if (DBG) Slog.d(TAG, "Unregistered for death Notification");
-            }
+            mBleApps.put(token, deathRec);
+            if (DBG) Slog.d(TAG, "Registered for death of " + packageName);
+        } else if (!enable && r != null) {
+            // Unregister death recipient as the app goes away.
+            token.unlinkToDeath(r, 0);
+            mBleApps.remove(token);
+            if (DBG) Slog.d(TAG, "Unregistered for death of " + packageName);
         }
         int appCount = mBleApps.size();
         if (DBG) Slog.d(TAG, appCount + " registered Ble Apps");
@@ -598,27 +635,33 @@
         mBleApps.clear();
     }
 
-    /** @hide*/
+    /** @hide */
     public boolean isBleAppPresent() {
         if (DBG) Slog.d(TAG, "isBleAppPresent() count: " + mBleApps.size());
         return mBleApps.size() > 0;
     }
 
     /**
-     * Action taken when GattService is turned off
+     * Action taken when GattService is turned on
      */
     private void onBluetoothGattServiceUp() {
         if (DBG) Slog.d(TAG,"BluetoothGatt Service is Up");
         try {
             mBluetoothLock.readLock().lock();
-            if (isBleAppPresent() == false && mBluetooth != null
-                  && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) {
+            if (mBluetooth == null) {
+                if (DBG) Slog.w(TAG, "onBluetoothServiceUp: mBluetooth is null!");
+                return;
+            }
+            int st = mBluetooth.getState();
+            if (st != BluetoothAdapter.STATE_BLE_ON) {
+                if (DBG) Slog.v(TAG, "onBluetoothServiceUp: state isn't BLE_ON: " +
+                        BluetoothAdapter.nameForState(st));
+                return;
+            }
+            if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) {
+                // This triggers transition to STATE_ON
                 mBluetooth.onLeServiceUp();
-
-                // waive WRITE_SECURE_SETTINGS permission check
-                long callingIdentity = Binder.clearCallingIdentity();
                 persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
-                Binder.restoreCallingIdentity(callingIdentity);
             }
         } catch (RemoteException e) {
             Slog.e(TAG,"Unable to call onServiceUp", e);
@@ -658,7 +701,7 @@
         }
     }
 
-    public boolean enableNoAutoConnect()
+    public boolean enableNoAutoConnect(String packageName)
     {
         if (isBluetoothDisallowed()) {
             if (DBG) {
@@ -683,7 +726,7 @@
         synchronized(mReceiver) {
             mQuietEnableExternal = true;
             mEnableExternal = true;
-            sendEnableMsg(true);
+            sendEnableMsg(true, packageName);
         }
         return true;
     }
@@ -708,15 +751,14 @@
             mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                     "Need BLUETOOTH ADMIN permission");
 
-            if (!isEnabled() && mPermissionReviewRequired
-                    && startConsentUiIfNeeded(packageName, callingUid,
-                            BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
+            if (!isEnabled() && mPermissionReviewRequired) {
+                startConsentUi(packageName, callingUid, BluetoothAdapter.ACTION_REQUEST_ENABLE);
                 return false;
             }
         }
 
         if (DBG) {
-            Slog.d(TAG,"enable():  mBluetooth =" + mBluetooth +
+            Slog.d(TAG,"enable(" + packageName + "):  mBluetooth =" + mBluetooth +
                     " mBinding = " + mBinding + " mState = " +
                     BluetoothAdapter.nameForState(mState));
         }
@@ -725,7 +767,7 @@
             mQuietEnableExternal = false;
             mEnableExternal = true;
             // waive WRITE_SECURE_SETTINGS permission check
-            sendEnableMsg(false);
+            sendEnableMsg(false, packageName);
         }
         if (DBG) Slog.d(TAG, "enable returning");
         return true;
@@ -744,9 +786,8 @@
             mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                     "Need BLUETOOTH ADMIN permission");
 
-            if (isEnabled() && mPermissionReviewRequired
-                    && startConsentUiIfNeeded(packageName, callingUid,
-                            BluetoothAdapter.ACTION_REQUEST_DISABLE)) {
+            if (isEnabled() && mPermissionReviewRequired) {
+                startConsentUi(packageName, callingUid, BluetoothAdapter.ACTION_REQUEST_DISABLE);
                 return false;
             }
         }
@@ -758,19 +799,16 @@
 
         synchronized(mReceiver) {
             if (persist) {
-                // waive WRITE_SECURE_SETTINGS permission check
-                long callingIdentity = Binder.clearCallingIdentity();
                 persistBluetoothSetting(BLUETOOTH_OFF);
-                Binder.restoreCallingIdentity(callingIdentity);
             }
             mEnableExternal = false;
-            sendDisableMsg();
+            sendDisableMsg(packageName);
         }
         return true;
     }
 
-    private boolean startConsentUiIfNeeded(String packageName,
-            int callingUid, String intentAction) throws RemoteException {
+    private void startConsentUi(String packageName, int callingUid, String intentAction)
+            throws RemoteException {
         try {
             // Validate the package only if we are going to use it
             ApplicationInfo applicationInfo = mContext.getPackageManager()
@@ -782,16 +820,12 @@
                         + " not in uid " + callingUid);
             }
 
-            // Legacy apps in permission review mode trigger a user prompt
-            if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-                Intent intent = new Intent(intentAction);
-                mContext.startActivity(intent);
-                return true;
-            }
+            // Permission review mode, trigger a user prompt
+            Intent intent = new Intent(intentAction);
+            mContext.startActivity(intent);
         } catch (PackageManager.NameNotFoundException e) {
             throw new RemoteException(e.getMessage());
         }
-        return false;
     }
 
     public void unbindAndFinish() {
@@ -909,7 +943,7 @@
         }
         if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
             if (DBG) Slog.d(TAG, "Auto-enabling Bluetooth.");
-            sendEnableMsg(mQuietEnableExternal);
+            sendEnableMsg(mQuietEnableExternal, "system boot");
         } else if (!isNameAndAddressSet()) {
             if (DBG) Slog.d(TAG, "Getting adapter name and address");
             Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
@@ -1276,8 +1310,9 @@
                         if (mBluetooth != null) {
                             int state = mBluetooth.getState();
                             if (state == BluetoothAdapter.STATE_BLE_ON) {
-                                Slog.w(TAG, "BT is in BLE_ON State");
+                                Slog.w(TAG, "BT Enable in BLE_ON State, going to ON");
                                 mBluetooth.onLeServiceUp();
+                                persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
                                 break;
                             }
                         }
@@ -1876,13 +1911,24 @@
         return false;
     }
 
-    private void sendDisableMsg() {
+    private void sendDisableMsg(String packageName) {
         mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE));
+        addActiveLog(packageName, false);
     }
 
-    private void sendEnableMsg(boolean quietMode) {
+    private void sendEnableMsg(boolean quietMode, String packageName) {
         mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,
                              quietMode ? 1 : 0, 0));
+        addActiveLog(packageName, true);
+    }
+
+    private void addActiveLog(String packageName, boolean enable) {
+        synchronized (mActiveLogs) {
+            if (mActiveLogs.size() > 10) {
+                mActiveLogs.remove();
+            }
+            mActiveLogs.add(new ActiveLog(packageName, enable, System.currentTimeMillis()));
+        }
     }
 
     private void recoverBluetoothServiceFromError(boolean clearBle) {
@@ -1953,19 +1999,50 @@
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
         String errorMsg = null;
+
+        boolean protoOut = (args.length > 0) && args[0].startsWith("--proto");
+
+        if (!protoOut) {
+            writer.println("Bluetooth Status");
+            writer.println("  enabled: " + isEnabled());
+            writer.println("  state: " + BluetoothAdapter.nameForState(mState));
+            writer.println("  address: " + mAddress);
+            writer.println("  name: " + mName);
+            if (mEnable) {
+                long onDuration = System.currentTimeMillis() - mActiveLogs.getLast().getTime();
+                String onDurationString = String.format("%02d:%02d:%02d.%03d",
+                                          (int)(onDuration / (1000 * 60 * 60)),
+                                          (int)((onDuration / (1000 * 60)) % 60),
+                                          (int)((onDuration / 1000) % 60),
+                                          (int)(onDuration % 1000));
+                writer.println("  time since enabled: " + onDurationString + "\n");
+            }
+
+            writer.println("Enable log:");
+            for (ActiveLog log : mActiveLogs) {
+                writer.println(log);
+            }
+
+            writer.println("\n" + mBleApps.size() + " BLE Apps registered:");
+            for (ClientDeathRecipient app : mBleApps.values()) {
+                writer.println(app.getPackageName());
+            }
+
+            writer.flush();
+        }
+
         if (mBluetoothBinder == null) {
             errorMsg = "Bluetooth Service not connected";
         } else {
             try {
                 mBluetoothBinder.dump(fd, args);
             } catch (RemoteException re) {
-                errorMsg = "RemoteException while calling Bluetooth Service";
+                errorMsg = "RemoteException while dumping Bluetooth Service";
             }
         }
         if (errorMsg != null) {
             // Silently return if we are extracting metrics in Protobuf format
-            if ((args.length > 0) && args[0].startsWith("--proto"))
-                return;
+            if (protoOut) return;
             writer.println(errorMsg);
         }
     }
diff --git a/services/core/java/com/android/server/ContextHubSystemService.java b/services/core/java/com/android/server/ContextHubSystemService.java
index 1b85632..06abca9 100644
--- a/services/core/java/com/android/server/ContextHubSystemService.java
+++ b/services/core/java/com/android/server/ContextHubSystemService.java
@@ -37,7 +37,7 @@
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
-            publishBinderService(ContextHubService.CONTEXTHUB_SERVICE, mContextHubService);
+            publishBinderService(Context.CONTEXTHUB_SERVICE, mContextHubService);
         }
     }
 }
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 74ff41c..6296375 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -154,7 +154,8 @@
 3110 unknown_sources_enabled (value|1)
 # Package Manager critical info
 3120 pm_critical_info (msg|3)
-
+# Disk usage stats for verifying quota correctness
+3121 pm_package_stats (manual_time|2|3),(quota_time|2|3),(manual_data|2|2),(quota_data|2|2),(manual_cache|2|2),(quota_cache|2|2)
 
 # ---------------------------
 # WindowManagerService.java
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index a1c3564..983d039 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -16,7 +16,11 @@
 
 package com.android.server;
 
+import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
+import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
+
 import android.Manifest.permission;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -25,27 +29,30 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.INetworkRecommendationProvider;
 import android.net.INetworkScoreCache;
 import android.net.INetworkScoreService;
 import android.net.NetworkKey;
-import android.net.NetworkScoreManager;
 import android.net.NetworkScorerAppManager;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
+import android.net.NetworkScoreManager;
 import android.net.RecommendationRequest;
 import android.net.RecommendationResult;
 import android.net.ScoredNetwork;
-import android.net.wifi.WifiConfiguration;
+import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
+import android.provider.Settings.Global;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.TimedRemoteCaller;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
@@ -59,6 +66,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 
 /**
@@ -67,17 +75,20 @@
  */
 public class NetworkScoreService extends INetworkScoreService.Stub {
     private static final String TAG = "NetworkScoreService";
-    private static final boolean DBG = false;
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final Context mContext;
     private final NetworkScorerAppManager mNetworkScorerAppManager;
+    private final RequestRecommendationCaller mRequestRecommendationCaller;
     @GuardedBy("mScoreCaches")
     private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
     /** Lock used to update mPackageMonitor when scorer package changes occur. */
     private final Object mPackageMonitorLock = new Object[0];
+    private final Object mServiceConnectionLock = new Object[0];
 
     @GuardedBy("mPackageMonitorLock")
     private NetworkScorerPackageMonitor mPackageMonitor;
+    @GuardedBy("mServiceConnectionLock")
     private ScoringServiceConnection mServiceConnection;
 
     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@@ -99,10 +110,10 @@
      * manages the service connection.
      */
     private class NetworkScorerPackageMonitor extends PackageMonitor {
-        final String mRegisteredPackage;
+        final List<String> mPackagesToWatch;
 
-        private NetworkScorerPackageMonitor(String mRegisteredPackage) {
-            this.mRegisteredPackage = mRegisteredPackage;
+        private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
+            mPackagesToWatch = packagesToWatch;
         }
 
         @Override
@@ -136,29 +147,61 @@
         }
 
         private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
-            if (mRegisteredPackage.equals(scorerPackageName)) {
-                if (DBG) {
-                    Log.d(TAG, "Evaluating binding for: " + scorerPackageName
-                            + ", forceUnbind=" + forceUnbind);
-                }
-                final NetworkScorerAppData activeScorer =
-                        mNetworkScorerAppManager.getActiveScorer();
-                if (activeScorer == null) {
-                    // Package change has invalidated a scorer, this will also unbind any service
-                    // connection.
-                    Log.i(TAG, "Package " + mRegisteredPackage +
-                            " is no longer valid, disabling scoring.");
-                    setScorerInternal(null);
-                } else if (activeScorer.mScoringServiceClassName == null) {
-                    // The scoring service is not available, make sure it's unbound.
-                    unbindFromScoringServiceIfNeeded();
-                } else { // The scoring service changed in some way.
-                    if (forceUnbind) {
-                        unbindFromScoringServiceIfNeeded();
-                    }
-                    bindToScoringServiceIfNeeded(activeScorer);
-                }
+            if (!mPackagesToWatch.contains(scorerPackageName)) {
+                // Early exit when we don't care about the package that has changed.
+                return;
             }
+
+            if (DBG) {
+                Log.d(TAG, "Evaluating binding for: " + scorerPackageName
+                        + ", forceUnbind=" + forceUnbind);
+            }
+            final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+            if (activeScorer == null) {
+                // Package change has invalidated a scorer, this will also unbind any service
+                // connection.
+                if (DBG) Log.d(TAG, "No active scorers available.");
+                unbindFromScoringServiceIfNeeded();
+            } else if (activeScorer.packageName.equals(scorerPackageName)) {
+                // The active scoring service changed in some way.
+                if (DBG) {
+                    Log.d(TAG, "Possible change to the active scorer: "
+                            + activeScorer.packageName);
+                }
+                if (forceUnbind) {
+                    unbindFromScoringServiceIfNeeded();
+                }
+                bindToScoringServiceIfNeeded(activeScorer);
+            } else {
+                // One of the scoring apps on the device has changed and we may no longer be
+                // bound to the correct scoring app. The logic in bindToScoringServiceIfNeeded()
+                // will sort that out to leave us bound to the most recent active scorer.
+                if (DBG) {
+                    Log.d(TAG, "Binding to " + activeScorer.packageName + " if needed.");
+                }
+                bindToScoringServiceIfNeeded(activeScorer);
+            }
+        }
+    }
+
+    /**
+     * Reevaluates the service binding when the Settings toggle is changed.
+     */
+    private class SettingsObserver extends ContentObserver {
+
+        public SettingsObserver() {
+            super(null /*handler*/);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            onChange(selfChange, null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
+            bindToScoringServiceIfNeeded();
         }
     }
 
@@ -176,24 +219,16 @@
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
                 null /* scheduler */);
+        // TODO(jjoslin): 12/15/16 - Make timeout configurable.
+        mRequestRecommendationCaller =
+            new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
     }
 
     /** Called when the system is ready to run third-party code but before it actually does so. */
     void systemReady() {
         if (DBG) Log.d(TAG, "systemReady");
-        ContentResolver cr = mContext.getContentResolver();
-        if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
-            // On first run, we try to initialize the scorer to the one configured at build time.
-            // This will be a no-op if the scorer isn't actually valid.
-            String defaultPackage = mContext.getResources().getString(
-                    R.string.config_defaultNetworkScorerPackageName);
-            if (!TextUtils.isEmpty(defaultPackage)) {
-                mNetworkScorerAppManager.setActiveScorer(defaultPackage);
-            }
-            Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
-        }
-
         registerPackageMonitorIfNeeded();
+        registerRecommendationSettingObserverIfNeeded();
     }
 
     /** Called when the system is ready for us to start third-party code. */
@@ -207,29 +242,40 @@
         bindToScoringServiceIfNeeded();
     }
 
+    private void registerRecommendationSettingObserverIfNeeded() {
+        final List<String> providerPackages =
+            mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
+        if (!providerPackages.isEmpty()) {
+            final ContentResolver resolver = mContext.getContentResolver();
+            final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
+            resolver.registerContentObserver(uri, false, new SettingsObserver());
+        }
+    }
+
     private void registerPackageMonitorIfNeeded() {
         if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
-        NetworkScorerAppData scorer = mNetworkScorerAppManager.getActiveScorer();
+        final List<String> providerPackages =
+            mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
         synchronized (mPackageMonitorLock) {
             // Unregister the current monitor if needed.
             if (mPackageMonitor != null) {
                 if (DBG) {
                     Log.d(TAG, "Unregistering package monitor for "
-                            + mPackageMonitor.mRegisteredPackage);
+                            + mPackageMonitor.mPackagesToWatch);
                 }
                 mPackageMonitor.unregister();
                 mPackageMonitor = null;
             }
 
-            // Create and register the monitor if a scorer is active.
-            if (scorer != null) {
-                mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName);
+            // Create and register the monitor if there are packages that could be providers.
+            if (!providerPackages.isEmpty()) {
+                mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
                 // TODO: Need to update when we support per-user scorers. http://b/23422763
                 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
                         false /* externalStorage */);
                 if (DBG) {
                     Log.d(TAG, "Registered package monitor for "
-                            + mPackageMonitor.mRegisteredPackage);
+                            + mPackageMonitor.mPackagesToWatch);
                 }
             }
         }
@@ -243,22 +289,24 @@
 
     private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
         if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
-        if (scorerData != null && scorerData.mScoringServiceClassName != null) {
-            ComponentName componentName =
-                    new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName);
-            // If we're connected to a different component then drop it.
-            if (mServiceConnection != null
-                    && !mServiceConnection.mComponentName.equals(componentName)) {
-                unbindFromScoringServiceIfNeeded();
-            }
+        if (scorerData != null && scorerData.recommendationServiceClassName != null) {
+            ComponentName componentName = new ComponentName(scorerData.packageName,
+                    scorerData.recommendationServiceClassName);
+            synchronized (mServiceConnectionLock) {
+                // If we're connected to a different component then drop it.
+                if (mServiceConnection != null
+                        && !mServiceConnection.mComponentName.equals(componentName)) {
+                    unbindFromScoringServiceIfNeeded();
+                }
 
-            // If we're not connected at all then create a new connection.
-            if (mServiceConnection == null) {
-                mServiceConnection = new ScoringServiceConnection(componentName);
-            }
+                // If we're not connected at all then create a new connection.
+                if (mServiceConnection == null) {
+                    mServiceConnection = new ScoringServiceConnection(componentName);
+                }
 
-            // Make sure the connection is connected (idempotent)
-            mServiceConnection.connect(mContext);
+                // Make sure the connection is connected (idempotent)
+                mServiceConnection.connect(mContext);
+            }
         } else { // otherwise make sure it isn't bound.
             unbindFromScoringServiceIfNeeded();
         }
@@ -266,10 +314,13 @@
 
     private void unbindFromScoringServiceIfNeeded() {
         if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
-        if (mServiceConnection != null) {
-            mServiceConnection.disconnect(mContext);
+        synchronized (mServiceConnectionLock) {
+            if (mServiceConnection != null) {
+                mServiceConnection.disconnect(mContext);
+            }
+            mServiceConnection = null;
         }
-        mServiceConnection = null;
+        clearInternal();
     }
 
     @Override
@@ -279,47 +330,54 @@
                     " is not the active scorer.");
         }
 
-        // Separate networks by type.
-        Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
-        for (ScoredNetwork network : networks) {
-            List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
-            if (networkList == null) {
-                networkList = new ArrayList<>();
-                networksByType.put(network.networkKey.type, networkList);
-            }
-            networkList.add(network);
-        }
-
-        // Pass the scores of each type down to the appropriate network scorer.
-        for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
-            final RemoteCallbackList<INetworkScoreCache> callbackList;
-            final boolean isEmpty;
-            synchronized (mScoreCaches) {
-                callbackList = mScoreCaches.get(entry.getKey());
-                isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0;
-            }
-            if (isEmpty) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // Separate networks by type.
+            Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
+            for (ScoredNetwork network : networks) {
+                List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
+                if (networkList == null) {
+                    networkList = new ArrayList<>();
+                    networksByType.put(network.networkKey.type, networkList);
                 }
-                continue;
+                networkList.add(network);
             }
 
-            sendCallback(new Consumer<INetworkScoreCache>() {
-                @Override
-                public void accept(INetworkScoreCache networkScoreCache) {
-                    try {
-                        networkScoreCache.updateScores(entry.getValue());
-                    } catch (RemoteException e) {
-                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                            Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+            // Pass the scores of each type down to the appropriate network scorer.
+            for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
+                final RemoteCallbackList<INetworkScoreCache> callbackList;
+                final boolean isEmpty;
+                synchronized (mScoreCaches) {
+                    callbackList = mScoreCaches.get(entry.getKey());
+                    isEmpty = callbackList == null
+                            || callbackList.getRegisteredCallbackCount() == 0;
+                }
+                if (isEmpty) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "No scorer registered for type " + entry.getKey()
+                                + ", discarding");
+                    }
+                    continue;
+                }
+
+                sendCallback(new Consumer<INetworkScoreCache>() {
+                    @Override
+                    public void accept(INetworkScoreCache networkScoreCache) {
+                        try {
+                            networkScoreCache.updateScores(entry.getValue());
+                        } catch (RemoteException e) {
+                            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                                Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+                            }
                         }
                     }
-                }
-            }, Collections.singleton(callbackList));
-        }
+                }, Collections.singleton(callbackList));
+            }
 
-        return true;
+            return true;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     @Override
@@ -329,8 +387,13 @@
         if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
                 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
                         PackageManager.PERMISSION_GRANTED) {
-            clearInternal();
-            return true;
+            final long token = Binder.clearCallingIdentity();
+            try {
+                clearInternal();
+                return true;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         } else {
             throw new SecurityException(
                     "Caller is neither the active scorer nor the scorer manager.");
@@ -349,7 +412,8 @@
         // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
         mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
 
-        return setScorerInternal(packageName);
+        // Scorers (recommendation providers) are selected and no longer set.
+        return false;
     }
 
     @Override
@@ -359,56 +423,13 @@
         if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
                 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
                         PackageManager.PERMISSION_GRANTED) {
-            // The return value is discarded here because at this point, the call should always
-            // succeed. The only reason for failure is if the new package is not a valid scorer, but
-            // we're disabling scoring altogether here.
-            setScorerInternal(null /* packageName */);
+            // no-op for now but we could write to the setting if needed.
         } else {
             throw new SecurityException(
                     "Caller is neither the active scorer nor the scorer manager.");
         }
     }
 
-    /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
-    private boolean setScorerInternal(String packageName) {
-        if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")");
-        long token = Binder.clearCallingIdentity();
-        try {
-            unbindFromScoringServiceIfNeeded();
-            // Preemptively clear scores even though the set operation could fail. We do this for
-            // safety as scores should never be compared across apps; in practice, Settings should
-            // only be allowing valid apps to be set as scorers, so failure here should be rare.
-            clearInternal();
-            // Get the scorer that is about to be replaced, if any, so we can notify it directly.
-            NetworkScorerAppData prevScorer = mNetworkScorerAppManager.getActiveScorer();
-            boolean result = mNetworkScorerAppManager.setActiveScorer(packageName);
-            // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed
-            // then we'll attempt to restore the previous binding (if any), otherwise an attempt
-            // will be made to bind to the new scorer.
-            bindToScoringServiceIfNeeded();
-            if (result) { // new scorer successfully set
-                registerPackageMonitorIfNeeded();
-
-                Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
-                if (prevScorer != null) { // Directly notify the old scorer.
-                    intent.setPackage(prevScorer.mPackageName);
-                    // TODO: Need to update when we support per-user scorers. http://b/23422763
-                    mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-                }
-
-                if (packageName != null) { // Then notify the new scorer
-                    intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
-                    intent.setPackage(packageName);
-                    // TODO: Need to update when we support per-user scorers. http://b/23422763
-                    mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-                }
-            }
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     /** Clear scores. Callers are responsible for checking permissions as appropriate. */
     private void clearInternal() {
         sendCallback(new Consumer<INetworkScoreCache>() {
@@ -430,52 +451,100 @@
                                           INetworkScoreCache scoreCache,
                                           int filterType) {
         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
-        synchronized (mScoreCaches) {
-            RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
-            if (callbackList == null) {
-                callbackList = new RemoteCallbackList<>();
-                mScoreCaches.put(networkType, callbackList);
-            }
-            if (!callbackList.register(scoreCache, filterType)) {
-                if (callbackList.getRegisteredCallbackCount() == 0) {
-                    mScoreCaches.remove(networkType);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mScoreCaches) {
+                RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+                if (callbackList == null) {
+                    callbackList = new RemoteCallbackList<>();
+                    mScoreCaches.put(networkType, callbackList);
                 }
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
+                if (!callbackList.register(scoreCache, filterType)) {
+                    if (callbackList.getRegisteredCallbackCount() == 0) {
+                        mScoreCaches.remove(networkType);
+                    }
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
+                    }
                 }
             }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
     @Override
     public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
-        synchronized (mScoreCaches) {
-            RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
-            if (callbackList == null || !callbackList.unregister(scoreCache)) {
-                if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, "Unable to unregister NetworkScoreCache for type " + networkType);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mScoreCaches) {
+                RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+                if (callbackList == null || !callbackList.unregister(scoreCache)) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Unable to unregister NetworkScoreCache for type "
+                                + networkType);
+                    }
+                } else if (callbackList.getRegisteredCallbackCount() == 0) {
+                    mScoreCaches.remove(networkType);
                 }
-            } else if (callbackList.getRegisteredCallbackCount() == 0) {
-                mScoreCaches.remove(networkType);
             }
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
     }
 
     @Override
     public RecommendationResult requestRecommendation(RecommendationRequest request) {
-        // TODO(jjoslin): 11/25/16 - Update with real impl.
-        WifiConfiguration selectedConfig = null;
-        if (request != null) {
-            selectedConfig = request.getCurrentSelectedConfig();
+        mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
+        throwIfCalledOnMainThread();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final INetworkRecommendationProvider provider = getRecommendationProvider();
+            if (provider != null) {
+                try {
+                    return mRequestRecommendationCaller.getRecommendationResult(provider, request);
+                } catch (RemoteException | TimeoutException e) {
+                    Log.w(TAG, "Failed to request a recommendation.", e);
+                    // TODO(jjoslin): 12/15/16 - Keep track of failures.
+                }
+            }
+
+            if (DBG) {
+                Log.d(TAG, "Returning the default network recommendation.");
+            }
+
+            if (request != null && request.getCurrentSelectedConfig() != null) {
+                return RecommendationResult.createConnectRecommendation(
+                        request.getCurrentSelectedConfig());
+            }
+            return RecommendationResult.createDoNotConnectRecommendation();
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
-        return new RecommendationResult(selectedConfig);
     }
 
     @Override
     public boolean requestScores(NetworkKey[] networks) {
-        // TODO(jjoslin): 12/13/16 - Implement
-        return false;
+        mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final INetworkRecommendationProvider provider = getRecommendationProvider();
+            if (provider != null) {
+                try {
+                    provider.requestScores(networks);
+                    // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to
+                    // prevent repeated requests for the same scores.
+                    return true;
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to request scores.", e);
+                    // TODO(jjoslin): 12/15/16 - Keep track of failures.
+                }
+            }
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     @Override
@@ -486,7 +555,7 @@
             writer.println("Scoring is disabled.");
             return;
         }
-        writer.println("Current scorer: " + currentScorer.mPackageName);
+        writer.println("Current scorer: " + currentScorer.packageName);
 
         sendCallback(new Consumer<INetworkScoreCache>() {
             @Override
@@ -499,10 +568,12 @@
             }
         }, getScoreCacheLists());
 
-        if (mServiceConnection != null) {
-            mServiceConnection.dump(fd, writer, args);
-        } else {
-            writer.println("ScoringServiceConnection: null");
+        synchronized (mServiceConnectionLock) {
+            if (mServiceConnection != null) {
+                mServiceConnection.dump(fd, writer, args);
+            } else {
+                writer.println("ScoringServiceConnection: null");
+            }
         }
         writer.flush();
     }
@@ -535,10 +606,27 @@
         }
     }
 
+    private void throwIfCalledOnMainThread() {
+        if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
+            throw new RuntimeException("Cannot invoke on the main thread");
+        }
+    }
+
+    @Nullable
+    private INetworkRecommendationProvider getRecommendationProvider() {
+        synchronized (mServiceConnectionLock) {
+            if (mServiceConnection != null) {
+                return mServiceConnection.getRecommendationProvider();
+            }
+        }
+        return null;
+    }
+
     private static class ScoringServiceConnection implements ServiceConnection {
         private final ComponentName mComponentName;
-        private boolean mBound = false;
-        private boolean mConnected = false;
+        private volatile boolean mBound = false;
+        private volatile boolean mConnected = false;
+        private volatile INetworkRecommendationProvider mRecommendationProvider;
 
         ScoringServiceConnection(ComponentName componentName) {
             mComponentName = componentName;
@@ -546,7 +634,7 @@
 
         void connect(Context context) {
             if (!mBound) {
-                Intent service = new Intent();
+                Intent service = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
                 service.setComponent(mComponentName);
                 mBound = context.bindServiceAsUser(service, this,
                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
@@ -569,12 +657,19 @@
             } catch (RuntimeException e) {
                 Log.e(TAG, "Unbind failed.", e);
             }
+
+            mRecommendationProvider = null;
+        }
+
+        INetworkRecommendationProvider getRecommendationProvider() {
+            return mRecommendationProvider;
         }
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
             mConnected = true;
+            mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
         }
 
         @Override
@@ -583,6 +678,7 @@
                 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
             }
             mConnected = false;
+            mRecommendationProvider = null;
         }
 
         public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -590,4 +686,43 @@
                     + ", connected: " + mConnected);
         }
     }
+
+    /**
+     * Executes the async requestRecommendation() call with a timeout.
+     */
+    private static final class RequestRecommendationCaller
+            extends TimedRemoteCaller<RecommendationResult> {
+        private final IRemoteCallback mCallback;
+
+        RequestRecommendationCaller(long callTimeoutMillis) {
+            super(callTimeoutMillis);
+            mCallback = new IRemoteCallback.Stub() {
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    final RecommendationResult result =
+                            data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
+                    final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
+                    onRemoteMethodResult(result, sequence);
+                }
+            };
+        }
+
+        /**
+         * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
+         * instance.
+         *
+         * @param target the {@link INetworkRecommendationProvider} to request a recommendation
+         *               from
+         * @param request the {@link RecommendationRequest} from the calling client
+         * @return a {@link RecommendationResult} from the provider
+         * @throws RemoteException if the call failed
+         * @throws TimeoutException if the call took longer than the set timeout
+         */
+        RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
+                RecommendationRequest request) throws RemoteException, TimeoutException {
+            final int sequence = onBeforeRemoteCall();
+            target.requestRecommendation(request, mCallback, sequence);
+            return getResultTimed(sequence);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fa54a61..bc03901 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3729,13 +3729,14 @@
                  * resources like shared libraries and access user-wide resources
                  */
                 if (ArrayUtils.isEmpty(permGids)) {
-                    gids = new int[2];
+                    gids = new int[3];
                 } else {
-                    gids = new int[permGids.length + 2];
-                    System.arraycopy(permGids, 0, gids, 2, permGids.length);
+                    gids = new int[permGids.length + 3];
+                    System.arraycopy(permGids, 0, gids, 3, permGids.length);
                 }
                 gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
-                gids[1] = UserHandle.getUserGid(UserHandle.getUserId(uid));
+                gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
+                gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
             }
             checkTime(startTime, "startProcess: building args");
             if (mFactoryTest != FactoryTest.FACTORY_TEST_OFF) {
@@ -3783,6 +3784,15 @@
                 mNativeDebuggingApp = null;
             }
 
+            String invokeWith = null;
+            if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+                // Debuggable apps may include a wrapper script with their library directory.
+                String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
+                if (new File(wrapperFileName).exists()) {
+                    invokeWith = "/system/bin/logwrapper " + wrapperFileName;
+                }
+            }
+
             String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
             if (requiredAbi == null) {
                 requiredAbi = Build.SUPPORTED_ABIS[0];
@@ -3809,12 +3819,12 @@
                 startResult = Process.startWebView(entryPoint,
                         app.processName, uid, uid, gids, debugFlags, mountExternal,
                         app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
-                        app.info.dataDir, entryPointArgs);
+                        app.info.dataDir, null, entryPointArgs);
             } else {
                 startResult = Process.start(entryPoint,
                         app.processName, uid, uid, gids, debugFlags, mountExternal,
                         app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
-                        app.info.dataDir, entryPointArgs);
+                        app.info.dataDir, invokeWith, entryPointArgs);
             }
             checkTime(startTime, "startProcess: returned from zygote!");
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7fe6c3e..5d619c1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -225,6 +225,7 @@
     private static final int MSG_SET_WIRED_DEVICE_CONNECTION_STATE = 100;
     private static final int MSG_SET_A2DP_SRC_CONNECTION_STATE = 101;
     private static final int MSG_SET_A2DP_SINK_CONNECTION_STATE = 102;
+    private static final int MSG_A2DP_DEVICE_CONFIG_CHANGE = 103;
     // end of messages handled under wakelock
 
     private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000;
@@ -3146,7 +3147,7 @@
                             queueMsgUnderWakeLock(mAudioHandler,
                                     MSG_SET_A2DP_SINK_CONNECTION_STATE,
                                     state,
-                                    0,
+                                    0 /* arg2 unused */,
                                     btDevice,
                                     delay);
                         }
@@ -3163,7 +3164,7 @@
                         queueMsgUnderWakeLock(mAudioHandler,
                                 MSG_SET_A2DP_SRC_CONNECTION_STATE,
                                 state,
-                                0,
+                                0 /* arg2 unused */,
                                 btDevice,
                                 0 /* delay */);
                     }
@@ -3809,8 +3810,8 @@
             int delay = checkSendBecomingNoisyIntent(type, state);
             queueMsgUnderWakeLock(mAudioHandler,
                     MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
-                    0,
-                    0,
+                    0 /* arg1 unused */,
+                    0 /* arg2 unused */,
                     new WiredDeviceConnectionState(type, state, address, name, caller),
                     delay);
         }
@@ -3833,13 +3834,25 @@
                     (profile == BluetoothProfile.A2DP ?
                         MSG_SET_A2DP_SINK_CONNECTION_STATE : MSG_SET_A2DP_SRC_CONNECTION_STATE),
                     state,
-                    0,
+                    0 /* arg2 unused */,
                     device,
                     delay);
         }
         return delay;
     }
 
+    public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device)
+    {
+        synchronized (mConnectedDevices) {
+            queueMsgUnderWakeLock(mAudioHandler,
+                    MSG_A2DP_DEVICE_CONFIG_CHANGE,
+                    0 /* arg1 unused */,
+                    0 /* arg1 unused */,
+                    device,
+                    0 /* delay */);
+        }
+    }
+
     ///////////////////////////////////////////////////////////////////////////
     // Inner classes
     ///////////////////////////////////////////////////////////////////////////
@@ -4644,6 +4657,11 @@
                     mAudioEventWakeLock.release();
                     break;
 
+                case MSG_A2DP_DEVICE_CONFIG_CHANGE:
+                    onBluetoothA2dpDeviceConfigChange((BluetoothDevice)msg.obj);
+                    mAudioEventWakeLock.release();
+                    break;
+
                 case MSG_REPORT_NEW_ROUTES: {
                     int N = mRoutesObservers.beginBroadcast();
                     if (N > 0) {
@@ -4866,7 +4884,7 @@
     private void onSetA2dpSinkConnectionState(BluetoothDevice btDevice, int state)
     {
         if (DEBUG_VOL) {
-            Log.d(TAG, "onSetA2dpSinkConnectionState btDevice="+btDevice+"state="+state);
+            Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice+"state=" + state);
         }
         if (btDevice == null) {
             return;
@@ -4877,9 +4895,9 @@
         }
 
         synchronized (mConnectedDevices) {
-            String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                                           btDevice.getAddress());
-            DeviceListSpec deviceSpec = mConnectedDevices.get(key);
+            final String key = makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                                                 btDevice.getAddress());
+            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
             boolean isConnected = deviceSpec != null;
 
             if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
@@ -4930,7 +4948,7 @@
     private void onSetA2dpSourceConnectionState(BluetoothDevice btDevice, int state)
     {
         if (DEBUG_VOL) {
-            Log.d(TAG, "onSetA2dpSourceConnectionState btDevice="+btDevice+" state="+state);
+            Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state=" + state);
         }
         if (btDevice == null) {
             return;
@@ -4941,8 +4959,8 @@
         }
 
         synchronized (mConnectedDevices) {
-            String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
-            DeviceListSpec deviceSpec = mConnectedDevices.get(key);
+            final String key = makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
+            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
             boolean isConnected = deviceSpec != null;
 
             if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
@@ -4953,6 +4971,31 @@
         }
     }
 
+    private void onBluetoothA2dpDeviceConfigChange(BluetoothDevice btDevice)
+    {
+        if (DEBUG_VOL) {
+            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+        }
+        if (btDevice == null) {
+            return;
+        }
+        String address = btDevice.getAddress();
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            address = "";
+        }
+
+        int device = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
+        synchronized (mConnectedDevices) {
+            final String key = makeDeviceListKey(device, address);
+            final DeviceListSpec deviceSpec = mConnectedDevices.get(key);
+            if (deviceSpec != null) {
+                // Device is connected
+                AudioSystem.handleDeviceConfigChange(device, address,
+                        btDevice.getName());
+            }
+        }
+    }
+
     public void avrcpSupportsAbsoluteVolume(String address, boolean support) {
         // address is not used for now, but may be used when multiple a2dp devices are supported
         synchronized (mA2dpAvrcpLock) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 3193974..605fa5d 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -20,10 +20,15 @@
 import android.content.Context;
 import android.content.pm.PackageStats;
 import android.os.Build;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
 import android.os.IInstalld;
+import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.text.format.DateUtils;
 import android.util.Slog;
 
+import com.android.internal.os.BackgroundThread;
 import com.android.server.SystemService;
 
 import dalvik.system.VMRuntime;
@@ -49,10 +54,10 @@
     // NOTE: keep in sync with installd
     public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
     public static final int FLAG_CLEAR_CODE_CACHE_ONLY = 1 << 9;
+    public static final int FLAG_USE_QUOTA = 1 << 12;
 
     private final boolean mIsolated;
 
-    // TODO: reconnect if installd restarts
     private volatile IInstalld mInstalld;
     private volatile Object mWarnIfHeld;
 
@@ -83,7 +88,33 @@
         if (mIsolated) {
             mInstalld = null;
         } else {
-            mInstalld = IInstalld.Stub.asInterface(ServiceManager.getService("installd"));
+            connect();
+        }
+    }
+
+    private void connect() {
+        IBinder binder = ServiceManager.getService("installd");
+        if (binder != null) {
+            try {
+                binder.linkToDeath(new DeathRecipient() {
+                    @Override
+                    public void binderDied() {
+                        Slog.w(TAG, "installd died; reconnecting");
+                        connect();
+                    }
+                }, 0);
+            } catch (RemoteException e) {
+                binder = null;
+            }
+        }
+
+        if (binder != null) {
+            mInstalld = IInstalld.Stub.asInterface(binder);
+        } else {
+            Slog.w(TAG, "installd not found; trying again");
+            BackgroundThread.getHandler().postDelayed(() -> {
+                connect();
+            }, DateUtils.SECOND_IN_MILLIS);
         }
     }
 
@@ -105,11 +136,11 @@
         }
     }
 
-    public void createAppData(String uuid, String packageName, int userId, int flags, int appId,
+    public long createAppData(String uuid, String packageName, int userId, int flags, int appId,
             String seInfo, int targetSdkVersion) throws InstallerException {
-        if (!checkBeforeRemote()) return;
+        if (!checkBeforeRemote()) return -1;
         try {
-            mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
+            return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
                     targetSdkVersion);
         } catch (Exception e) {
             throw InstallerException.from(e);
@@ -168,12 +199,13 @@
         }
     }
 
-    public void getAppSize(String uuid, String packageName, int userId, int flags, long ceDataInode,
-            String codePath, PackageStats stats) throws InstallerException {
+    public void getAppSize(String uuid, String packageName, int userId, int flags, int appId,
+            long ceDataInode, String codePath, String externalUuid, PackageStats stats)
+            throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
-            final long[] res = mInstalld.getAppSize(uuid, packageName, userId, flags, ceDataInode,
-                    codePath);
+            final long[] res = mInstalld.getAppSize(uuid, packageName, userId, flags, appId,
+                    ceDataInode, codePath, externalUuid);
             stats.codeSize += res[0];
             stats.dataSize += res[1];
             stats.cacheSize += res[2];
@@ -182,16 +214,6 @@
         }
     }
 
-    public long getAppDataInode(String uuid, String packageName, int userId, int flags)
-            throws InstallerException {
-        if (!checkBeforeRemote()) return -1;
-        try {
-            return mInstalld.getAppDataInode(uuid, packageName, userId, flags);
-        } catch (Exception e) {
-            throw InstallerException.from(e);
-        }
-    }
-
     public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
             int dexoptNeeded, @Nullable String outputPath, int dexFlags,
             String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries)
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fb06f01..614230c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -370,6 +370,9 @@
     private static final boolean DISABLE_EPHEMERAL_APPS = false;
     private static final boolean HIDE_EPHEMERAL_APIS = true;
 
+    private static final boolean ENABLE_QUOTA =
+            SystemProperties.getBoolean("persist.fw.quota", false);
+
     private static final int RADIO_UID = Process.PHONE_UID;
     private static final int LOG_UID = Process.LOG_UID;
     private static final int NFC_UID = Process.NFC_UID;
@@ -7360,6 +7363,11 @@
         }
     }
 
+    @Override
+    public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) {
+      // TODO(calin): b/32871170
+    }
+
     // TODO: this is not used nor needed. Delete it.
     @Override
     public boolean performDexOptIfNeeded(String packageName) {
@@ -16864,6 +16872,11 @@
         mHandler.sendMessage(msg);
     }
 
+    private boolean equals(PackageStats a, PackageStats b) {
+        return (a.codeSize == b.codeSize) && (a.dataSize == b.dataSize)
+                && (a.cacheSize == b.cacheSize);
+    }
+
     private boolean getPackageSizeInfoLI(String packageName, int userId, PackageStats stats) {
         final PackageSetting ps;
         synchronized (mPackages) {
@@ -16873,20 +16886,51 @@
                 return false;
             }
         }
+
+        final long ceDataInode = ps.getCeDataInode(userId);
+        final PackageStats quotaStats = new PackageStats(stats.packageName, stats.userHandle);
+
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final String externalUuid = storage.getPrimaryStorageUuid();
         try {
-            mInstaller.getAppSize(ps.volumeUuid, packageName, userId,
-                    StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE,
-                    ps.getCeDataInode(userId), ps.codePathString, stats);
+            final long start = SystemClock.elapsedRealtimeNanos();
+            mInstaller.getAppSize(ps.volumeUuid, packageName, userId, 0,
+                    ps.appId, ceDataInode, ps.codePathString, externalUuid, stats);
+            final long stopManual = SystemClock.elapsedRealtimeNanos();
+            if (ENABLE_QUOTA) {
+                mInstaller.getAppSize(ps.volumeUuid, packageName, userId, Installer.FLAG_USE_QUOTA,
+                        ps.appId, ceDataInode, ps.codePathString, externalUuid, quotaStats);
+            }
+            final long stopQuota = SystemClock.elapsedRealtimeNanos();
+
+            // For now, ignore code size of packages on system partition
+            if (isSystemApp(ps) && !isUpdatedSystemApp(ps)) {
+                stats.codeSize = 0;
+                quotaStats.codeSize = 0;
+            }
+
+            if (ENABLE_QUOTA && Build.IS_ENG && !ps.isSharedUser()) {
+                if (!equals(stats, quotaStats)) {
+                    Log.w(TAG, "Found discrepancy between statistics:");
+                    Log.w(TAG, "Manual: " + stats);
+                    Log.w(TAG, "Quota:  " + quotaStats);
+                }
+                final long manualTime = stopManual - start;
+                final long quotaTime = stopQuota - stopManual;
+                EventLogTags.writePmPackageStats(manualTime, quotaTime,
+                        stats.dataSize, quotaStats.dataSize,
+                        stats.cacheSize, quotaStats.cacheSize);
+            }
+
+            // External clients expect these to be tracked separately
+            stats.dataSize -= stats.cacheSize;
+            quotaStats.dataSize -= quotaStats.cacheSize;
+
         } catch (InstallerException e) {
             Slog.w(TAG, String.valueOf(e));
             return false;
         }
 
-        // For now, ignore code size of packages on system partition
-        if (isSystemApp(ps) && !isUpdatedSystemApp(ps)) {
-            stats.codeSize = 0;
-        }
-
         return true;
     }
 
@@ -19969,8 +20013,9 @@
 
         Preconditions.checkNotNull(app.seinfo);
 
+        long ceDataInode = -1;
         try {
-            mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+            ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
                     appId, app.seinfo, app.targetSdkVersion);
         } catch (InstallerException e) {
             if (app.isSystemApp()) {
@@ -19978,7 +20023,7 @@
                         + ", but trying to recover: " + e);
                 destroyAppDataLeafLIF(pkg, userId, flags);
                 try {
-                    mInstaller.createAppData(volumeUuid, packageName, userId, flags,
+                    ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
                             appId, app.seinfo, app.targetSdkVersion);
                     logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
                 } catch (InstallerException e2) {
@@ -19989,21 +20034,13 @@
             }
         }
 
-        if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
-            try {
-                // CE storage is unlocked right now, so read out the inode and
-                // remember for use later when it's locked
-                // TODO: mark this structure as dirty so we persist it!
-                final long ceDataInode = mInstaller.getAppDataInode(volumeUuid, packageName, userId,
-                        StorageManager.FLAG_STORAGE_CE);
-                synchronized (mPackages) {
-                    final PackageSetting ps = mSettings.mPackages.get(packageName);
-                    if (ps != null) {
-                        ps.setCeDataInode(ceDataInode, userId);
-                    }
+        if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
+            // TODO: mark this structure as dirty so we persist it!
+            synchronized (mPackages) {
+                final PackageSetting ps = mSettings.mPackages.get(packageName);
+                if (ps != null) {
+                    ps.setCeDataInode(ceDataInode, userId);
                 }
-            } catch (InstallerException e) {
-                Slog.e(TAG, "Failed to find inode for " + packageName + ": " + e);
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index 2176eb1..0fe1539 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -59,8 +59,9 @@
     private static List<Policy> sPolicies = new ArrayList<>();
 
     /** Path to MAC permissions on system image */
-    private static final File MAC_PERMISSIONS = new File(Environment.getRootDirectory(),
-            "/etc/security/mac_permissions.xml");
+    private static final File[] MAC_PERMISSIONS =
+    { new File(Environment.getRootDirectory(), "/etc/security/plat_mac_permissions.xml"),
+      new File(Environment.getRootDirectory(), "/etc/security/nonplat_mac_permissions.xml") };
 
     // Append privapp to existing seinfo label
     private static final String PRIVILEGED_APP_STR = ":privapp";
@@ -87,49 +88,51 @@
 
         FileReader policyFile = null;
         XmlPullParser parser = Xml.newPullParser();
-        try {
-            policyFile = new FileReader(MAC_PERMISSIONS);
-            Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS);
+        for (int i = 0; i < MAC_PERMISSIONS.length; i++) {
+            try {
+                policyFile = new FileReader(MAC_PERMISSIONS[i]);
+                Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS[i]);
 
-            parser.setInput(policyFile);
-            parser.nextTag();
-            parser.require(XmlPullParser.START_TAG, null, "policy");
+                parser.setInput(policyFile);
+                parser.nextTag();
+                parser.require(XmlPullParser.START_TAG, null, "policy");
 
-            while (parser.next() != XmlPullParser.END_TAG) {
-                if (parser.getEventType() != XmlPullParser.START_TAG) {
-                    continue;
+                while (parser.next() != XmlPullParser.END_TAG) {
+                    if (parser.getEventType() != XmlPullParser.START_TAG) {
+                        continue;
+                    }
+
+                    switch (parser.getName()) {
+                        case "signer":
+                            policies.add(readSignerOrThrow(parser));
+                            break;
+                        default:
+                            skip(parser);
+                    }
                 }
-
-                switch (parser.getName()) {
-                    case "signer":
-                        policies.add(readSignerOrThrow(parser));
-                        break;
-                    default:
-                        skip(parser);
-                }
+            } catch (IllegalStateException | IllegalArgumentException |
+                     XmlPullParserException ex) {
+                StringBuilder sb = new StringBuilder("Exception @");
+                sb.append(parser.getPositionDescription());
+                sb.append(" while parsing ");
+                sb.append(MAC_PERMISSIONS[i]);
+                sb.append(":");
+                sb.append(ex);
+                Slog.w(TAG, sb.toString());
+                return false;
+            } catch (IOException ioe) {
+                Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS[i], ioe);
+                return false;
+            } finally {
+                IoUtils.closeQuietly(policyFile);
             }
-        } catch (IllegalStateException | IllegalArgumentException |
-                XmlPullParserException ex) {
-            StringBuilder sb = new StringBuilder("Exception @");
-            sb.append(parser.getPositionDescription());
-            sb.append(" while parsing ");
-            sb.append(MAC_PERMISSIONS);
-            sb.append(":");
-            sb.append(ex);
-            Slog.w(TAG, sb.toString());
-            return false;
-        } catch (IOException ioe) {
-            Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS, ioe);
-            return false;
-        } finally {
-            IoUtils.closeQuietly(policyFile);
         }
 
         // Now sort the policy stanzas
         PolicyComparator policySort = new PolicyComparator();
         Collections.sort(policies, policySort);
         if (policySort.foundDuplicate()) {
-            Slog.w(TAG, "ERROR! Duplicate entries found parsing " + MAC_PERMISSIONS);
+            Slog.w(TAG, "ERROR! Duplicate entries found parsing mac_permissions.xml files");
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index f0c6210..2a525d4 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1013,15 +1013,10 @@
             if (attachedTransformation != null) {
                 tmpMatrix.postConcat(attachedTransformation.getMatrix());
             }
+            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
             if (appTransformation != null) {
                 tmpMatrix.postConcat(appTransformation.getMatrix());
             }
-
-            // The translation that applies the position of the window needs to be applied at the
-            // end in case that other translations include scaling. Otherwise the scaling will
-            // affect this translation. But it needs to be set before the screen rotation animation
-            // so the pivot point is at the center of the screen for all windows.
-            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
             if (screenAnimation) {
                 tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
             }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index ef95fb8..a545af9 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -447,6 +447,17 @@
     }
 
     @Test
+    public void testLastCycleBoundaryJanuaryDST() throws Exception {
+        final long currentTime = parseTime("1989-01-26T21:00:00.000Z");
+        final long expectedCycle = parseTime("1989-01-01T01:59:59.000Z");
+
+        final NetworkPolicy policy = new NetworkPolicy(
+                sTemplateWifi, 32, "America/Argentina/Buenos_Aires", 1024L, 1024L, false);
+        final long actualCycle = computeLastCycleBoundary(currentTime, policy);
+        assertTimeEquals(expectedCycle, actualCycle);
+    }
+
+    @Test
     public void testNetworkPolicyAppliedCycleLastMonth() throws Exception {
         NetworkState[] state = null;
         NetworkStats stats = null;
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 50911cb..69d27f2 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -16,10 +16,13 @@
 
 package com.android.server;
 
+import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
+import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
 import static android.net.NetworkScoreManager.CACHE_FILTER_NONE;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -28,12 +31,15 @@
 import static org.mockito.Matchers.anyListOf;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.Manifest.permission;
@@ -44,37 +50,42 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.net.INetworkRecommendationProvider;
 import android.net.INetworkScoreCache;
 import android.net.NetworkKey;
-import android.net.NetworkScoreManager;
 import android.net.NetworkScorerAppManager;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
+import android.net.RecommendationRequest;
+import android.net.RecommendationResult;
 import android.net.ScoredNetwork;
 import android.net.WifiKey;
+import android.net.wifi.WifiConfiguration;
+import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import com.android.internal.R;
 import com.android.server.devicepolicy.MockUtils;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
 
 /**
  * Tests for {@link NetworkScoreService}.
@@ -85,12 +96,8 @@
     private static final ScoredNetwork SCORED_NETWORK =
             new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
                     null /* rssiCurve*/);
-    private static final NetworkScorerAppData PREV_SCORER = new NetworkScorerAppData(
-            "prevPackageName", 0, "prevScorerName", null /* configurationActivityClassName */,
-            "prevScoringServiceClass");
-    private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData(
-            "newPackageName", 1, "newScorerName", null /* configurationActivityClassName */,
-            "newScoringServiceClass");
+    private static final NetworkScorerAppData NEW_SCORER =
+        new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass");
 
     @Mock private PackageManager mPackageManager;
     @Mock private NetworkScorerAppManager mNetworkScorerAppManager;
@@ -98,10 +105,12 @@
     @Mock private Resources mResources;
     @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
     @Mock private IBinder mIBinder, mIBinder2;
+    @Mock private INetworkRecommendationProvider mRecommendationProvider;
     @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
 
     private ContentResolver mContentResolver;
     private NetworkScoreService mNetworkScoreService;
+    private RecommendationRequest mRecommendationRequest;
 
     @Before
     public void setUp() throws Exception {
@@ -112,44 +121,11 @@
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getResources()).thenReturn(mResources);
         mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
-    }
-
-    @Test
-    public void testSystemReady_networkScorerProvisioned() throws Exception {
-        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 1);
-
-        mNetworkScoreService.systemReady();
-
-        verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
-    }
-
-    @Test
-    public void testSystemReady_networkScorerNotProvisioned_defaultScorer() throws Exception {
-        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
-
-        when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
-                .thenReturn(NEW_SCORER.mPackageName);
-
-        mNetworkScoreService.systemReady();
-
-        verify(mNetworkScorerAppManager).setActiveScorer(NEW_SCORER.mPackageName);
-        assertEquals(1,
-                Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
-
-    }
-
-    @Test
-    public void testSystemReady_networkScorerNotProvisioned_noDefaultScorer() throws Exception {
-        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
-
-        when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
-                .thenReturn(null);
-
-        mNetworkScoreService.systemReady();
-
-        verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
-        assertEquals(1,
-                Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
+        WifiConfiguration configuration = new WifiConfiguration();
+        configuration.SSID = "NetworkScoreServiceTest_SSID";
+        configuration.BSSID = "NetworkScoreServiceTest_BSSID";
+        mRecommendationRequest = new RecommendationRequest.Builder()
+            .setCurrentRecommendedWifiConfig(configuration).build();
     }
 
     @Test
@@ -159,13 +135,129 @@
         mNetworkScoreService.systemRunning();
 
         verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
-                new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
+                new ComponentName(NEW_SCORER.packageName,
+                    NEW_SCORER.recommendationServiceClassName))),
                 any(ServiceConnection.class),
                 eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
                 eq(UserHandle.SYSTEM));
     }
 
     @Test
+    public void testRequestScores_noPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+            .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
+                anyString());
+        try {
+            mNetworkScoreService.requestScores(null);
+            fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestScores_providerNotConnected() throws Exception {
+        assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
+        verifyZeroInteractions(mRecommendationProvider);
+    }
+
+    @Test
+    public void testRequestScores_providerThrowsRemoteException() throws Exception {
+        injectProvider();
+        doThrow(new RemoteException()).when(mRecommendationProvider)
+            .requestScores(any(NetworkKey[].class));
+
+        assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
+    }
+
+    @Test
+    public void testRequestScores_providerAvailable() throws Exception {
+        injectProvider();
+
+        final NetworkKey[] networks = new NetworkKey[0];
+        assertTrue(mNetworkScoreService.requestScores(networks));
+        verify(mRecommendationProvider).requestScores(networks);
+    }
+
+    @Test
+    public void testRequestRecommendation_noPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+            .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
+                anyString());
+        try {
+            mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+            fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestRecommendation_mainThread() throws Exception {
+        when(mContext.getMainLooper()).thenReturn(Looper.myLooper());
+        try {
+            mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+            fail("requestRecommendation run on main thread.");
+        } catch (RuntimeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestRecommendation_providerNotConnected() throws Exception {
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+
+        final RecommendationResult result =
+                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+        assertNotNull(result);
+        assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
+                result.getWifiConfiguration());
+    }
+
+    @Test
+    public void testRequestRecommendation_providerThrowsRemoteException() throws Exception {
+        injectProvider();
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+        doThrow(new RemoteException()).when(mRecommendationProvider)
+                .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
+                        anyInt());
+
+        final RecommendationResult result =
+                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+        assertNotNull(result);
+        assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
+                result.getWifiConfiguration());
+    }
+
+    @Test
+    public void testRequestRecommendation_resultReturned() throws Exception {
+        injectProvider();
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+        final WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.SSID = "testRequestRecommendation_resultReturned_SSID";
+        wifiConfiguration.BSSID = "testRequestRecommendation_resultReturned_BSSID";
+        final RecommendationResult providerResult = RecommendationResult
+                .createConnectRecommendation(wifiConfiguration);
+        final Bundle bundle = new Bundle();
+        bundle.putParcelable(EXTRA_RECOMMENDATION_RESULT, providerResult);
+        doAnswer(invocation -> {
+            bundle.putInt(EXTRA_SEQUENCE, invocation.getArgumentAt(2, int.class));
+            invocation.getArgumentAt(1, IRemoteCallback.class).sendResult(bundle);
+            return null;
+        }).when(mRecommendationProvider)
+                .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
+                        anyInt());
+
+        final RecommendationResult result =
+                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+        assertNotNull(result);
+        assertEquals(providerResult.getWifiConfiguration().SSID,
+                result.getWifiConfiguration().SSID);
+        assertEquals(providerResult.getWifiConfiguration().BSSID,
+                result.getWifiConfiguration().BSSID);
+    }
+
+    @Test
     public void testUpdateScores_notActiveScorer() {
         when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
 
@@ -288,45 +380,6 @@
     }
 
     @Test
-    public void testSetActiveScorer_failure() throws RemoteException {
-        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER);
-        when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
-
-        boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
-
-        assertFalse(success);
-        verify(mNetworkScoreCache).clearScores();
-        verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
-                new ComponentName(PREV_SCORER.mPackageName, PREV_SCORER.mScoringServiceClassName))),
-                any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
-                eq(UserHandle.SYSTEM));
-    }
-
-    @Test
-    public void testSetActiveScorer_success() throws RemoteException {
-        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER);
-        when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
-
-        boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
-
-        assertTrue(success);
-        verify(mNetworkScoreCache).clearScores();
-        verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
-                new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
-                any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
-                eq(UserHandle.SYSTEM));
-        verify(mContext, times(2)).sendBroadcastAsUser(
-                MockUtils.checkIntentAction(NetworkScoreManager.ACTION_SCORER_CHANGED),
-                eq(UserHandle.SYSTEM));
-    }
-
-    @Test
     public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() {
         when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
         when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
@@ -338,48 +391,6 @@
         } catch (SecurityException e) {
             // expected
         }
-
-    }
-
-    @Test
-    public void testDisableScoring_activeScorer() throws RemoteException {
-        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
-        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
-        when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
-
-        mNetworkScoreService.disableScoring();
-
-        verify(mNetworkScoreCache).clearScores();
-        verify(mContext).sendBroadcastAsUser(
-                MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
-                        .setPackage(PREV_SCORER.mPackageName)),
-                eq(UserHandle.SYSTEM));
-        verify(mContext, never()).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
-    }
-
-    @Test
-    public void testDisableScoring_notActiveScorer_hasBroadcastNetworkPermission()
-            throws RemoteException {
-        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
-        when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
-        when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
-
-        mNetworkScoreService.disableScoring();
-
-        verify(mNetworkScoreCache).clearScores();
-        verify(mContext).sendBroadcastAsUser(
-                MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
-                        .setPackage(PREV_SCORER.mPackageName)),
-                eq(UserHandle.SYSTEM));
-        verify(mContext, never()).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
     }
 
     @Test
@@ -434,4 +445,24 @@
 
         assertFalse(stringWriter.toString().isEmpty());
     }
+
+    // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService.
+    private void injectProvider() {
+        final ComponentName componentName = new ComponentName(NEW_SCORER.packageName,
+                NEW_SCORER.recommendationServiceClassName);
+        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
+        when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(),
+                isA(UserHandle.class))).thenAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                IBinder mockBinder = mock(IBinder.class);
+                when(mockBinder.queryLocalInterface(anyString()))
+                        .thenReturn(mRecommendationProvider);
+                invocation.getArgumentAt(1, ServiceConnection.class)
+                        .onServiceConnected(componentName, mockBinder);
+                return true;
+            }
+        });
+        mNetworkScoreService.systemRunning();
+    }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 8225110..2c16ca0 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -146,7 +146,6 @@
     /**
      * Control whether users receive a simplified network settings UI and improved network
      * selection.
-     * @hide
      */
     public static final String
             KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
@@ -626,6 +625,15 @@
     public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
 
     /**
+     * Determines whether High Definition audio property is displayed in the dialer UI.
+     * If {@code false}, remove the HD audio property from the connection so that HD audio related
+     * UI is not displayed. If {@code true}, keep HD audio property as it is configured.
+     * @hide
+     */
+    public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL =
+            "display_hd_audio_property_bool";
+
+    /**
      * Determines whether video conference calls are supported by a carrier.  When {@code true},
      * video calls can be merged into conference calls, {@code false} otherwiwse.
      * <p>
@@ -1155,6 +1163,7 @@
         sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
+        sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
         sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
         sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
         sDefaults.putBoolean(KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL, false);
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 0334254..811c996 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -233,6 +233,13 @@
      */
     public static final int DIALED_ON_WRONG_SLOT = 56;
 
+    /**
+     * The network does not accept the emergency call request because IMEI was used as
+     * identification and this cability is not supported by the network.
+     * {@hide}
+     */
+    public static final int IMEI_NOT_ACCEPTED = 57;
+
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Please assign the new type the next id value below.
@@ -241,14 +248,14 @@
     // 4) Update toString() with the newly added disconnect type.
     // 5) Update android.telecom.DisconnectCauseUtil with any mappings to a telecom.DisconnectCause.
     //
-    // NextId: 57
+    // NextId: 58
     //*********************************************************************************************
 
     /** Smallest valid value for call disconnect codes. */
     public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED;
 
     /** Largest valid value for call disconnect codes. */
-    public static final int MAXIMUM_VALID_VALUE = DIALED_ON_WRONG_SLOT;
+    public static final int MAXIMUM_VALID_VALUE = IMEI_NOT_ACCEPTED;
 
     /** Private constructor to avoid class instantiation. */
     private DisconnectCause() {
@@ -370,6 +377,8 @@
             return "DATA_LIMIT_REACHED";
         case DIALED_ON_WRONG_SLOT:
             return "DIALED_ON_WRONG_SLOT";
+        case IMEI_NOT_ACCEPTED:
+            return "IMEI_NOT_ACCEPTED";
         default:
             return "INVALID: " + cause;
         }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 000abff..45d0576 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1824,21 +1824,14 @@
     public static final int SIM_STATE_NETWORK_LOCKED = 4;
     /** SIM card state: Ready */
     public static final int SIM_STATE_READY = 5;
-    /** SIM card state: SIM Card is NOT READY
-     *@hide
-     */
+    /** SIM card state: SIM Card is NOT READY */
     public static final int SIM_STATE_NOT_READY = 6;
-    /** SIM card state: SIM Card Error, permanently disabled
-     *@hide
-     */
+    /** SIM card state: SIM Card Error, permanently disabled */
     public static final int SIM_STATE_PERM_DISABLED = 7;
-    /** SIM card state: SIM Card Error, present but faulty
-     *@hide
-     */
+    /** SIM card state: SIM Card Error, present but faulty */
     public static final int SIM_STATE_CARD_IO_ERROR = 8;
     /** SIM card state: SIM Card restricted, present but not usable due to
      * carrier restrictions.
-     *@hide
      */
     public static final int SIM_STATE_CARD_RESTRICTED = 9;
 
@@ -1884,6 +1877,7 @@
      * @see #SIM_STATE_NOT_READY
      * @see #SIM_STATE_PERM_DISABLED
      * @see #SIM_STATE_CARD_IO_ERROR
+     * @see #SIM_STATE_CARD_RESTRICTED
      */
     public int getSimState() {
         int slotIdx = getDefaultSim();
@@ -1921,8 +1915,8 @@
      * @see #SIM_STATE_NOT_READY
      * @see #SIM_STATE_PERM_DISABLED
      * @see #SIM_STATE_CARD_IO_ERROR
+     * @see #SIM_STATE_CARD_RESTRICTED
      */
-    /** {@hide} */
     public int getSimState(int slotIdx) {
         int simState = SubscriptionManager.getSimStateForSlotIdx(slotIdx);
         return simState;
diff --git a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
index 21b7a04..84ec8b7 100644
--- a/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
+++ b/tools/preload2/src/com/android/preload/classdataretrieval/hprof/Hprof.java
@@ -171,6 +171,9 @@
                     arg1.getDevice().getSyncService().pullFile(arg0,
                             target.getAbsoluteFile().toString(), new NullProgressMonitor());
                 } catch (Exception e) {
+                    if (target != null) {
+                        target.delete();
+                    }
                     e.printStackTrace();
                     target = null;
                 }
@@ -189,6 +192,9 @@
                     out.write(arg0);
                     out.close();
                 } catch (Exception e) {
+                    if (target != null) {
+                        target.delete();
+                    }
                     e.printStackTrace();
                     target = null;
                 }
@@ -215,6 +221,8 @@
             return analyzeHprof(hprofLocalFile);
         } catch (Exception e) {
             throw new RuntimeException(e);
+        } finally {
+            hprofLocalFile.delete();
         }
     }
 }
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index da87135..da9aa06 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -259,10 +260,10 @@
     public long blackListTimestamp;
 
     /**
-     * Status: indicating the scan result is not a result
-     * that is part of user's saved configurations
+     * Status indicating the scan result does not correspond to a user's saved configuration
      * @hide
      */
+    @SystemApi
     public boolean untrusted;
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 43e6246..3b7f721 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -701,6 +701,7 @@
      * {@link com.android.server.wifi.WifiStateMachine}, or via a network score in
      * {@link com.android.server.wifi.ExternalScoreEvaluator}.
      */
+    @SystemApi
     public boolean meteredHint;
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
index c328748..9dd118b 100755
--- a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
+++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
@@ -17,13 +17,19 @@
 package android.net.wifi;
 
 import android.Manifest.permission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.os.Handler;
 import android.net.INetworkScoreCache;
 import android.net.NetworkKey;
 import android.net.ScoredNetwork;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashMap;
@@ -43,30 +49,55 @@
     // We treat the lowest possible score as though there were no score, effectively allowing the
     // scorer to provide an RSSI threshold below which a network should not be used.
     public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE;
+
+    // See {@link #CacheListener}.
+    @Nullable
+    @GuardedBy("mCacheLock")
+    private CacheListener mListener;
+
     private final Context mContext;
+    private final Object mCacheLock = new Object();
 
     // The key is of the form "<ssid>"<bssid>
     // TODO: What about SSIDs that can't be encoded as UTF-8?
     private final Map<String, ScoredNetwork> mNetworkCache;
 
+
     public WifiNetworkScoreCache(Context context) {
-        mContext = context;
+        this(context, null /* listener */);
+    }
+
+    /**
+     * Instantiates a WifiNetworkScoreCache.
+     *
+     * @param context Application context
+     * @param listener CacheListener for cache updates
+     */
+    public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) {
+        mContext = context.getApplicationContext();
+        mListener = listener;
         mNetworkCache = new HashMap<String, ScoredNetwork>();
     }
 
     @Override public final void updateScores(List<ScoredNetwork> networks) {
-      if (networks == null) {
+        if (networks == null || networks.isEmpty()) {
            return;
-       }
-       Log.e(TAG, "updateScores list size=" + networks.size());
+        }
+        Log.d(TAG, "updateScores list size=" + networks.size());
 
-       synchronized(mNetworkCache) {
-           for (ScoredNetwork network : networks) {
-               String networkKey = buildNetworkKey(network);
-               if (networkKey == null) continue;
-               mNetworkCache.put(networkKey, network);
-           }
-       }
+        synchronized(mNetworkCache) {
+            for (ScoredNetwork network : networks) {
+                String networkKey = buildNetworkKey(network);
+                if (networkKey == null) continue;
+                mNetworkCache.put(networkKey, network);
+            }
+        }
+
+        synchronized (mCacheLock) {
+            if (mListener != null) {
+                mListener.post(networks);
+            }
+        }
     }
 
     @Override public final void clearScores() {
@@ -193,4 +224,53 @@
         }
     }
 
+    /** Registers a CacheListener instance, replacing the previous listener if it existed. */
+    public void registerListener(CacheListener listener) {
+        synchronized (mCacheLock) {
+            mListener = listener;
+        }
+    }
+
+    /** Removes the registered CacheListener. */
+    public void unregisterListener() {
+        synchronized (mCacheLock) {
+            mListener = null;
+        }
+    }
+
+    /** Listener for updates to the cache inside WifiNetworkScoreCache. */
+    public abstract static class CacheListener {
+
+        private Handler mHandler;
+
+        /**
+         * Constructor for CacheListener.
+         *
+         * @param handler the Handler on which to invoke the {@link #networkCacheUpdated} method.
+         *          This cannot be null.
+         */
+        public CacheListener(@NonNull Handler handler) {
+            Preconditions.checkNotNull(handler);
+            mHandler = handler;
+        }
+
+        /** Invokes the {@link #networkCacheUpdated(List<ScoredNetwork>)} method on the handler. */
+        void post(List<ScoredNetwork> updatedNetworks) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    networkCacheUpdated(updatedNetworks);
+                }
+            });
+        }
+
+        /**
+         * Invoked whenever the cache is updated.
+         *
+         * <p>Clearing the cache does not invoke this method.
+         *
+         * @param updatedNetworks the networks that were updated
+         */
+        public abstract void networkCacheUpdated(List<ScoredNetwork> updatedNetworks);
+    }
 }
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
index f8549b9..18f6bc8 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
@@ -17,6 +17,8 @@
 package android.net.wifi;
 
 import static org.junit.Assert.*;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -24,6 +26,9 @@
 import android.net.RssiCurve;
 import android.net.ScoredNetwork;
 import android.net.WifiKey;
+import android.net.wifi.WifiNetworkScoreCache.CacheListener;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -33,124 +38,166 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
 /** Unit tests for {@link WifiNetworkScoreCache}. */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class WifiNetworkScoreCacheTest {
 
-  @Mock public Context mockContext; // isn't used, can be null
-  @Mock private RssiCurve mockRssiCurve;
+    public static final String SSID = "ssid";
+    public static final String FORMATTED_SSID = "\"" + SSID + "\"";
+    public static final String BSSID = "AA:AA:AA:AA:AA:AA";
 
-  public static final String SSID = "ssid";
-  public static final String FORMATTED_SSID = "\"" + SSID + "\"";
-  public static final String BSSID = "AA:AA:AA:AA:AA:AA";
+    public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID);
 
-  public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID);
+    public static final ScanResult VALID_SCAN_RESULT = buildScanResult(SSID, BSSID);
 
-  public static final ScanResult VALID_SCAN_RESULT = buildScanResult(SSID, BSSID);
-
-  private ScoredNetwork mValidScoredNetwork;
-  private WifiNetworkScoreCache mScoreCache =
-      new WifiNetworkScoreCache(mockContext);
-
-  private static ScanResult buildScanResult(String ssid, String bssid) {
-    return new ScanResult(
-         WifiSsid.createFromAsciiEncoded(ssid),
-         bssid,
-         "" /* caps */,
-         0 /* level */,
-         0 /* frequency */,
-         0 /* tsf */,
-         0 /* distCm */,
-         0 /* distSdCm*/);
-  }
-
-  private static ScoredNetwork buildScoredNetwork(WifiKey key, RssiCurve curve) {
-    return new ScoredNetwork(new NetworkKey(key), curve);
-  }
-
-  // Called from setup
-  private void initializeCacheWithValidScoredNetwork() {
-    mScoreCache.updateScores(ImmutableList.of(mValidScoredNetwork));
-  }
-
-  @Before
-  public void setUp() {
-    MockitoAnnotations.initMocks(this);
-    mValidScoredNetwork = buildScoredNetwork(VALID_KEY, mockRssiCurve);
-    mScoreCache = new WifiNetworkScoreCache(mockContext);
-    initializeCacheWithValidScoredNetwork();
-  }
+    @Mock private Context mockApplicationContext;
+    @Mock private Context mockContext; // isn't used, can be null
+    @Mock private RssiCurve mockRssiCurve;
 
 
-  @Test
-  public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() {
-    assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
-  }
+    private CacheListener mCacheListener;
+    private CountDownLatch mLatch;
+    private Handler mHandler;
+    private List<ScoredNetwork> mUpdatedNetworksCaptor;
+    private ScoredNetwork mValidScoredNetwork;
+    private WifiNetworkScoreCache mScoreCache;
 
-  @Test
-  public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() {
-    mScoreCache.clearScores();
-    assertFalse(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
-  }
+    private static ScanResult buildScanResult(String ssid, String bssid) {
+        return new ScanResult(
+                WifiSsid.createFromAsciiEncoded(ssid),
+                bssid,
+                "" /* caps */,
+                0 /* level */,
+                0 /* frequency */,
+                0 /* tsf */,
+                0 /* distCm */,
+                0 /* distSdCm*/);
+    }
 
-  @Test
-  public void updateScoresShouldAddNewNetwork() {
-    WifiKey key2 = new WifiKey("\"ssid2\"", BSSID);
-    ScoredNetwork network2 = buildScoredNetwork(key2, mockRssiCurve);
-    ScanResult result2 = buildScanResult("ssid2", BSSID);
+    private static ScoredNetwork buildScoredNetwork(WifiKey key, RssiCurve curve) {
+        return new ScoredNetwork(new NetworkKey(key), curve);
+    }
 
-    mScoreCache.updateScores(ImmutableList.of(network2));
+    // Called from setup
+    private void initializeCacheWithValidScoredNetwork() {
+        mScoreCache.updateScores(ImmutableList.of(mValidScoredNetwork));
+    }
 
-    assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
-    assertTrue(mScoreCache.isScoredNetwork(result2));
-  }
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
 
-  @Test
-  public void hasScoreCurveShouldReturnTrue() {
-    assertTrue(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
-  }
+        when(mockContext.getApplicationContext()).thenReturn(mockApplicationContext);
 
-  @Test
-  public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() {
-    ScanResult unscored = buildScanResult("fake", BSSID);
-    assertFalse(mScoreCache.hasScoreCurve(unscored));
-  }
+        mValidScoredNetwork = buildScoredNetwork(VALID_KEY, mockRssiCurve);
+        mScoreCache = new WifiNetworkScoreCache(mockContext);
+        initializeCacheWithValidScoredNetwork();
 
-  @Test
-  public void hasScoreCurveShouldReturnFalseWhenScoredNetworkHasNoCurve() {
-    ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */);
-    mScoreCache.updateScores(ImmutableList.of(noCurve));
+        HandlerThread thread = new HandlerThread("WifiNetworkScoreCacheTest Handler Thread");
+        thread.start();
+        mHandler = new Handler(thread.getLooper());
+        mLatch = new CountDownLatch(1);
+        mCacheListener = new CacheListener(mHandler) {
+            @Override
+            public void networkCacheUpdated(List<ScoredNetwork> updatedNetworks) {
+                mUpdatedNetworksCaptor = updatedNetworks;
+                mLatch.countDown();
+            }
+        };
+    }
 
-    assertFalse(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
-  }
 
-  @Test
-  public void getNetworkScoreShouldReturnScore() {
-    final byte score = 50;
-    final int rssi = -70;
-    ScanResult result = new ScanResult(VALID_SCAN_RESULT);
-    result.level = rssi;
+    @Test
+    public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() {
+        assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+    }
 
-    when(mockRssiCurve.lookupScore(rssi)).thenReturn(score);
+    @Test
+    public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() {
+        mScoreCache.clearScores();
+        assertFalse(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+    }
 
-    assertEquals(score, mScoreCache.getNetworkScore(result));
-  }
+    @Test
+    public void updateScoresShouldAddNewNetwork() {
+        WifiKey key2 = new WifiKey("\"ssid2\"", BSSID);
+        ScoredNetwork network2 = buildScoredNetwork(key2, mockRssiCurve);
+        ScanResult result2 = buildScanResult("ssid2", BSSID);
 
-  @Test
-  public void getMeteredHintShouldReturnFalse() {
-    assertFalse(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
-  }
+        mScoreCache.updateScores(ImmutableList.of(network2));
 
-  @Test
-  public void getMeteredHintShouldReturnTrue() {
-    ScoredNetwork network =
-        new ScoredNetwork(new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */);
-    mScoreCache.updateScores(ImmutableList.of(network));
+        assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+        assertTrue(mScoreCache.isScoredNetwork(result2));
+    }
 
-    assertTrue(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
-  }
+    @Test
+    public void hasScoreCurveShouldReturnTrue() {
+        assertTrue(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
+    }
+
+    @Test
+    public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() {
+        ScanResult unscored = buildScanResult("fake", BSSID);
+        assertFalse(mScoreCache.hasScoreCurve(unscored));
+    }
+
+    @Test
+    public void hasScoreCurveShouldReturnFalseWhenScoredNetworkHasNoCurve() {
+        ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */);
+        mScoreCache.updateScores(ImmutableList.of(noCurve));
+
+        assertFalse(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
+    }
+
+    @Test
+    public void getNetworkScoreShouldReturnScore() {
+        final byte score = 50;
+        final int rssi = -70;
+        ScanResult result = new ScanResult(VALID_SCAN_RESULT);
+        result.level = rssi;
+
+        when(mockRssiCurve.lookupScore(rssi)).thenReturn(score);
+
+        assertEquals(score, mScoreCache.getNetworkScore(result));
+    }
+
+    @Test
+    public void getMeteredHintShouldReturnFalse() {
+        assertFalse(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
+    }
+
+    @Test
+    public void getMeteredHintShouldReturnTrue() {
+        ScoredNetwork network =
+                new ScoredNetwork(
+                    new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */);
+        mScoreCache.updateScores(ImmutableList.of(network));
+
+        assertTrue(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
+    }
+
+    @Test
+    public void updateScoresShouldInvokeCacheListener_networkCacheUpdated() {
+        mScoreCache = new WifiNetworkScoreCache(mockContext, mCacheListener);
+        initializeCacheWithValidScoredNetwork();
+
+        try {
+            mLatch.await(1, TimeUnit.SECONDS); // wait for listener to be executed
+        } catch (InterruptedException e) {
+            fail("Interrupted Exception while waiting for listener to be invoked.");
+        }
+        assertEquals("One network should be updated", 1, mUpdatedNetworksCaptor.size());
+        assertEquals(mValidScoredNetwork, mUpdatedNetworksCaptor.get(0));
+    }
 }