Merge "LocalSocketImpl.cpp: Set O_CLOEXEC on received FDs"
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 20cc231..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";
diff --git a/api/system-current.txt b/api/system-current.txt
index d84a31e..b2f2a3c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -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";
diff --git a/api/test-current.txt b/api/test-current.txt
index 0349939..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";
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/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/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..0f0e050
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -0,0 +1,126 @@
+/*
+ * 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 == null ?
+            null : pluggedDevice.toString()) + " 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.toString() + " state=" + state);
+    }
+
+    /**
+     * Callback called when GET_REPORT is received from remote host. Should be
+     * replied by application using
+     * {@link BluetoothHidDevice#replyReport(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(byte type, byte id, int bufferSize) {
+        Log.d(TAG, "onGetReport: 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()}.
+     *
+     * @param type Report Type.
+     * @param id Report Id.
+     * @param data Report data.
+     */
+    public void onSetReport(byte type, byte id, byte[] data) {
+        Log.d(TAG, "onSetReport: 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(byte protocol) {
+        Log.d(TAG, "onSetProtocol: 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(byte reportId, byte[] data) {
+        Log.d(TAG, "onIntrData: reportId=" + reportId);
+    }
+
+    /**
+     * Callback called when Virtual Cable is removed. This can be either due to
+     * {@link BluetoothHidDevice#unplug()} or request from remote side. After
+     * this callback is received connection will be disconnected automatically.
+     */
+    public void onVirtualCableUnplug() {
+        Log.d(TAG, "onVirtualCableUnplug");
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothInputHost.java b/core/java/android/bluetooth/BluetoothInputHost.java
new file mode 100644
index 0000000..129fe7e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothInputHost.java
@@ -0,0 +1,521 @@
+/*
+ * 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.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(byte type, byte id, int bufferSize) {
+            mCallback.onGetReport(type, id, bufferSize);
+        }
+
+        @Override
+        public void onSetReport(byte type, byte id, byte[] data) {
+            mCallback.onSetReport(type, id, data);
+        }
+
+        @Override
+        public void onSetProtocol(byte protocol) {
+            mCallback.onSetProtocol(protocol);
+        }
+
+        @Override
+        public void onIntrData(byte reportId, byte[] data) {
+            mCallback.onIntrData(reportId, data);
+        }
+
+        @Override
+        public void onVirtualCableUnplug() {
+            mCallback.onVirtualCableUnplug();
+        }
+    }
+
+    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;
+    }
+
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        Log.v(TAG, "getConnectedDevices()");
+        return null;
+    }
+
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
+        return null;
+    }
+
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        Log.v(TAG, "getConnectionState(): device=" + device.getAddress());
+
+        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(int id, byte[] data) {
+        Log.v(TAG, "sendReport(): id=" + id);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.sendReport(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(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(byte type, byte id, byte[] data) {
+        Log.v(TAG, "replyReport(): type=" + type + " id=" + id);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.replyReport(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(byte, byte, byte[])}.
+     *
+     * @param error Error to be sent for SET_REPORT via HANDSHAKE.
+     * @return
+     */
+    public boolean reportError(byte error) {
+        Log.v(TAG, "reportError(): error = " + error);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.reportError(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() {
+        Log.v(TAG, "unplug()");
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.unplug();
+            } 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() {
+        Log.v(TAG, "connect()");
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.connect();
+            } 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() {
+        Log.v(TAG, "disconnect()");
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.disconnect();
+            } 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/IBluetoothHidDeviceCallback.aidl b/core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl
new file mode 100644
index 0000000..1252876
--- /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 byte type, in byte id, in int bufferSize);
+   void onSetReport(in byte type, in byte id, in byte[] data);
+   void onSetProtocol(in byte protocol);
+   void onIntrData(in byte reportId, in byte[] data);
+   void onVirtualCableUnplug();
+}
diff --git a/core/java/android/bluetooth/IBluetoothInputHost.aidl b/core/java/android/bluetooth/IBluetoothInputHost.aidl
new file mode 100644
index 0000000..b2c421c
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothInputHost.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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 int id, in byte[] data);
+    boolean replyReport(in byte type, in byte id, in byte[] data);
+    boolean reportError(byte error);
+    boolean unplug();
+    boolean connect();
+    boolean disconnect();
+}
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/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/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/HwRemoteBinder.java b/core/java/android/os/HwRemoteBinder.java
index 83866b3..e617e0a 100644
--- a/core/java/android/os/HwRemoteBinder.java
+++ b/core/java/android/os/HwRemoteBinder.java
@@ -39,6 +39,9 @@
     public native final void transact(
             int code, HwParcel request, HwParcel reply, int flags);
 
+    public native boolean linkToDeath(DeathRecipient recipient, long cookie);
+    public native boolean unlinkToDeath(DeathRecipient recipient);
+
     private static native final long native_init();
 
     private native final void native_setup_empty();
@@ -52,5 +55,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..f93bfd7 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -26,4 +26,16 @@
             int code, HwParcel request, HwParcel reply, int flags);
 
     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/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/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 2ae4a17..95e031b 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -242,9 +242,19 @@
     using android::hidl::manager::V1_0::IServiceManager;
 
     sp<hardware::IBinder> binder = JHwBinder::GetNativeContext(env, thiz);
+
     /* TODO(b/33440494) this is not right */
     sp<hidl::base::V1_0::IBase> base = new hidl::base::V1_0::BpBase(binder);
-    bool ok = hardware::defaultServiceManager()->add(
+
+    auto manager = hardware::defaultServiceManager();
+
+    if (manager == nullptr) {
+        LOG(ERROR) << "Could not get hwservicemanager.";
+        signalExceptionForError(env, UNKNOWN_ERROR);
+        return;
+    }
+
+    bool ok = manager->add(
                 interfaceChain,
                 serviceName,
                 base);
@@ -289,8 +299,16 @@
               << 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) {
diff --git a/core/jni/android_os_HwRemoteBinder.cpp b/core/jni/android_os_HwRemoteBinder.cpp
index 1d5d6d5..0a7d84d 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
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -174,6 +350,73 @@
     signalExceptionForError(env, err);
 }
 
+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[] = {
     { "native_init", "()J", (void *)JHwRemoteBinder_native_init },
 
@@ -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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4e98e34..60cf810 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -181,6 +181,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/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/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/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/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 1035ac8..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;
     }
 
     /**
@@ -484,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;
@@ -504,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() {
@@ -565,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");
@@ -601,7 +635,7 @@
         mBleApps.clear();
     }
 
-    /** @hide*/
+    /** @hide */
     public boolean isBleAppPresent() {
         if (DBG) Slog.d(TAG, "isBleAppPresent() count: " + mBleApps.size());
         return mBleApps.size() > 0;
@@ -667,7 +701,7 @@
         }
     }
 
-    public boolean enableNoAutoConnect()
+    public boolean enableNoAutoConnect(String packageName)
     {
         if (isBluetoothDisallowed()) {
             if (DBG) {
@@ -692,7 +726,7 @@
         synchronized(mReceiver) {
             mQuietEnableExternal = true;
             mEnableExternal = true;
-            sendEnableMsg(true);
+            sendEnableMsg(true, packageName);
         }
         return true;
     }
@@ -717,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));
         }
@@ -734,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;
@@ -753,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;
             }
         }
@@ -770,13 +802,13 @@
                 persistBluetoothSetting(BLUETOOTH_OFF);
             }
             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()
@@ -788,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() {
@@ -915,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);
@@ -1282,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;
                             }
                         }
@@ -1882,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) {
@@ -1959,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/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index f712f12..983d039 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -36,11 +36,12 @@
 import android.net.NetworkKey;
 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.Uri;
-import android.net.wifi.WifiConfiguration;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
@@ -146,29 +147,39 @@
         }
 
         private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
-            if (mPackagesToWatch.contains(scorerPackageName)) {
+            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, "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)) {
-                    if (DBG) {
-                        Log.d(TAG, "Possible change to the active scorer: "
+                    Log.d(TAG, "Possible change to the active scorer: "
                             + activeScorer.packageName);
-                    }
-                    // The scoring service changed in some way.
-                    if (forceUnbind) {
-                        unbindFromScoringServiceIfNeeded();
-                    }
-                    bindToScoringServiceIfNeeded(activeScorer);
                 }
+                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);
             }
         }
     }
@@ -319,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
@@ -369,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.");
@@ -428,35 +451,46 @@
                                           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);
         }
     }
 
@@ -464,43 +498,53 @@
     public RecommendationResult requestRecommendation(RecommendationRequest request) {
         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
         throwIfCalledOnMainThread();
-        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.
+        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 (DBG) {
+                Log.d(TAG, "Returning the default network recommendation.");
+            }
 
-        WifiConfiguration selectedConfig = null;
-        if (request != null) {
-            selectedConfig = request.getCurrentSelectedConfig();
+            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) {
         mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
-        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.
+        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);
         }
-        return false;
     }
 
     @Override
@@ -590,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,
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 203f841..1f83d9e 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;
@@ -52,7 +57,6 @@
 
     private final boolean mIsolated;
 
-    // TODO: reconnect if installd restarts
     private volatile IInstalld mInstalld;
     private volatile Object mWarnIfHeld;
 
@@ -83,7 +87,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);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1c78b16..7ce5aa8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7360,6 +7360,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) {
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 c653b8e..69d27f2 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -122,6 +122,8 @@
         when(mContext.getResources()).thenReturn(mResources);
         mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
         WifiConfiguration configuration = new WifiConfiguration();
+        configuration.SSID = "NetworkScoreServiceTest_SSID";
+        configuration.BSSID = "NetworkScoreServiceTest_BSSID";
         mRecommendationRequest = new RecommendationRequest.Builder()
             .setCurrentRecommendedWifiConfig(configuration).build();
     }
@@ -232,9 +234,10 @@
         injectProvider();
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
         final WifiConfiguration wifiConfiguration = new WifiConfiguration();
-        wifiConfiguration.SSID = "testRequestRecommendation_resultReturned";
-        final RecommendationResult providerResult =
-                new RecommendationResult(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 -> {
@@ -250,6 +253,8 @@
         assertNotNull(result);
         assertEquals(providerResult.getWifiConfiguration().SSID,
                 result.getWifiConfiguration().SSID);
+        assertEquals(providerResult.getWifiConfiguration().BSSID,
+                result.getWifiConfiguration().BSSID);
     }
 
     @Test
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/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));
+    }
 }